Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ repos:
hooks:
- id: ruff
name: ruff
files: ^(telegram|examples|tests)/.*\.py$
additional_dependencies:
- httpx~=0.27.0
- tornado~=6.4
Expand All @@ -32,7 +31,7 @@ repos:
rev: v3.0.3
hooks:
- id: pylint
files: ^(telegram|examples)/.*\.py$
files: ^(?!(tests|docs)).*\.py$
additional_dependencies:
- httpx~=0.27.0
- tornado~=6.4
Expand All @@ -45,7 +44,7 @@ repos:
hooks:
- id: mypy
name: mypy-ptb
files: ^telegram/.*\.py$
files: ^(?!(tests|examples|docs)).*\.py$
additional_dependencies:
- types-pytz
- types-cryptography
Expand All @@ -71,7 +70,6 @@ repos:
rev: v3.15.0
hooks:
- id: pyupgrade
files: ^(telegram|examples|tests|docs)/.*\.py$
args:
- --py38-plus
- repo: https://github.com/pycqa/isort
Expand Down
4 changes: 2 additions & 2 deletions README_RAW.rst
Original file line number Diff line number Diff line change
Expand Up @@ -108,12 +108,12 @@ You can also install ``python-telegram-bot-raw`` from source, though this is usu

$ git clone https://github.com/python-telegram-bot/python-telegram-bot
$ cd python-telegram-bot
$ python setup-raw.py install
$ python setup_raw.py install

Note
----

Installing the ``.tar.gz`` archive available on PyPi directly via ``pip`` will *not* work as expected, as ``pip`` does not recognize that it should use ``setup-raw.py`` instead of ``setup.py``.
Installing the ``.tar.gz`` archive available on PyPi directly via ``pip`` will *not* work as expected, as ``pip`` does not recognize that it should use ``setup_raw.py`` instead of ``setup.py``.

Verifying Releases
------------------
Expand Down
Empty file added docs/__init__.py
Empty file.
Empty file added docs/auxil/__init__.py
Empty file.
59 changes: 31 additions & 28 deletions docs/auxil/admonition_inserter.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ class AdmonitionInserter:
ForwardRef('DefaultValue[DVValueType]')
"""

METHOD_NAMES_FOR_BOT_AND_APPBUILDER: dict[type, str] = {
METHOD_NAMES_FOR_BOT_AND_APPBUILDER: typing.ClassVar[dict[type, str]] = {
cls: tuple(m[0] for m in _iter_own_public_methods(cls)) # m[0] means we take only names
for cls in (telegram.Bot, telegram.ext.ApplicationBuilder)
}
Expand Down Expand Up @@ -159,7 +159,7 @@ def _create_available_in(self) -> dict[type, str]:
telegram.ext, inspect.isclass
)

for class_name, inspected_class in classes_to_inspect:
for _class_name, inspected_class in classes_to_inspect:
# We need to make "<class 'telegram._files.sticker.StickerSet'>" into
# "telegram.StickerSet" because that's the way the classes are mentioned in
# docstrings.
Expand Down Expand Up @@ -197,8 +197,8 @@ def _create_available_in(self) -> dict[type, str]:
"Error generating Sphinx 'Available in' admonition "
f"(admonition_inserter.py). Class {name_of_class_in_attr} present in "
f"attribute {target_attr} of class {name_of_inspected_class_in_docstr}"
f" could not be resolved. {str(e)}"
)
f" could not be resolved. {e!s}"
) from e

# Properties need to be parsed separately because they act like attributes but not
# listed as attributes.
Expand Down Expand Up @@ -240,8 +240,8 @@ def _create_available_in(self) -> dict[type, str]:
"Error generating Sphinx 'Available in' admonition "
f"(admonition_inserter.py). Class {name_of_class_in_prop} present in "
f"property {prop_name} of class {name_of_inspected_class_in_docstr}"
f" could not be resolved. {str(e)}"
)
f" could not be resolved. {e!s}"
) from e

return self._generate_admonitions(attrs_for_class, admonition_type="available_in")

Expand Down Expand Up @@ -271,8 +271,8 @@ def _create_returned_in(self) -> dict[type, str]:
raise NotImplementedError(
"Error generating Sphinx 'Returned in' admonition "
f"(admonition_inserter.py). {cls}, method {method_name}. "
f"Couldn't resolve type hint in return annotation {ret_annot}. {str(e)}"
)
f"Couldn't resolve type hint in return annotation {ret_annot}. {e!s}"
) from e

return self._generate_admonitions(methods_for_class, admonition_type="returned_in")

Expand All @@ -297,7 +297,7 @@ def _create_shortcuts(self) -> dict[collections.abc.Callable, str]:

# inspect methods of all telegram classes for return statements that indicate
# that this given method is a shortcut for a Bot method
for class_name, cls in inspect.getmembers(telegram, predicate=inspect.isclass):
for _class_name, cls in inspect.getmembers(telegram, predicate=inspect.isclass):
# no need to inspect Bot's own methods, as Bot can't have shortcuts in Bot
if cls is telegram.Bot:
continue
Expand Down Expand Up @@ -344,8 +344,8 @@ def _create_use_in(self) -> dict[type, str]:
raise NotImplementedError(
"Error generating Sphinx 'Use in' admonition "
f"(admonition_inserter.py). {cls}, method {method_name}, parameter "
f"{param}: Couldn't resolve type hint {param.annotation}. {str(e)}"
)
f"{param}: Couldn't resolve type hint {param.annotation}. {e!s}"
) from e

return self._generate_admonitions(methods_for_class, admonition_type="use_in")

Expand All @@ -359,17 +359,19 @@ def _find_insert_pos_for_admonition(lines: list[str]) -> int:
If no key phrases are found, the admonition will be inserted at the very end.
"""
for idx, value in list(enumerate(lines)):
if (
value.startswith(".. seealso:")
# The docstring contains heading "Examples:", but Sphinx will have it converted
# to ".. admonition: Examples":
or value.startswith(".. admonition:: Examples")
or value.startswith(".. version")
# The space after ":param" is important because docstring can contain ":paramref:"
# in its plain text in the beginning of a line (e.g. ExtBot):
or value.startswith(":param ")
# some classes (like "Credentials") have no params, so insert before attrs:
or value.startswith(".. attribute::")
if value.startswith(
(
".. seealso:",
# The docstring contains heading "Examples:", but Sphinx will have it converted
# to ".. admonition: Examples":
".. admonition:: Examples",
".. version",
# The space after ":param" is important because docstring can contain
# ":paramref:" in its plain text in the beginning of a line (e.g. ExtBot):
":param ",
# some classes (like "Credentials") have no params, so insert before attrs:
".. attribute::",
)
):
return idx
return len(lines) - 1
Expand Down Expand Up @@ -411,19 +413,19 @@ def _generate_admonitions(
# so its page needs no admonitions.
continue

attrs = sorted(attrs)
sorted_attrs = sorted(attrs)

# e.g. for admonition type "use_in" the title will be "Use in" and CSS class "use-in".
admonition = f"""

.. admonition:: {admonition_type.title().replace("_", " ")}
:class: {admonition_type.replace("_", "-")}
"""
if len(attrs) > 1:
for target_attr in attrs:
if len(sorted_attrs) > 1:
for target_attr in sorted_attrs:
admonition += "\n * " + target_attr
else:
admonition += f"\n {attrs[0]}"
admonition += f"\n {sorted_attrs[0]}"

admonition += "\n " # otherwise an unexpected unindent warning will be issued
admonition_for_class[cls] = admonition
Expand Down Expand Up @@ -516,12 +518,12 @@ def _resolve_arg(self, arg: Any) -> Iterator[Union[type, None]]:
# If it isn't resolved, we'll have the program throw an exception to be sure.
try:
cls = self._resolve_class(m.group("class_name"))
except AttributeError:
except AttributeError as exc:
# skip known ForwardRef's that need not be resolved to a Telegram class
if self.FORWARD_REF_SKIP_PATTERN.match(str(arg)):
pass
else:
raise NotImplementedError(f"Could not process ForwardRef: {arg}")
raise NotImplementedError(f"Could not process ForwardRef: {arg}") from exc
else:
yield cls

Expand Down Expand Up @@ -587,6 +589,7 @@ def _resolve_class(name: str) -> Union[type, None]:
# If neither option works, this is not a PTB class.
except (NameError, AttributeError):
continue
return None


if __name__ == "__main__":
Expand Down
6 changes: 3 additions & 3 deletions docs/auxil/kwargs_insertion.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
import inspect
from typing import List

keyword_args = [
"Keyword Arguments:",
Expand Down Expand Up @@ -84,13 +85,12 @@
]


def find_insert_pos_for_kwargs(lines: list[str]) -> int:
def find_insert_pos_for_kwargs(lines: List[str]) -> int:
"""Finds the correct position to insert the keyword arguments and returns the index."""
for idx, value in reversed(list(enumerate(lines))): # reversed since :returns: is at the end
if value.startswith("Returns"):
return idx
else:
return False
return False


def check_timeout_and_api_kwargs_presence(obj: object) -> int:
Expand Down
8 changes: 5 additions & 3 deletions docs/auxil/link_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
https://github.com/sphinx-doc/sphinx/issues/1556 is closed
"""
import subprocess
from pathlib import Path
from typing import Dict, Tuple

from sphinx.util import logging

Expand All @@ -30,7 +32,7 @@

# must be a module-level variable so that it can be written to by the `autodoc-process-docstring`
# event handler in `sphinx_hooks.py`
LINE_NUMBERS = {}
LINE_NUMBERS: Dict[str, Tuple[Path, int, int]] = {}


def _git_branch() -> str:
Expand All @@ -52,7 +54,7 @@ def _git_branch() -> str:
base_url = "https://github.com/python-telegram-bot/python-telegram-bot/blob/"


def linkcode_resolve(_, info):
def linkcode_resolve(_, info) -> str:
"""See www.sphinx-doc.org/en/master/usage/extensions/linkcode.html"""
combined = ".".join((info["module"], info["fullname"]))
# special casing for ExtBot which is due to the special structure of extbot.rst
Expand All @@ -71,7 +73,7 @@ def linkcode_resolve(_, info):
line_info = LINE_NUMBERS.get(info["module"])

if not line_info:
return
return None

file, start_line, end_line = line_info
return f"{base_url}{git_branch}/{file}#L{start_line}-L{end_line}"
14 changes: 7 additions & 7 deletions docs/auxil/sphinx_hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,9 @@ def autodoc_skip_member(app, what, name, obj, skip, options):
return True
break

if name == "filter" and obj.__module__ == "telegram.ext.filters":
if not included_in_obj:
return True # return True to exclude from docs.
if name == "filter" and obj.__module__ == "telegram.ext.filters" and not included_in_obj:
return True # return True to exclude from docs.
return None


def autodoc_process_docstring(
Expand Down Expand Up @@ -118,7 +118,7 @@ def autodoc_process_docstring(
):
effective_insert: list[str] = media_write_timeout_deprecation
elif get_updates and to_insert.lstrip().startswith("read_timeout"):
effective_insert = [to_insert] + get_updates_read_timeout_addition
effective_insert = [to_insert, *get_updates_read_timeout_addition]
else:
effective_insert = [to_insert]

Expand Down Expand Up @@ -166,11 +166,11 @@ def autodoc_process_docstring(
autodoc_process_docstring(app, "method", f"{name}.__init__", obj.__init__, options, lines)


def autodoc_process_bases(app, name, obj, option, bases: list):
def autodoc_process_bases(app, name, obj, option, bases: list) -> None:
"""Here we fine tune how the base class's classes are displayed."""
for idx, base in enumerate(bases):
for idx, raw_base in enumerate(bases):
# let's use a string representation of the object
base = str(base)
base = str(raw_base)

# Special case for abstract context managers which are wrongly resoled for some reason
if base.startswith("typing.AbstractAsyncContextManager"):
Expand Down
3 changes: 2 additions & 1 deletion docs/auxil/tg_const_role.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,12 @@ def process_link(
):
return repr(value), target
sphinx_logger.warning(
f"%s:%d: WARNING: Did not convert reference %s. :{CONSTANTS_ROLE}: is not supposed"
"%s:%d: WARNING: Did not convert reference %s. :%s: is not supposed"
" to be used with this type of target.",
refnode.source,
refnode.line,
refnode.rawsource,
CONSTANTS_ROLE,
)
return title, target
except Exception as exc:
Expand Down
9 changes: 4 additions & 5 deletions docs/source/conf.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import os
import re
import sys
from pathlib import Path
Expand All @@ -8,7 +7,7 @@
# documentation root, use os.path.abspath to make it absolute, like shown here.
from sphinx.application import Sphinx

sys.path.insert(0, os.path.abspath("../.."))
sys.path.insert(0, str(Path("../..").resolve().absolute()))

# -- General configuration ------------------------------------------------
# General information about the project.
Expand Down Expand Up @@ -310,13 +309,13 @@
# Due to Sphinx behaviour, these imports only work when imported here, not at top of module.

# Not used but must be imported for the linkcode extension to find it
from docs.auxil.link_code import linkcode_resolve
from docs.auxil.sphinx_hooks import (
from docs.auxil.link_code import linkcode_resolve # noqa: E402, F401
from docs.auxil.sphinx_hooks import ( # noqa: E402
autodoc_process_bases,
autodoc_process_docstring,
autodoc_skip_member,
)
from docs.auxil.tg_const_role import CONSTANTS_ROLE, TGConstXRefRole
from docs.auxil.tg_const_role import CONSTANTS_ROLE, TGConstXRefRole # noqa: E402


def setup(app: Sphinx):
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ select = ["E", "F", "I", "PL", "UP", "RUF", "PTH", "C4", "B", "PIE", "SIM", "RET
[tool.ruff.lint.per-file-ignores]
"tests/*.py" = ["B018"]
"tests/**.py" = ["RUF012", "ASYNC101"]
"docs/**.py" = ["INP001"]

# PYLINT:
[tool.pylint."messages control"]
Expand Down
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ license_files = LICENSE, LICENSE.dual, LICENSE.lesser
max-line-length = 99
ignore = W503, W605
extend-ignore = E203, E704
exclude = setup.py, setup-raw.py docs/source/conf.py
exclude = setup.py, setup_raw.py docs/source/conf.py
Loading