mirror of
https://github.com/python-telegram-bot/python-telegram-bot.git
synced 2024-12-22 14:35:00 +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
|
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
|
.. code-block:: bash
|
||||||
|
|
||||||
|
|
2
.github/workflows/docs.yml
vendored
2
.github/workflows/docs.yml
vendored
|
@ -30,6 +30,8 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
python -W ignore -m pip install --upgrade pip
|
python -W ignore -m pip install --upgrade pip
|
||||||
python -W ignore -m pip install -r requirements-all.txt
|
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
|
- name: Build docs
|
||||||
run: sphinx-build docs/source docs/build/html -W --keep-going -j auto
|
run: sphinx-build docs/source docs/build/html -W --keep-going -j auto
|
||||||
- name: Upload docs
|
- name: Upload docs
|
||||||
|
|
|
@ -68,7 +68,7 @@ repos:
|
||||||
rev: v3.3.1
|
rev: v3.3.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: pyupgrade
|
- id: pyupgrade
|
||||||
files: ^(telegram|examples|tests)/.*\.py$
|
files: ^(telegram|examples|tests|docs)/.*\.py$
|
||||||
args:
|
args:
|
||||||
- --py37-plus
|
- --py37-plus
|
||||||
- repo: https://github.com/pycqa/isort
|
- 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>`_
|
- `Sahil Sharma <https://github.com/sahilsharma811>`_
|
||||||
- `Sascha <https://github.com/saschalalala>`_
|
- `Sascha <https://github.com/saschalalala>`_
|
||||||
- `Shelomentsev D <https://github.com/shelomentsevd>`_
|
- `Shelomentsev D <https://github.com/shelomentsevd>`_
|
||||||
|
- `Shivam Saini <https://github.com/shivamsn97>`_
|
||||||
- `Simon Schürrle <https://github.com/SitiSchu>`_
|
- `Simon Schürrle <https://github.com/SitiSchu>`_
|
||||||
- `sooyhwang <https://github.com/sooyhwang>`_
|
- `sooyhwang <https://github.com/sooyhwang>`_
|
||||||
- `syntx <https://github.com/syntx>`_
|
- `syntx <https://github.com/syntx>`_
|
||||||
|
|
|
@ -56,6 +56,8 @@ html:
|
||||||
@echo
|
@echo
|
||||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
||||||
|
|
||||||
|
rebuild: clean html
|
||||||
|
|
||||||
dirhtml:
|
dirhtml:
|
||||||
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
||||||
@echo
|
@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
|
sphinx-pypi-upload
|
||||||
furo==2022.12.7
|
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
|
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 os
|
||||||
import re
|
import re
|
||||||
import subprocess
|
|
||||||
import sys
|
import sys
|
||||||
from enum import Enum
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import List, Tuple
|
|
||||||
|
|
||||||
# If extensions (or modules to document with autodoc) are in another directory,
|
# 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
|
# 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.
|
# 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.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("../.."))
|
sys.path.insert(0, os.path.abspath("../.."))
|
||||||
|
|
||||||
|
@ -34,7 +26,7 @@ version = "20.0" # telegram.__version__[:3]
|
||||||
release = "20.0" # telegram.__version__
|
release = "20.0" # telegram.__version__
|
||||||
|
|
||||||
# If your documentation needs a minimal Sphinx version, state it here.
|
# 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
|
# Add any Sphinx extension module names here, as strings. They can be
|
||||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||||
|
@ -46,6 +38,7 @@ extensions = [
|
||||||
"sphinx.ext.linkcode",
|
"sphinx.ext.linkcode",
|
||||||
"sphinx.ext.extlinks",
|
"sphinx.ext.extlinks",
|
||||||
"sphinx_paramlinks",
|
"sphinx_paramlinks",
|
||||||
|
"sphinx_copybutton",
|
||||||
"sphinxcontrib.mermaid",
|
"sphinxcontrib.mermaid",
|
||||||
"sphinx_search.extension",
|
"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,
|
# relative to this directory. They are copied after the builtin static files,
|
||||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||||
html_static_path = ["_static"]
|
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.
|
html_permalinks_icon = "¶" # Furo's default permalink icon is `#` which doesn't look great imo.
|
||||||
|
|
||||||
|
@ -284,326 +283,16 @@ texinfo_documents = [
|
||||||
|
|
||||||
# -- script stuff --------------------------------------------------------
|
# -- script stuff --------------------------------------------------------
|
||||||
|
|
||||||
# get the sphinx(!) logger
|
# Due to Sphinx behaviour, these imports only work when imported here, not at top of module.
|
||||||
# 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"
|
# Not used but must be imported for the linkcode extension to find it
|
||||||
import telegram # We need this so that the `eval` below works
|
from docs.auxil.link_code import linkcode_resolve
|
||||||
|
from docs.auxil.sphinx_hooks import (
|
||||||
|
autodoc_process_bases,
|
||||||
class TGConstXRefRole(PyXRefRole):
|
autodoc_process_docstring,
|
||||||
"""This is a bit of Sphinx magic. We add a new role type called tg-const that allows us to
|
autodoc_skip_member,
|
||||||
reference values from the `telegram.constants.module` while using the actual value as title
|
)
|
||||||
of the link.
|
from docs.auxil.tg_const_role import CONSTANTS_ROLE, TGConstXRefRole
|
||||||
|
|
||||||
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}`"
|
|
||||||
|
|
||||||
|
|
||||||
def setup(app: Sphinx):
|
def setup(app: Sphinx):
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.Animation
|
Animation
|
||||||
==================
|
=========
|
||||||
|
|
||||||
.. Also lists methods of _BaseThumbedMedium, but not the ones of TelegramObject
|
.. 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
|
.. Also lists methods of _BaseThumbedMedium, but not the ones of TelegramObject
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.Bot
|
Bot
|
||||||
============
|
===
|
||||||
|
|
||||||
.. autoclass:: telegram.Bot
|
.. autoclass:: telegram.Bot
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.BotCommand
|
BotCommand
|
||||||
===================
|
==========
|
||||||
|
|
||||||
.. autoclass:: telegram.BotCommand
|
.. autoclass:: telegram.BotCommand
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.BotCommandScope
|
BotCommandScope
|
||||||
========================
|
===============
|
||||||
|
|
||||||
.. autoclass:: telegram.BotCommandScope
|
.. autoclass:: telegram.BotCommandScope
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.BotCommandScopeAllChatAdministrators
|
BotCommandScopeAllChatAdministrators
|
||||||
=============================================
|
====================================
|
||||||
|
|
||||||
.. autoclass:: telegram.BotCommandScopeAllChatAdministrators
|
.. autoclass:: telegram.BotCommandScopeAllChatAdministrators
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.BotCommandScopeAllGroupChats
|
BotCommandScopeAllGroupChats
|
||||||
=======================================
|
============================
|
||||||
|
|
||||||
.. autoclass:: telegram.BotCommandScopeAllGroupChats
|
.. autoclass:: telegram.BotCommandScopeAllGroupChats
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.BotCommandScopeAllPrivateChats
|
BotCommandScopeAllPrivateChats
|
||||||
=======================================
|
==============================
|
||||||
|
|
||||||
.. autoclass:: telegram.BotCommandScopeAllPrivateChats
|
.. autoclass:: telegram.BotCommandScopeAllPrivateChats
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.BotCommandScopeChat
|
BotCommandScopeChat
|
||||||
============================
|
===================
|
||||||
|
|
||||||
.. autoclass:: telegram.BotCommandScopeChat
|
.. autoclass:: telegram.BotCommandScopeChat
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.BotCommandScopeChatAdministrators
|
BotCommandScopeChatAdministrators
|
||||||
==========================================
|
=================================
|
||||||
|
|
||||||
.. autoclass:: telegram.BotCommandScopeChatAdministrators
|
.. autoclass:: telegram.BotCommandScopeChatAdministrators
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.BotCommandScopeChatMember
|
BotCommandScopeChatMember
|
||||||
==================================
|
=========================
|
||||||
|
|
||||||
.. autoclass:: telegram.BotCommandScopeChatMember
|
.. autoclass:: telegram.BotCommandScopeChatMember
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.BotCommandScopeDefault
|
BotCommandScopeDefault
|
||||||
===============================
|
======================
|
||||||
|
|
||||||
.. autoclass:: telegram.BotCommandScopeDefault
|
.. autoclass:: telegram.BotCommandScopeDefault
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.Callbackgame
|
Callbackgame
|
||||||
=====================
|
============
|
||||||
|
|
||||||
.. autoclass:: telegram.CallbackGame
|
.. autoclass:: telegram.CallbackGame
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.CallbackQuery
|
CallbackQuery
|
||||||
======================
|
=============
|
||||||
|
|
||||||
.. autoclass:: telegram.CallbackQuery
|
.. autoclass:: telegram.CallbackQuery
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.Chat
|
Chat
|
||||||
=============
|
====
|
||||||
|
|
||||||
.. autoclass:: telegram.Chat
|
.. autoclass:: telegram.Chat
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.ChatAdministratorRights
|
ChatAdministratorRights
|
||||||
================================
|
=======================
|
||||||
|
|
||||||
.. versionadded:: 20.0
|
.. versionadded:: 20.0
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.ChatInviteLink
|
ChatInviteLink
|
||||||
=======================
|
==============
|
||||||
|
|
||||||
.. autoclass:: telegram.ChatInviteLink
|
.. autoclass:: telegram.ChatInviteLink
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.ChatJoinRequest
|
ChatJoinRequest
|
||||||
========================
|
===============
|
||||||
|
|
||||||
.. autoclass:: telegram.ChatJoinRequest
|
.. autoclass:: telegram.ChatJoinRequest
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.ChatLocation
|
ChatLocation
|
||||||
=====================
|
============
|
||||||
|
|
||||||
.. autoclass:: telegram.ChatLocation
|
.. autoclass:: telegram.ChatLocation
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.ChatMember
|
ChatMember
|
||||||
===================
|
==========
|
||||||
|
|
||||||
.. autoclass:: telegram.ChatMember
|
.. autoclass:: telegram.ChatMember
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.ChatMemberAdministrator
|
ChatMemberAdministrator
|
||||||
================================
|
=======================
|
||||||
|
|
||||||
.. autoclass:: telegram.ChatMemberAdministrator
|
.. autoclass:: telegram.ChatMemberAdministrator
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.ChatMemberBanned
|
ChatMemberBanned
|
||||||
=========================
|
================
|
||||||
|
|
||||||
.. autoclass:: telegram.ChatMemberBanned
|
.. autoclass:: telegram.ChatMemberBanned
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.ChatMemberLeft
|
ChatMemberLeft
|
||||||
=======================
|
==============
|
||||||
|
|
||||||
.. autoclass:: telegram.ChatMemberLeft
|
.. autoclass:: telegram.ChatMemberLeft
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.ChatMemberMember
|
ChatMemberMember
|
||||||
=========================
|
================
|
||||||
|
|
||||||
.. autoclass:: telegram.ChatMemberMember
|
.. autoclass:: telegram.ChatMemberMember
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.ChatMemberOwner
|
ChatMemberOwner
|
||||||
========================
|
===============
|
||||||
|
|
||||||
.. autoclass:: telegram.ChatMemberOwner
|
.. autoclass:: telegram.ChatMemberOwner
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.ChatMemberRestricted
|
ChatMemberRestricted
|
||||||
=============================
|
====================
|
||||||
|
|
||||||
.. autoclass:: telegram.ChatMemberRestricted
|
.. autoclass:: telegram.ChatMemberRestricted
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.ChatMemberUpdated
|
ChatMemberUpdated
|
||||||
==========================
|
=================
|
||||||
|
|
||||||
.. autoclass:: telegram.ChatMemberUpdated
|
.. autoclass:: telegram.ChatMemberUpdated
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.ChatPermissions
|
ChatPermissions
|
||||||
========================
|
===============
|
||||||
|
|
||||||
.. autoclass:: telegram.ChatPermissions
|
.. autoclass:: telegram.ChatPermissions
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.ChatPhoto
|
ChatPhoto
|
||||||
==================
|
=========
|
||||||
|
|
||||||
.. autoclass:: telegram.ChatPhoto
|
.. autoclass:: telegram.ChatPhoto
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.ChosenInlineResult
|
ChosenInlineResult
|
||||||
===========================
|
==================
|
||||||
|
|
||||||
.. autoclass:: telegram.ChosenInlineResult
|
.. autoclass:: telegram.ChosenInlineResult
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.Contact
|
Contact
|
||||||
================
|
=======
|
||||||
|
|
||||||
.. autoclass:: telegram.Contact
|
.. autoclass:: telegram.Contact
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.Credentials
|
Credentials
|
||||||
====================
|
===========
|
||||||
|
|
||||||
.. autoclass:: telegram.Credentials
|
.. autoclass:: telegram.Credentials
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.DataCredentials
|
DataCredentials
|
||||||
========================
|
===============
|
||||||
|
|
||||||
.. autoclass:: telegram.DataCredentials
|
.. autoclass:: telegram.DataCredentials
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.Dice
|
Dice
|
||||||
=============
|
====
|
||||||
|
|
||||||
.. autoclass:: telegram.Dice
|
.. autoclass:: telegram.Dice
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.Document
|
Document
|
||||||
=================
|
========
|
||||||
.. Also lists methods of _BaseThumbedMedium, but not the ones of TelegramObject
|
.. Also lists methods of _BaseThumbedMedium, but not the ones of TelegramObject
|
||||||
|
|
||||||
.. autoclass:: telegram.Document
|
.. autoclass:: telegram.Document
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.EncryptedCredentials
|
EncryptedCredentials
|
||||||
=============================
|
====================
|
||||||
|
|
||||||
.. autoclass:: telegram.EncryptedCredentials
|
.. autoclass:: telegram.EncryptedCredentials
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.EncryptedPassportElement
|
EncryptedPassportElement
|
||||||
=================================
|
========================
|
||||||
|
|
||||||
.. autoclass:: telegram.EncryptedPassportElement
|
.. autoclass:: telegram.EncryptedPassportElement
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.ext.AIORateLimiter
|
AIORateLimiter
|
||||||
============================
|
==============
|
||||||
|
|
||||||
.. autoclass:: telegram.ext.AIORateLimiter
|
.. autoclass:: telegram.ext.AIORateLimiter
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.ext.Application
|
Application
|
||||||
========================
|
===========
|
||||||
|
|
||||||
.. autoclass:: telegram.ext.Application
|
.. autoclass:: telegram.ext.Application
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.ext.ApplicationBuilder
|
ApplicationBuilder
|
||||||
===============================
|
==================
|
||||||
|
|
||||||
.. autoclass:: telegram.ext.ApplicationBuilder
|
.. autoclass:: telegram.ext.ApplicationBuilder
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.ext.ApplicationHandlerStop
|
ApplicationHandlerStop
|
||||||
===================================
|
======================
|
||||||
|
|
||||||
.. autoclass:: telegram.ext.ApplicationHandlerStop
|
.. autoclass:: telegram.ext.ApplicationHandlerStop
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.ext.BaseHandler
|
BaseHandler
|
||||||
========================
|
===========
|
||||||
|
|
||||||
.. autoclass:: telegram.ext.BaseHandler
|
.. autoclass:: telegram.ext.BaseHandler
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.ext.BasePersistence
|
BasePersistence
|
||||||
============================
|
===============
|
||||||
|
|
||||||
.. autoclass:: telegram.ext.BasePersistence
|
.. autoclass:: telegram.ext.BasePersistence
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.ext.BaseRateLimiter
|
BaseRateLimiter
|
||||||
============================
|
===============
|
||||||
|
|
||||||
.. autoclass:: telegram.ext.BaseRateLimiter
|
.. autoclass:: telegram.ext.BaseRateLimiter
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.ext.CallbackContext
|
CallbackContext
|
||||||
============================
|
===============
|
||||||
|
|
||||||
.. autoclass:: telegram.ext.CallbackContext
|
.. autoclass:: telegram.ext.CallbackContext
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.ext.CallbackDataCache
|
CallbackDataCache
|
||||||
==============================
|
=================
|
||||||
|
|
||||||
.. autoclass:: telegram.ext.CallbackDataCache
|
.. autoclass:: telegram.ext.CallbackDataCache
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.ext.CallbackQueryHandler
|
CallbackQueryHandler
|
||||||
=================================
|
====================
|
||||||
|
|
||||||
.. autoclass:: telegram.ext.CallbackQueryHandler
|
.. autoclass:: telegram.ext.CallbackQueryHandler
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.ext.ChatJoinRequestHandler
|
ChatJoinRequestHandler
|
||||||
===================================
|
======================
|
||||||
|
|
||||||
.. autoclass:: telegram.ext.ChatJoinRequestHandler
|
.. autoclass:: telegram.ext.ChatJoinRequestHandler
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.ext.ChatMemberHandler
|
ChatMemberHandler
|
||||||
==============================
|
=================
|
||||||
|
|
||||||
.. autoclass:: telegram.ext.ChatMemberHandler
|
.. autoclass:: telegram.ext.ChatMemberHandler
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.ext.ChosenInlineResultHandler
|
ChosenInlineResultHandler
|
||||||
======================================
|
=========================
|
||||||
|
|
||||||
.. autoclass:: telegram.ext.ChosenInlineResultHandler
|
.. autoclass:: telegram.ext.ChosenInlineResultHandler
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.ext.CommandHandler
|
CommandHandler
|
||||||
===========================
|
==============
|
||||||
|
|
||||||
.. autoclass:: telegram.ext.CommandHandler
|
.. autoclass:: telegram.ext.CommandHandler
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.ext.ContextTypes
|
ContextTypes
|
||||||
=========================
|
============
|
||||||
|
|
||||||
.. autoclass:: telegram.ext.ContextTypes
|
.. autoclass:: telegram.ext.ContextTypes
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.ext.ConversationHandler
|
ConversationHandler
|
||||||
================================
|
===================
|
||||||
|
|
||||||
.. autoclass:: telegram.ext.ConversationHandler
|
.. autoclass:: telegram.ext.ConversationHandler
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.ext.Defaults
|
Defaults
|
||||||
=====================
|
========
|
||||||
|
|
||||||
.. autoclass:: telegram.ext.Defaults
|
.. autoclass:: telegram.ext.Defaults
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.ext.DictPersistence
|
DictPersistence
|
||||||
============================
|
===============
|
||||||
|
|
||||||
.. autoclass:: telegram.ext.DictPersistence
|
.. autoclass:: telegram.ext.DictPersistence
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.ext.ExtBot
|
ExtBot
|
||||||
===================
|
======
|
||||||
|
|
||||||
.. autoclass:: telegram.ext.ExtBot
|
.. autoclass:: telegram.ext.ExtBot
|
||||||
:show-inheritance:
|
: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.
|
.. :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
|
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
|
.. autoclass:: telegram.ext.InlineQueryHandler
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.ext.InvalidCallbackData
|
InvalidCallbackData
|
||||||
================================
|
===================
|
||||||
|
|
||||||
.. autoclass:: telegram.ext.InvalidCallbackData
|
.. autoclass:: telegram.ext.InvalidCallbackData
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.ext.Job
|
Job
|
||||||
=====================
|
===
|
||||||
|
|
||||||
.. autoclass:: telegram.ext.Job
|
.. autoclass:: telegram.ext.Job
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.ext.JobQueue
|
JobQueue
|
||||||
=====================
|
========
|
||||||
|
|
||||||
.. autoclass:: telegram.ext.JobQueue
|
.. autoclass:: telegram.ext.JobQueue
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.ext.MessageHandler
|
MessageHandler
|
||||||
===========================
|
==============
|
||||||
|
|
||||||
.. autoclass:: telegram.ext.MessageHandler
|
.. autoclass:: telegram.ext.MessageHandler
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.ext.PersistenceInput
|
PersistenceInput
|
||||||
=============================
|
================
|
||||||
|
|
||||||
.. autoclass:: telegram.ext.PersistenceInput
|
.. autoclass:: telegram.ext.PersistenceInput
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.ext.PicklePersistence
|
PicklePersistence
|
||||||
==============================
|
=================
|
||||||
|
|
||||||
.. autoclass:: telegram.ext.PicklePersistence
|
.. autoclass:: telegram.ext.PicklePersistence
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.ext.PollAnswerHandler
|
PollAnswerHandler
|
||||||
==============================
|
=================
|
||||||
|
|
||||||
.. autoclass:: telegram.ext.PollAnswerHandler
|
.. autoclass:: telegram.ext.PollAnswerHandler
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.ext.PollHandler
|
PollHandler
|
||||||
========================
|
===========
|
||||||
|
|
||||||
.. autoclass:: telegram.ext.PollHandler
|
.. autoclass:: telegram.ext.PollHandler
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.ext.PreCheckoutQueryHandler
|
PreCheckoutQueryHandler
|
||||||
====================================
|
=======================
|
||||||
|
|
||||||
.. autoclass:: telegram.ext.PreCheckoutQueryHandler
|
.. autoclass:: telegram.ext.PreCheckoutQueryHandler
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.ext.PrefixHandler
|
PrefixHandler
|
||||||
===========================
|
=============
|
||||||
|
|
||||||
.. autoclass:: telegram.ext.PrefixHandler
|
.. autoclass:: telegram.ext.PrefixHandler
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.ext.ShippingQueryHandler
|
ShippingQueryHandler
|
||||||
=================================
|
====================
|
||||||
|
|
||||||
.. autoclass:: telegram.ext.ShippingQueryHandler
|
.. autoclass:: telegram.ext.ShippingQueryHandler
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.ext.StringCommandHandler
|
StringCommandHandler
|
||||||
=================================
|
====================
|
||||||
|
|
||||||
.. autoclass:: telegram.ext.StringCommandHandler
|
.. autoclass:: telegram.ext.StringCommandHandler
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.ext.StringRegexHandler
|
StringRegexHandler
|
||||||
===============================
|
==================
|
||||||
|
|
||||||
.. autoclass:: telegram.ext.StringRegexHandler
|
.. autoclass:: telegram.ext.StringRegexHandler
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.ext.TypeHandler
|
TypeHandler
|
||||||
========================
|
===========
|
||||||
|
|
||||||
.. autoclass:: telegram.ext.TypeHandler
|
.. autoclass:: telegram.ext.TypeHandler
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.ext.Updater
|
Updater
|
||||||
====================
|
=======
|
||||||
|
|
||||||
.. autoclass:: telegram.ext.Updater
|
.. autoclass:: telegram.ext.Updater
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.File
|
File
|
||||||
=============
|
====
|
||||||
|
|
||||||
.. autoclass:: telegram.File
|
.. autoclass:: telegram.File
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.FileCredentials
|
FileCredentials
|
||||||
========================
|
===============
|
||||||
|
|
||||||
.. autoclass:: telegram.FileCredentials
|
.. autoclass:: telegram.FileCredentials
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.ForceReply
|
ForceReply
|
||||||
===================
|
==========
|
||||||
|
|
||||||
.. autoclass:: telegram.ForceReply
|
.. autoclass:: telegram.ForceReply
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.ForumTopic
|
ForumTopic
|
||||||
===================
|
==========
|
||||||
|
|
||||||
.. autoclass:: telegram.ForumTopic
|
.. autoclass:: telegram.ForumTopic
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.ForumTopicClosed
|
ForumTopicClosed
|
||||||
=========================
|
================
|
||||||
|
|
||||||
.. autoclass:: telegram.ForumTopicClosed
|
.. autoclass:: telegram.ForumTopicClosed
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.ForumTopicCreated
|
ForumTopicCreated
|
||||||
==========================
|
=================
|
||||||
|
|
||||||
.. autoclass:: telegram.ForumTopicCreated
|
.. autoclass:: telegram.ForumTopicCreated
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.ForumTopicEdited
|
ForumTopicEdited
|
||||||
=========================
|
================
|
||||||
|
|
||||||
.. autoclass:: telegram.ForumTopicEdited
|
.. autoclass:: telegram.ForumTopicEdited
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.ForumTopicReopened
|
ForumTopicReopened
|
||||||
===========================
|
==================
|
||||||
|
|
||||||
.. autoclass:: telegram.ForumTopicReopened
|
.. autoclass:: telegram.ForumTopicReopened
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.Game
|
Game
|
||||||
=============
|
====
|
||||||
|
|
||||||
.. autoclass:: telegram.Game
|
.. autoclass:: telegram.Game
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.GameHighScore
|
GameHighScore
|
||||||
======================
|
=============
|
||||||
|
|
||||||
.. autoclass:: telegram.GameHighScore
|
.. autoclass:: telegram.GameHighScore
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.GeneralForumTopicHidden
|
GeneralForumTopicHidden
|
||||||
================================
|
=======================
|
||||||
|
|
||||||
.. autoclass:: telegram.GeneralForumTopicHidden
|
.. autoclass:: telegram.GeneralForumTopicHidden
|
||||||
:members:
|
:members:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
telegram.GeneralForumTopicUnhidden
|
GeneralForumTopicUnhidden
|
||||||
==================================
|
=========================
|
||||||
|
|
||||||
.. autoclass:: telegram.GeneralForumTopicUnhidden
|
.. autoclass:: telegram.GeneralForumTopicUnhidden
|
||||||
:members:
|
:members:
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue