mirror of
https://github.com/python-telegram-bot/python-telegram-bot.git
synced 2024-12-22 06:25:12 +01:00
Co-authored-by: Harshil <37377066+harshil21@users.noreply.github.com> Co-authored-by: Shivam Saini <51438830+shivamsn97@users.noreply.github.com> Co-authored-by: Aditya Yadav <adityayadav11082@gmail.com> Co-authored-by: Dmitry Kolomatskiy <58207913+lemontree210@users.noreply.github.com> Co-authored-by: Crsi <47722349+CrsiX@users.noreply.github.com> Co-authored-by: poolitzer <github@poolitzer.eu> Co-authored-by: Aditya <clot27@apx_managed.vanilla>
This commit is contained in:
parent
bacdeb37fd
commit
9953216980
232 changed files with 1905 additions and 1010 deletions
2
.github/CONTRIBUTING.rst
vendored
2
.github/CONTRIBUTING.rst
vendored
|
@ -185,7 +185,7 @@ doc strings don't have a separate documentation site they generate, instead, the
|
|||
|
||||
User facing documentation
|
||||
-------------------------
|
||||
We use `sphinx`_ to generate static HTML docs. To build them, first make sure you have the required dependencies:
|
||||
We use `sphinx`_ to generate static HTML docs. To build them, first make sure you're running Python 3.9 or above and have the required dependencies:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
|
|
2
.github/workflows/docs.yml
vendored
2
.github/workflows/docs.yml
vendored
|
@ -30,6 +30,8 @@ jobs:
|
|||
run: |
|
||||
python -W ignore -m pip install --upgrade pip
|
||||
python -W ignore -m pip install -r requirements-all.txt
|
||||
- name: Test autogeneration of admonitions
|
||||
run: pytest -v --tb=short tests/docs/admonition_inserter.py
|
||||
- name: Build docs
|
||||
run: sphinx-build docs/source docs/build/html -W --keep-going -j auto
|
||||
- name: Upload docs
|
||||
|
|
|
@ -68,7 +68,7 @@ repos:
|
|||
rev: v3.3.1
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
files: ^(telegram|examples|tests)/.*\.py$
|
||||
files: ^(telegram|examples|tests|docs)/.*\.py$
|
||||
args:
|
||||
- --py37-plus
|
||||
- repo: https://github.com/pycqa/isort
|
||||
|
|
|
@ -102,6 +102,7 @@ The following wonderful people contributed directly or indirectly to this projec
|
|||
- `Sahil Sharma <https://github.com/sahilsharma811>`_
|
||||
- `Sascha <https://github.com/saschalalala>`_
|
||||
- `Shelomentsev D <https://github.com/shelomentsevd>`_
|
||||
- `Shivam Saini <https://github.com/shivamsn97>`_
|
||||
- `Simon Schürrle <https://github.com/SitiSchu>`_
|
||||
- `sooyhwang <https://github.com/sooyhwang>`_
|
||||
- `syntx <https://github.com/syntx>`_
|
||||
|
|
|
@ -56,6 +56,8 @@ html:
|
|||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
||||
|
||||
rebuild: clean html
|
||||
|
||||
dirhtml:
|
||||
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
||||
@echo
|
||||
|
|
599
docs/auxil/admonition_inserter.py
Normal file
599
docs/auxil/admonition_inserter.py
Normal file
|
@ -0,0 +1,599 @@
|
|||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2015-2023
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser Public License for more details.
|
||||
#
|
||||
# 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 collections.abc
|
||||
import inspect
|
||||
import re
|
||||
import typing
|
||||
from collections import defaultdict
|
||||
from typing import Any, Iterator, Union
|
||||
|
||||
import telegram
|
||||
import telegram.ext
|
||||
|
||||
|
||||
def _iter_own_public_methods(cls: type) -> Iterator[tuple[str, type]]:
|
||||
"""Iterates over methods of a class that are not protected/private,
|
||||
not camelCase and not inherited from the parent class.
|
||||
|
||||
Returns pairs of method names and methods.
|
||||
|
||||
This function is defined outside the class because it is used to create class constants.
|
||||
"""
|
||||
return (
|
||||
m
|
||||
for m in inspect.getmembers(cls, predicate=inspect.isfunction) # not .ismethod
|
||||
if not m[0].startswith("_")
|
||||
and m[0].islower() # to avoid camelCase methods
|
||||
and m[0] in cls.__dict__ # method is not inherited from parent class
|
||||
)
|
||||
|
||||
|
||||
class AdmonitionInserter:
|
||||
"""Class for inserting admonitions into docs of Telegram classes."""
|
||||
|
||||
CLASS_ADMONITION_TYPES = ("use_in", "available_in", "returned_in")
|
||||
METHOD_ADMONITION_TYPES = ("shortcuts",)
|
||||
ALL_ADMONITION_TYPES = CLASS_ADMONITION_TYPES + METHOD_ADMONITION_TYPES
|
||||
|
||||
FORWARD_REF_PATTERN = re.compile(r"^ForwardRef\('(?P<class_name>\w+)'\)$")
|
||||
""" A pattern to find a class name in a ForwardRef typing annotation.
|
||||
Class name (in a named group) is surrounded by parentheses and single quotes.
|
||||
Note that since we're analyzing argument by argument, the pattern can be strict, with
|
||||
start and end markers.
|
||||
"""
|
||||
|
||||
FORWARD_REF_SKIP_PATTERN = re.compile(r"^ForwardRef\('DefaultValue\[\w+]'\)$")
|
||||
"""A pattern that will be used to skip known ForwardRef's that need not be resolved
|
||||
to a Telegram class, e.g.:
|
||||
ForwardRef('DefaultValue[None]')
|
||||
ForwardRef('DefaultValue[DVValueType]')
|
||||
"""
|
||||
|
||||
METHOD_NAMES_FOR_BOT_AND_APPBUILDER: 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)
|
||||
}
|
||||
"""A dictionary mapping Bot and ApplicationBuilder classes to their relevant methods that will
|
||||
be mentioned in 'Returned in' and 'Use in' admonitions in other classes' docstrings.
|
||||
Methods must be public, not aliases, not inherited from TelegramObject.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.admonitions: dict[str, dict[Union[type, collections.abc.Callable], str]] = {
|
||||
# dynamically determine which method to use to create a sub-dictionary
|
||||
admonition_type: getattr(self, f"_create_{admonition_type}")()
|
||||
for admonition_type in self.ALL_ADMONITION_TYPES
|
||||
}
|
||||
"""Dictionary with admonitions. Contains sub-dictionaries, one per admonition type.
|
||||
Each sub-dictionary matches bot methods (for "Shortcuts") or telegram classes (for other
|
||||
admonition types) to texts of admonitions, e.g.:
|
||||
```
|
||||
{
|
||||
"use_in": {<class 'telegram._chatinvitelink.ChatInviteLink'>:
|
||||
<"Use in" admonition for ChatInviteLink>, ...},
|
||||
"available_in": {<class 'telegram._chatinvitelink.ChatInviteLink'>:
|
||||
<"Available in" admonition">, ...},
|
||||
"returned_in": {...}
|
||||
}
|
||||
```
|
||||
"""
|
||||
|
||||
def insert_admonitions(
|
||||
self,
|
||||
obj: Union[type, collections.abc.Callable],
|
||||
docstring_lines: list[str],
|
||||
):
|
||||
"""Inserts admonitions into docstring lines for a given class or method.
|
||||
|
||||
**Modifies lines in place**.
|
||||
"""
|
||||
# A better way would be to copy the lines and return them, but that will not work with
|
||||
# docs.auxil.sphinx_hooks.autodoc_process_docstring()
|
||||
|
||||
for admonition_type in self.ALL_ADMONITION_TYPES:
|
||||
|
||||
# If there is no admonition of the given type for the given class or method,
|
||||
# continue to the next admonition type, maybe the class/method is listed there.
|
||||
if obj not in self.admonitions[admonition_type]:
|
||||
continue
|
||||
|
||||
insert_idx = self._find_insert_pos_for_admonition(docstring_lines)
|
||||
admonition_lines = self.admonitions[admonition_type][obj].splitlines()
|
||||
|
||||
for idx in range(insert_idx, insert_idx + len(admonition_lines)):
|
||||
docstring_lines.insert(idx, admonition_lines[idx - insert_idx])
|
||||
|
||||
def _create_available_in(self) -> dict[type, str]:
|
||||
"""Creates a dictionary with 'Available in' admonitions for classes that are available
|
||||
in attributes of other classes.
|
||||
"""
|
||||
|
||||
# Generate a mapping of classes to ReST links to attributes in other classes that
|
||||
# correspond to instances of a given class
|
||||
# i.e. {telegram._files.sticker.Sticker: {":attr:`telegram.Message.sticker`", ...}}
|
||||
attrs_for_class = defaultdict(set)
|
||||
|
||||
# The following regex is supposed to capture a class name in a line like this:
|
||||
# media (:obj:`str` | :class:`telegram.InputFile`): Audio file to send.
|
||||
#
|
||||
# Note that even if such typing description spans over multiple lines but each line ends
|
||||
# with a backslash (otherwise Sphinx will throw an error)
|
||||
# (e.g. EncryptedPassportElement.data), then Sphinx will combine these lines into a single
|
||||
# line automatically, and it will contain no backslash (only some extra many whitespaces
|
||||
# from the indentation).
|
||||
|
||||
attr_docstr_pattern = re.compile(
|
||||
r"^\s*(?P<attr_name>[a-z_]+)" # Any number of spaces, named group for attribute
|
||||
r"\s?\(" # Optional whitespace, opening parenthesis
|
||||
r".*" # Any number of characters (that could denote a built-in type)
|
||||
r":class:`.+`" # Marker of a classref, class name in backticks
|
||||
r".*\):" # Any number of characters, closing parenthesis, colon.
|
||||
# The ^ colon above along with parenthesis is important because it makes sure that
|
||||
# the class is mentioned in the attribute description, not in free text.
|
||||
r".*$", # Any number of characters, end of string (end of line)
|
||||
re.VERBOSE,
|
||||
)
|
||||
|
||||
# for properties: there is no attr name in docstring. Just check if there's a class name.
|
||||
prop_docstring_pattern = re.compile(r":class:`.+`.*:")
|
||||
|
||||
# pattern for iterating over potentially many class names in docstring for one attribute.
|
||||
# Tilde is optional (sometimes it is in the docstring, sometimes not).
|
||||
single_class_name_pattern = re.compile(r":class:`~?(?P<class_name>[\w.]*)`")
|
||||
|
||||
classes_to_inspect = inspect.getmembers(telegram, inspect.isclass) + inspect.getmembers(
|
||||
telegram.ext, inspect.isclass
|
||||
)
|
||||
|
||||
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.
|
||||
name_of_inspected_class_in_docstr = self._generate_class_name_for_link(inspected_class)
|
||||
|
||||
# Parsing part of the docstring with attributes (parsing of properties follows later)
|
||||
docstring_lines = inspect.getdoc(inspected_class).splitlines()
|
||||
lines_with_attrs = []
|
||||
for idx, line in enumerate(docstring_lines):
|
||||
if line.strip() == "Attributes:":
|
||||
lines_with_attrs = docstring_lines[idx + 1 :]
|
||||
break
|
||||
|
||||
for line in lines_with_attrs:
|
||||
line_match = attr_docstr_pattern.match(line)
|
||||
if not line_match:
|
||||
continue
|
||||
|
||||
target_attr = line_match.group("attr_name")
|
||||
# a typing description of one attribute can contain multiple classes
|
||||
for match in single_class_name_pattern.finditer(line):
|
||||
name_of_class_in_attr = match.group("class_name")
|
||||
|
||||
# Writing to dictionary: matching the class found in the docstring
|
||||
# and its subclasses to the attribute of the class being inspected.
|
||||
# The class in the attribute docstring (or its subclass) is the key,
|
||||
# ReST link to attribute of the class currently being inspected is the value.
|
||||
try:
|
||||
self._resolve_arg_and_add_link(
|
||||
arg=name_of_class_in_attr,
|
||||
dict_of_methods_for_class=attrs_for_class,
|
||||
link=f":attr:`{name_of_inspected_class_in_docstr}.{target_attr}`",
|
||||
)
|
||||
except NotImplementedError as e:
|
||||
raise NotImplementedError(
|
||||
f"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)}"
|
||||
)
|
||||
|
||||
# Properties need to be parsed separately because they act like attributes but not
|
||||
# listed as attributes.
|
||||
properties = inspect.getmembers(inspected_class, lambda o: isinstance(o, property))
|
||||
for prop_name, _ in properties:
|
||||
# Make sure this property is really defined in the class being inspected.
|
||||
# A property can be inherited from a parent class, then a link to it will not work.
|
||||
if prop_name not in inspected_class.__dict__:
|
||||
continue
|
||||
|
||||
# 1. Can't use typing.get_type_hints because double-quoted type hints
|
||||
# (like "Application") will throw a NameError
|
||||
# 2. Can't use inspect.signature because return annotations of properties can be
|
||||
# hard to parse (like "(self) -> BD").
|
||||
# 3. fget is used to access the actual function under the property wrapper
|
||||
docstring = inspect.getdoc(getattr(inspected_class, prop_name).fget)
|
||||
if docstring is None:
|
||||
continue
|
||||
|
||||
first_line = docstring.splitlines()[0]
|
||||
if not prop_docstring_pattern.match(first_line):
|
||||
continue
|
||||
|
||||
for match in single_class_name_pattern.finditer(first_line):
|
||||
name_of_class_in_prop = match.group("class_name")
|
||||
|
||||
# Writing to dictionary: matching the class found in the docstring and its
|
||||
# subclasses to the property of the class being inspected.
|
||||
# The class in the property docstring (or its subclass) is the key,
|
||||
# ReST link to property of the class currently being inspected is the value.
|
||||
try:
|
||||
self._resolve_arg_and_add_link(
|
||||
arg=name_of_class_in_prop,
|
||||
dict_of_methods_for_class=attrs_for_class,
|
||||
link=f":attr:`{name_of_inspected_class_in_docstr}.{prop_name}`",
|
||||
)
|
||||
except NotImplementedError as e:
|
||||
raise NotImplementedError(
|
||||
f"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)}"
|
||||
)
|
||||
|
||||
return self._generate_admonitions(attrs_for_class, admonition_type="available_in")
|
||||
|
||||
def _create_returned_in(self) -> dict[type, str]:
|
||||
"""Creates a dictionary with 'Returned in' admonitions for classes that are returned
|
||||
in Bot's and ApplicationBuilder's methods.
|
||||
"""
|
||||
|
||||
# Generate a mapping of classes to ReST links to Bot methods which return it,
|
||||
# i.e. {<class 'telegram._message.Message'>: {:meth:`telegram.Bot.send_message`, ...}}
|
||||
methods_for_class = defaultdict(set)
|
||||
|
||||
for cls, method_names in self.METHOD_NAMES_FOR_BOT_AND_APPBUILDER.items():
|
||||
for method_name in method_names:
|
||||
|
||||
sig = inspect.signature(getattr(cls, method_name))
|
||||
ret_annot = sig.return_annotation
|
||||
|
||||
method_link = self._generate_link_to_method(method_name, cls)
|
||||
|
||||
try:
|
||||
self._resolve_arg_and_add_link(
|
||||
arg=ret_annot,
|
||||
dict_of_methods_for_class=methods_for_class,
|
||||
link=method_link,
|
||||
)
|
||||
except NotImplementedError as e:
|
||||
raise NotImplementedError(
|
||||
f"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)}"
|
||||
)
|
||||
|
||||
return self._generate_admonitions(methods_for_class, admonition_type="returned_in")
|
||||
|
||||
def _create_shortcuts(self) -> dict[collections.abc.Callable, str]:
|
||||
"""Creates a dictionary with 'Shortcuts' admonitions for Bot methods that
|
||||
have shortcuts in other classes.
|
||||
"""
|
||||
|
||||
# pattern for looking for calls to Bot methods only
|
||||
bot_method_pattern = re.compile(
|
||||
r"""\s* # any number of whitespaces
|
||||
(?<=return\sawait\sself\.get_bot\(\)\.) # lookbehind
|
||||
\w+ # the method name we are looking for, letters/underscores
|
||||
(?=\() # lookahead: opening bracket before the args of the method start
|
||||
""",
|
||||
re.VERBOSE,
|
||||
)
|
||||
|
||||
# Generate a mapping of methods of classes to links to Bot methods which they are shortcuts
|
||||
# for, i.e. {<function Bot.send_voice at ...>: {:meth:`telegram.User.send_voice`, ...}
|
||||
shortcuts_for_bot_method = defaultdict(set)
|
||||
|
||||
# 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):
|
||||
|
||||
# no need to inspect Bot's own methods, as Bot can't have shortcuts in Bot
|
||||
if cls is telegram.Bot:
|
||||
continue
|
||||
|
||||
for method_name, method in _iter_own_public_methods(cls):
|
||||
|
||||
# .getsourcelines() returns a tuple. Item [1] is an int
|
||||
for line in inspect.getsourcelines(method)[0]:
|
||||
|
||||
if not (bot_method_match := bot_method_pattern.search(line)):
|
||||
continue
|
||||
|
||||
bot_method = getattr(telegram.Bot, bot_method_match.group())
|
||||
|
||||
link_to_shortcut_method = self._generate_link_to_method(method_name, cls)
|
||||
|
||||
shortcuts_for_bot_method[bot_method].add(link_to_shortcut_method)
|
||||
|
||||
return self._generate_admonitions(shortcuts_for_bot_method, admonition_type="shortcuts")
|
||||
|
||||
def _create_use_in(self) -> dict[type, str]:
|
||||
"""Creates a dictionary with 'Use in' admonitions for classes whose instances are
|
||||
accepted as arguments for Bot's and ApplicationBuilder's methods.
|
||||
"""
|
||||
|
||||
# Generate a mapping of classes to links to Bot methods which accept them as arguments,
|
||||
# i.e. {<class 'telegram._inline.inlinequeryresult.InlineQueryResult'>:
|
||||
# {:meth:`telegram.Bot.answer_inline_query`, ...}}
|
||||
methods_for_class = defaultdict(set)
|
||||
|
||||
for cls, method_names in self.METHOD_NAMES_FOR_BOT_AND_APPBUILDER.items():
|
||||
for method_name in method_names:
|
||||
method_link = self._generate_link_to_method(method_name, cls)
|
||||
|
||||
sig = inspect.signature(getattr(cls, method_name))
|
||||
parameters = sig.parameters
|
||||
|
||||
for param in parameters.values():
|
||||
try:
|
||||
self._resolve_arg_and_add_link(
|
||||
arg=param.annotation,
|
||||
dict_of_methods_for_class=methods_for_class,
|
||||
link=method_link,
|
||||
)
|
||||
except NotImplementedError as e:
|
||||
raise NotImplementedError(
|
||||
f"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)}"
|
||||
)
|
||||
|
||||
return self._generate_admonitions(methods_for_class, admonition_type="use_in")
|
||||
|
||||
@staticmethod
|
||||
def _find_insert_pos_for_admonition(lines: list[str]) -> int:
|
||||
"""Finds the correct position to insert the class admonition and returns the index.
|
||||
|
||||
The admonition will be insert above "See also", "Examples:", version added/changed notes
|
||||
and args, whatever comes first.
|
||||
|
||||
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::")
|
||||
):
|
||||
return idx
|
||||
return len(lines) - 1
|
||||
|
||||
def _generate_admonitions(
|
||||
self,
|
||||
attrs_or_methods_for_class: dict[type, set[str]],
|
||||
admonition_type: str,
|
||||
) -> dict[type, str]:
|
||||
"""Generates admonitions of a given type.
|
||||
Takes a dictionary of classes matched to ReST links to methods or attributes, e.g.:
|
||||
|
||||
```
|
||||
{<class 'telegram._files.sticker.StickerSet'>:
|
||||
[":meth: `telegram.Bot.get_sticker_set`", ...]}.
|
||||
```
|
||||
|
||||
Returns a dictionary of classes matched to full admonitions, e.g.
|
||||
for `admonition_type` "returned_in" (note that title and CSS class are generated
|
||||
automatically):
|
||||
|
||||
```
|
||||
{<class 'telegram._files.sticker.StickerSet'>:
|
||||
".. admonition:: Returned in:
|
||||
:class: returned-in
|
||||
|
||||
:meth: `telegram.Bot.get_sticker_set`"}.
|
||||
```
|
||||
"""
|
||||
|
||||
if admonition_type not in self.ALL_ADMONITION_TYPES:
|
||||
raise TypeError(f"Admonition type {admonition_type} not supported.")
|
||||
|
||||
admonition_for_class = {}
|
||||
|
||||
for cls, attrs in attrs_or_methods_for_class.items():
|
||||
|
||||
if cls is telegram.ext.ApplicationBuilder:
|
||||
# ApplicationBuilder is only used in and returned from its own methods,
|
||||
# so its page needs no admonitions.
|
||||
continue
|
||||
|
||||
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:
|
||||
admonition += "\n * " + target_attr
|
||||
else:
|
||||
admonition += f"\n {attrs[0]}"
|
||||
|
||||
admonition += "\n " # otherwise an unexpected unindent warning will be issued
|
||||
admonition_for_class[cls] = admonition
|
||||
|
||||
return admonition_for_class
|
||||
|
||||
@staticmethod
|
||||
def _generate_class_name_for_link(cls: type) -> str:
|
||||
"""Generates class name that can be used in a ReST link."""
|
||||
|
||||
# Check for potential presence of ".ext.", we will need to keep it.
|
||||
ext = ".ext" if ".ext." in str(cls) else ""
|
||||
return f"telegram{ext}.{cls.__name__}"
|
||||
|
||||
def _generate_link_to_method(self, method_name: str, cls: type) -> str:
|
||||
"""Generates a ReST link to a method of a telegram class."""
|
||||
|
||||
return f":meth:`{self._generate_class_name_for_link(cls)}.{method_name}`"
|
||||
|
||||
@staticmethod
|
||||
def _iter_subclasses(cls: type) -> Iterator:
|
||||
return (
|
||||
# exclude private classes
|
||||
c
|
||||
for c in cls.__subclasses__()
|
||||
if not str(c).split(".")[-1].startswith("_")
|
||||
)
|
||||
|
||||
def _resolve_arg_and_add_link(
|
||||
self,
|
||||
arg: Any,
|
||||
dict_of_methods_for_class: defaultdict,
|
||||
link: str,
|
||||
) -> None:
|
||||
"""A helper method. Tries to resolve the arg into a valid class. In case of success,
|
||||
adds the link (to a method, attribute, or property) for that class' and its subclasses'
|
||||
sets of links in the dictionary of admonitions.
|
||||
|
||||
**Modifies dictionary in place.**
|
||||
"""
|
||||
for cls in self._resolve_arg(arg):
|
||||
|
||||
# When trying to resolve an argument from args or return annotation,
|
||||
# the method _resolve_arg returns None if nothing could be resolved.
|
||||
# Also, if class was resolved correctly, "telegram" will definitely be in its str().
|
||||
if cls is None or "telegram" not in str(cls):
|
||||
continue
|
||||
|
||||
dict_of_methods_for_class[cls].add(link)
|
||||
|
||||
for subclass in self._iter_subclasses(cls):
|
||||
dict_of_methods_for_class[subclass].add(link)
|
||||
|
||||
def _resolve_arg(self, arg: Any) -> Iterator[Union[type, None]]:
|
||||
"""Analyzes an argument of a method and recursively yields classes that the argument
|
||||
or its sub-arguments (in cases like Union[...]) belong to, if they can be resolved to
|
||||
telegram or telegram.ext classes.
|
||||
|
||||
Raises `NotImplementedError`.
|
||||
"""
|
||||
|
||||
origin = typing.get_origin(arg)
|
||||
|
||||
if (
|
||||
origin in (collections.abc.Callable, typing.IO)
|
||||
or arg is None
|
||||
# no other check available (by type or origin) for these:
|
||||
or str(type(arg)) in ("<class 'typing._SpecialForm'>", "<class 'ellipsis'>")
|
||||
):
|
||||
pass
|
||||
|
||||
# RECURSIVE CALLS
|
||||
# for cases like Union[Sequence....
|
||||
elif origin in (
|
||||
Union,
|
||||
collections.abc.Coroutine,
|
||||
collections.abc.Sequence,
|
||||
):
|
||||
for sub_arg in typing.get_args(arg):
|
||||
yield from self._resolve_arg(sub_arg)
|
||||
|
||||
elif isinstance(arg, typing.TypeVar):
|
||||
# gets access to the "bound=..." parameter
|
||||
yield from self._resolve_arg(arg.__bound__)
|
||||
# END RECURSIVE CALLS
|
||||
|
||||
elif isinstance(arg, typing.ForwardRef):
|
||||
m = self.FORWARD_REF_PATTERN.match(str(arg))
|
||||
# We're sure it's a ForwardRef, so, unless it belongs to known exceptions,
|
||||
# the class must be resolved.
|
||||
# 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:
|
||||
# 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}")
|
||||
else:
|
||||
yield cls
|
||||
|
||||
# For custom generics like telegram.ext._application.Application[~BT, ~CCT, ~UD...].
|
||||
# This must come before the check for isinstance(type) because GenericAlias can also be
|
||||
# recognized as type if it belongs to <class 'types.GenericAlias'>.
|
||||
elif str(type(arg)) in ("<class 'typing._GenericAlias'>", "<class 'types.GenericAlias'>"):
|
||||
if "telegram" in str(arg):
|
||||
# get_origin() of telegram.ext._application.Application[~BT, ~CCT, ~UD...]
|
||||
# will produce <class 'telegram.ext._application.Application'>
|
||||
yield origin
|
||||
|
||||
elif isinstance(arg, type):
|
||||
if "telegram" in str(arg):
|
||||
yield arg
|
||||
|
||||
# For some reason "InlineQueryResult", "InputMedia" & some others are currently not
|
||||
# recognized as ForwardRefs and are identified as plain strings.
|
||||
elif isinstance(arg, str):
|
||||
|
||||
# args like "ApplicationBuilder[BT, CCT, UD, CD, BD, JQ]" can be recognized as strings.
|
||||
# Remove whatever is in the square brackets because it doesn't need to be parsed.
|
||||
arg = re.sub(r"\[.+]", "", arg)
|
||||
|
||||
cls = self._resolve_class(arg)
|
||||
# Here we don't want an exception to be thrown since we're not sure it's ForwardRef
|
||||
if cls is not None:
|
||||
yield cls
|
||||
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
f"Cannot process argument {arg} of type {type(arg)} (origin {origin})"
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _resolve_class(name: str) -> Union[type, None]:
|
||||
"""The keys in the admonitions dictionary are not strings like "telegram.StickerSet"
|
||||
but classes like <class 'telegram._files.sticker.StickerSet'>.
|
||||
|
||||
This method attempts to resolve a PTB class from a name that does or does not
|
||||
contain the word 'telegram', e.g.
|
||||
<class 'telegram._files.sticker.StickerSet'> from "telegram.StickerSet" or "StickerSet".
|
||||
|
||||
Returns a class on success, :obj:`None` if nothing could be resolved.
|
||||
"""
|
||||
|
||||
for option in (
|
||||
name,
|
||||
f"telegram.{name}",
|
||||
f"telegram.ext.{name}",
|
||||
f"telegram.ext.filters.{name}",
|
||||
):
|
||||
try:
|
||||
return eval(option)
|
||||
# NameError will be raised if trying to eval just name and it doesn't work, e.g.
|
||||
# "Name 'ApplicationBuilder' is not defined".
|
||||
# AttributeError will be raised if trying to e.g. eval f"telegram.{name}" when the
|
||||
# class denoted by `name` actually belongs to `telegram.ext`:
|
||||
# "module 'telegram' has no attribute 'ApplicationBuilder'".
|
||||
# If neither option works, this is not a PTB class.
|
||||
except (NameError, AttributeError):
|
||||
continue
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# just try instantiating for debugging purposes
|
||||
AdmonitionInserter()
|
79
docs/auxil/kwargs_insertion.py
Normal file
79
docs/auxil/kwargs_insertion.py
Normal file
|
@ -0,0 +1,79 @@
|
|||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2015-2023
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser Public License for more details.
|
||||
#
|
||||
# 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
|
||||
|
||||
keyword_args = [
|
||||
":keyword _sphinx_paramlinks_telegram.Bot.{method}.read_timeout: Value to pass to "
|
||||
":paramref:`telegram.request.BaseRequest.post.read_timeout`. Defaults to {read_timeout}.",
|
||||
":kwtype _sphinx_paramlinks_telegram.Bot.{method}.read_timeout: {read_timeout_type}, optional",
|
||||
":keyword _sphinx_paramlinks_telegram.Bot.{method}.write_timeout: Value to pass to "
|
||||
":paramref:`telegram.request.BaseRequest.post.write_timeout`. Defaults to {write_timeout}.",
|
||||
":kwtype _sphinx_paramlinks_telegram.Bot.{method}.write_timeout: :obj:`float` | :obj:`None`, "
|
||||
"optional",
|
||||
":keyword _sphinx_paramlinks_telegram.Bot.{method}.connect_timeout: Value to pass to "
|
||||
":paramref:`telegram.request.BaseRequest.post.connect_timeout`. Defaults to "
|
||||
":attr:`~telegram.request.BaseRequest.DEFAULT_NONE`.",
|
||||
":kwtype _sphinx_paramlinks_telegram.Bot.{method}.connect_timeout: :obj:`float` | "
|
||||
":obj:`None`, optional",
|
||||
":keyword _sphinx_paramlinks_telegram.Bot.{method}.pool_timeout: Value to pass to "
|
||||
":paramref:`telegram.request.BaseRequest.post.pool_timeout`. Defaults to "
|
||||
":attr:`~telegram.request.BaseRequest.DEFAULT_NONE`.",
|
||||
":kwtype _sphinx_paramlinks_telegram.Bot.{method}.pool_timeout: :obj:`float` | :obj:`None`, "
|
||||
"optional",
|
||||
":keyword _sphinx_paramlinks_telegram.Bot.{method}.api_kwargs: Arbitrary keyword arguments "
|
||||
"to be passed to the Telegram API.",
|
||||
":kwtype _sphinx_paramlinks_telegram.Bot.{method}.api_kwargs: :obj:`dict`, optional",
|
||||
"",
|
||||
]
|
||||
write_timeout_sub = [":attr:`~telegram.request.BaseRequest.DEFAULT_NONE`", "``20``"]
|
||||
read_timeout_sub = [
|
||||
":attr:`~telegram.request.BaseRequest.DEFAULT_NONE`.",
|
||||
"``2``. :paramref:`timeout` will be added to this value",
|
||||
]
|
||||
read_timeout_type = [":obj:`float` | :obj:`None`", ":obj:`float`"]
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
def is_write_timeout_20(obj: object) -> int:
|
||||
"""inspects the default value of write_timeout parameter of the bot method."""
|
||||
sig = inspect.signature(obj)
|
||||
return 1 if (sig.parameters["write_timeout"].default == 20) else 0
|
||||
|
||||
|
||||
def check_timeout_and_api_kwargs_presence(obj: object) -> int:
|
||||
"""Checks if the method has timeout and api_kwargs keyword only parameters."""
|
||||
sig = inspect.signature(obj)
|
||||
params_to_check = (
|
||||
"read_timeout",
|
||||
"write_timeout",
|
||||
"connect_timeout",
|
||||
"pool_timeout",
|
||||
"api_kwargs",
|
||||
)
|
||||
return all(
|
||||
param in sig.parameters and sig.parameters[param].kind == inspect.Parameter.KEYWORD_ONLY
|
||||
for param in params_to_check
|
||||
)
|
77
docs/auxil/link_code.py
Normal file
77
docs/auxil/link_code.py
Normal file
|
@ -0,0 +1,77 @@
|
|||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2015-2023
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser Public License
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
"""Functionality in this file is used for getting the [source] links on the classes, methods etc
|
||||
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 sphinx.util import logging
|
||||
|
||||
# get the sphinx(!) logger
|
||||
# Makes sure logs render in red and also plays nicely with e.g. the `nitpicky` option.
|
||||
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 = {}
|
||||
|
||||
|
||||
def _git_branch() -> str:
|
||||
"""Get's the current git sha if available or fall back to `master`"""
|
||||
try:
|
||||
output = subprocess.check_output( # skipcq: BAN-B607
|
||||
["git", "describe", "--tags", "--always"], stderr=subprocess.STDOUT
|
||||
)
|
||||
return output.decode().strip()
|
||||
except Exception as exc:
|
||||
sphinx_logger.exception(
|
||||
"Failed to get a description of the current commit. Falling back to `master`.",
|
||||
exc_info=exc,
|
||||
)
|
||||
return "master"
|
||||
|
||||
|
||||
git_branch = _git_branch()
|
||||
base_url = "https://github.com/python-telegram-bot/python-telegram-bot/blob/"
|
||||
|
||||
|
||||
def linkcode_resolve(_, info):
|
||||
"""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
|
||||
combined = combined.replace("ExtBot.ExtBot", "ExtBot")
|
||||
|
||||
line_info = LINE_NUMBERS.get(combined)
|
||||
|
||||
if not line_info:
|
||||
# Try the __init__
|
||||
line_info = LINE_NUMBERS.get(f"{combined.rsplit('.', 1)[0]}.__init__")
|
||||
if not line_info:
|
||||
# Try the class
|
||||
line_info = LINE_NUMBERS.get(f"{combined.rsplit('.', 1)[0]}")
|
||||
if not line_info:
|
||||
# Try the module
|
||||
line_info = LINE_NUMBERS.get(info["module"])
|
||||
|
||||
if not line_info:
|
||||
return
|
||||
|
||||
file, start_line, end_line = line_info
|
||||
return f"{base_url}{git_branch}/{file}#L{start_line}-L{end_line}"
|
209
docs/auxil/sphinx_hooks.py
Normal file
209
docs/auxil/sphinx_hooks.py
Normal file
|
@ -0,0 +1,209 @@
|
|||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2015-2023
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser Public License for more details.
|
||||
#
|
||||
# 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 collections.abc
|
||||
import inspect
|
||||
import re
|
||||
import typing
|
||||
from pathlib import Path
|
||||
|
||||
from sphinx.application import Sphinx
|
||||
|
||||
import telegram
|
||||
import telegram.ext
|
||||
from docs.auxil.admonition_inserter import AdmonitionInserter
|
||||
from docs.auxil.kwargs_insertion import (
|
||||
check_timeout_and_api_kwargs_presence,
|
||||
find_insert_pos_for_kwargs,
|
||||
is_write_timeout_20,
|
||||
keyword_args,
|
||||
read_timeout_sub,
|
||||
read_timeout_type,
|
||||
write_timeout_sub,
|
||||
)
|
||||
from docs.auxil.link_code import LINE_NUMBERS
|
||||
|
||||
ADMONITION_INSERTER = AdmonitionInserter()
|
||||
|
||||
# Some base classes are implementation detail
|
||||
# We want to instead show *their* base class
|
||||
PRIVATE_BASE_CLASSES = {
|
||||
"_ChatUserBaseFilter": "MessageFilter",
|
||||
"_Dice": "MessageFilter",
|
||||
"_BaseThumbedMedium": "TelegramObject",
|
||||
"_BaseMedium": "TelegramObject",
|
||||
"_CredentialsBase": "TelegramObject",
|
||||
}
|
||||
|
||||
|
||||
FILE_ROOT = Path(inspect.getsourcefile(telegram)).parent.parent.resolve()
|
||||
|
||||
|
||||
def autodoc_skip_member(app, what, name, obj, skip, options):
|
||||
"""We use this to not document certain members like filter() or check_update() for filters.
|
||||
See https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html#skipping-members"""
|
||||
|
||||
included = {"MessageFilter", "UpdateFilter"} # filter() and check_update() only for these.
|
||||
included_in_obj = any(inc in repr(obj) for inc in included)
|
||||
|
||||
if included_in_obj: # it's difficult to see if check_update is from an inherited-member or not
|
||||
for frame in inspect.stack(): # From https://github.com/sphinx-doc/sphinx/issues/9533
|
||||
if frame.function == "filter_members":
|
||||
docobj = frame.frame.f_locals["self"].object
|
||||
if not any(inc in str(docobj) for inc in included) and name == "check_update":
|
||||
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.
|
||||
|
||||
|
||||
def autodoc_process_docstring(
|
||||
app: Sphinx, what, name: str, obj: object, options, lines: list[str]
|
||||
):
|
||||
"""We do the following things:
|
||||
1) Use this method to automatically insert the Keyword Args and "Shortcuts" admonitions
|
||||
for the Bot methods.
|
||||
|
||||
2) Use this method to automatically insert "Returned in" admonition into classes
|
||||
that are returned from the Bot methods
|
||||
|
||||
3) Use this method to automatically insert "Available in" admonition into classes
|
||||
whose instances are available as attributes of other classes
|
||||
|
||||
4) Use this method to automatically insert "Use in" admonition into classes
|
||||
whose instances can be used as arguments of the Bot methods
|
||||
|
||||
5) Misuse this autodoc hook to get the file names & line numbers because we have access
|
||||
to the actual object here.
|
||||
"""
|
||||
|
||||
# 1) Insert the Keyword Args and "Shortcuts" admonitions for the Bot methods
|
||||
method_name = name.split(".")[-1]
|
||||
if (
|
||||
name.startswith("telegram.Bot.")
|
||||
and what == "method"
|
||||
and method_name.islower()
|
||||
and check_timeout_and_api_kwargs_presence(obj)
|
||||
):
|
||||
insert_index = find_insert_pos_for_kwargs(lines)
|
||||
if not insert_index:
|
||||
raise ValueError(
|
||||
f"Couldn't find the correct position to insert the keyword args for {obj}."
|
||||
)
|
||||
|
||||
long_write_timeout = is_write_timeout_20(obj)
|
||||
get_updates_sub = 1 if (method_name == "get_updates") else 0
|
||||
# The below can be done in 1 line with itertools.chain, but this must be modified in-place
|
||||
for i in range(insert_index, insert_index + len(keyword_args)):
|
||||
lines.insert(
|
||||
i,
|
||||
keyword_args[i - insert_index].format(
|
||||
method=method_name,
|
||||
write_timeout=write_timeout_sub[long_write_timeout],
|
||||
read_timeout=read_timeout_sub[get_updates_sub],
|
||||
read_timeout_type=read_timeout_type[get_updates_sub],
|
||||
),
|
||||
)
|
||||
|
||||
ADMONITION_INSERTER.insert_admonitions(
|
||||
obj=typing.cast(collections.abc.Callable, obj),
|
||||
docstring_lines=lines,
|
||||
)
|
||||
|
||||
# 2-4) Insert "Returned in", "Available in", "Use in" admonitions into classes
|
||||
# (where applicable)
|
||||
if what == "class":
|
||||
ADMONITION_INSERTER.insert_admonitions(
|
||||
obj=typing.cast(type, obj), # since "what" == class, we know it's not just object
|
||||
docstring_lines=lines,
|
||||
)
|
||||
|
||||
# 5) Get the file names & line numbers
|
||||
# We can't properly handle ordinary attributes.
|
||||
# In linkcode_resolve we'll resolve to the `__init__` or module instead
|
||||
if what == "attribute":
|
||||
return
|
||||
|
||||
# Special casing for properties
|
||||
if hasattr(obj, "fget"):
|
||||
obj = obj.fget
|
||||
|
||||
# Special casing for filters
|
||||
if isinstance(obj, telegram.ext.filters.BaseFilter):
|
||||
obj = obj.__class__
|
||||
|
||||
try:
|
||||
source_lines, start_line = inspect.getsourcelines(obj)
|
||||
end_line = start_line + len(source_lines)
|
||||
file = Path(inspect.getsourcefile(obj)).relative_to(FILE_ROOT)
|
||||
LINE_NUMBERS[name] = (file, start_line, end_line)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Since we don't document the `__init__`, we call this manually to have it available for
|
||||
# attributes -- see the note above
|
||||
if what == "class":
|
||||
autodoc_process_docstring(app, "method", f"{name}.__init__", obj.__init__, options, lines)
|
||||
|
||||
|
||||
def autodoc_process_bases(app, name, obj, option, bases: list):
|
||||
"""Here we fine tune how the base class's classes are displayed."""
|
||||
for idx, base in enumerate(bases):
|
||||
# let's use a string representation of the object
|
||||
base = str(base)
|
||||
|
||||
# Special case for abstract context managers which are wrongly resoled for some reason
|
||||
if base.startswith("typing.AbstractAsyncContextManager"):
|
||||
bases[idx] = ":class:`contextlib.AbstractAsyncContextManager`"
|
||||
continue
|
||||
|
||||
# Special case because base classes are in std lib:
|
||||
if "StringEnum" in base == "<enum 'StringEnum'>":
|
||||
bases[idx] = ":class:`enum.Enum`"
|
||||
bases.insert(0, ":class:`str`")
|
||||
continue
|
||||
|
||||
if "IntEnum" in base:
|
||||
bases[idx] = ":class:`enum.IntEnum`"
|
||||
continue
|
||||
|
||||
# Drop generics (at least for now)
|
||||
if base.endswith("]"):
|
||||
base = base.split("[", maxsplit=1)[0]
|
||||
bases[idx] = f":class:`{base}`"
|
||||
|
||||
# Now convert `telegram._message.Message` to `telegram.Message` etc
|
||||
match = re.search(pattern=r"(telegram(\.ext|))\.[_\w\.]+", string=base)
|
||||
if not match or "_utils" in base:
|
||||
continue
|
||||
|
||||
parts = match.group(0).split(".")
|
||||
|
||||
# Remove private paths
|
||||
for index, part in enumerate(parts):
|
||||
if part.startswith("_"):
|
||||
parts = parts[:index] + parts[-1:]
|
||||
break
|
||||
|
||||
# Replace private base classes with their respective parent
|
||||
parts = [PRIVATE_BASE_CLASSES.get(part, part) for part in parts]
|
||||
|
||||
base = ".".join(parts)
|
||||
|
||||
bases[idx] = f":class:`{base}`"
|
92
docs/auxil/tg_const_role.py
Normal file
92
docs/auxil/tg_const_role.py
Normal file
|
@ -0,0 +1,92 @@
|
|||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2015-2023
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser Public License
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
from enum import Enum
|
||||
|
||||
from docutils.nodes import Element
|
||||
from sphinx.domains.python import PyXRefRole
|
||||
from sphinx.environment import BuildEnvironment
|
||||
from sphinx.util import logging
|
||||
|
||||
import telegram
|
||||
|
||||
# get the sphinx(!) logger
|
||||
# Makes sure logs render in red and also plays nicely with e.g. the `nitpicky` option.
|
||||
sphinx_logger = logging.getLogger(__name__)
|
||||
|
||||
CONSTANTS_ROLE = "tg-const"
|
||||
|
||||
|
||||
class TGConstXRefRole(PyXRefRole):
|
||||
"""This is a bit of Sphinx magic. We add a new role type called tg-const that allows us to
|
||||
reference values from the `telegram.constants.module` while using the actual value as title
|
||||
of the link.
|
||||
|
||||
Example:
|
||||
|
||||
:tg-const:`telegram.constants.MessageLimit.MAX_TEXT_LENGTH` renders as `4096` but links to
|
||||
the constant.
|
||||
"""
|
||||
|
||||
def process_link(
|
||||
self,
|
||||
env: BuildEnvironment,
|
||||
refnode: Element,
|
||||
has_explicit_title: bool,
|
||||
title: str,
|
||||
target: str,
|
||||
) -> tuple[str, str]:
|
||||
title, target = super().process_link(env, refnode, has_explicit_title, title, target)
|
||||
try:
|
||||
# We use `eval` to get the value of the expression. Maybe there are better ways to
|
||||
# do this via importlib or so, but it does the job for now
|
||||
value = eval(target)
|
||||
# Maybe we need a better check if the target is actually from tg.constants
|
||||
# for now checking if it's an Enum suffices since those are used nowhere else in PTB
|
||||
if isinstance(value, Enum):
|
||||
# Special casing for file size limits
|
||||
if isinstance(value, telegram.constants.FileSizeLimit):
|
||||
return f"{int(value.value / 1e6)} MB", target
|
||||
return repr(value.value), target
|
||||
# Just for (Bot API) versions number auto add in constants:
|
||||
if isinstance(value, str) and target in (
|
||||
"telegram.constants.BOT_API_VERSION",
|
||||
"telegram.__version__",
|
||||
):
|
||||
return value, target
|
||||
if isinstance(value, tuple) and target in (
|
||||
"telegram.constants.BOT_API_VERSION_INFO",
|
||||
"telegram.__version_info__",
|
||||
):
|
||||
return repr(value), target
|
||||
sphinx_logger.warning(
|
||||
f"%s:%d: WARNING: Did not convert reference %s. :{CONSTANTS_ROLE}: is not supposed"
|
||||
" to be used with this type of target.",
|
||||
refnode.source,
|
||||
refnode.line,
|
||||
refnode.rawsource,
|
||||
)
|
||||
return title, target
|
||||
except Exception as exc:
|
||||
sphinx_logger.exception(
|
||||
"%s:%d: WARNING: Did not convert reference %s due to an exception.",
|
||||
refnode.source,
|
||||
refnode.line,
|
||||
refnode.rawsource,
|
||||
exc_info=exc,
|
||||
)
|
||||
return title, target
|
|
@ -1,6 +1,7 @@
|
|||
sphinx==5.3.0
|
||||
sphinx==6.1.3
|
||||
sphinx-pypi-upload
|
||||
furo==2022.12.7
|
||||
git+https://github.com/harshil21/furo-sphinx-search@be5cfa221a01f6e259bb2bb1f76d6ede7ffc1f11#egg=furo-sphinx-search
|
||||
git+https://github.com/harshil21/furo-sphinx-search@01efc7be422d7dc02390aab9be68d6f5ce1a5618#egg=furo-sphinx-search
|
||||
sphinx-paramlinks==0.5.4
|
||||
sphinxcontrib-mermaid==0.7.1
|
||||
sphinxcontrib-mermaid==0.7.1
|
||||
sphinx-copybutton==0.5.1
|
||||
|
|
65
docs/source/_static/style_admonitions.css
Normal file
65
docs/source/_static/style_admonitions.css
Normal file
|
@ -0,0 +1,65 @@
|
|||
:root {
|
||||
--icon--shortcuts: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='currentColor' class='bi bi-signpost' viewBox='0 0 16 16'%3E%3Cpath d='M7 1.414V4H2a1 1 0 0 0-1 1v4a1 1 0 0 0 1 1h5v6h2v-6h3.532a1 1 0 0 0 .768-.36l1.933-2.32a.5.5 0 0 0 0-.64L13.3 4.36a1 1 0 0 0-.768-.36H9V1.414a1 1 0 0 0-2 0zM12.532 5l1.666 2-1.666 2H2V5h10.532z'/%3E%3C/svg%3E");
|
||||
--icon--returned-in: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='currentColor' class='bi bi-arrow-return-right' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M1.5 1.5A.5.5 0 0 0 1 2v4.8a2.5 2.5 0 0 0 2.5 2.5h9.793l-3.347 3.346a.5.5 0 0 0 .708.708l4.2-4.2a.5.5 0 0 0 0-.708l-4-4a.5.5 0 0 0-.708.708L13.293 8.3H3.5A1.5 1.5 0 0 1 2 6.8V2a.5.5 0 0 0-.5-.5z'/%3E%3C/svg%3E");
|
||||
--icon--available-in: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='currentColor' class='bi bi-geo-fill' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M4 4a4 4 0 1 1 4.5 3.969V13.5a.5.5 0 0 1-1 0V7.97A4 4 0 0 1 4 3.999zm2.493 8.574a.5.5 0 0 1-.411.575c-.712.118-1.28.295-1.655.493a1.319 1.319 0 0 0-.37.265.301.301 0 0 0-.057.09V14l.002.008a.147.147 0 0 0 .016.033.617.617 0 0 0 .145.15c.165.13.435.27.813.395.751.25 1.82.414 3.024.414s2.273-.163 3.024-.414c.378-.126.648-.265.813-.395a.619.619 0 0 0 .146-.15.148.148 0 0 0 .015-.033L12 14v-.004a.301.301 0 0 0-.057-.09 1.318 1.318 0 0 0-.37-.264c-.376-.198-.943-.375-1.655-.493a.5.5 0 1 1 .164-.986c.77.127 1.452.328 1.957.594C12.5 13 13 13.4 13 14c0 .426-.26.752-.544.977-.29.228-.68.413-1.116.558-.878.293-2.059.465-3.34.465-1.281 0-2.462-.172-3.34-.465-.436-.145-.826-.33-1.116-.558C3.26 14.752 3 14.426 3 14c0-.599.5-1 .961-1.243.505-.266 1.187-.467 1.957-.594a.5.5 0 0 1 .575.411z'/%3E%3C/svg%3E");
|
||||
--icon--use-in:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='currentColor' class='bi bi-funnel' viewBox='0 0 16 16'%3E%3Cpath d='M1.5 1.5A.5.5 0 0 1 2 1h12a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-.128.334L10 8.692V13.5a.5.5 0 0 1-.342.474l-3 1A.5.5 0 0 1 6 14.5V8.692L1.628 3.834A.5.5 0 0 1 1.5 3.5v-2zm1 .5v1.308l4.372 4.858A.5.5 0 0 1 7 8.5v5.306l2-.666V8.5a.5.5 0 0 1 .128-.334L13.5 3.308V2h-11z'/%3E%3C/svg%3E");
|
||||
}
|
||||
.admonition.shortcuts {
|
||||
border-color: rgb(43, 155, 70);
|
||||
}
|
||||
.admonition.shortcuts > .admonition-title {
|
||||
background-color: rgba(43, 155, 70, 0.1);
|
||||
border-color: rgb(43, 155, 70);
|
||||
}
|
||||
.admonition.shortcuts > .admonition-title::before {
|
||||
background-color: rgb(43, 155, 70);
|
||||
-webkit-mask-image: var(--icon--shortcuts);
|
||||
mask-image: var(--icon--shortcuts);
|
||||
}
|
||||
|
||||
.admonition.returned-in {
|
||||
border-color: rgb(230, 109, 15);
|
||||
}
|
||||
.admonition.returned-in > .admonition-title {
|
||||
background-color: rgba(177, 108, 51, 0.1);
|
||||
border-color: rgb(230, 109, 15);
|
||||
}
|
||||
.admonition.returned-in > .admonition-title::before {
|
||||
background-color: rgb(230, 109, 15);
|
||||
-webkit-mask-image: var(--icon--returned-in);
|
||||
mask-image: var(--icon--returned-in);
|
||||
}
|
||||
|
||||
.admonition.available-in {
|
||||
border-color: rgb(183, 4, 215);
|
||||
}
|
||||
.admonition.available-in > .admonition-title {
|
||||
background-color: rgba(165, 99, 177, 0.1);
|
||||
border-color: rgb(183, 4, 215);
|
||||
}
|
||||
.admonition.available-in > .admonition-title::before {
|
||||
background-color: rgb(183, 4, 215);
|
||||
-webkit-mask-image: var(--icon--available-in);
|
||||
mask-image: var(--icon--available-in);
|
||||
}
|
||||
|
||||
.admonition.use-in {
|
||||
border-color: rgb(203, 147, 1);
|
||||
}
|
||||
.admonition.use-in > .admonition-title {
|
||||
background-color: rgba(176, 144, 60, 0.1);
|
||||
border-color: rgb(203, 147, 1);
|
||||
}
|
||||
.admonition.use-in > .admonition-title::before {
|
||||
background-color: rgb(203, 147, 1);
|
||||
-webkit-mask-image: var(--icon--use-in);
|
||||
mask-image: var(--icon--use-in);
|
||||
}
|
||||
|
||||
.admonition.returned-in > ul:hover, .admonition.available-in > ul:hover, .admonition.use-in > ul:hover, .admonition.shortcuts > ul:hover {
|
||||
cursor: move;
|
||||
}
|
||||
.admonition.returned-in > ul, .admonition.available-in > ul, .admonition.use-in > ul, .admonition.shortcuts > ul {
|
||||
max-height: 200px;
|
||||
overflow-y: scroll;
|
||||
}
|
3
docs/source/_static/style_general.css
Normal file
3
docs/source/_static/style_general.css
Normal file
|
@ -0,0 +1,3 @@
|
|||
.article-container h1 {
|
||||
overflow-wrap: anywhere;
|
||||
}
|
11
docs/source/_static/style_sidebar_brand.css
Normal file
11
docs/source/_static/style_sidebar_brand.css
Normal file
|
@ -0,0 +1,11 @@
|
|||
.sidebar-sticky .sidebar-brand {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.sidebar-sticky .sidebar-brand .sidebar-logo-container {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.sidebar-sticky .sidebar-brand .sidebar-brand-text {
|
||||
align-self: center;
|
||||
}
|
|
@ -1,20 +1,12 @@
|
|||
import inspect
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from enum import Enum
|
||||
from pathlib import Path
|
||||
from typing import List, Tuple
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
from docutils.nodes import Element
|
||||
from sphinx.application import Sphinx
|
||||
from sphinx.domains.python import PyXRefRole
|
||||
from sphinx.environment import BuildEnvironment
|
||||
from sphinx.util import logging
|
||||
|
||||
sys.path.insert(0, os.path.abspath("../.."))
|
||||
|
||||
|
@ -34,7 +26,7 @@ version = "20.0" # telegram.__version__[:3]
|
|||
release = "20.0" # telegram.__version__
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
needs_sphinx = "5.1.1"
|
||||
needs_sphinx = "6.1.3"
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
|
@ -46,6 +38,7 @@ extensions = [
|
|||
"sphinx.ext.linkcode",
|
||||
"sphinx.ext.extlinks",
|
||||
"sphinx_paramlinks",
|
||||
"sphinx_copybutton",
|
||||
"sphinxcontrib.mermaid",
|
||||
"sphinx_search.extension",
|
||||
]
|
||||
|
@ -220,7 +213,13 @@ html_favicon = "ptb-logo_1024.ico"
|
|||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ["_static"]
|
||||
html_css_files = ["style_external_link.css", "style_mermaid_diagrams.css"]
|
||||
html_css_files = [
|
||||
"style_external_link.css",
|
||||
"style_mermaid_diagrams.css",
|
||||
"style_sidebar_brand.css",
|
||||
"style_general.css",
|
||||
"style_admonitions.css",
|
||||
]
|
||||
|
||||
html_permalinks_icon = "¶" # Furo's default permalink icon is `#` which doesn't look great imo.
|
||||
|
||||
|
@ -284,326 +283,16 @@ texinfo_documents = [
|
|||
|
||||
# -- script stuff --------------------------------------------------------
|
||||
|
||||
# get the sphinx(!) logger
|
||||
# Makes sure logs render in red and also plays nicely with e.g. the `nitpicky` option.
|
||||
sphinx_logger = logging.getLogger(__name__)
|
||||
# Due to Sphinx behaviour, these imports only work when imported here, not at top of module.
|
||||
|
||||
CONSTANTS_ROLE = "tg-const"
|
||||
import telegram # We need this so that the `eval` below works
|
||||
|
||||
|
||||
class TGConstXRefRole(PyXRefRole):
|
||||
"""This is a bit of Sphinx magic. We add a new role type called tg-const that allows us to
|
||||
reference values from the `telegram.constants.module` while using the actual value as title
|
||||
of the link.
|
||||
|
||||
Example:
|
||||
|
||||
:tg-const:`telegram.constants.MessageLimit.MAX_TEXT_LENGTH` renders as `4096` but links to the
|
||||
constant.
|
||||
"""
|
||||
|
||||
def process_link(
|
||||
self,
|
||||
env: BuildEnvironment,
|
||||
refnode: Element,
|
||||
has_explicit_title: bool,
|
||||
title: str,
|
||||
target: str,
|
||||
) -> Tuple[str, str]:
|
||||
title, target = super().process_link(env, refnode, has_explicit_title, title, target)
|
||||
try:
|
||||
# We use `eval` to get the value of the expression. Maybe there are better ways to
|
||||
# do this via importlib or so, but it does the job for now
|
||||
value = eval(target)
|
||||
# Maybe we need a better check if the target is actually from tg.constants
|
||||
# for now checking if it's an Enum suffices since those are used nowhere else in PTB
|
||||
if isinstance(value, Enum):
|
||||
# Special casing for file size limits
|
||||
if isinstance(value, telegram.constants.FileSizeLimit):
|
||||
return f"{int(value.value / 1e6)} MB", target
|
||||
return repr(value.value), target
|
||||
# Just for (Bot API) versions number auto add in constants:
|
||||
if isinstance(value, str) and target in (
|
||||
"telegram.constants.BOT_API_VERSION",
|
||||
"telegram.__version__",
|
||||
):
|
||||
return value, target
|
||||
if isinstance(value, tuple) and target in (
|
||||
"telegram.constants.BOT_API_VERSION_INFO",
|
||||
"telegram.__version_info__",
|
||||
):
|
||||
return repr(value), target
|
||||
sphinx_logger.warning(
|
||||
f"%s:%d: WARNING: Did not convert reference %s. :{CONSTANTS_ROLE}: is not supposed"
|
||||
" to be used with this type of target.",
|
||||
refnode.source,
|
||||
refnode.line,
|
||||
refnode.rawsource,
|
||||
)
|
||||
return title, target
|
||||
except Exception as exc:
|
||||
sphinx_logger.exception(
|
||||
"%s:%d: WARNING: Did not convert reference %s due to an exception.",
|
||||
refnode.source,
|
||||
refnode.line,
|
||||
refnode.rawsource,
|
||||
exc_info=exc,
|
||||
)
|
||||
return title, target
|
||||
|
||||
|
||||
def autodoc_skip_member(app, what, name, obj, skip, options):
|
||||
"""We use this to not document certain members like filter() or check_update() for filters.
|
||||
See https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html#skipping-members"""
|
||||
|
||||
included = {"MessageFilter", "UpdateFilter"} # filter() and check_update() only for these.
|
||||
included_in_obj = any(inc in repr(obj) for inc in included)
|
||||
|
||||
if included_in_obj: # it's difficult to see if check_update is from an inherited-member or not
|
||||
for frame in inspect.stack(): # From https://github.com/sphinx-doc/sphinx/issues/9533
|
||||
if frame.function == "filter_members":
|
||||
docobj = frame.frame.f_locals["self"].object
|
||||
if not any(inc in str(docobj) for inc in included) and name == "check_update":
|
||||
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.
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------------------------------
|
||||
# This part is for getting the [source] links on the classes, methods etc link to the correct
|
||||
# files & lines on github. Can be simplified once https://github.com/sphinx-doc/sphinx/issues/1556
|
||||
# is closed
|
||||
|
||||
line_numbers = {}
|
||||
file_root = Path(inspect.getsourcefile(telegram)).parent.parent.resolve()
|
||||
import telegram.ext # Needed for checking if an object is a BaseFilter
|
||||
|
||||
keyword_args = [
|
||||
":keyword _sphinx_paramlinks_telegram.Bot.{method}.read_timeout: Value to pass to :paramref:`telegram.request.BaseRequest.post.read_timeout`. Defaults to {read_timeout}.",
|
||||
":kwtype _sphinx_paramlinks_telegram.Bot.{method}.read_timeout: {read_timeout_type}, optional",
|
||||
":keyword _sphinx_paramlinks_telegram.Bot.{method}.write_timeout: Value to pass to :paramref:`telegram.request.BaseRequest.post.write_timeout`. Defaults to {write_timeout}.",
|
||||
":kwtype _sphinx_paramlinks_telegram.Bot.{method}.write_timeout: :obj:`float` | :obj:`None`, optional",
|
||||
":keyword _sphinx_paramlinks_telegram.Bot.{method}.connect_timeout: Value to pass to :paramref:`telegram.request.BaseRequest.post.connect_timeout`. Defaults to :attr:`~telegram.request.BaseRequest.DEFAULT_NONE`.",
|
||||
":kwtype _sphinx_paramlinks_telegram.Bot.{method}.connect_timeout: :obj:`float` | :obj:`None`, optional",
|
||||
":keyword _sphinx_paramlinks_telegram.Bot.{method}.pool_timeout: Value to pass to :paramref:`telegram.request.BaseRequest.post.pool_timeout`. Defaults to :attr:`~telegram.request.BaseRequest.DEFAULT_NONE`.",
|
||||
":kwtype _sphinx_paramlinks_telegram.Bot.{method}.pool_timeout: :obj:`float` | :obj:`None`, optional",
|
||||
":keyword _sphinx_paramlinks_telegram.Bot.{method}.api_kwargs: Arbitrary keyword arguments to be passed to the Telegram API.",
|
||||
":kwtype _sphinx_paramlinks_telegram.Bot.{method}.api_kwargs: :obj:`dict`, optional",
|
||||
"",
|
||||
]
|
||||
|
||||
write_timeout_sub = [":attr:`~telegram.request.BaseRequest.DEFAULT_NONE`", "``20``"]
|
||||
read_timeout_sub = [
|
||||
":attr:`~telegram.request.BaseRequest.DEFAULT_NONE`.",
|
||||
"``2``. :paramref:`timeout` will be added to this value",
|
||||
]
|
||||
read_timeout_type = [":obj:`float` | :obj:`None`", ":obj:`float`"]
|
||||
|
||||
|
||||
def find_insert_pos(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
|
||||
|
||||
|
||||
def is_write_timeout_20(obj: object) -> int:
|
||||
"""inspects the default value of write_timeout parameter of the bot method."""
|
||||
sig = inspect.signature(obj)
|
||||
return 1 if (sig.parameters["write_timeout"].default == 20) else 0
|
||||
|
||||
|
||||
def check_timeout_and_api_kwargs_presence(obj: object) -> int:
|
||||
"""Checks if the method has timeout and api_kwargs keyword only parameters."""
|
||||
sig = inspect.signature(obj)
|
||||
params_to_check = (
|
||||
"read_timeout",
|
||||
"write_timeout",
|
||||
"connect_timeout",
|
||||
"pool_timeout",
|
||||
"api_kwargs",
|
||||
)
|
||||
return all(
|
||||
param in sig.parameters and sig.parameters[param].kind == inspect.Parameter.KEYWORD_ONLY
|
||||
for param in params_to_check
|
||||
)
|
||||
|
||||
|
||||
def autodoc_process_docstring(
|
||||
app: Sphinx, what, name: str, obj: object, options, lines: List[str]
|
||||
):
|
||||
"""We do two things:
|
||||
1) Use this method to automatically insert the Keyword Args for the Bot methods.
|
||||
|
||||
2) Misuse this autodoc hook to get the file names & line numbers because we have access
|
||||
to the actual object here.
|
||||
"""
|
||||
# 1) Insert the Keyword Args for the Bot methods
|
||||
method_name = name.split(".")[-1]
|
||||
if (
|
||||
name.startswith("telegram.Bot.")
|
||||
and what == "method"
|
||||
and method_name.islower()
|
||||
and check_timeout_and_api_kwargs_presence(obj)
|
||||
):
|
||||
insert_index = find_insert_pos(lines)
|
||||
if not insert_index:
|
||||
raise ValueError(
|
||||
f"Couldn't find the correct position to insert the keyword args for {obj}."
|
||||
)
|
||||
|
||||
long_write_timeout = is_write_timeout_20(obj)
|
||||
get_updates_sub = 1 if (method_name == "get_updates") else 0
|
||||
# The below can be done in 1 line with itertools.chain, but this must be modified in-place
|
||||
for i in range(insert_index, insert_index + len(keyword_args)):
|
||||
lines.insert(
|
||||
i,
|
||||
keyword_args[i - insert_index].format(
|
||||
method=method_name,
|
||||
write_timeout=write_timeout_sub[long_write_timeout],
|
||||
read_timeout=read_timeout_sub[get_updates_sub],
|
||||
read_timeout_type=read_timeout_type[get_updates_sub],
|
||||
),
|
||||
)
|
||||
|
||||
# 2) Get the file names & line numbers
|
||||
# We can't properly handle ordinary attributes.
|
||||
# In linkcode_resolve we'll resolve to the `__init__` or module instead
|
||||
if what == "attribute":
|
||||
return
|
||||
|
||||
# Special casing for properties
|
||||
if hasattr(obj, "fget"):
|
||||
obj = obj.fget
|
||||
|
||||
# Special casing for filters
|
||||
if isinstance(obj, telegram.ext.filters.BaseFilter):
|
||||
obj = obj.__class__
|
||||
|
||||
try:
|
||||
source_lines, start_line = inspect.getsourcelines(obj)
|
||||
end_line = start_line + len(source_lines)
|
||||
file = Path(inspect.getsourcefile(obj)).relative_to(file_root)
|
||||
line_numbers[name] = (file, start_line, end_line)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Since we don't document the `__init__`, we call this manually to have it available for
|
||||
# attributes -- see the note above
|
||||
if what == "class":
|
||||
autodoc_process_docstring(app, "method", f"{name}.__init__", obj.__init__, options, lines)
|
||||
|
||||
|
||||
def _git_branch() -> str:
|
||||
"""Get's the current git sha if available or fall back to `master`"""
|
||||
try:
|
||||
output = subprocess.check_output( # skipcq: BAN-B607
|
||||
["git", "describe", "--tags", "--always"], stderr=subprocess.STDOUT
|
||||
)
|
||||
return output.decode().strip()
|
||||
except Exception as exc:
|
||||
sphinx_logger.exception(
|
||||
"Failed to get a description of the current commit. Falling back to `master`.",
|
||||
exc_info=exc,
|
||||
)
|
||||
return "master"
|
||||
|
||||
|
||||
git_branch = _git_branch()
|
||||
base_url = "https://github.com/python-telegram-bot/python-telegram-bot/blob/"
|
||||
|
||||
|
||||
def linkcode_resolve(_, info):
|
||||
"""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
|
||||
combined = combined.replace("ExtBot.ExtBot", "ExtBot")
|
||||
|
||||
line_info = line_numbers.get(combined)
|
||||
|
||||
if not line_info:
|
||||
# Try the __init__
|
||||
line_info = line_numbers.get(f"{combined.rsplit('.', 1)[0]}.__init__")
|
||||
if not line_info:
|
||||
# Try the class
|
||||
line_info = line_numbers.get(f"{combined.rsplit('.', 1)[0]}")
|
||||
if not line_info:
|
||||
# Try the module
|
||||
line_info = line_numbers.get(info["module"])
|
||||
|
||||
if not line_info:
|
||||
return
|
||||
|
||||
file, start_line, end_line = line_info
|
||||
return f"{base_url}{git_branch}/{file}#L{start_line}-L{end_line}"
|
||||
|
||||
|
||||
# End of logic for the [source] links
|
||||
# ------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
# Some base classes are implementation detail
|
||||
# We want to instead show *their* base class
|
||||
PRIVATE_BASE_CLASSES = {
|
||||
"_ChatUserBaseFilter": "MessageFilter",
|
||||
"_Dice": "MessageFilter",
|
||||
"_BaseThumbedMedium": "TelegramObject",
|
||||
"_BaseMedium": "TelegramObject",
|
||||
"_CredentialsBase": "TelegramObject",
|
||||
}
|
||||
|
||||
|
||||
def autodoc_process_bases(app, name, obj, option, bases: list):
|
||||
"""Here we fine tune how the base class's classes are displayed."""
|
||||
for idx, base in enumerate(bases):
|
||||
# let's use a string representation of the object
|
||||
base = str(base)
|
||||
|
||||
# Special case for abstract context managers which are wrongly resoled for some reason
|
||||
if base.startswith("typing.AbstractAsyncContextManager"):
|
||||
bases[idx] = ":class:`contextlib.AbstractAsyncContextManager`"
|
||||
continue
|
||||
|
||||
# Special case because base classes are in std lib:
|
||||
if "StringEnum" in base == "<enum 'StringEnum'>":
|
||||
bases[idx] = ":class:`enum.Enum`"
|
||||
bases.insert(0, ":class:`str`")
|
||||
continue
|
||||
|
||||
if "IntEnum" in base:
|
||||
bases[idx] = ":class:`enum.IntEnum`"
|
||||
continue
|
||||
|
||||
# Drop generics (at least for now)
|
||||
if base.endswith("]"):
|
||||
base = base.split("[", maxsplit=1)[0]
|
||||
bases[idx] = f":class:`{base}`"
|
||||
|
||||
# Now convert `telegram._message.Message` to `telegram.Message` etc
|
||||
match = re.search(pattern=r"(telegram(\.ext|))\.[_\w\.]+", string=base)
|
||||
if not match or "_utils" in base:
|
||||
continue
|
||||
|
||||
parts = match.group(0).split(".")
|
||||
|
||||
# Remove private paths
|
||||
for index, part in enumerate(parts):
|
||||
if part.startswith("_"):
|
||||
parts = parts[:index] + parts[-1:]
|
||||
break
|
||||
|
||||
# Replace private base classes with their respective parent
|
||||
parts = [PRIVATE_BASE_CLASSES.get(part, part) for part in parts]
|
||||
|
||||
base = ".".join(parts)
|
||||
|
||||
bases[idx] = f":class:`{base}`"
|
||||
# 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 (
|
||||
autodoc_process_bases,
|
||||
autodoc_process_docstring,
|
||||
autodoc_skip_member,
|
||||
)
|
||||
from docs.auxil.tg_const_role import CONSTANTS_ROLE, TGConstXRefRole
|
||||
|
||||
|
||||
def setup(app: Sphinx):
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.Animation
|
||||
==================
|
||||
Animation
|
||||
=========
|
||||
|
||||
.. Also lists methods of _BaseThumbedMedium, but not the ones of TelegramObject
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.Audio
|
||||
==============
|
||||
Audio
|
||||
=====
|
||||
|
||||
.. Also lists methods of _BaseThumbedMedium, but not the ones of TelegramObject
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.Bot
|
||||
============
|
||||
Bot
|
||||
===
|
||||
|
||||
.. autoclass:: telegram.Bot
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.BotCommand
|
||||
===================
|
||||
BotCommand
|
||||
==========
|
||||
|
||||
.. autoclass:: telegram.BotCommand
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.BotCommandScope
|
||||
========================
|
||||
BotCommandScope
|
||||
===============
|
||||
|
||||
.. autoclass:: telegram.BotCommandScope
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.BotCommandScopeAllChatAdministrators
|
||||
=============================================
|
||||
BotCommandScopeAllChatAdministrators
|
||||
====================================
|
||||
|
||||
.. autoclass:: telegram.BotCommandScopeAllChatAdministrators
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.BotCommandScopeAllGroupChats
|
||||
=======================================
|
||||
BotCommandScopeAllGroupChats
|
||||
============================
|
||||
|
||||
.. autoclass:: telegram.BotCommandScopeAllGroupChats
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.BotCommandScopeAllPrivateChats
|
||||
=======================================
|
||||
BotCommandScopeAllPrivateChats
|
||||
==============================
|
||||
|
||||
.. autoclass:: telegram.BotCommandScopeAllPrivateChats
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.BotCommandScopeChat
|
||||
============================
|
||||
BotCommandScopeChat
|
||||
===================
|
||||
|
||||
.. autoclass:: telegram.BotCommandScopeChat
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.BotCommandScopeChatAdministrators
|
||||
==========================================
|
||||
BotCommandScopeChatAdministrators
|
||||
=================================
|
||||
|
||||
.. autoclass:: telegram.BotCommandScopeChatAdministrators
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.BotCommandScopeChatMember
|
||||
==================================
|
||||
BotCommandScopeChatMember
|
||||
=========================
|
||||
|
||||
.. autoclass:: telegram.BotCommandScopeChatMember
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.BotCommandScopeDefault
|
||||
===============================
|
||||
BotCommandScopeDefault
|
||||
======================
|
||||
|
||||
.. autoclass:: telegram.BotCommandScopeDefault
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.Callbackgame
|
||||
=====================
|
||||
Callbackgame
|
||||
============
|
||||
|
||||
.. autoclass:: telegram.CallbackGame
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.CallbackQuery
|
||||
======================
|
||||
CallbackQuery
|
||||
=============
|
||||
|
||||
.. autoclass:: telegram.CallbackQuery
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.Chat
|
||||
=============
|
||||
Chat
|
||||
====
|
||||
|
||||
.. autoclass:: telegram.Chat
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.ChatAdministratorRights
|
||||
================================
|
||||
ChatAdministratorRights
|
||||
=======================
|
||||
|
||||
.. versionadded:: 20.0
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.ChatInviteLink
|
||||
=======================
|
||||
ChatInviteLink
|
||||
==============
|
||||
|
||||
.. autoclass:: telegram.ChatInviteLink
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.ChatJoinRequest
|
||||
========================
|
||||
ChatJoinRequest
|
||||
===============
|
||||
|
||||
.. autoclass:: telegram.ChatJoinRequest
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.ChatLocation
|
||||
=====================
|
||||
ChatLocation
|
||||
============
|
||||
|
||||
.. autoclass:: telegram.ChatLocation
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.ChatMember
|
||||
===================
|
||||
ChatMember
|
||||
==========
|
||||
|
||||
.. autoclass:: telegram.ChatMember
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.ChatMemberAdministrator
|
||||
================================
|
||||
ChatMemberAdministrator
|
||||
=======================
|
||||
|
||||
.. autoclass:: telegram.ChatMemberAdministrator
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.ChatMemberBanned
|
||||
=========================
|
||||
ChatMemberBanned
|
||||
================
|
||||
|
||||
.. autoclass:: telegram.ChatMemberBanned
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.ChatMemberLeft
|
||||
=======================
|
||||
ChatMemberLeft
|
||||
==============
|
||||
|
||||
.. autoclass:: telegram.ChatMemberLeft
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.ChatMemberMember
|
||||
=========================
|
||||
ChatMemberMember
|
||||
================
|
||||
|
||||
.. autoclass:: telegram.ChatMemberMember
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.ChatMemberOwner
|
||||
========================
|
||||
ChatMemberOwner
|
||||
===============
|
||||
|
||||
.. autoclass:: telegram.ChatMemberOwner
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.ChatMemberRestricted
|
||||
=============================
|
||||
ChatMemberRestricted
|
||||
====================
|
||||
|
||||
.. autoclass:: telegram.ChatMemberRestricted
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.ChatMemberUpdated
|
||||
==========================
|
||||
ChatMemberUpdated
|
||||
=================
|
||||
|
||||
.. autoclass:: telegram.ChatMemberUpdated
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.ChatPermissions
|
||||
========================
|
||||
ChatPermissions
|
||||
===============
|
||||
|
||||
.. autoclass:: telegram.ChatPermissions
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.ChatPhoto
|
||||
==================
|
||||
ChatPhoto
|
||||
=========
|
||||
|
||||
.. autoclass:: telegram.ChatPhoto
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.ChosenInlineResult
|
||||
===========================
|
||||
ChosenInlineResult
|
||||
==================
|
||||
|
||||
.. autoclass:: telegram.ChosenInlineResult
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.Contact
|
||||
================
|
||||
Contact
|
||||
=======
|
||||
|
||||
.. autoclass:: telegram.Contact
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.Credentials
|
||||
====================
|
||||
Credentials
|
||||
===========
|
||||
|
||||
.. autoclass:: telegram.Credentials
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.DataCredentials
|
||||
========================
|
||||
DataCredentials
|
||||
===============
|
||||
|
||||
.. autoclass:: telegram.DataCredentials
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.Dice
|
||||
=============
|
||||
Dice
|
||||
====
|
||||
|
||||
.. autoclass:: telegram.Dice
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.Document
|
||||
=================
|
||||
Document
|
||||
========
|
||||
.. Also lists methods of _BaseThumbedMedium, but not the ones of TelegramObject
|
||||
|
||||
.. autoclass:: telegram.Document
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.EncryptedCredentials
|
||||
=============================
|
||||
EncryptedCredentials
|
||||
====================
|
||||
|
||||
.. autoclass:: telegram.EncryptedCredentials
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.EncryptedPassportElement
|
||||
=================================
|
||||
EncryptedPassportElement
|
||||
========================
|
||||
|
||||
.. autoclass:: telegram.EncryptedPassportElement
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.ext.AIORateLimiter
|
||||
============================
|
||||
AIORateLimiter
|
||||
==============
|
||||
|
||||
.. autoclass:: telegram.ext.AIORateLimiter
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.ext.Application
|
||||
========================
|
||||
Application
|
||||
===========
|
||||
|
||||
.. autoclass:: telegram.ext.Application
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.ext.ApplicationBuilder
|
||||
===============================
|
||||
ApplicationBuilder
|
||||
==================
|
||||
|
||||
.. autoclass:: telegram.ext.ApplicationBuilder
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.ext.ApplicationHandlerStop
|
||||
===================================
|
||||
ApplicationHandlerStop
|
||||
======================
|
||||
|
||||
.. autoclass:: telegram.ext.ApplicationHandlerStop
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.ext.BaseHandler
|
||||
========================
|
||||
BaseHandler
|
||||
===========
|
||||
|
||||
.. autoclass:: telegram.ext.BaseHandler
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.ext.BasePersistence
|
||||
============================
|
||||
BasePersistence
|
||||
===============
|
||||
|
||||
.. autoclass:: telegram.ext.BasePersistence
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.ext.BaseRateLimiter
|
||||
============================
|
||||
BaseRateLimiter
|
||||
===============
|
||||
|
||||
.. autoclass:: telegram.ext.BaseRateLimiter
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.ext.CallbackContext
|
||||
============================
|
||||
CallbackContext
|
||||
===============
|
||||
|
||||
.. autoclass:: telegram.ext.CallbackContext
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.ext.CallbackDataCache
|
||||
==============================
|
||||
CallbackDataCache
|
||||
=================
|
||||
|
||||
.. autoclass:: telegram.ext.CallbackDataCache
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.ext.CallbackQueryHandler
|
||||
=================================
|
||||
CallbackQueryHandler
|
||||
====================
|
||||
|
||||
.. autoclass:: telegram.ext.CallbackQueryHandler
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.ext.ChatJoinRequestHandler
|
||||
===================================
|
||||
ChatJoinRequestHandler
|
||||
======================
|
||||
|
||||
.. autoclass:: telegram.ext.ChatJoinRequestHandler
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.ext.ChatMemberHandler
|
||||
==============================
|
||||
ChatMemberHandler
|
||||
=================
|
||||
|
||||
.. autoclass:: telegram.ext.ChatMemberHandler
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.ext.ChosenInlineResultHandler
|
||||
======================================
|
||||
ChosenInlineResultHandler
|
||||
=========================
|
||||
|
||||
.. autoclass:: telegram.ext.ChosenInlineResultHandler
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.ext.CommandHandler
|
||||
===========================
|
||||
CommandHandler
|
||||
==============
|
||||
|
||||
.. autoclass:: telegram.ext.CommandHandler
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.ext.ContextTypes
|
||||
=========================
|
||||
ContextTypes
|
||||
============
|
||||
|
||||
.. autoclass:: telegram.ext.ContextTypes
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.ext.ConversationHandler
|
||||
================================
|
||||
ConversationHandler
|
||||
===================
|
||||
|
||||
.. autoclass:: telegram.ext.ConversationHandler
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.ext.Defaults
|
||||
=====================
|
||||
Defaults
|
||||
========
|
||||
|
||||
.. autoclass:: telegram.ext.Defaults
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.ext.DictPersistence
|
||||
============================
|
||||
DictPersistence
|
||||
===============
|
||||
|
||||
.. autoclass:: telegram.ext.DictPersistence
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.ext.ExtBot
|
||||
===================
|
||||
ExtBot
|
||||
======
|
||||
|
||||
.. autoclass:: telegram.ext.ExtBot
|
||||
:show-inheritance:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.ext.filters Module
|
||||
===========================
|
||||
filters Module
|
||||
==============
|
||||
|
||||
.. :bysource: since e.g filters.CHAT is much above filters.Chat() in the docs when it shouldn't.
|
||||
The classes in `filters.py` are sorted alphabetically such that :bysource: still is readable
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.ext.InlineQueryHandler
|
||||
===============================
|
||||
InlineQueryHandler
|
||||
==================
|
||||
|
||||
.. autoclass:: telegram.ext.InlineQueryHandler
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.ext.InvalidCallbackData
|
||||
================================
|
||||
InvalidCallbackData
|
||||
===================
|
||||
|
||||
.. autoclass:: telegram.ext.InvalidCallbackData
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.ext.Job
|
||||
=====================
|
||||
Job
|
||||
===
|
||||
|
||||
.. autoclass:: telegram.ext.Job
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.ext.JobQueue
|
||||
=====================
|
||||
JobQueue
|
||||
========
|
||||
|
||||
.. autoclass:: telegram.ext.JobQueue
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.ext.MessageHandler
|
||||
===========================
|
||||
MessageHandler
|
||||
==============
|
||||
|
||||
.. autoclass:: telegram.ext.MessageHandler
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.ext.PersistenceInput
|
||||
=============================
|
||||
PersistenceInput
|
||||
================
|
||||
|
||||
.. autoclass:: telegram.ext.PersistenceInput
|
||||
:show-inheritance:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.ext.PicklePersistence
|
||||
==============================
|
||||
PicklePersistence
|
||||
=================
|
||||
|
||||
.. autoclass:: telegram.ext.PicklePersistence
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.ext.PollAnswerHandler
|
||||
==============================
|
||||
PollAnswerHandler
|
||||
=================
|
||||
|
||||
.. autoclass:: telegram.ext.PollAnswerHandler
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.ext.PollHandler
|
||||
========================
|
||||
PollHandler
|
||||
===========
|
||||
|
||||
.. autoclass:: telegram.ext.PollHandler
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.ext.PreCheckoutQueryHandler
|
||||
====================================
|
||||
PreCheckoutQueryHandler
|
||||
=======================
|
||||
|
||||
.. autoclass:: telegram.ext.PreCheckoutQueryHandler
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.ext.PrefixHandler
|
||||
===========================
|
||||
PrefixHandler
|
||||
=============
|
||||
|
||||
.. autoclass:: telegram.ext.PrefixHandler
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.ext.ShippingQueryHandler
|
||||
=================================
|
||||
ShippingQueryHandler
|
||||
====================
|
||||
|
||||
.. autoclass:: telegram.ext.ShippingQueryHandler
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.ext.StringCommandHandler
|
||||
=================================
|
||||
StringCommandHandler
|
||||
====================
|
||||
|
||||
.. autoclass:: telegram.ext.StringCommandHandler
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.ext.StringRegexHandler
|
||||
===============================
|
||||
StringRegexHandler
|
||||
==================
|
||||
|
||||
.. autoclass:: telegram.ext.StringRegexHandler
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.ext.TypeHandler
|
||||
========================
|
||||
TypeHandler
|
||||
===========
|
||||
|
||||
.. autoclass:: telegram.ext.TypeHandler
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.ext.Updater
|
||||
====================
|
||||
Updater
|
||||
=======
|
||||
|
||||
.. autoclass:: telegram.ext.Updater
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.File
|
||||
=============
|
||||
File
|
||||
====
|
||||
|
||||
.. autoclass:: telegram.File
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.FileCredentials
|
||||
========================
|
||||
FileCredentials
|
||||
===============
|
||||
|
||||
.. autoclass:: telegram.FileCredentials
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.ForceReply
|
||||
===================
|
||||
ForceReply
|
||||
==========
|
||||
|
||||
.. autoclass:: telegram.ForceReply
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.ForumTopic
|
||||
===================
|
||||
ForumTopic
|
||||
==========
|
||||
|
||||
.. autoclass:: telegram.ForumTopic
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.ForumTopicClosed
|
||||
=========================
|
||||
ForumTopicClosed
|
||||
================
|
||||
|
||||
.. autoclass:: telegram.ForumTopicClosed
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.ForumTopicCreated
|
||||
==========================
|
||||
ForumTopicCreated
|
||||
=================
|
||||
|
||||
.. autoclass:: telegram.ForumTopicCreated
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.ForumTopicEdited
|
||||
=========================
|
||||
ForumTopicEdited
|
||||
================
|
||||
|
||||
.. autoclass:: telegram.ForumTopicEdited
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.ForumTopicReopened
|
||||
===========================
|
||||
ForumTopicReopened
|
||||
==================
|
||||
|
||||
.. autoclass:: telegram.ForumTopicReopened
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.Game
|
||||
=============
|
||||
Game
|
||||
====
|
||||
|
||||
.. autoclass:: telegram.Game
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.GameHighScore
|
||||
======================
|
||||
GameHighScore
|
||||
=============
|
||||
|
||||
.. autoclass:: telegram.GameHighScore
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.GeneralForumTopicHidden
|
||||
================================
|
||||
GeneralForumTopicHidden
|
||||
=======================
|
||||
|
||||
.. autoclass:: telegram.GeneralForumTopicHidden
|
||||
:members:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
telegram.GeneralForumTopicUnhidden
|
||||
==================================
|
||||
GeneralForumTopicUnhidden
|
||||
=========================
|
||||
|
||||
.. autoclass:: telegram.GeneralForumTopicUnhidden
|
||||
:members:
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue