Apply pre-commit Checks More Widely (#4135)

This commit is contained in:
Bibo-Joshi 2024-02-29 19:11:03 +01:00 committed by GitHub
parent 9c263fbd1a
commit 26f943771b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 104 additions and 100 deletions

View file

@ -10,7 +10,6 @@ repos:
hooks:
- id: ruff
name: ruff
files: ^(telegram|examples|tests)/.*\.py$
additional_dependencies:
- httpx~=0.27.0
- tornado~=6.4
@ -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
@ -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
@ -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

View file

@ -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
------------------

0
docs/__init__.py Normal file
View file

0
docs/auxil/__init__.py Normal file
View file

View file

@ -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)
}
@ -159,7 +159,7 @@ class AdmonitionInserter:
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.
@ -197,8 +197,8 @@ class AdmonitionInserter:
"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.
@ -240,8 +240,8 @@ class AdmonitionInserter:
"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")
@ -271,8 +271,8 @@ class AdmonitionInserter:
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")
@ -297,7 +297,7 @@ class AdmonitionInserter:
# 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
@ -344,8 +344,8 @@ class AdmonitionInserter:
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")
@ -359,17 +359,19 @@ class AdmonitionInserter:
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
@ -411,7 +413,7 @@ class AdmonitionInserter:
# 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"""
@ -419,11 +421,11 @@ class AdmonitionInserter:
.. 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
@ -516,12 +518,12 @@ class AdmonitionInserter:
# 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
@ -587,6 +589,7 @@ class AdmonitionInserter:
# If neither option works, this is not a PTB class.
except (NameError, AttributeError):
continue
return None
if __name__ == "__main__":

View file

@ -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:",
@ -84,13 +85,12 @@ get_updates_read_timeout_addition = [
]
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:

View file

@ -20,6 +20,8 @@ to link to the correct files & lines on github. Can be simplified once
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
@ -30,7 +32,7 @@ sphinx_logger = logging.getLogger(__name__)
# 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:
@ -52,7 +54,7 @@ git_branch = _git_branch()
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
@ -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}"

View file

@ -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(
@ -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]
@ -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"):

View file

@ -81,11 +81,12 @@ class TGConstXRefRole(PyXRefRole):
):
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:

View file

@ -1,4 +1,3 @@
import os
import re
import sys
from pathlib import Path
@ -8,7 +7,7 @@ from pathlib import Path
# 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.
@ -310,13 +309,13 @@ texinfo_documents = [
# 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):

View file

@ -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"]

View file

@ -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

View file

@ -4,15 +4,16 @@ import subprocess
import sys
from collections import defaultdict
from pathlib import Path
from typing import Any, Dict, List, Tuple
from setuptools import find_packages, setup
def get_requirements():
def get_requirements() -> List[str]:
"""Build the requirements list for this project"""
requirements_list = []
with Path("requirements.txt").open() as reqs:
with Path("requirements.txt").open(encoding="utf-8") as reqs:
for install in reqs:
if install.startswith("#"):
continue
@ -21,7 +22,7 @@ def get_requirements():
return requirements_list
def get_packages_requirements(raw=False):
def get_packages_requirements(raw: bool = False) -> Tuple[List[str], List[str]]:
"""Build the package & requirements list for this project"""
reqs = get_requirements()
@ -34,68 +35,69 @@ def get_packages_requirements(raw=False):
return packs, reqs
def get_optional_requirements(raw=False):
def get_optional_requirements(raw: bool = False) -> Dict[str, List[str]]:
"""Build the optional dependencies"""
requirements = defaultdict(list)
with Path("requirements-opts.txt").open() as reqs:
with Path("requirements-opts.txt").open(encoding="utf-8") as reqs:
for line in reqs:
line = line.strip()
if not line or line.startswith("#"):
effective_line = line.strip()
if not effective_line or effective_line.startswith("#"):
continue
dependency, names = line.split("#")
dependency, names = effective_line.split("#")
dependency = dependency.strip()
for name in names.split(","):
name = name.strip()
if name.endswith("!ext"):
effective_name = name.strip()
if effective_name.endswith("!ext"):
if raw:
continue
else:
name = name[:-4]
requirements["ext"].append(dependency)
requirements[name].append(dependency)
effective_name = effective_name[:-4]
requirements["ext"].append(dependency)
requirements[effective_name].append(dependency)
requirements["all"].append(dependency)
return requirements
def get_setup_kwargs(raw=False):
def get_setup_kwargs(raw: bool = False) -> Dict[str, Any]:
"""Builds a dictionary of kwargs for the setup function"""
packages, requirements = get_packages_requirements(raw=raw)
raw_ext = "-raw" if raw else ""
readme = Path(f'README{"_RAW" if raw else ""}.rst')
version_file = Path("telegram/_version.py").read_text()
version_file = Path("telegram/_version.py").read_text(encoding="utf-8")
first_part = version_file.split("# SETUP.PY MARKER")[0]
exec(first_part)
exec(first_part) # pylint: disable=exec-used
kwargs = dict(
script_name=f"setup{raw_ext}.py",
name=f"python-telegram-bot{raw_ext}",
version=locals()["__version__"],
author="Leandro Toledo",
author_email="devs@python-telegram-bot.org",
license="LGPLv3",
url="https://python-telegram-bot.org/",
# Keywords supported by PyPI can be found at https://github.com/pypa/warehouse/blob/aafc5185e57e67d43487ce4faa95913dd4573e14/warehouse/templates/packaging/detail.html#L20-L58
project_urls={
return {
"script_name": f"setup{raw_ext}.py",
"name": f"python-telegram-bot{raw_ext}",
"version": locals()["__version__"],
"author": "Leandro Toledo",
"author_email": "devs@python-telegram-bot.org",
"license": "LGPLv3",
"url": "https://python-telegram-bot.org/",
# Keywords supported by PyPI can be found at
# https://github.com/pypa/warehouse/blob/aafc5185e57e67d43487ce4faa95913dd4573e14/
# warehouse/templates/packaging/detail.html#L20-L58
"project_urls": {
"Documentation": "https://docs.python-telegram-bot.org",
"Bug Tracker": "https://github.com/python-telegram-bot/python-telegram-bot/issues",
"Source Code": "https://github.com/python-telegram-bot/python-telegram-bot",
"News": "https://t.me/pythontelegrambotchannel",
"Changelog": "https://docs.python-telegram-bot.org/en/stable/changelog.html",
},
download_url=f"https://pypi.org/project/python-telegram-bot{raw_ext}/",
keywords="python telegram bot api wrapper",
description="We have made you a wrapper you can't refuse",
long_description=readme.read_text(),
long_description_content_type="text/x-rst",
packages=packages,
install_requires=requirements,
extras_require=get_optional_requirements(raw=raw),
include_package_data=True,
classifiers=[
"download_url": f"https://pypi.org/project/python-telegram-bot{raw_ext}/",
"keywords": "python telegram bot api wrapper",
"description": "We have made you a wrapper you can't refuse",
"long_description": readme.read_text(encoding="utf-8"),
"long_description_content_type": "text/x-rst",
"packages": packages,
"install_requires": requirements,
"extras_require": get_optional_requirements(raw=raw),
"include_package_data": True,
"classifiers": [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)",
@ -111,16 +113,14 @@ def get_setup_kwargs(raw=False):
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
],
python_requires=">=3.8",
)
return kwargs
"python_requires": ">=3.8",
}
def main():
def main() -> None:
# If we're building, build ptb-raw as well
if set(sys.argv[1:]) in [{"bdist_wheel"}, {"sdist"}, {"sdist", "bdist_wheel"}]:
args = ["python", "setup-raw.py"]
args = ["python", "setup_raw.py"]
args.extend(sys.argv[1:])
subprocess.run(args, check=True, capture_output=True)

View file

@ -60,7 +60,7 @@ class StringEnum(str, _enum.Enum):
# Apply the __repr__ modification and __str__ fix to IntEnum
class IntEnum(_enum.IntEnum): # pylint: disable=invalid-slots
class IntEnum(_enum.IntEnum):
"""Helper class for int enums where ``str(member)`` prints the value, but ``repr(member)``
gives ``EnumName.MEMBER_NAME``.
"""

View file

@ -29,7 +29,7 @@ those classes.
* Most of the constants in this module are grouped into enums.
"""
# TODO: Remove this when https://github.com/PyCQA/pylint/issues/6887 is resolved.
# pylint: disable=invalid-enum-extension,invalid-slots
# pylint: disable=invalid-enum-extension
__all__ = [
"BOT_API_VERSION",

View file

@ -40,4 +40,4 @@ def test_build():
@skip_disabled
def test_build_raw():
assert os.system("python setup-raw.py bdist_dumb") == 0 # pragma: no cover
assert os.system("python setup_raw.py bdist_dumb") == 0 # pragma: no cover