Compare commits

..

22 Commits

Author SHA1 Message Date
Hinrich Mahler a8f1164b0c Bump Version to v20.7 2023-11-27 19:01:57 +01:00
Bibo-Joshi da11561f87 Adjust read_timeout Behavior for Bot.get_updates (#3963) 2023-11-27 18:24:21 +01:00
Bibo-Joshi 354a8e0854 Improve write_timeout Handling for Media Methods (#3952) 2023-11-26 16:44:18 +01:00
dependabot[bot] bc68488c14 Update httpx requirement from ~=0.25.1 to ~=0.25.2 (#3983)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <dependabot[bot]@users.noreply.github.com>
Co-authored-by: Bibo-Joshi <22366557+Bibo-Joshi@users.noreply.github.com>
2023-11-25 06:46:55 +01:00
dependabot[bot] 19d7939355 Bump pytest-xdist from 3.4.0 to 3.5.0 (#3982)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-25 06:26:30 +01:00
dependabot[bot] 3495ce3aeb Bump pytest-xdist from 3.3.1 to 3.4.0 (#3975)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-22 22:21:39 +01:00
pre-commit-ci[bot] dd9af64a5c [pre-commit.ci] pre-commit autoupdate (#3967)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Harshil <37377066+harshil21@users.noreply.github.com>
2023-11-12 10:11:22 +01:00
Bibo-Joshi da3bc6974a Adjust Tests to New Error Messages (#3970) 2023-11-11 11:35:23 +01:00
Bibo-Joshi b1fc0596b9 Fix Persistency Issue with Ended Non-Blocking Conversations (#3962) 2023-11-05 11:48:44 +01:00
Bibo-Joshi 6d2334c88b Improve Insertion of Kwargs into Bot Methods (#3965) 2023-11-05 11:47:50 +01:00
dependabot[bot] a0c81ec3d4 Update httpx requirement from ~=0.25.0 to ~=0.25.1 (#3961)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <dependabot[bot]@users.noreply.github.com>
2023-11-04 08:34:31 +01:00
dependabot[bot] c8d9898eaa Bump srvaroa/labeler from 1.6.1 to 1.7.0 (#3958)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-03 23:38:03 +01:00
Bibo-Joshi 616b0b55ef Add ApplicationBuilder.(get_updates_)socket_options (#3943) 2023-10-31 16:27:30 +01:00
dependabot[bot] c71612ffae Update cachetools requirement from ~=5.3.1 to ~=5.3.2 (#3954)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <dependabot[bot]@users.noreply.github.com>
Co-authored-by: Bibo-Joshi <22366557+Bibo-Joshi@users.noreply.github.com>
2023-10-28 10:30:45 +02:00
dependabot[bot] 4143d99f56 Bump pytest from 7.4.2 to 7.4.3 (#3953)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-28 10:01:21 +02:00
Harshil cbe808e471 Improve Type Hinting for Arguments with Default Values in Bot (#3942) 2023-10-25 21:53:43 +02:00
Bibo-Joshi 300ec920a1 Add filters.Mention (#3941)
Co-authored-by: Javohir Elmurodov <elmurodovjavohir@gmail.com>
2023-10-23 21:11:56 +02:00
Bibo-Joshi 075f517458 Rename proxy_url to proxy and Allow httpx.{Proxy, URL} as Input (#3939) 2023-10-23 21:09:28 +02:00
Bibo-Joshi c82a0808d1 Improve BaseHandler.__repr__ for Callbacks without __qualname__ (#3934) 2023-10-22 12:43:23 +02:00
Bibo-Joshi ea7e5a69aa Add Parameter socket_options to HTTPXRequest (#3935) 2023-10-22 12:42:22 +02:00
Bibo-Joshi f67e8c0804 Add JobQueue.scheduler_configuration and Corresponding Warnings (#3913) 2023-10-16 20:25:25 +02:00
Aditya Yadav af130ef5e7 Add Documentation for __aenter__ and __aexit__ Methods (#3907)
Co-authored-by: Aditya <clot27@apx_managed.vanilla>
2023-10-09 18:59:52 +02:00
57 changed files with 1530 additions and 429 deletions
+1 -1
View File
@@ -11,7 +11,7 @@ jobs:
pull-requests: write # for srvaroa/labeler to add labels in PR
runs-on: ubuntu-latest
steps:
- uses: srvaroa/labeler@v1.6.1
- uses: srvaroa/labeler@v1.7.0
# Config file at .github/labeler.yml
env:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
+12 -12
View File
@@ -6,7 +6,7 @@ ci:
repos:
- repo: https://github.com/psf/black
rev: 23.9.1
rev: 23.10.1
hooks:
- id: black
args:
@@ -17,7 +17,7 @@ repos:
hooks:
- id: flake8
- repo: https://github.com/PyCQA/pylint
rev: v3.0.0
rev: v3.0.1
hooks:
- id: pylint
files: ^(telegram|examples)/.*\.py$
@@ -28,14 +28,14 @@ repos:
- --jobs=0
additional_dependencies:
- httpx~=0.24.1
- httpx~=0.25.2
- tornado~=6.3.3
- APScheduler~=3.10.4
- cachetools~=5.3.1
- cachetools~=5.3.2
- aiolimiter~=1.1.0
- . # this basically does `pip install -e .`
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.5.1
rev: v1.6.1
hooks:
- id: mypy
name: mypy-ptb
@@ -44,10 +44,10 @@ repos:
- types-pytz
- types-cryptography
- types-cachetools
- httpx~=0.24.1
- httpx~=0.25.2
- tornado~=6.3.3
- APScheduler~=3.10.4
- cachetools~=5.3.1
- cachetools~=5.3.2
- aiolimiter~=1.1.0
- . # this basically does `pip install -e .`
- id: mypy
@@ -59,10 +59,10 @@ repos:
additional_dependencies:
- tornado~=6.3.3
- APScheduler~=3.10.4
- cachetools~=5.3.1
- cachetools~=5.3.2
- . # this basically does `pip install -e .`
- repo: https://github.com/asottile/pyupgrade
rev: v3.13.0
rev: v3.15.0
hooks:
- id: pyupgrade
files: ^(telegram|examples|tests|docs)/.*\.py$
@@ -77,14 +77,14 @@ repos:
- --diff
- --check
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: 'v0.0.292'
rev: 'v0.1.5'
hooks:
- id: ruff
name: ruff
files: ^(telegram|examples|tests)/.*\.py$
additional_dependencies:
- httpx~=0.24.1
- httpx~=0.25.2
- tornado~=6.3.3
- APScheduler~=3.10.4
- cachetools~=5.3.1
- cachetools~=5.3.2
- aiolimiter~=1.1.0
+49
View File
@@ -4,6 +4,55 @@
Changelog
=========
Version 20.6
============
*Released 2023-11-27*
This is the technical changelog for version 20.6. More elaborate release notes can be found in the news channel `@pythontelegrambotchannel <https://t.me/pythontelegrambotchannel>`__.
New Features
------------
- Add ``JobQueue.scheduler_configuration`` and Corresponding Warnings (:pr:`3913` closes :issue:`3837`)
- Add Parameter ``socket_options`` to ``HTTPXRequest`` (:pr:`3935` closes :issue:`2965`)
- Add ``ApplicationBuilder.(get_updates_)socket_options`` (:pr:`3943`)
- Improve ``write_timeout`` Handling for Media Methods (:pr:`3952`)
- Add ``filters.Mention`` (:pr:`3941` closes :issue:`3799`)
- Rename ``proxy_url`` to ``proxy`` and Allow ``httpx.{Proxy, URL}`` as Input (:pr:`3939` closes :issue:`3844`)
Bug Fixes & Changes
-------------------
- Adjust ``read_timeout`` Behavior for ``Bot.get_updates`` (:pr:`3963` closes :issue:`3893`)
- Improve ``BaseHandler.__repr__`` for Callbacks without ``__qualname__`` (:pr:`3934`)
- Fix Persistency Issue with Ended Non-Blocking Conversations (:pr:`3962`)
- Improve Type Hinting for Arguments with Default Values in ``Bot`` (:pr:`3942`)
Documentation Improvements
--------------------------
- Add Documentation for ``__aenter__`` and ``__aexit__`` Methods (:pr:`3907` closes :issue:`3886`)
- Improve Insertion of Kwargs into ``Bot`` Methods (:pr:`3965`)
Internal Changes
----------------
- Adjust Tests to New Error Messages (:pr:`3970`)
Dependency Updates
------------------
- Bump ``pytest-xdist`` from 3.3.1 to 3.4.0 (:pr:`3975`)
- ``pre-commit`` autoupdate (:pr:`3967`)
- Update ``httpx`` requirement from ~=0.25.1 to ~=0.25.2 (:pr:`3983`)
- Bump ``pytest-xdist`` from 3.4.0 to 3.5.0 (:pr:`3982`)
- Update ``httpx`` requirement from ~=0.25.0 to ~=0.25.1 (:pr:`3961`)
- Bump ``srvaroa/labeler`` from 1.6.1 to 1.7.0 (:pr:`3958`)
- Update ``cachetools`` requirement from ~=5.3.1 to ~=5.3.2 (:pr:`3954`)
- Bump ``pytest`` from 7.4.2 to 7.4.3 (:pr:`3953`)
Version 20.6
============
+2 -2
View File
@@ -135,7 +135,7 @@ As these features are *optional*, the corresponding 3rd party dependencies are n
Instead, they are listed as optional dependencies.
This allows to avoid unnecessary dependency conflicts for users who don't need the optional features.
The only required dependency is `httpx ~= 0.24.1 <https://www.python-httpx.org>`_ for
The only required dependency is `httpx ~= 0.25.2 <https://www.python-httpx.org>`_ for
``telegram.request.HTTPXRequest``, the default networking backend.
``python-telegram-bot`` is most useful when used along with additional libraries.
@@ -153,7 +153,7 @@ PTB can be installed with optional dependencies:
* ``pip install "python-telegram-bot[http2]"`` installs `httpx[http2] <https://www.python-httpx.org/#dependencies>`_. Use this, if you want to use HTTP/2.
* ``pip install "python-telegram-bot[rate-limiter]"`` installs `aiolimiter~=1.1.0 <https://aiolimiter.readthedocs.io/en/stable/>`_. Use this, if you want to use ``telegram.ext.AIORateLimiter``.
* ``pip install "python-telegram-bot[webhooks]"`` installs the `tornado~=6.3.3 <https://www.tornadoweb.org/en/stable/>`_ library. Use this, if you want to use ``telegram.ext.Updater.start_webhook``/``telegram.ext.Application.run_webhook``.
* ``pip install "python-telegram-bot[callback-data]"`` installs the `cachetools~=5.3.1 <https://cachetools.readthedocs.io/en/latest/>`_ library. Use this, if you want to use `arbitrary callback_data <https://github.com/python-telegram-bot/python-telegram-bot/wiki/Arbitrary-callback_data>`_.
* ``pip install "python-telegram-bot[callback-data]"`` installs the `cachetools~=5.3.2 <https://cachetools.readthedocs.io/en/latest/>`_ library. Use this, if you want to use `arbitrary callback_data <https://github.com/python-telegram-bot/python-telegram-bot/wiki/Arbitrary-callback_data>`_.
* ``pip install "python-telegram-bot[job-queue]"`` installs the `APScheduler~=3.10.4 <https://apscheduler.readthedocs.io/en/3.x/>`_ library and enforces `pytz>=2018.6 <https://pypi.org/project/pytz/>`_, where ``pytz`` is a dependency of ``APScheduler``. Use this, if you want to use the ``telegram.ext.JobQueue``.
To install multiple optional dependencies, separate them by commas, e.g. ``pip install "python-telegram-bot[socks,webhooks]"``.
+1 -1
View File
@@ -136,7 +136,7 @@ As these features are *optional*, the corresponding 3rd party dependencies are n
Instead, they are listed as optional dependencies.
This allows to avoid unnecessary dependency conflicts for users who don't need the optional features.
The only required dependency is `httpx ~= 0.24.1 <https://www.python-httpx.org>`_ for
The only required dependency is `httpx ~= 0.25.2 <https://www.python-httpx.org>`_ for
``telegram.request.HTTPXRequest``, the default networking backend.
``python-telegram-bot`` is most useful when used along with additional libraries.
+51 -38
View File
@@ -18,67 +18,80 @@
import inspect
keyword_args = [
"Keyword Arguments:",
(
":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}."
" read_timeout (:obj:`float` | :obj:`None`, optional): Value to pass to "
" :paramref:`telegram.request.BaseRequest.post.read_timeout`. Defaults to "
" :attr:`~telegram.request.BaseRequest.DEFAULT_NONE`. "
),
(
":kwtype _sphinx_paramlinks_telegram.Bot.{method}.write_timeout: :obj:`float` |"
" :obj:`None`, optional"
" write_timeout (:obj:`float` | :obj:`None`, optional): Value to pass to "
" :paramref:`telegram.request.BaseRequest.post.write_timeout`. Defaults to "
" :attr:`~telegram.request.BaseRequest.DEFAULT_NONE`."
),
(
":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`."
" connect_timeout (:obj:`float` | :obj:`None`, optional): 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"
" pool_timeout (:obj:`float` | :obj:`None`, optional): Value to pass to "
" :paramref:`telegram.request.BaseRequest.post.pool_timeout`. Defaults to "
" :attr:`~telegram.request.BaseRequest.DEFAULT_NONE`."
),
(
":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`."
" api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments"
" to be passed to the Telegram API."
),
(
":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",
media_write_timeout_deprecation_methods = [
"send_photo",
"send_audio",
"send_document",
"send_sticker",
"send_video",
"send_video_note",
"send_animation",
"send_voice",
"send_media_group",
"set_chat_photo",
"upload_sticker_file",
"add_sticker_to_set",
"create_new_sticker_set",
]
media_write_timeout_deprecation = [
" write_timeout (:obj:`float` | :obj:`None`, optional): Value to pass to "
" :paramref:`telegram.request.BaseRequest.post.write_timeout`. By default, ``20`` "
" seconds are used as write timeout."
"",
"",
" .. deprecated:: 20.7",
" In future versions, the default value will be changed to "
" :attr:`~telegram.request.BaseRequest.DEFAULT_NONE`.",
"",
"",
]
get_updates_read_timeout_addition = [
" :paramref:`timeout` will be added to this value.",
"",
"",
" .. versionchanged:: 20.7",
" Defaults to :attr:`~telegram.request.BaseRequest.DEFAULT_NONE` instead of ",
" ``2``.",
]
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:"):
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)
+19 -15
View File
@@ -29,11 +29,10 @@ 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,
get_updates_read_timeout_addition,
keyword_args,
read_timeout_sub,
read_timeout_type,
write_timeout_sub,
media_write_timeout_deprecation,
media_write_timeout_deprecation_methods,
)
from docs.auxil.link_code import LINE_NUMBERS
@@ -107,19 +106,24 @@ def autodoc_process_docstring(
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
get_updates: bool = method_name == "get_updates"
# The below can be done in 1 line with itertools.chain, but this must be modified in-place
insert_idx = insert_index
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],
),
)
to_insert = keyword_args[i - insert_index]
if (
"post.write_timeout`. Defaults to" in to_insert
and method_name in media_write_timeout_deprecation_methods
):
effective_insert: list[str] = media_write_timeout_deprecation
elif get_updates and to_insert.lstrip().startswith("read_timeout"):
effective_insert = [to_insert] + get_updates_read_timeout_addition
else:
effective_insert = [to_insert]
lines[insert_idx:insert_idx] = effective_insert
insert_idx += len(effective_insert)
ADMONITION_INSERTER.insert_admonitions(
obj=typing.cast(collections.abc.Callable, obj),
+12 -3
View File
@@ -21,9 +21,9 @@ author = "Leandro Toledo"
# built documents.
#
# The short X.Y version.
version = "20.6" # telegram.__version__[:3]
version = "20.7" # telegram.__version__[:3]
# The full version, including alpha/beta/rc tags.
release = "20.6" # telegram.__version__
release = "20.7" # telegram.__version__
# If your documentation needs a minimal Sphinx version, state it here.
needs_sphinx = "6.1.3"
@@ -77,6 +77,12 @@ napoleon_use_admonition_for_examples = True
# and we document the types anyway
autodoc_typehints = "none"
# Show docstring for special members
autodoc_default_options = {
"special-members": True,
"exclude-members": "__init__",
}
# Fail on warnings & unresolved references etc
nitpicky = True
@@ -314,5 +320,8 @@ from docs.auxil.tg_const_role import CONSTANTS_ROLE, TGConstXRefRole
def setup(app: Sphinx):
app.connect("autodoc-skip-member", autodoc_skip_member)
app.connect("autodoc-process-bases", autodoc_process_bases)
app.connect("autodoc-process-docstring", autodoc_process_docstring)
# The default priority is 500. We want our function to run before napoleon doc-conversion
# and sphinx-paramlinks do, b/c otherwise the inserted kwargs in the bot methods won't show
# up in the objects.inv file that Sphinx generates (i.e. not in the search).
app.connect("autodoc-process-docstring", autodoc_process_docstring, priority=100)
app.add_role_to_domain("py", CONSTANTS_ROLE, TGConstXRefRole())
+2
View File
@@ -61,3 +61,5 @@
.. |removed_thumb_url_note| replace:: Removed the deprecated argument and attribute ``thumb_url``.
.. |removed_thumb_wildcard_note| replace:: Removed the deprecated arguments and attributes ``thumb_*``.
.. |async_context_manager| replace:: Asynchronous context manager which
+2 -2
View File
@@ -1,9 +1,9 @@
pre-commit # needed for pre-commit hooks in the git commit command
# For the test suite
pytest==7.4.2
pytest==7.4.3
pytest-asyncio==0.21.1 # needed because pytest doesn't come with native support for coroutines as tests
pytest-xdist==3.3.1 # xdist runs tests in parallel
pytest-xdist==3.5.0 # xdist runs tests in parallel
flaky # Used for flaky tests (flaky decorator)
beautifulsoup4 # used in test_official for parsing tg docs
+1 -1
View File
@@ -20,7 +20,7 @@ tornado~=6.3.3 # webhooks!ext
# Cachetools and APS don't have a strict stability policy.
# Let's be cautious for now.
cachetools~=5.3.1 # callback-data!ext
cachetools~=5.3.2 # callback-data!ext
APScheduler~=3.10.4 # job-queue!ext
# pytz is required by APS and just needs the lower bound due to #2120
+1 -1
View File
@@ -6,4 +6,4 @@
# versions and only increase the lower bound if necessary
# httpx has no stable release yet, so let's be cautious for now
httpx ~= 0.25.0
httpx ~= 0.25.2
+63 -41
View File
@@ -93,14 +93,7 @@ from telegram._utils.defaultvalue import DEFAULT_NONE, DefaultValue
from telegram._utils.files import is_local_file, parse_file_input
from telegram._utils.logging import get_logger
from telegram._utils.repr import build_repr_with_selected_attrs
from telegram._utils.types import (
CorrectOptionID,
DVInput,
FileInput,
JSONDict,
ODVInput,
ReplyMarkup,
)
from telegram._utils.types import CorrectOptionID, FileInput, JSONDict, ODVInput, ReplyMarkup
from telegram._utils.warnings import warn
from telegram._webhookinfo import WebhookInfo
from telegram.constants import InlineQueryLimit
@@ -108,7 +101,7 @@ from telegram.error import InvalidToken
from telegram.request import BaseRequest, RequestData
from telegram.request._httpxrequest import HTTPXRequest
from telegram.request._requestparameter import RequestParameter
from telegram.warnings import PTBUserWarning
from telegram.warnings import PTBDeprecationWarning, PTBUserWarning
if TYPE_CHECKING:
from telegram import (
@@ -149,6 +142,8 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
finally:
await bot.shutdown()
.. seealso:: :meth:`__aenter__` and :meth:`__aexit__`.
Note:
* Most bot methods have the argument ``api_kwargs`` which allows passing arbitrary keywords
to the Telegram API. This can be used to access new features of the API before they are
@@ -310,6 +305,16 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
self._freeze()
async def __aenter__(self: BT) -> BT:
"""
|async_context_manager| :meth:`initializes <initialize>` the Bot.
Returns:
The initialized Bot instance.
Raises:
:exc:`Exception`: If an exception is raised during initialization, :meth:`shutdown`
is called in this case.
"""
try:
await self.initialize()
return self
@@ -323,6 +328,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
exc_val: Optional[BaseException],
exc_tb: Optional[TracebackType],
) -> None:
"""|async_context_manager| :meth:`shuts down <shutdown>` the Bot."""
# Make sure not to return `True` so that exceptions are not suppressed
# https://docs.python.org/3/reference/datamodel.html?#object.__aexit__
await self.shutdown()
@@ -785,7 +791,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
parse_mode: ODVInput[str] = DEFAULT_NONE,
entities: Optional[Sequence["MessageEntity"]] = None,
disable_web_page_preview: ODVInput[bool] = DEFAULT_NONE,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
protect_content: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -920,7 +926,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
chat_id: Union[int, str],
from_chat_id: Union[str, int],
message_id: int,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
*,
@@ -986,7 +992,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
chat_id: Union[int, str],
photo: Union[FileInput, "PhotoSize"],
caption: Optional[str] = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional[ReplyMarkup] = None,
parse_mode: ODVInput[str] = DEFAULT_NONE,
@@ -998,7 +1004,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
*,
filename: Optional[str] = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = 20,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
@@ -1100,7 +1106,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
performer: Optional[str] = None,
title: Optional[str] = None,
caption: Optional[str] = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional[ReplyMarkup] = None,
parse_mode: ODVInput[str] = DEFAULT_NONE,
@@ -1112,7 +1118,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
*,
filename: Optional[str] = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = 20,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
@@ -1224,7 +1230,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
chat_id: Union[int, str],
document: Union[FileInput, "Document"],
caption: Optional[str] = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional[ReplyMarkup] = None,
parse_mode: ODVInput[str] = DEFAULT_NONE,
@@ -1237,7 +1243,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
*,
filename: Optional[str] = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = 20,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
@@ -1343,7 +1349,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
self,
chat_id: Union[int, str],
sticker: Union[FileInput, "Sticker"],
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional[ReplyMarkup] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -1352,7 +1358,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
emoji: Optional[str] = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = 20,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
@@ -1431,7 +1437,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
video: Union[FileInput, "Video"],
duration: Optional[int] = None,
caption: Optional[str] = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional[ReplyMarkup] = None,
width: Optional[int] = None,
@@ -1447,7 +1453,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
*,
filename: Optional[str] = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = 20,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
@@ -1570,7 +1576,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
video_note: Union[FileInput, "VideoNote"],
duration: Optional[int] = None,
length: Optional[int] = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional[ReplyMarkup] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -1580,7 +1586,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
*,
filename: Optional[str] = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = 20,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
@@ -1685,7 +1691,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
height: Optional[int] = None,
caption: Optional[str] = None,
parse_mode: ODVInput[str] = DEFAULT_NONE,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional[ReplyMarkup] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -1697,7 +1703,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
*,
filename: Optional[str] = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = 20,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
@@ -1813,7 +1819,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
voice: Union[FileInput, "Voice"],
duration: Optional[int] = None,
caption: Optional[str] = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional[ReplyMarkup] = None,
parse_mode: ODVInput[str] = DEFAULT_NONE,
@@ -1824,7 +1830,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
*,
filename: Optional[str] = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = 20,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
@@ -1936,7 +1942,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
message_thread_id: Optional[int] = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = 20,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
@@ -2057,7 +2063,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
chat_id: Union[int, str],
latitude: Optional[float] = None,
longitude: Optional[float] = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional[ReplyMarkup] = None,
live_period: Optional[int] = None,
@@ -2318,7 +2324,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
title: Optional[str] = None,
address: Optional[str] = None,
foursquare_id: Optional[str] = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional[ReplyMarkup] = None,
foursquare_type: Optional[str] = None,
@@ -2442,7 +2448,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
phone_number: Optional[str] = None,
first_name: Optional[str] = None,
last_name: Optional[str] = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional[ReplyMarkup] = None,
vcard: Optional[str] = None,
@@ -2543,7 +2549,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
self,
chat_id: int,
game_short_name: str,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional["InlineKeyboardMarkup"] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -3490,7 +3496,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
timeout: Optional[int] = None,
allowed_updates: Optional[Sequence[str]] = None,
*,
read_timeout: float = 2,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -3552,6 +3558,22 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
"allowed_updates": allowed_updates,
}
# The "or 0" is needed for the case where read_timeout is None.
if not isinstance(read_timeout, DefaultValue):
arg_read_timeout: float = read_timeout or 0
else:
try:
arg_read_timeout = self._request[0].read_timeout or 0
except NotImplementedError:
arg_read_timeout = 2
self._warn(
f"The class {self._request[0].__class__.__name__} does not override "
"the property `read_timeout`. Overriding this property will be mandatory in "
"future versions. Using 2 seconds as fallback.",
PTBDeprecationWarning,
stacklevel=3,
)
# Ideally we'd use an aggressive read timeout for the polling. However,
# * Short polling should return within 2 seconds.
# * Long polling poses a different problem: the connection might have been dropped while
@@ -3562,7 +3584,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
await self._post(
"getUpdates",
data,
read_timeout=read_timeout + timeout if timeout else read_timeout,
read_timeout=arg_read_timeout + timeout if timeout else arg_read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
@@ -4174,7 +4196,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
need_email: Optional[bool] = None,
need_shipping_address: Optional[bool] = None,
is_flexible: Optional[bool] = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional["InlineKeyboardMarkup"] = None,
provider_data: Optional[Union[str, object]] = None,
@@ -5141,7 +5163,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
photo: FileInput,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = 20,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
@@ -5521,7 +5543,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
sticker_format: Optional[str],
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = 20,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
@@ -5581,7 +5603,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
sticker: Optional["InputSticker"],
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = 20,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
@@ -5683,7 +5705,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
needs_repainting: Optional[bool] = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = 20,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
@@ -6747,9 +6769,9 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
caption: Optional[str] = None,
parse_mode: ODVInput[str] = DEFAULT_NONE,
caption_entities: Optional[Sequence["MessageEntity"]] = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: DVInput[bool] = DEFAULT_NONE,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
reply_markup: Optional[ReplyMarkup] = None,
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
+3 -3
View File
@@ -26,7 +26,7 @@ from telegram._message import Message
from telegram._telegramobject import TelegramObject
from telegram._user import User
from telegram._utils.defaultvalue import DEFAULT_NONE
from telegram._utils.types import DVInput, JSONDict, ODVInput, ReplyMarkup
from telegram._utils.types import JSONDict, ODVInput, ReplyMarkup
if TYPE_CHECKING:
from telegram import (
@@ -725,9 +725,9 @@ class CallbackQuery(TelegramObject):
caption: Optional[str] = None,
parse_mode: ODVInput[str] = DEFAULT_NONE,
caption_entities: Optional[Sequence["MessageEntity"]] = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: DVInput[bool] = DEFAULT_NONE,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
reply_markup: Optional[ReplyMarkup] = None,
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
+31 -38
View File
@@ -33,14 +33,7 @@ from telegram._utils import enum
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
from telegram._utils.defaultvalue import DEFAULT_NONE
from telegram._utils.types import (
CorrectOptionID,
DVInput,
FileInput,
JSONDict,
ODVInput,
ReplyMarkup,
)
from telegram._utils.types import CorrectOptionID, FileInput, JSONDict, ODVInput, ReplyMarkup
from telegram.helpers import escape_markdown
from telegram.helpers import mention_html as helpers_mention_html
from telegram.helpers import mention_markdown as helpers_mention_markdown
@@ -1095,7 +1088,7 @@ class Chat(TelegramObject):
photo: FileInput,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = 20,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
@@ -1326,7 +1319,7 @@ class Chat(TelegramObject):
text: str,
parse_mode: ODVInput[str] = DEFAULT_NONE,
disable_web_page_preview: ODVInput[bool] = DEFAULT_NONE,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional[ReplyMarkup] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -1381,7 +1374,7 @@ class Chat(TelegramObject):
message_thread_id: Optional[int] = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = 20,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
@@ -1457,7 +1450,7 @@ class Chat(TelegramObject):
self,
photo: Union[FileInput, "PhotoSize"],
caption: Optional[str] = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional[ReplyMarkup] = None,
parse_mode: ODVInput[str] = DEFAULT_NONE,
@@ -1469,7 +1462,7 @@ class Chat(TelegramObject):
*,
filename: Optional[str] = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = 20,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
@@ -1510,7 +1503,7 @@ class Chat(TelegramObject):
phone_number: Optional[str] = None,
first_name: Optional[str] = None,
last_name: Optional[str] = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional[ReplyMarkup] = None,
vcard: Optional[str] = None,
@@ -1562,7 +1555,7 @@ class Chat(TelegramObject):
performer: Optional[str] = None,
title: Optional[str] = None,
caption: Optional[str] = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional[ReplyMarkup] = None,
parse_mode: ODVInput[str] = DEFAULT_NONE,
@@ -1574,7 +1567,7 @@ class Chat(TelegramObject):
*,
filename: Optional[str] = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = 20,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
@@ -1617,7 +1610,7 @@ class Chat(TelegramObject):
self,
document: Union[FileInput, "Document"],
caption: Optional[str] = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional[ReplyMarkup] = None,
parse_mode: ODVInput[str] = DEFAULT_NONE,
@@ -1630,7 +1623,7 @@ class Chat(TelegramObject):
*,
filename: Optional[str] = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = 20,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
@@ -1712,7 +1705,7 @@ class Chat(TelegramObject):
async def send_game(
self,
game_short_name: str,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional["InlineKeyboardMarkup"] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -1769,7 +1762,7 @@ class Chat(TelegramObject):
need_email: Optional[bool] = None,
need_shipping_address: Optional[bool] = None,
is_flexible: Optional[bool] = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional["InlineKeyboardMarkup"] = None,
provider_data: Optional[Union[str, object]] = None,
@@ -1847,7 +1840,7 @@ class Chat(TelegramObject):
self,
latitude: Optional[float] = None,
longitude: Optional[float] = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional[ReplyMarkup] = None,
live_period: Optional[int] = None,
@@ -1905,7 +1898,7 @@ class Chat(TelegramObject):
height: Optional[int] = None,
caption: Optional[str] = None,
parse_mode: ODVInput[str] = DEFAULT_NONE,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional[ReplyMarkup] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -1917,7 +1910,7 @@ class Chat(TelegramObject):
*,
filename: Optional[str] = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = 20,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
@@ -1960,7 +1953,7 @@ class Chat(TelegramObject):
async def send_sticker(
self,
sticker: Union[FileInput, "Sticker"],
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional[ReplyMarkup] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -1969,7 +1962,7 @@ class Chat(TelegramObject):
emoji: Optional[str] = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = 20,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
@@ -2008,7 +2001,7 @@ class Chat(TelegramObject):
title: Optional[str] = None,
address: Optional[str] = None,
foursquare_id: Optional[str] = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional[ReplyMarkup] = None,
foursquare_type: Optional[str] = None,
@@ -2064,7 +2057,7 @@ class Chat(TelegramObject):
video: Union[FileInput, "Video"],
duration: Optional[int] = None,
caption: Optional[str] = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional[ReplyMarkup] = None,
width: Optional[int] = None,
@@ -2080,7 +2073,7 @@ class Chat(TelegramObject):
*,
filename: Optional[str] = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = 20,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
@@ -2126,7 +2119,7 @@ class Chat(TelegramObject):
video_note: Union[FileInput, "VideoNote"],
duration: Optional[int] = None,
length: Optional[int] = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional[ReplyMarkup] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -2136,7 +2129,7 @@ class Chat(TelegramObject):
*,
filename: Optional[str] = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = 20,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
@@ -2176,7 +2169,7 @@ class Chat(TelegramObject):
voice: Union[FileInput, "Voice"],
duration: Optional[int] = None,
caption: Optional[str] = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional[ReplyMarkup] = None,
parse_mode: ODVInput[str] = DEFAULT_NONE,
@@ -2187,7 +2180,7 @@ class Chat(TelegramObject):
*,
filename: Optional[str] = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = 20,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
@@ -2294,9 +2287,9 @@ class Chat(TelegramObject):
caption: Optional[str] = None,
parse_mode: ODVInput[str] = DEFAULT_NONE,
caption_entities: Optional[Sequence["MessageEntity"]] = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: DVInput[bool] = DEFAULT_NONE,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
reply_markup: Optional[ReplyMarkup] = None,
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
@@ -2344,9 +2337,9 @@ class Chat(TelegramObject):
caption: Optional[str] = None,
parse_mode: ODVInput[str] = DEFAULT_NONE,
caption_entities: Optional[Sequence["MessageEntity"]] = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: DVInput[bool] = DEFAULT_NONE,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
reply_markup: Optional[ReplyMarkup] = None,
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
@@ -2391,7 +2384,7 @@ class Chat(TelegramObject):
self,
from_chat_id: Union[str, int],
message_id: int,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
*,
@@ -2433,7 +2426,7 @@ class Chat(TelegramObject):
self,
chat_id: Union[int, str],
message_id: int,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
*,
+4 -1
View File
@@ -55,7 +55,10 @@ class MenuButton(TelegramObject):
__slots__ = ("type",)
def __init__(
self, type: str, *, api_kwargs: Optional[JSONDict] = None # skipcq: PYL-W0622
self,
type: str, # skipcq: PYL-W0622
*,
api_kwargs: Optional[JSONDict] = None,
): # pylint: disable=redefined-builtin
super().__init__(api_kwargs=api_kwargs)
self.type: str = type
+31 -32
View File
@@ -61,7 +61,6 @@ from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestam
from telegram._utils.defaultvalue import DEFAULT_NONE, DefaultValue
from telegram._utils.types import (
CorrectOptionID,
DVInput,
FileInput,
JSONDict,
MarkdownVersion,
@@ -1061,7 +1060,7 @@ class Message(TelegramObject):
text: str,
parse_mode: ODVInput[str] = DEFAULT_NONE,
disable_web_page_preview: ODVInput[bool] = DEFAULT_NONE,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional[ReplyMarkup] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -1115,7 +1114,7 @@ class Message(TelegramObject):
self,
text: str,
disable_web_page_preview: ODVInput[bool] = DEFAULT_NONE,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional[ReplyMarkup] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -1179,7 +1178,7 @@ class Message(TelegramObject):
self,
text: str,
disable_web_page_preview: ODVInput[bool] = DEFAULT_NONE,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional[ReplyMarkup] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -1239,7 +1238,7 @@ class Message(TelegramObject):
self,
text: str,
disable_web_page_preview: ODVInput[bool] = DEFAULT_NONE,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional[ReplyMarkup] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -1308,7 +1307,7 @@ class Message(TelegramObject):
*,
quote: Optional[bool] = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = 20,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
@@ -1357,7 +1356,7 @@ class Message(TelegramObject):
self,
photo: Union[FileInput, "PhotoSize"],
caption: Optional[str] = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional[ReplyMarkup] = None,
parse_mode: ODVInput[str] = DEFAULT_NONE,
@@ -1370,7 +1369,7 @@ class Message(TelegramObject):
filename: Optional[str] = None,
quote: Optional[bool] = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = 20,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
@@ -1419,7 +1418,7 @@ class Message(TelegramObject):
performer: Optional[str] = None,
title: Optional[str] = None,
caption: Optional[str] = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional[ReplyMarkup] = None,
parse_mode: ODVInput[str] = DEFAULT_NONE,
@@ -1432,7 +1431,7 @@ class Message(TelegramObject):
filename: Optional[str] = None,
quote: Optional[bool] = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = 20,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
@@ -1481,7 +1480,7 @@ class Message(TelegramObject):
self,
document: Union[FileInput, "Document"],
caption: Optional[str] = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional[ReplyMarkup] = None,
parse_mode: ODVInput[str] = DEFAULT_NONE,
@@ -1495,7 +1494,7 @@ class Message(TelegramObject):
filename: Optional[str] = None,
quote: Optional[bool] = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = 20,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
@@ -1546,7 +1545,7 @@ class Message(TelegramObject):
height: Optional[int] = None,
caption: Optional[str] = None,
parse_mode: ODVInput[str] = DEFAULT_NONE,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional[ReplyMarkup] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -1559,7 +1558,7 @@ class Message(TelegramObject):
filename: Optional[str] = None,
quote: Optional[bool] = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = 20,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
@@ -1609,7 +1608,7 @@ class Message(TelegramObject):
async def reply_sticker(
self,
sticker: Union[FileInput, "Sticker"],
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional[ReplyMarkup] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -1619,7 +1618,7 @@ class Message(TelegramObject):
*,
quote: Optional[bool] = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = 20,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
@@ -1662,7 +1661,7 @@ class Message(TelegramObject):
video: Union[FileInput, "Video"],
duration: Optional[int] = None,
caption: Optional[str] = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional[ReplyMarkup] = None,
width: Optional[int] = None,
@@ -1679,7 +1678,7 @@ class Message(TelegramObject):
filename: Optional[str] = None,
quote: Optional[bool] = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = 20,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
@@ -1731,7 +1730,7 @@ class Message(TelegramObject):
video_note: Union[FileInput, "VideoNote"],
duration: Optional[int] = None,
length: Optional[int] = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional[ReplyMarkup] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -1742,7 +1741,7 @@ class Message(TelegramObject):
filename: Optional[str] = None,
quote: Optional[bool] = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = 20,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
@@ -1789,7 +1788,7 @@ class Message(TelegramObject):
voice: Union[FileInput, "Voice"],
duration: Optional[int] = None,
caption: Optional[str] = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional[ReplyMarkup] = None,
parse_mode: ODVInput[str] = DEFAULT_NONE,
@@ -1801,7 +1800,7 @@ class Message(TelegramObject):
filename: Optional[str] = None,
quote: Optional[bool] = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = 20,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
@@ -1848,7 +1847,7 @@ class Message(TelegramObject):
self,
latitude: Optional[float] = None,
longitude: Optional[float] = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional[ReplyMarkup] = None,
live_period: Optional[int] = None,
@@ -1912,7 +1911,7 @@ class Message(TelegramObject):
title: Optional[str] = None,
address: Optional[str] = None,
foursquare_id: Optional[str] = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional[ReplyMarkup] = None,
foursquare_type: Optional[str] = None,
@@ -1975,7 +1974,7 @@ class Message(TelegramObject):
phone_number: Optional[str] = None,
first_name: Optional[str] = None,
last_name: Optional[str] = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional[ReplyMarkup] = None,
vcard: Optional[str] = None,
@@ -2184,7 +2183,7 @@ class Message(TelegramObject):
async def reply_game(
self,
game_short_name: str,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional["InlineKeyboardMarkup"] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -2250,7 +2249,7 @@ class Message(TelegramObject):
need_email: Optional[bool] = None,
need_shipping_address: Optional[bool] = None,
is_flexible: Optional[bool] = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional["InlineKeyboardMarkup"] = None,
provider_data: Optional[Union[str, object]] = None,
@@ -2336,7 +2335,7 @@ class Message(TelegramObject):
async def forward(
self,
chat_id: Union[int, str],
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
*,
@@ -2389,9 +2388,9 @@ class Message(TelegramObject):
caption: Optional[str] = None,
parse_mode: ODVInput[str] = DEFAULT_NONE,
caption_entities: Optional[Sequence["MessageEntity"]] = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: DVInput[bool] = DEFAULT_NONE,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
reply_markup: Optional[ReplyMarkup] = None,
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
@@ -2445,9 +2444,9 @@ class Message(TelegramObject):
caption: Optional[str] = None,
parse_mode: ODVInput[str] = DEFAULT_NONE,
caption_entities: Optional[Sequence["MessageEntity"]] = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: DVInput[bool] = DEFAULT_NONE,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
reply_markup: Optional[ReplyMarkup] = None,
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
+5 -1
View File
@@ -471,7 +471,11 @@ class _CredentialsBase(TelegramObject):
__slots__ = ("hash", "secret", "file_hash", "data_hash")
def __init__(
self, hash: str, secret: str, *, api_kwargs: Optional[JSONDict] = None # skipcq: PYL-W0622
self,
hash: str, # skipcq: PYL-W0622
secret: str,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
with self._unfrozen():
+28 -35
View File
@@ -25,14 +25,7 @@ from telegram._inline.inlinekeyboardbutton import InlineKeyboardButton
from telegram._menubutton import MenuButton
from telegram._telegramobject import TelegramObject
from telegram._utils.defaultvalue import DEFAULT_NONE
from telegram._utils.types import (
CorrectOptionID,
DVInput,
FileInput,
JSONDict,
ODVInput,
ReplyMarkup,
)
from telegram._utils.types import CorrectOptionID, FileInput, JSONDict, ODVInput, ReplyMarkup
from telegram.helpers import mention_html as helpers_mention_html
from telegram.helpers import mention_markdown as helpers_mention_markdown
@@ -391,7 +384,7 @@ class User(TelegramObject):
text: str,
parse_mode: ODVInput[str] = DEFAULT_NONE,
disable_web_page_preview: ODVInput[bool] = DEFAULT_NONE,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional[ReplyMarkup] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -441,7 +434,7 @@ class User(TelegramObject):
self,
photo: Union[FileInput, "PhotoSize"],
caption: Optional[str] = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional[ReplyMarkup] = None,
parse_mode: ODVInput[str] = DEFAULT_NONE,
@@ -453,7 +446,7 @@ class User(TelegramObject):
*,
filename: Optional[str] = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = 20,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
@@ -504,7 +497,7 @@ class User(TelegramObject):
message_thread_id: Optional[int] = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = 20,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
@@ -551,7 +544,7 @@ class User(TelegramObject):
performer: Optional[str] = None,
title: Optional[str] = None,
caption: Optional[str] = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional[ReplyMarkup] = None,
parse_mode: ODVInput[str] = DEFAULT_NONE,
@@ -563,7 +556,7 @@ class User(TelegramObject):
*,
filename: Optional[str] = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = 20,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
@@ -648,7 +641,7 @@ class User(TelegramObject):
phone_number: Optional[str] = None,
first_name: Optional[str] = None,
last_name: Optional[str] = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional[ReplyMarkup] = None,
vcard: Optional[str] = None,
@@ -745,7 +738,7 @@ class User(TelegramObject):
self,
document: Union[FileInput, "Document"],
caption: Optional[str] = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional[ReplyMarkup] = None,
parse_mode: ODVInput[str] = DEFAULT_NONE,
@@ -758,7 +751,7 @@ class User(TelegramObject):
*,
filename: Optional[str] = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = 20,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
@@ -801,7 +794,7 @@ class User(TelegramObject):
async def send_game(
self,
game_short_name: str,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional["InlineKeyboardMarkup"] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -861,7 +854,7 @@ class User(TelegramObject):
need_email: Optional[bool] = None,
need_shipping_address: Optional[bool] = None,
is_flexible: Optional[bool] = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional["InlineKeyboardMarkup"] = None,
provider_data: Optional[Union[str, object]] = None,
@@ -942,7 +935,7 @@ class User(TelegramObject):
self,
latitude: Optional[float] = None,
longitude: Optional[float] = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional[ReplyMarkup] = None,
live_period: Optional[int] = None,
@@ -1003,7 +996,7 @@ class User(TelegramObject):
height: Optional[int] = None,
caption: Optional[str] = None,
parse_mode: ODVInput[str] = DEFAULT_NONE,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional[ReplyMarkup] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -1015,7 +1008,7 @@ class User(TelegramObject):
*,
filename: Optional[str] = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = 20,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
@@ -1061,7 +1054,7 @@ class User(TelegramObject):
async def send_sticker(
self,
sticker: Union[FileInput, "Sticker"],
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional[ReplyMarkup] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -1070,7 +1063,7 @@ class User(TelegramObject):
emoji: Optional[str] = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = 20,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
@@ -1110,7 +1103,7 @@ class User(TelegramObject):
video: Union[FileInput, "Video"],
duration: Optional[int] = None,
caption: Optional[str] = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional[ReplyMarkup] = None,
width: Optional[int] = None,
@@ -1126,7 +1119,7 @@ class User(TelegramObject):
*,
filename: Optional[str] = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = 20,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
@@ -1177,7 +1170,7 @@ class User(TelegramObject):
title: Optional[str] = None,
address: Optional[str] = None,
foursquare_id: Optional[str] = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional[ReplyMarkup] = None,
foursquare_type: Optional[str] = None,
@@ -1236,7 +1229,7 @@ class User(TelegramObject):
video_note: Union[FileInput, "VideoNote"],
duration: Optional[int] = None,
length: Optional[int] = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional[ReplyMarkup] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -1246,7 +1239,7 @@ class User(TelegramObject):
*,
filename: Optional[str] = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = 20,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
@@ -1289,7 +1282,7 @@ class User(TelegramObject):
voice: Union[FileInput, "Voice"],
duration: Optional[int] = None,
caption: Optional[str] = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional[ReplyMarkup] = None,
parse_mode: ODVInput[str] = DEFAULT_NONE,
@@ -1300,7 +1293,7 @@ class User(TelegramObject):
*,
filename: Optional[str] = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = 20,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
@@ -1413,9 +1406,9 @@ class User(TelegramObject):
caption: Optional[str] = None,
parse_mode: ODVInput[str] = DEFAULT_NONE,
caption_entities: Optional[Sequence["MessageEntity"]] = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: DVInput[bool] = DEFAULT_NONE,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
reply_markup: Optional[ReplyMarkup] = None,
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
@@ -1466,9 +1459,9 @@ class User(TelegramObject):
caption: Optional[str] = None,
parse_mode: ODVInput[str] = DEFAULT_NONE,
caption_entities: Optional[Sequence["MessageEntity"]] = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: DVInput[bool] = DEFAULT_NONE,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
reply_markup: Optional[ReplyMarkup] = None,
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
+6
View File
@@ -95,3 +95,9 @@ HTTPVersion = Literal["1.1", "2.0", "2"]
CorrectOptionID = Literal[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
MarkdownVersion = Literal[1, 2]
SocketOpt = Union[
Tuple[int, int, int],
Tuple[int, int, Union[bytes, bytearray]],
Tuple[int, int, None, int],
]
+1 -1
View File
@@ -51,7 +51,7 @@ class Version(NamedTuple):
__version_info__: Final[Version] = Version(
major=20, minor=6, micro=0, releaselevel="final", serial=0
major=20, minor=7, micro=0, releaselevel="final", serial=0
)
__version__: Final[str] = str(__version_info__)
+46 -7
View File
@@ -149,6 +149,8 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
finally:
await application.shutdown()
.. seealso:: :meth:`__aenter__` and :meth:`__aexit__`.
Examples:
:any:`Echo Bot <examples.echobot>`
@@ -345,7 +347,15 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
self.__create_task_tasks: Set[asyncio.Task] = set() # Used for awaiting tasks upon exit
async def __aenter__(self: _AppType) -> _AppType: # noqa: PYI019
"""Simple context manager which initializes the App."""
"""|async_context_manager| :meth:`initializes <initialize>` the App.
Returns:
The initialized App instance.
Raises:
:exc:`Exception`: If an exception is raised during initialization, :meth:`shutdown`
is called in this case.
"""
try:
await self.initialize()
return self
@@ -359,7 +369,7 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
exc_val: Optional[BaseException],
exc_tb: Optional[TracebackType],
) -> None:
"""Shutdown the App from the context manager."""
"""|async_context_manager| :meth:`shuts down <shutdown>` the App."""
# Make sure not to return `True` so that exceptions are not suppressed
# https://docs.python.org/3/reference/datamodel.html?#object.__aexit__
await self.shutdown()
@@ -690,7 +700,7 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
poll_interval: float = 0.0,
timeout: int = 10,
bootstrap_retries: int = -1,
read_timeout: float = 2,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -735,16 +745,37 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
* > 0 - retry up to X times
read_timeout (:obj:`float`, optional): Value to pass to
:paramref:`telegram.Bot.get_updates.read_timeout`. Defaults to ``2``.
:paramref:`telegram.Bot.get_updates.read_timeout`. Defaults to
:attr:`~telegram.request.BaseRequest.DEFAULT_NONE`.
.. versionchanged:: 20.7
Defaults to :attr:`~telegram.request.BaseRequest.DEFAULT_NONE` instead of
``2``.
.. deprecated:: 20.7
Deprecated in favor of setting the timeout via
:meth:`telegram.ext.ApplicationBuilder.get_updates_read_timeout`.
write_timeout (:obj:`float` | :obj:`None`, optional): Value to pass to
:paramref:`telegram.Bot.get_updates.write_timeout`. Defaults to
:attr:`~telegram.request.BaseRequest.DEFAULT_NONE`.
.. deprecated:: 20.7
Deprecated in favor of setting the timeout via
:meth:`telegram.ext.ApplicationBuilder.get_updates_write_timeout`.
connect_timeout (:obj:`float` | :obj:`None`, optional): Value to pass to
:paramref:`telegram.Bot.get_updates.connect_timeout`. Defaults to
:attr:`~telegram.request.BaseRequest.DEFAULT_NONE`.
.. deprecated:: 20.7
Deprecated in favor of setting the timeout via
:meth:`telegram.ext.ApplicationBuilder.get_updates_connect_timeout`.
pool_timeout (:obj:`float` | :obj:`None`, optional): Value to pass to
:paramref:`telegram.Bot.get_updates.pool_timeout`. Defaults to
:attr:`~telegram.request.BaseRequest.DEFAULT_NONE`.
.. deprecated:: 20.7
Deprecated in favor of setting the timeout via
:meth:`telegram.ext.ApplicationBuilder.get_updates_pool_timeout`.
drop_pending_updates (:obj:`bool`, optional): Whether to clean any pending updates on
Telegram servers before actually starting to poll. Default is :obj:`False`.
allowed_updates (List[:obj:`str`], optional): Passed to
@@ -773,6 +804,14 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
"Application.run_polling is only available if the application has an Updater."
)
if (read_timeout, write_timeout, connect_timeout, pool_timeout) != ((DEFAULT_NONE,) * 4):
warn(
"Setting timeouts via `Application.run_polling` is deprecated. "
"Please use `ApplicationBuilder.get_updates_*_timeout` instead.",
PTBDeprecationWarning,
stacklevel=2,
)
def error_callback(exc: TelegramError) -> None:
self.create_task(self.process_error(error=exc, update=None))
@@ -1076,7 +1115,7 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
return await asyncio.create_task(coroutine)
# If user uses generator in python 3.12+, Exception will happen and we cannot do
# anything about it. (hence the type ignore if mypy is run on python 3.12-)
return await coroutine # type: ignore
return await coroutine # type: ignore[misc]
except Exception as exception:
if isinstance(exception, ApplicationHandlerStop):
warn(
@@ -1357,7 +1396,7 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
chat_id (:obj:`int`): The chat id to delete. The entry will be deleted even if it is
not empty.
"""
self._chat_data.pop(chat_id, None) # type: ignore[arg-type]
self._chat_data.pop(chat_id, None)
self._chat_ids_to_be_deleted_in_persistence.add(chat_id)
def drop_user_data(self, user_id: int) -> None:
@@ -1376,7 +1415,7 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
user_id (:obj:`int`): The user id to delete. The entry will be deleted even if it is
not empty.
"""
self._user_data.pop(user_id, None) # type: ignore[arg-type]
self._user_data.pop(user_id, None)
self._user_ids_to_be_deleted_in_persistence.add(user_id)
def migrate_chat_data(
+138 -31
View File
@@ -23,6 +23,7 @@ from typing import (
TYPE_CHECKING,
Any,
Callable,
Collection,
Coroutine,
Dict,
Generic,
@@ -32,9 +33,12 @@ from typing import (
Union,
)
import httpx
from telegram._bot import Bot
from telegram._utils.defaultvalue import DEFAULT_FALSE, DEFAULT_NONE, DefaultValue
from telegram._utils.types import DVInput, DVType, FilePathInput, HTTPVersion, ODVInput
from telegram._utils.types import DVInput, DVType, FilePathInput, HTTPVersion, ODVInput, SocketOpt
from telegram._utils.warnings import warn
from telegram.ext._application import Application
from telegram.ext._baseupdateprocessor import BaseUpdateProcessor, SimpleUpdateProcessor
from telegram.ext._contexttypes import ContextTypes
@@ -44,8 +48,10 @@ from telegram.ext._updater import Updater
from telegram.ext._utils.types import BD, BT, CCT, CD, JQ, UD
from telegram.request import BaseRequest
from telegram.request._httpxrequest import HTTPXRequest
from telegram.warnings import PTBDeprecationWarning
if TYPE_CHECKING:
from telegram import Update
from telegram.ext import BasePersistence, BaseRateLimiter, CallbackContext, Defaults
from telegram.ext._utils.types import RLARGS
@@ -66,14 +72,16 @@ _BOT_CHECKS = [
("request", "request instance"),
("get_updates_request", "get_updates_request instance"),
("connection_pool_size", "connection_pool_size"),
("proxy_url", "proxy_url"),
("proxy", "proxy"),
("socket_options", "socket_options"),
("pool_timeout", "pool_timeout"),
("connect_timeout", "connect_timeout"),
("read_timeout", "read_timeout"),
("write_timeout", "write_timeout"),
("http_version", "http_version"),
("get_updates_connection_pool_size", "get_updates_connection_pool_size"),
("get_updates_proxy_url", "get_updates_proxy_url"),
("get_updates_proxy", "get_updates_proxy"),
("get_updates_socket_options", "get_updates_socket_options"),
("get_updates_pool_timeout", "get_updates_pool_timeout"),
("get_updates_connect_timeout", "get_updates_connect_timeout"),
("get_updates_read_timeout", "get_updates_read_timeout"),
@@ -136,9 +144,10 @@ class ApplicationBuilder(Generic[BT, CCT, UD, CD, BD, JQ]):
"_get_updates_connect_timeout",
"_get_updates_connection_pool_size",
"_get_updates_pool_timeout",
"_get_updates_proxy_url",
"_get_updates_proxy",
"_get_updates_read_timeout",
"_get_updates_request",
"_get_updates_socket_options",
"_get_updates_write_timeout",
"_get_updates_http_version",
"_job_queue",
@@ -149,10 +158,11 @@ class ApplicationBuilder(Generic[BT, CCT, UD, CD, BD, JQ]):
"_post_stop",
"_private_key",
"_private_key_password",
"_proxy_url",
"_proxy",
"_rate_limiter",
"_read_timeout",
"_request",
"_socket_options",
"_token",
"_update_queue",
"_updater",
@@ -166,14 +176,16 @@ class ApplicationBuilder(Generic[BT, CCT, UD, CD, BD, JQ]):
self._base_url: DVType[str] = DefaultValue("https://api.telegram.org/bot")
self._base_file_url: DVType[str] = DefaultValue("https://api.telegram.org/file/bot")
self._connection_pool_size: DVInput[int] = DEFAULT_NONE
self._proxy_url: DVInput[str] = DEFAULT_NONE
self._proxy: DVInput[Union[str, httpx.Proxy, httpx.URL]] = DEFAULT_NONE
self._socket_options: DVInput[Collection[SocketOpt]] = DEFAULT_NONE
self._connect_timeout: ODVInput[float] = DEFAULT_NONE
self._read_timeout: ODVInput[float] = DEFAULT_NONE
self._write_timeout: ODVInput[float] = DEFAULT_NONE
self._pool_timeout: ODVInput[float] = DEFAULT_NONE
self._request: DVInput[BaseRequest] = DEFAULT_NONE
self._get_updates_connection_pool_size: DVInput[int] = DEFAULT_NONE
self._get_updates_proxy_url: DVInput[str] = DEFAULT_NONE
self._get_updates_proxy: DVInput[Union[str, httpx.Proxy, httpx.URL]] = DEFAULT_NONE
self._get_updates_socket_options: DVInput[Collection[SocketOpt]] = DEFAULT_NONE
self._get_updates_connect_timeout: ODVInput[float] = DEFAULT_NONE
self._get_updates_read_timeout: ODVInput[float] = DEFAULT_NONE
self._get_updates_write_timeout: ODVInput[float] = DEFAULT_NONE
@@ -186,7 +198,7 @@ class ApplicationBuilder(Generic[BT, CCT, UD, CD, BD, JQ]):
self._arbitrary_callback_data: Union[DefaultValue[bool], int] = DEFAULT_FALSE
self._local_mode: DVType[bool] = DEFAULT_FALSE
self._bot: DVInput[Bot] = DEFAULT_NONE
self._update_queue: DVType[Queue] = DefaultValue(Queue())
self._update_queue: DVType[Queue[Union[Update, object]]] = DefaultValue(Queue())
try:
self._job_queue: ODVInput[JobQueue] = DefaultValue(JobQueue())
@@ -214,7 +226,8 @@ class ApplicationBuilder(Generic[BT, CCT, UD, CD, BD, JQ]):
if not isinstance(getattr(self, f"{prefix}request"), DefaultValue):
return getattr(self, f"{prefix}request")
proxy_url = DefaultValue.get_value(getattr(self, f"{prefix}proxy_url"))
proxy = DefaultValue.get_value(getattr(self, f"{prefix}proxy"))
socket_options = DefaultValue.get_value(getattr(self, f"{prefix}socket_options"))
if get_updates:
connection_pool_size = (
DefaultValue.get_value(getattr(self, f"{prefix}connection_pool_size")) or 1
@@ -239,8 +252,9 @@ class ApplicationBuilder(Generic[BT, CCT, UD, CD, BD, JQ]):
return HTTPXRequest(
connection_pool_size=connection_pool_size,
proxy_url=proxy_url,
proxy=proxy,
http_version=http_version, # type: ignore[arg-type]
socket_options=socket_options,
**effective_timeouts,
)
@@ -258,7 +272,7 @@ class ApplicationBuilder(Generic[BT, CCT, UD, CD, BD, JQ]):
arbitrary_callback_data=DefaultValue.get_value(self._arbitrary_callback_data),
request=self._build_request(get_updates=False),
get_updates_request=self._build_request(get_updates=True),
rate_limiter=DefaultValue.get_value(self._rate_limiter),
rate_limiter=DefaultValue.get_value(self._rate_limiter), # type: ignore[arg-type]
local_mode=DefaultValue.get_value(self._local_mode),
)
@@ -303,7 +317,7 @@ class ApplicationBuilder(Generic[BT, CCT, UD, CD, BD, JQ]):
application: Application[
BT, CCT, UD, CD, BD, JQ
] = DefaultValue.get_value( # pylint: disable=not-callable
] = DefaultValue.get_value( # type: ignore[operator] # pylint: disable=not-callable
self._application_class
)(
bot=bot,
@@ -311,7 +325,7 @@ class ApplicationBuilder(Generic[BT, CCT, UD, CD, BD, JQ]):
updater=updater,
update_processor=self._update_processor,
job_queue=job_queue,
persistence=persistence,
persistence=persistence, # type: ignore[arg-type]
context_types=DefaultValue.get_value(self._context_types),
post_init=self._post_init,
post_shutdown=self._post_shutdown,
@@ -320,12 +334,12 @@ class ApplicationBuilder(Generic[BT, CCT, UD, CD, BD, JQ]):
)
if job_queue is not None:
job_queue.set_application(application) # type: ignore[arg-type]
job_queue.set_application(application) # type: ignore[arg-type, union-attr]
if persistence is not None:
# This raises an exception if persistence.store_data.callback_data is True
# but self.bot is not an instance of ExtBot - so no need to check that later on
persistence.set_bot(bot)
persistence.set_bot(bot) # type: ignore[union-attr]
return application
@@ -419,8 +433,11 @@ class ApplicationBuilder(Generic[BT, CCT, UD, CD, BD, JQ]):
if not isinstance(getattr(self, f"_{prefix}connection_pool_size"), DefaultValue):
raise RuntimeError(_TWO_ARGS_REQ.format(name, "connection_pool_size"))
if not isinstance(getattr(self, f"_{prefix}proxy_url"), DefaultValue):
raise RuntimeError(_TWO_ARGS_REQ.format(name, "proxy_url"))
if not isinstance(getattr(self, f"_{prefix}proxy"), DefaultValue):
raise RuntimeError(_TWO_ARGS_REQ.format(name, "proxy"))
if not isinstance(getattr(self, f"_{prefix}socket_options"), DefaultValue):
raise RuntimeError(_TWO_ARGS_REQ.format(name, "socket_options"))
if not isinstance(getattr(self, f"_{prefix}http_version"), DefaultValue):
raise RuntimeError(_TWO_ARGS_REQ.format(name, "http_version"))
@@ -486,21 +503,64 @@ class ApplicationBuilder(Generic[BT, CCT, UD, CD, BD, JQ]):
return self
def proxy_url(self: BuilderType, proxy_url: str) -> BuilderType:
"""Sets the proxy for the :paramref:`~telegram.request.HTTPXRequest.proxy_url`
parameter of :attr:`telegram.Bot.request`. Defaults to :obj:`None`.
"""Legacy name for :meth:`proxy`, kept for backward compatibility.
.. seealso:: :meth:`get_updates_proxy`
.. seealso:: :meth:`get_updates_proxy_url`
.. deprecated:: 20.7
Args:
proxy_url (:obj:`str`): The URL to the proxy server. See
:paramref:`telegram.request.HTTPXRequest.proxy_url` for more information.
proxy_url (:obj:`str` | ``httpx.Proxy`` | ``httpx.URL``): See
:paramref:`telegram.ext.ApplicationBuilder.proxy.proxy`.
Returns:
:class:`ApplicationBuilder`: The same builder with the updated argument.
"""
self._request_param_check(name="proxy_url", get_updates=False)
self._proxy_url = proxy_url
warn(
"`ApplicationBuilder.proxy_url` is deprecated since version "
"20.7. Use `ApplicationBuilder.proxy` instead.",
PTBDeprecationWarning,
stacklevel=2,
)
return self.proxy(proxy_url)
def proxy(self: BuilderType, proxy: Union[str, httpx.Proxy, httpx.URL]) -> BuilderType:
"""Sets the proxy for the :paramref:`~telegram.request.HTTPXRequest.proxy`
parameter of :attr:`telegram.Bot.request`. Defaults to :obj:`None`.
.. seealso:: :meth:`get_updates_proxy`
.. versionadded:: 20.7
Args:
proxy (:obj:`str` | ``httpx.Proxy`` | ``httpx.URL``): The URL to a proxy
server, a ``httpx.Proxy`` object or a ``httpx.URL`` object. See
:paramref:`telegram.request.HTTPXRequest.proxy` for more information.
Returns:
:class:`ApplicationBuilder`: The same builder with the updated argument.
"""
self._request_param_check(name="proxy", get_updates=False)
self._proxy = proxy
return self
def socket_options(self: BuilderType, socket_options: Collection[SocketOpt]) -> BuilderType:
"""Sets the options for the :paramref:`~telegram.request.HTTPXRequest.socket_options`
parameter of :attr:`telegram.Bot.request`. Defaults to :obj:`None`.
.. seealso:: :meth:`get_updates_socket_options`
.. versionadded:: 20.7
Args:
socket_options (Collection[:obj:`tuple`], optional): Socket options. See
:paramref:`telegram.request.HTTPXRequest.socket_options` for more information.
Returns:
:class:`ApplicationBuilder`: The same builder with the updated argument.
"""
self._request_param_check(name="socket_options", get_updates=False)
self._socket_options = socket_options
return self
def connect_timeout(self: BuilderType, connect_timeout: Optional[float]) -> BuilderType:
@@ -655,21 +715,68 @@ class ApplicationBuilder(Generic[BT, CCT, UD, CD, BD, JQ]):
return self
def get_updates_proxy_url(self: BuilderType, get_updates_proxy_url: str) -> BuilderType:
"""Sets the proxy for the :paramref:`telegram.request.HTTPXRequest.proxy_url`
parameter which is used for :meth:`telegram.Bot.get_updates`. Defaults to :obj:`None`.
"""Legacy name for :meth:`get_updates_proxy`, kept for backward compatibility.
.. seealso:: :meth:`proxy`
.. seealso:: :meth:`proxy_url`
.. deprecated:: 20.7
Args:
get_updates_proxy_url (:obj:`str`): The URL to the proxy server. See
:paramref:`telegram.request.HTTPXRequest.proxy_url` for more information.
get_updates_proxy_url (:obj:`str` | ``httpx.Proxy`` | ``httpx.URL``): See
:paramref:`telegram.ext.ApplicationBuilder.get_updates_proxy.get_updates_proxy`.
Returns:
:class:`ApplicationBuilder`: The same builder with the updated argument.
"""
self._request_param_check(name="proxy_url", get_updates=True)
self._get_updates_proxy_url = get_updates_proxy_url
warn(
"`ApplicationBuilder.get_updates_proxy_url` is deprecated since version "
"20.7. Use `ApplicationBuilder.get_updates_proxy` instead.",
PTBDeprecationWarning,
stacklevel=2,
)
return self.get_updates_proxy(get_updates_proxy_url)
def get_updates_proxy(
self: BuilderType, get_updates_proxy: Union[str, httpx.Proxy, httpx.URL]
) -> BuilderType:
"""Sets the proxy for the :paramref:`telegram.request.HTTPXRequest.proxy`
parameter which is used for :meth:`telegram.Bot.get_updates`. Defaults to :obj:`None`.
.. seealso:: :meth:`proxy`
.. versionadded:: 20.7
Args:
proxy (:obj:`str` | ``httpx.Proxy`` | ``httpx.URL``): The URL to a proxy server,
a ``httpx.Proxy`` object or a ``httpx.URL`` object. See
:paramref:`telegram.request.HTTPXRequest.proxy` for more information.
Returns:
:class:`ApplicationBuilder`: The same builder with the updated argument.
"""
self._request_param_check(name="proxy", get_updates=True)
self._get_updates_proxy = get_updates_proxy
return self
def get_updates_socket_options(
self: BuilderType, get_updates_socket_options: Collection[SocketOpt]
) -> BuilderType:
"""Sets the options for the :paramref:`~telegram.request.HTTPXRequest.socket_options`
parameter of :paramref:`telegram.Bot.get_updates_request`. Defaults to :obj:`None`.
.. seealso:: :meth:`socket_options`
.. versionadded:: 20.7
Args:
get_updates_socket_options (Collection[:obj:`tuple`], optional): Socket options. See
:paramref:`telegram.request.HTTPXRequest.socket_options` for more information.
Returns:
:class:`ApplicationBuilder`: The same builder with the updated argument.
"""
self._request_param_check(name="socket_options", get_updates=True)
self._get_updates_socket_options = get_updates_socket_options
return self
def get_updates_connect_timeout(
+5 -1
View File
@@ -105,7 +105,11 @@ class BaseHandler(Generic[UT, CCT], ABC):
Returns:
:obj:`str`
"""
return build_repr_with_selected_attrs(self, callback=self.callback.__qualname__)
try:
callback_name = self.callback.__qualname__
except AttributeError:
callback_name = repr(self.callback)
return build_repr_with_selected_attrs(self, callback=callback_name)
@abstractmethod
def check_update(self, update: object) -> Optional[Union[bool, object]]:
+29 -2
View File
@@ -27,6 +27,25 @@ class BaseUpdateProcessor(ABC):
"""An abstract base class for update processors. You can use this class to implement
your own update processor.
Instances of this class can be used as asyncio context managers, where
.. code:: python
async with processor:
# code
is roughly equivalent to
.. code:: python
try:
await processor.initialize()
# code
finally:
await processor.shutdown()
.. seealso:: :meth:`__aenter__` and :meth:`__aexit__`.
.. seealso:: :wiki:`Concurrency`
.. versionadded:: 20.4
@@ -49,7 +68,15 @@ class BaseUpdateProcessor(ABC):
self._semaphore = BoundedSemaphore(self.max_concurrent_updates)
async def __aenter__(self) -> "BaseUpdateProcessor":
"""Simple context manager which initializes the Processor."""
"""|async_context_manager| :meth:`initializes <initialize>` the Processor.
Returns:
The initialized Processor instance.
Raises:
:exc:`Exception`: If an exception is raised during initialization, :meth:`shutdown`
is called in this case.
"""
try:
await self.initialize()
return self
@@ -63,7 +90,7 @@ class BaseUpdateProcessor(ABC):
exc_val: Optional[BaseException],
exc_tb: Optional[TracebackType],
) -> None:
"""Simple context manager which shuts down the Processor."""
"""|async_context_manager| :meth:`shuts down <shutdown>` the Processor."""
await self.shutdown()
@property
+4 -2
View File
@@ -236,11 +236,13 @@ class CallbackContext(Generic[BT, UD, CD, BD]):
await self.application.persistence.refresh_bot_data(self.bot_data)
if self.application.persistence.store_data.chat_data and self._chat_id is not None:
await self.application.persistence.refresh_chat_data(
chat_id=self._chat_id, chat_data=self.chat_data # type: ignore[arg-type]
chat_id=self._chat_id,
chat_data=self.chat_data, # type: ignore[arg-type]
)
if self.application.persistence.store_data.user_data and self._user_id is not None:
await self.application.persistence.refresh_user_data(
user_id=self._user_id, user_data=self.user_data # type: ignore[arg-type]
user_id=self._user_id,
user_data=self.user_data, # type: ignore[arg-type]
)
def drop_callback_data(self, callback_query: CallbackQuery) -> None:
+10 -3
View File
@@ -621,9 +621,16 @@ class ConversationHandler(BaseHandler[Update, CCT]):
self._conversations.update(current_conversations)
# above might be partly overridden but that's okay since we warn about that in
# add_handler
self._conversations.update_no_track(
await application.persistence.get_conversations(self.name)
)
stored_data = await application.persistence.get_conversations(self.name)
self._conversations.update_no_track(stored_data)
# Since CH.END is stored as normal state, we need to properly parse it here in order to
# actually end the conversation, i.e. delete the key from the _conversations dict
# This also makes sure that these entries are deleted from the persisted data on the next
# run of Application.update_persistence
for key, state in stored_data.items():
if state == self.END:
self._update_state(new_state=self.END, key=key)
out = {self.name: self._conversations}
+32 -39
View File
@@ -86,14 +86,7 @@ from telegram._utils.datetime import to_timestamp
from telegram._utils.defaultvalue import DEFAULT_NONE, DefaultValue
from telegram._utils.logging import get_logger
from telegram._utils.repr import build_repr_with_selected_attrs
from telegram._utils.types import (
CorrectOptionID,
DVInput,
FileInput,
JSONDict,
ODVInput,
ReplyMarkup,
)
from telegram._utils.types import CorrectOptionID, FileInput, JSONDict, ODVInput, ReplyMarkup
from telegram.ext._callbackdatacache import CallbackDataCache
from telegram.ext._utils.types import RLARGS
from telegram.request import BaseRequest
@@ -556,7 +549,7 @@ class ExtBot(Bot, Generic[RLARGS]):
timeout: Optional[int] = None,
allowed_updates: Optional[Sequence[str]] = None,
*,
read_timeout: float = 2,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -686,9 +679,9 @@ class ExtBot(Bot, Generic[RLARGS]):
caption: Optional[str] = None,
parse_mode: ODVInput[str] = DEFAULT_NONE,
caption_entities: Optional[Sequence["MessageEntity"]] = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: DVInput[bool] = DEFAULT_NONE,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
reply_markup: Optional[ReplyMarkup] = None,
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
@@ -750,7 +743,7 @@ class ExtBot(Bot, Generic[RLARGS]):
sticker: Optional["InputSticker"],
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = 20,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
@@ -1068,7 +1061,7 @@ class ExtBot(Bot, Generic[RLARGS]):
needs_repainting: Optional[bool] = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = 20,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
@@ -1518,7 +1511,7 @@ class ExtBot(Bot, Generic[RLARGS]):
chat_id: Union[int, str],
from_chat_id: Union[str, int],
message_id: int,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
*,
@@ -2174,7 +2167,7 @@ class ExtBot(Bot, Generic[RLARGS]):
height: Optional[int] = None,
caption: Optional[str] = None,
parse_mode: ODVInput[str] = DEFAULT_NONE,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional[ReplyMarkup] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -2186,7 +2179,7 @@ class ExtBot(Bot, Generic[RLARGS]):
*,
filename: Optional[str] = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = 20,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
@@ -2225,7 +2218,7 @@ class ExtBot(Bot, Generic[RLARGS]):
performer: Optional[str] = None,
title: Optional[str] = None,
caption: Optional[str] = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional[ReplyMarkup] = None,
parse_mode: ODVInput[str] = DEFAULT_NONE,
@@ -2237,7 +2230,7 @@ class ExtBot(Bot, Generic[RLARGS]):
*,
filename: Optional[str] = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = 20,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
@@ -2297,7 +2290,7 @@ class ExtBot(Bot, Generic[RLARGS]):
phone_number: Optional[str] = None,
first_name: Optional[str] = None,
last_name: Optional[str] = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional[ReplyMarkup] = None,
vcard: Optional[str] = None,
@@ -2372,7 +2365,7 @@ class ExtBot(Bot, Generic[RLARGS]):
chat_id: Union[int, str],
document: Union[FileInput, "Document"],
caption: Optional[str] = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional[ReplyMarkup] = None,
parse_mode: ODVInput[str] = DEFAULT_NONE,
@@ -2385,7 +2378,7 @@ class ExtBot(Bot, Generic[RLARGS]):
*,
filename: Optional[str] = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = 20,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
@@ -2417,7 +2410,7 @@ class ExtBot(Bot, Generic[RLARGS]):
self,
chat_id: int,
game_short_name: str,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional["InlineKeyboardMarkup"] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -2466,7 +2459,7 @@ class ExtBot(Bot, Generic[RLARGS]):
need_email: Optional[bool] = None,
need_shipping_address: Optional[bool] = None,
is_flexible: Optional[bool] = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional["InlineKeyboardMarkup"] = None,
provider_data: Optional[Union[str, object]] = None,
@@ -2526,7 +2519,7 @@ class ExtBot(Bot, Generic[RLARGS]):
chat_id: Union[int, str],
latitude: Optional[float] = None,
longitude: Optional[float] = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional[ReplyMarkup] = None,
live_period: Optional[int] = None,
@@ -2580,7 +2573,7 @@ class ExtBot(Bot, Generic[RLARGS]):
message_thread_id: Optional[int] = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = 20,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
@@ -2614,7 +2607,7 @@ class ExtBot(Bot, Generic[RLARGS]):
parse_mode: ODVInput[str] = DEFAULT_NONE,
entities: Optional[Sequence["MessageEntity"]] = None,
disable_web_page_preview: ODVInput[bool] = DEFAULT_NONE,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
protect_content: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -2652,7 +2645,7 @@ class ExtBot(Bot, Generic[RLARGS]):
chat_id: Union[int, str],
photo: Union[FileInput, "PhotoSize"],
caption: Optional[str] = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional[ReplyMarkup] = None,
parse_mode: ODVInput[str] = DEFAULT_NONE,
@@ -2664,7 +2657,7 @@ class ExtBot(Bot, Generic[RLARGS]):
*,
filename: Optional[str] = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = 20,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
@@ -2751,7 +2744,7 @@ class ExtBot(Bot, Generic[RLARGS]):
self,
chat_id: Union[int, str],
sticker: Union[FileInput, "Sticker"],
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional[ReplyMarkup] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -2760,7 +2753,7 @@ class ExtBot(Bot, Generic[RLARGS]):
emoji: Optional[str] = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = 20,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
@@ -2791,7 +2784,7 @@ class ExtBot(Bot, Generic[RLARGS]):
title: Optional[str] = None,
address: Optional[str] = None,
foursquare_id: Optional[str] = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional[ReplyMarkup] = None,
foursquare_type: Optional[str] = None,
@@ -2839,7 +2832,7 @@ class ExtBot(Bot, Generic[RLARGS]):
video: Union[FileInput, "Video"],
duration: Optional[int] = None,
caption: Optional[str] = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional[ReplyMarkup] = None,
width: Optional[int] = None,
@@ -2855,7 +2848,7 @@ class ExtBot(Bot, Generic[RLARGS]):
*,
filename: Optional[str] = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = 20,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
@@ -2893,7 +2886,7 @@ class ExtBot(Bot, Generic[RLARGS]):
video_note: Union[FileInput, "VideoNote"],
duration: Optional[int] = None,
length: Optional[int] = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional[ReplyMarkup] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -2903,7 +2896,7 @@ class ExtBot(Bot, Generic[RLARGS]):
*,
filename: Optional[str] = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = 20,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
@@ -2935,7 +2928,7 @@ class ExtBot(Bot, Generic[RLARGS]):
voice: Union[FileInput, "Voice"],
duration: Optional[int] = None,
caption: Optional[str] = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional[ReplyMarkup] = None,
parse_mode: ODVInput[str] = DEFAULT_NONE,
@@ -2946,7 +2939,7 @@ class ExtBot(Bot, Generic[RLARGS]):
*,
filename: Optional[str] = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = 20,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
@@ -3071,7 +3064,7 @@ class ExtBot(Bot, Generic[RLARGS]):
photo: FileInput,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = 20,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
@@ -3472,7 +3465,7 @@ class ExtBot(Bot, Generic[RLARGS]):
sticker_format: Optional[str],
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = 20,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
+50 -8
View File
@@ -76,6 +76,17 @@ class JobQueue(Generic[CCT]):
Attributes:
scheduler (:class:`apscheduler.schedulers.asyncio.AsyncIOScheduler`): The scheduler.
Warning:
This scheduler is configured by :meth:`set_application`. Additional configuration
settings can be made by users. However, calling
:meth:`~apscheduler.schedulers.base.BaseScheduler.configure` will delete any
previous configuration settings. Therefore, please make sure to pass the values
returned by :attr:`scheduler_configuration` to the method call in addition to your
custom values.
Alternatively, you can also use methods like
:meth:`~apscheduler.schedulers.base.BaseScheduler.add_jobstore` to avoid using
:meth:`~apscheduler.schedulers.base.BaseScheduler.configure` altogether.
.. versionchanged:: 20.0
Uses :class:`~apscheduler.schedulers.asyncio.AsyncIOScheduler` instead of
:class:`~apscheduler.schedulers.background.BackgroundScheduler`
@@ -94,9 +105,7 @@ class JobQueue(Generic[CCT]):
self._application: Optional[weakref.ReferenceType[Application]] = None
self._executor = AsyncIOExecutor()
self.scheduler: AsyncIOScheduler = AsyncIOScheduler(
timezone=pytz.utc, executors={"default": self._executor}
)
self.scheduler: AsyncIOScheduler = AsyncIOScheduler(**self.scheduler_configuration)
def __repr__(self) -> str:
"""Give a string representation of the JobQueue in the form ``JobQueue[application=...]``.
@@ -119,6 +128,43 @@ class JobQueue(Generic[CCT]):
return application
raise RuntimeError("The application instance is no longer alive.")
@property
def scheduler_configuration(self) -> JSONDict:
"""Provides configuration values that are used by :class:`JobQueue` for :attr:`scheduler`.
Tip:
Since calling
:meth:`scheduler.configure() <apscheduler.schedulers.base.BaseScheduler.configure>`
deletes any previous setting, please make sure to pass these values to the method call
in addition to your custom values:
.. code-block:: python
scheduler.configure(..., **job_queue.scheduler_configuration)
Alternatively, you can also use methods like
:meth:`~apscheduler.schedulers.base.BaseScheduler.add_jobstore` to avoid using
:meth:`~apscheduler.schedulers.base.BaseScheduler.configure` altogether.
.. versionadded:: 20.7
Returns:
Dict[:obj:`str`, :obj:`object`]: The configuration values as dictionary.
"""
timezone: object = pytz.utc
if (
self._application
and isinstance(self.application.bot, ExtBot)
and self.application.bot.defaults
):
timezone = self.application.bot.defaults.tzinfo or pytz.utc
return {
"timezone": timezone,
"executors": {"default": self._executor},
}
def _tz_now(self) -> datetime.datetime:
return datetime.datetime.now(self.scheduler.timezone)
@@ -166,11 +212,7 @@ class JobQueue(Generic[CCT]):
"""
self._application = weakref.ref(application)
if isinstance(application.bot, ExtBot) and application.bot.defaults:
self.scheduler.configure(
timezone=application.bot.defaults.tzinfo or pytz.utc,
executors={"default": self._executor},
)
self.scheduler.configure(**self.scheduler_configuration)
@staticmethod
async def job_callback(job_queue: "JobQueue[CCT]", job: "Job[CCT]") -> None:
+2 -2
View File
@@ -492,7 +492,7 @@ class PicklePersistence(BasePersistence[UD, CD, BD]):
"""
if self.chat_data is None:
return
self.chat_data.pop(chat_id, None) # type: ignore[arg-type]
self.chat_data.pop(chat_id, None)
if not self.on_flush:
if not self.single_file:
@@ -511,7 +511,7 @@ class PicklePersistence(BasePersistence[UD, CD, BD]):
"""
if self.user_data is None:
return
self.user_data.pop(user_id, None) # type: ignore[arg-type]
self.user_data.pop(user_id, None)
if not self.on_flush:
if not self.single_file:
+63 -15
View File
@@ -78,6 +78,8 @@ class Updater(AsyncContextManager["Updater"]):
finally:
await updater.shutdown()
.. seealso:: :meth:`__aenter__` and :meth:`__aexit__`.
.. seealso:: :wiki:`Architecture Overview <Architecture>`,
:wiki:`Builder Pattern <Builder-Pattern>`
@@ -126,7 +128,16 @@ class Updater(AsyncContextManager["Updater"]):
self.__polling_cleanup_cb: Optional[Callable[[], Coroutine[Any, Any, None]]] = None
async def __aenter__(self: _UpdaterType) -> _UpdaterType: # noqa: PYI019
"""Simple context manager which initializes the Updater."""
"""
|async_context_manager| :meth:`initializes <initialize>` the Updater.
Returns:
The initialized Updater instance.
Raises:
:exc:`Exception`: If an exception is raised during initialization, :meth:`shutdown`
is called in this case.
"""
try:
await self.initialize()
return self
@@ -140,7 +151,7 @@ class Updater(AsyncContextManager["Updater"]):
exc_val: Optional[BaseException],
exc_tb: Optional[TracebackType],
) -> None:
"""Shutdown the Updater from the context manager."""
"""|async_context_manager| :meth:`shuts down <shutdown>` the Updater."""
# Make sure not to return `True` so that exceptions are not suppressed
# https://docs.python.org/3/reference/datamodel.html?#object.__aexit__
await self.shutdown()
@@ -200,7 +211,7 @@ class Updater(AsyncContextManager["Updater"]):
poll_interval: float = 0.0,
timeout: int = 10,
bootstrap_retries: int = -1,
read_timeout: float = 2,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -225,16 +236,40 @@ class Updater(AsyncContextManager["Updater"]):
* 0 - no retries
* > 0 - retry up to X times
read_timeout (:obj:`float`, optional): Value to pass to
:paramref:`telegram.Bot.get_updates.read_timeout`. Defaults to ``2``.
:paramref:`telegram.Bot.get_updates.read_timeout`. Defaults to
:attr:`~telegram.request.BaseRequest.DEFAULT_NONE`.
.. versionchanged:: 20.7
Defaults to :attr:`~telegram.request.BaseRequest.DEFAULT_NONE` instead of
``2``.
.. deprecated:: 20.7
Deprecated in favor of setting the timeout via
:meth:`telegram.ext.ApplicationBuilder.get_updates_read_timeout` or
:paramref:`telegram.Bot.get_updates_request`.
write_timeout (:obj:`float` | :obj:`None`, optional): Value to pass to
:paramref:`telegram.Bot.get_updates.write_timeout`. Defaults to
:attr:`~telegram.request.BaseRequest.DEFAULT_NONE`.
.. deprecated:: 20.7
Deprecated in favor of setting the timeout via
:meth:`telegram.ext.ApplicationBuilder.get_updates_write_timeout` or
:paramref:`telegram.Bot.get_updates_request`.
connect_timeout (:obj:`float` | :obj:`None`, optional): Value to pass to
:paramref:`telegram.Bot.get_updates.connect_timeout`. Defaults to
:attr:`~telegram.request.BaseRequest.DEFAULT_NONE`.
.. deprecated:: 20.7
Deprecated in favor of setting the timeout via
:meth:`telegram.ext.ApplicationBuilder.get_updates_connect_timeout` or
:paramref:`telegram.Bot.get_updates_request`.
pool_timeout (:obj:`float` | :obj:`None`, optional): Value to pass to
:paramref:`telegram.Bot.get_updates.pool_timeout`. Defaults to
:attr:`~telegram.request.BaseRequest.DEFAULT_NONE`.
.. deprecated:: 20.7
Deprecated in favor of setting the timeout via
:meth:`telegram.ext.ApplicationBuilder.get_updates_pool_timeout` or
:paramref:`telegram.Bot.get_updates_request`.
allowed_updates (List[:obj:`str`], optional): Passed to
:meth:`telegram.Bot.get_updates`.
drop_pending_updates (:obj:`bool`, optional): Whether to clean any pending updates on
@@ -260,6 +295,10 @@ class Updater(AsyncContextManager["Updater"]):
:exc:`RuntimeError`: If the updater is already running or was not initialized.
"""
# We refrain from issuing deprecation warnings for the timeout parameters here, as we
# already issue them in `Application`. This means that there are no warnings when using
# `Updater` without `Application`, but this is a rather special use case.
if error_callback and asyncio.iscoroutinefunction(error_callback):
raise TypeError(
"The `error_callback` must not be a coroutine function! Use an ordinary function "
@@ -305,7 +344,7 @@ class Updater(AsyncContextManager["Updater"]):
self,
poll_interval: float,
timeout: int,
read_timeout: float,
read_timeout: ODVInput[float],
write_timeout: ODVInput[float],
connect_timeout: ODVInput[float],
pool_timeout: ODVInput[float],
@@ -390,16 +429,25 @@ class Updater(AsyncContextManager["Updater"]):
_LOGGER.debug(
"Calling `get_updates` one more time to mark all fetched updates as read."
)
await self.bot.get_updates(
offset=self._last_update_id,
# We don't want to do long polling here!
timeout=0,
read_timeout=read_timeout,
connect_timeout=connect_timeout,
write_timeout=write_timeout,
pool_timeout=pool_timeout,
allowed_updates=allowed_updates,
)
try:
await self.bot.get_updates(
offset=self._last_update_id,
# We don't want to do long polling here!
timeout=0,
read_timeout=read_timeout,
connect_timeout=connect_timeout,
write_timeout=write_timeout,
pool_timeout=pool_timeout,
allowed_updates=allowed_updates,
)
except TelegramError as exc:
_LOGGER.error(
"Error while calling `get_updates` one more time to mark all fetched updates "
"as read: %s. Suppressing error to ensure graceful shutdown. When polling for "
"updates is restarted, updates may be fetched again. Please adjust timeouts "
"via `ApplicationBuilder` or the parameter `get_updates_request` of `Bot`.",
exc_info=exc,
)
self.__polling_cleanup_cb = _get_updates_cleanup
+3 -1
View File
@@ -99,7 +99,9 @@ class TrackingDict(UserDict, Generic[_KT, _VT]):
# Mypy seems a bit inconsistent about what it wants as types for `default` and return value
# so we just ignore a bit
def pop( # type: ignore[override]
self, key: _KT, default: _VT = DEFAULT_NONE # type: ignore[assignment]
self,
key: _KT,
default: _VT = DEFAULT_NONE, # type: ignore[assignment]
) -> _VT:
if key in self:
self.__track_write(key)
+69 -1
View File
@@ -66,6 +66,7 @@ __all__ = (
"LOCATION",
"Language",
"MessageFilter",
"Mention",
"PASSPORT_DATA",
"PHOTO",
"POLL",
@@ -91,7 +92,6 @@ __all__ = (
"VOICE",
"ViaBot",
)
import mimetypes
import re
from abc import ABC, abstractmethod
@@ -99,6 +99,7 @@ from typing import (
Collection,
Dict,
FrozenSet,
Iterable,
List,
Match,
NoReturn,
@@ -1521,6 +1522,73 @@ LOCATION = _Location(name="filters.LOCATION")
"""Messages that contain :attr:`telegram.Message.location`."""
class Mention(MessageFilter):
"""Messages containing mentions of specified users or chats.
Examples:
.. code-block:: python
MessageHandler(filters.Mention("username"), callback)
MessageHandler(filters.Mention(["@username", 123456]), callback)
.. versionadded:: 20.7
Args:
mentions (:obj:`int` | :obj:`str` | :class:`telegram.User` | Collection[:obj:`int` | \
:obj:`str` | :class:`telegram.User`]):
Specifies the users and chats to filter for. Messages that do not mention at least one
of the specified users or chats will not be handled. Leading ``'@'`` s in usernames
will be discarded.
"""
__slots__ = ("_mentions",)
def __init__(self, mentions: SCT[Union[int, str, TGUser]]):
super().__init__(name=f"filters.Mention({mentions})")
if isinstance(mentions, Iterable) and not isinstance(mentions, str):
self._mentions = {self._fix_mention_username(mention) for mention in mentions}
else:
self._mentions = {self._fix_mention_username(mentions)}
@staticmethod
def _fix_mention_username(mention: Union[int, str, TGUser]) -> Union[int, str, TGUser]:
if not isinstance(mention, str):
return mention
return mention.lstrip("@")
@classmethod
def _check_mention(cls, message: Message, mention: Union[int, str, TGUser]) -> bool:
if not message.entities:
return False
entity_texts = message.parse_entities(
types=[MessageEntity.MENTION, MessageEntity.TEXT_MENTION]
)
if isinstance(mention, TGUser):
return any(
mention.id == entity.user.id
or mention.username == entity.user.username
or mention.username == cls._fix_mention_username(entity_texts[entity])
for entity in message.entities
if entity.user
) or any(
mention.username == cls._fix_mention_username(entity_text)
for entity_text in entity_texts.values()
)
if isinstance(mention, int):
return bool(
any(mention == entity.user.id for entity in message.entities if entity.user)
)
return any(
mention == cls._fix_mention_username(entity_text)
for entity_text in entity_texts.values()
)
def filter(self, message: Message) -> bool:
return any(self._check_mention(message, mention) for mention in self._mentions)
class _PassportData(MessageFilter):
__slots__ = ()
+56 -2
View File
@@ -27,6 +27,7 @@ from telegram._utils.defaultvalue import DEFAULT_NONE as _DEFAULT_NONE
from telegram._utils.defaultvalue import DefaultValue
from telegram._utils.logging import get_logger
from telegram._utils.types import JSONDict, ODVInput
from telegram._utils.warnings import warn
from telegram._version import __version__ as ptb_ver
from telegram.error import (
BadRequest,
@@ -39,6 +40,7 @@ from telegram.error import (
TelegramError,
)
from telegram.request._requestdata import RequestData
from telegram.warnings import PTBDeprecationWarning
RT = TypeVar("RT", bound="BaseRequest")
@@ -70,6 +72,8 @@ class BaseRequest(
finally:
await request_object.shutdown()
.. seealso:: :meth:`__aenter__` and :meth:`__aexit__`.
Tip:
JSON encoding and decoding is done with the standard library's :mod:`json` by default.
To use a custom library for this, you can override :meth:`parse_json_payload` and implement
@@ -99,6 +103,15 @@ class BaseRequest(
"""
async def __aenter__(self: RT) -> RT:
"""|async_context_manager| :meth:`initializes <initialize>` the Request.
Returns:
The initialized Request instance.
Raises:
:exc:`Exception`: If an exception is raised during initialization, :meth:`shutdown`
is called in this case.
"""
try:
await self.initialize()
return self
@@ -112,10 +125,29 @@ class BaseRequest(
exc_val: Optional[BaseException],
exc_tb: Optional[TracebackType],
) -> None:
"""|async_context_manager| :meth:`shuts down <shutdown>` the Request."""
# Make sure not to return `True` so that exceptions are not suppressed
# https://docs.python.org/3/reference/datamodel.html?#object.__aexit__
await self.shutdown()
@property
def read_timeout(self) -> Optional[float]:
"""This property must return the default read timeout in seconds used by this class.
More precisely, the returned value should be the one used when
:paramref:`post.read_timeout` of :meth:post` is not passed/equal to :attr:`DEFAULT_NONE`.
.. versionadded:: 20.7
Warning:
For now this property does not need to be implemented by subclasses and will raise
:exc:`NotImplementedError` if accessed without being overridden. However, in future
versions, this property will be abstract and must be implemented by subclasses.
Returns:
:obj:`float` | :obj:`None`: The read timeout in seconds.
"""
raise NotImplementedError
@abc.abstractmethod
async def initialize(self) -> None:
"""Initialize resources used by this class. Must be implemented by a subclass."""
@@ -271,8 +303,28 @@ class BaseRequest(
TelegramError
"""
# TGs response also has the fields 'ok' and 'error_code'.
# However, we rather rely on the HTTP status code for now.
# Import needs to be here since HTTPXRequest is a subclass of BaseRequest
from telegram.request import HTTPXRequest # pylint: disable=import-outside-toplevel
# 20 is the documented default value for all the media related bot methods and custom
# implementations of BaseRequest may explicitly rely on that. Hence, we follow the
# standard deprecation policy and deprecate starting with version 20.7.
# For our own implementation HTTPXRequest, we can handle that ourselves, so we skip the
# warning in that case.
has_files = request_data and request_data.multipart_data
if (
has_files
and not isinstance(self, HTTPXRequest)
and isinstance(write_timeout, DefaultValue)
):
warn(
f"The `write_timeout` parameter passed to {self.__class__.__name__}.do_request "
"will default to `BaseRequest.DEFAULT_NONE` instead of 20 in future versions "
"for *all* methods of the `Bot` class, including methods sending media.",
PTBDeprecationWarning,
stacklevel=3,
)
write_timeout = 20
try:
code, payload = await self.do_request(
@@ -301,6 +353,8 @@ class BaseRequest(
# In some special cases, we can raise more informative exceptions:
# see https://core.telegram.org/bots/api#responseparameters and
# https://core.telegram.org/bots/api#making-requests
# TGs response also has the fields 'ok' and 'error_code'.
# However, we rather rely on the HTTP status code for now.
parameters = response_data.get("parameters")
if parameters:
migrate_to_chat_id = parameters.get("migrate_to_chat_id")
+82 -33
View File
@@ -17,16 +17,18 @@
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains methods to make POST and GET requests using the httpx library."""
from typing import Optional, Tuple
from typing import Collection, Optional, Tuple, Union
import httpx
from telegram._utils.defaultvalue import DefaultValue
from telegram._utils.logging import get_logger
from telegram._utils.types import HTTPVersion, ODVInput
from telegram._utils.types import HTTPVersion, ODVInput, SocketOpt
from telegram._utils.warnings import warn
from telegram.error import NetworkError, TimedOut
from telegram.request._baserequest import BaseRequest
from telegram.request._requestdata import RequestData
from telegram.warnings import PTBDeprecationWarning
# Note to future devs:
# Proxies are currently only tested manually. The httpx development docs have a nice guide on that:
@@ -49,17 +51,10 @@ class HTTPXRequest(BaseRequest):
Note:
Independent of the value, one additional connection will be reserved for
:meth:`telegram.Bot.get_updates`.
proxy_url (:obj:`str`, optional): The URL to the proxy server. For example
``'http://127.0.0.1:3128'`` or ``'socks5://127.0.0.1:3128'``. Defaults to :obj:`None`.
proxy_url (:obj:`str`, optional): Legacy name for :paramref:`proxy`, kept for backward
compatibility. Defaults to :obj:`None`.
Note:
* The proxy URL can also be set via the environment variables ``HTTPS_PROXY`` or
``ALL_PROXY``. See `the docs of httpx`_ for more info.
* For Socks5 support, additional dependencies are required. Make sure to install
PTB via :command:`pip install "python-telegram-bot[socks]"` in this case.
* Socks5 proxies can not be set via environment variables.
.. _the docs of httpx: https://www.python-httpx.org/environment_variables/#proxies
.. deprecated:: 20.7
read_timeout (:obj:`float` | :obj:`None`, optional): If passed, specifies the maximum
amount of time (in seconds) to wait for a response from Telegram's server.
This value is used unless a different value is passed to :meth:`do_request`.
@@ -91,6 +86,32 @@ class HTTPXRequest(BaseRequest):
.. versionchanged:: 20.5
Accept ``"2"`` as a valid value.
socket_options (Collection[:obj:`tuple`], optional): Socket options to be passed to the
underlying `library \
<https://www.encode.io/httpcore/async/#httpcore.AsyncConnectionPool.__init__>`_.
Note:
The values accepted by this parameter depend on the operating system.
This is a low-level parameter and should only be used if you are familiar with
these concepts.
.. versionadded:: 20.7
proxy (:obj:`str` | ``httpx.Proxy`` | ``httpx.URL``, optional): The URL to a proxy server,
a ``httpx.Proxy`` object or a ``httpx.URL`` object. For example
``'http://127.0.0.1:3128'`` or ``'socks5://127.0.0.1:3128'``. Defaults to :obj:`None`.
Note:
* The proxy URL can also be set via the environment variables ``HTTPS_PROXY`` or
``ALL_PROXY``. See `the docs of httpx`_ for more info.
* HTTPS proxies can be configured by passing a ``httpx.Proxy`` object with
a corresponding ``ssl_context``.
* For Socks5 support, additional dependencies are required. Make sure to install
PTB via :command:`pip install "python-telegram-bot[socks]"` in this case.
* Socks5 proxies can not be set via environment variables.
.. _the docs of httpx: https://www.python-httpx.org/environment_variables/#proxies
.. versionadded:: 20.7
"""
@@ -99,13 +120,27 @@ class HTTPXRequest(BaseRequest):
def __init__(
self,
connection_pool_size: int = 1,
proxy_url: Optional[str] = None,
proxy_url: Optional[Union[str, httpx.Proxy, httpx.URL]] = None,
read_timeout: Optional[float] = 5.0,
write_timeout: Optional[float] = 5.0,
connect_timeout: Optional[float] = 5.0,
pool_timeout: Optional[float] = 1.0,
http_version: HTTPVersion = "1.1",
socket_options: Optional[Collection[SocketOpt]] = None,
proxy: Optional[Union[str, httpx.Proxy, httpx.URL]] = None,
):
if proxy_url is not None and proxy is not None:
raise ValueError("The parameters `proxy_url` and `proxy` are mutually exclusive.")
if proxy_url is not None:
proxy = proxy_url
warn(
"The parameter `proxy_url` is deprecated since version 20.7. Use `proxy` "
"instead.",
PTBDeprecationWarning,
stacklevel=2,
)
self._http_version = http_version
timeout = httpx.Timeout(
connect=connect_timeout,
@@ -122,16 +157,21 @@ class HTTPXRequest(BaseRequest):
raise ValueError("`http_version` must be either '1.1', '2.0' or '2'.")
http1 = http_version == "1.1"
# See https://github.com/python-telegram-bot/python-telegram-bot/pull/3542
# for why we need to use `dict()` here.
self._client_kwargs = dict( # pylint: disable=use-dict-literal # noqa: C408
timeout=timeout,
proxies=proxy_url,
limits=limits,
http1=http1,
http2=not http1,
http_kwargs = {"http1": http1, "http2": not http1}
transport = (
httpx.AsyncHTTPTransport(
socket_options=socket_options,
)
if socket_options
else None
)
self._client_kwargs = {
"timeout": timeout,
"proxies": proxy,
"limits": limits,
"transport": transport,
**http_kwargs,
}
try:
self._client = self._build_client()
@@ -158,6 +198,16 @@ class HTTPXRequest(BaseRequest):
"""
return self._http_version
@property
def read_timeout(self) -> Optional[float]:
"""See :attr:`BaseRequest.read_timeout`.
Returns:
:obj:`float` | :obj:`None`: The default read timeout in seconds as passed to
:paramref:`HTTPXRequest.read_timeout`.
"""
return self._client.timeout.read
def _build_client(self) -> httpx.AsyncClient:
return httpx.AsyncClient(**self._client_kwargs) # type: ignore[arg-type]
@@ -188,17 +238,25 @@ class HTTPXRequest(BaseRequest):
if self._client.is_closed:
raise RuntimeError("This HTTPXRequest is not initialized!")
files = request_data.multipart_data if request_data else None
data = request_data.json_parameters if request_data else None
# If user did not specify timeouts (for e.g. in a bot method), use the default ones when we
# created this instance.
if isinstance(read_timeout, DefaultValue):
read_timeout = self._client.timeout.read
if isinstance(write_timeout, DefaultValue):
write_timeout = self._client.timeout.write
if isinstance(connect_timeout, DefaultValue):
connect_timeout = self._client.timeout.connect
if isinstance(pool_timeout, DefaultValue):
pool_timeout = self._client.timeout.pool
if isinstance(write_timeout, DefaultValue):
# Making the networking backend decide on the proper timeout values instead of doing
# it via the default values of the Bot methods was introduced in version 20.7.
# We hard-code the value here for now until we add additional parameters to this
# class to control the media_write_timeout separately.
write_timeout = self._client.timeout.write if not files else 20
timeout = httpx.Timeout(
connect=connect_timeout,
read=read_timeout,
@@ -206,15 +264,6 @@ class HTTPXRequest(BaseRequest):
pool=pool_timeout,
)
# TODO p0: On Linux, use setsockopt to properly set socket level keepalive.
# (socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 120)
# (socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 30)
# (socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 8)
# TODO p4: Support setsockopt on lesser platforms than Linux.
files = request_data.multipart_data if request_data else None
data = request_data.json_parameters if request_data else None
try:
res = await self._client.request(
method=method,
+1 -1
View File
@@ -322,7 +322,7 @@ class TestAnimationWithRequest(TestAnimationBase):
)
assert message.reply_to_message is None
else:
with pytest.raises(BadRequest, match="message not found"):
with pytest.raises(BadRequest, match="Message to reply not found"):
await default_bot.send_animation(
chat_id, animation, reply_to_message_id=reply_to_message.message_id
)
+1 -1
View File
@@ -156,7 +156,7 @@ class TestContactWithRequest(TestContactBase):
)
assert message.reply_to_message is None
else:
with pytest.raises(BadRequest, match="message not found"):
with pytest.raises(BadRequest, match="Message to reply not found"):
await default_bot.send_contact(
chat_id, contact=contact, reply_to_message_id=reply_to_message.message_id
)
+1 -1
View File
@@ -335,7 +335,7 @@ class TestDocumentWithRequest(TestDocumentBase):
)
assert message.reply_to_message is None
else:
with pytest.raises(BadRequest, match="message not found"):
with pytest.raises(BadRequest, match="Message to reply not found"):
await default_bot.send_document(
chat_id, document, reply_to_message_id=reply_to_message.message_id
)
+1 -1
View File
@@ -822,7 +822,7 @@ class TestSendMediaGroupWithRequest:
)
assert [m.reply_to_message is None for m in messages]
else:
with pytest.raises(BadRequest, match="message not found"):
with pytest.raises(BadRequest, match="Message to reply not found"):
await default_bot.send_media_group(
chat_id, media_group, reply_to_message_id=reply_to_message.message_id
)
+1 -1
View File
@@ -189,7 +189,7 @@ class TestLocationWithRequest:
)
assert message.reply_to_message is None
else:
with pytest.raises(BadRequest, match="message not found"):
with pytest.raises(BadRequest, match="Message to reply not found"):
await default_bot.send_location(
chat_id, location=location, reply_to_message_id=reply_to_message.message_id
)
+1 -1
View File
@@ -357,7 +357,7 @@ class TestPhotoWithRequest(TestPhotoBase):
)
assert message.reply_to_message is None
else:
with pytest.raises(BadRequest, match="message not found"):
with pytest.raises(BadRequest, match="Message to reply not found"):
await default_bot.send_photo(
chat_id, photo_file, reply_to_message_id=reply_to_message.message_id
)
+1 -1
View File
@@ -399,7 +399,7 @@ class TestStickerWithRequest(TestStickerBase):
)
assert message.reply_to_message is None
else:
with pytest.raises(BadRequest, match="message not found"):
with pytest.raises(BadRequest, match="Message to reply not found"):
await default_bot.send_sticker(
chat_id, sticker, reply_to_message_id=reply_to_message.message_id
)
+1 -1
View File
@@ -171,7 +171,7 @@ class TestVenueWithRequest(TestVenueBase):
)
assert message.reply_to_message is None
else:
with pytest.raises(BadRequest, match="message not found"):
with pytest.raises(BadRequest, match="Message to reply not found"):
await default_bot.send_venue(
chat_id, venue=venue, reply_to_message_id=reply_to_message.message_id
)
+1 -1
View File
@@ -359,7 +359,7 @@ class TestVideoWithRequest(TestVideoBase):
)
assert message.reply_to_message is None
else:
with pytest.raises(BadRequest, match="message not found"):
with pytest.raises(BadRequest, match="Message to reply not found"):
await default_bot.send_video(
chat_id, video, reply_to_message_id=reply_to_message.message_id
)
+1 -1
View File
@@ -258,7 +258,7 @@ class TestVideoNoteWithRequest(TestVideoNoteBase):
)
assert message.reply_to_message is None
else:
with pytest.raises(BadRequest, match="message not found"):
with pytest.raises(BadRequest, match="Message to reply not found"):
await default_bot.send_video_note(
chat_id, video_note, reply_to_message_id=reply_to_message.message_id
)
+1 -1
View File
@@ -308,7 +308,7 @@ class TestVoiceWithRequest(TestVoiceBase):
)
assert message.reply_to_message is None
else:
with pytest.raises(BadRequest, match="message not found"):
with pytest.raises(BadRequest, match="Message to reply not found"):
await default_bot.send_voice(
chat_id, voice, reply_to_message_id=reply_to_message.message_id
)
+1 -1
View File
@@ -276,7 +276,7 @@ class TestInvoiceWithRequest(TestInvoiceBase):
)
assert message.reply_to_message is None
else:
with pytest.raises(BadRequest, match="message not found"):
with pytest.raises(BadRequest, match="Message to reply not found"):
await default_bot.send_invoice(
chat_id,
self.title,
+50 -1
View File
@@ -1498,6 +1498,54 @@ class TestApplication:
found_log = True
assert found_log
@pytest.mark.parametrize(
"timeout_name",
["read_timeout", "connect_timeout", "write_timeout", "pool_timeout", "poll_interval"],
)
@pytest.mark.skipif(
platform.system() == "Windows",
reason="Can't send signals without stopping whole process on windows",
)
def test_run_polling_timeout_deprecation_warnings(
self, timeout_name, monkeypatch, recwarn, app
):
async def get_updates(*args, **kwargs):
# This makes sure that other coroutines have a chance of running as well
await asyncio.sleep(0)
return []
def thread_target():
waited = 0
while not app.running:
time.sleep(0.05)
waited += 0.05
if waited > 5:
pytest.fail("App apparently won't start")
time.sleep(0.05)
os.kill(os.getpid(), signal.SIGINT)
monkeypatch.setattr(app.bot, "get_updates", get_updates)
thread = Thread(target=thread_target)
thread.start()
kwargs = {timeout_name: 42}
app.run_polling(drop_pending_updates=True, close_loop=False, **kwargs)
thread.join()
if timeout_name == "poll_interval":
assert len(recwarn) == 0
return
assert len(recwarn) == 1
assert "Setting timeouts via `Application.run_polling` is deprecated." in str(
recwarn[0].message
)
assert recwarn[0].category is PTBDeprecationWarning
assert recwarn[0].filename == __file__, "wrong stacklevel"
@pytest.mark.skipif(
platform.system() == "Windows",
reason="Can't send signals without stopping whole process on windows",
@@ -2210,7 +2258,8 @@ class TestApplication:
else:
app.run_webhook(close_loop=False, stop_signals=None)
assert len(recwarn) == 0
for record in recwarn:
assert not str(record.message).startswith("Could not add signal handlers for the stop")
@pytest.mark.flaky(3, 1) # loop.call_later will error the test when a flood error is received
def test_signal_handlers(self, app, monkeypatch):
+145 -9
View File
@@ -17,11 +17,15 @@
# 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 asyncio
import inspect
from dataclasses import dataclass
from http import HTTPStatus
import httpx
import pytest
from telegram import Bot
from telegram._utils.defaultvalue import DEFAULT_NONE
from telegram.ext import (
AIORateLimiter,
Application,
@@ -37,6 +41,7 @@ from telegram.ext import (
from telegram.ext._applicationbuilder import _BOT_CHECKS
from telegram.ext._baseupdateprocessor import SimpleUpdateProcessor
from telegram.request import HTTPXRequest
from telegram.warnings import PTBDeprecationWarning
from tests.auxil.constants import PRIVATE_KEY
from tests.auxil.envvars import TEST_WITH_OPT_DEPS
from tests.auxil.files import data_file
@@ -64,6 +69,34 @@ class TestApplicationBuilder:
assert getattr(builder, attr, "err") != "err", f"got extra slot '{attr}'"
assert len(mro_slots(builder)) == len(set(mro_slots(builder))), "duplicate slot"
@pytest.mark.parametrize("get_updates", [True, False])
def test_all_methods_request(self, builder, get_updates):
arguments = inspect.signature(HTTPXRequest.__init__).parameters.keys()
prefix = "get_updates_" if get_updates else ""
for argument in arguments:
if argument == "self":
continue
assert hasattr(builder, prefix + argument), f"missing method {prefix}{argument}"
@pytest.mark.parametrize("bot_class", [Bot, ExtBot])
def test_all_methods_bot(self, builder, bot_class):
arguments = inspect.signature(bot_class.__init__).parameters.keys()
for argument in arguments:
if argument == "self":
continue
if argument == "private_key_password":
argument = "private_key" # noqa: PLW2901
assert hasattr(builder, argument), f"missing method {argument}"
def test_all_methods_application(self, builder):
arguments = inspect.signature(Application.__init__).parameters.keys()
for argument in arguments:
if argument == "self":
continue
if argument == "update_processor":
argument = "concurrent_updates" # noqa: PLW2901
assert hasattr(builder, argument), f"missing method {argument}"
def test_job_queue_init_exception(self, monkeypatch):
def init_raises_runtime_error(*args, **kwargs):
raise RuntimeError("RuntimeError")
@@ -91,6 +124,7 @@ class TestApplicationBuilder:
limits: object
http1: object
http2: object
transport: object = None
monkeypatch.setattr(httpx, "AsyncClient", Client)
@@ -168,7 +202,9 @@ class TestApplicationBuilder:
"pool_timeout",
"read_timeout",
"write_timeout",
"proxy",
"proxy_url",
"socket_options",
"bot",
"updater",
"http_version",
@@ -177,8 +213,9 @@ class TestApplicationBuilder:
def test_mutually_exclusive_for_request(self, builder, method):
builder.request(1)
method_name = method.replace("proxy_url", "proxy")
with pytest.raises(
RuntimeError, match=f"`{method}` may only be set, if no request instance"
RuntimeError, match=f"`{method_name}` may only be set, if no request instance"
):
getattr(builder, method)(data_file("private.key"))
@@ -195,7 +232,9 @@ class TestApplicationBuilder:
"get_updates_pool_timeout",
"get_updates_read_timeout",
"get_updates_write_timeout",
"get_updates_proxy",
"get_updates_proxy_url",
"get_updates_socket_options",
"get_updates_http_version",
"bot",
"updater",
@@ -204,9 +243,10 @@ class TestApplicationBuilder:
def test_mutually_exclusive_for_get_updates_request(self, builder, method):
builder.get_updates_request(1)
method_name = method.replace("proxy_url", "proxy")
with pytest.raises(
RuntimeError,
match=f"`{method}` may only be set, if no get_updates_request instance",
match=f"`{method_name}` may only be set, if no get_updates_request instance",
):
getattr(builder, method)(data_file("private.key"))
@@ -224,13 +264,17 @@ class TestApplicationBuilder:
"get_updates_read_timeout",
"get_updates_write_timeout",
"get_updates_proxy_url",
"get_updates_proxy",
"get_updates_socket_options",
"get_updates_http_version",
"connection_pool_size",
"connect_timeout",
"pool_timeout",
"read_timeout",
"write_timeout",
"proxy",
"proxy_url",
"socket_options",
"http_version",
"bot",
"update_queue",
@@ -241,14 +285,17 @@ class TestApplicationBuilder:
def test_mutually_exclusive_for_updater(self, builder, method):
builder.updater(1)
method_name = method.replace("proxy_url", "proxy")
with pytest.raises(
RuntimeError,
match=f"`{method}` may only be set, if no updater",
match=f"`{method_name}` may only be set, if no updater",
):
getattr(builder, method)(data_file("private.key"))
builder = ApplicationBuilder()
getattr(builder, method)(data_file("private.key"))
method = method.replace("proxy_url", "proxy")
with pytest.raises(RuntimeError, match=f"`updater` may only be set, if no {method}"):
builder.updater(1)
@@ -260,14 +307,18 @@ class TestApplicationBuilder:
"get_updates_pool_timeout",
"get_updates_read_timeout",
"get_updates_write_timeout",
"get_updates_proxy",
"get_updates_proxy_url",
"get_updates_socket_options",
"get_updates_http_version",
"connection_pool_size",
"connect_timeout",
"pool_timeout",
"read_timeout",
"write_timeout",
"proxy",
"proxy_url",
"socket_options",
"bot",
"http_version",
]
@@ -284,7 +335,16 @@ class TestApplicationBuilder:
getattr(builder, method)(data_file("private.key"))
builder.updater(None)
def test_all_bot_args_custom(self, builder, bot, monkeypatch):
# We test with bot the new & legacy version to ensure that the legacy version still works
@pytest.mark.parametrize(
("proxy_method", "get_updates_proxy_method"),
[("proxy", "get_updates_proxy"), ("proxy_url", "get_updates_proxy_url")],
ids=["new", "legacy"],
)
def test_all_bot_args_custom(
self, builder, bot, monkeypatch, proxy_method, get_updates_proxy_method
):
# Only socket_options is tested in a standalone test, since that's easier
defaults = Defaults()
request = HTTPXRequest()
get_updates_request = HTTPXRequest()
@@ -322,19 +382,21 @@ class TestApplicationBuilder:
limits: object
http1: object
http2: object
transport: object = None
monkeypatch.setattr(httpx, "AsyncClient", Client)
builder = ApplicationBuilder().token(bot.token)
builder.connection_pool_size(1).connect_timeout(2).pool_timeout(3).read_timeout(
4
).write_timeout(5).proxy_url("proxy_url").http_version("1.1")
).write_timeout(5).http_version("1.1")
getattr(builder, proxy_method)("proxy")
app = builder.build()
client = app.bot.request._client
assert client.timeout == httpx.Timeout(pool=3, connect=2, read=4, write=5)
assert client.limits == httpx.Limits(max_connections=1, max_keepalive_connections=1)
assert client.proxies == "proxy_url"
assert client.proxies == "proxy"
assert client.http1 is True
assert client.http2 is False
@@ -343,20 +405,49 @@ class TestApplicationBuilder:
2
).get_updates_pool_timeout(3).get_updates_read_timeout(4).get_updates_write_timeout(
5
).get_updates_proxy_url(
"proxy_url"
).get_updates_http_version(
"1.1"
)
getattr(builder, get_updates_proxy_method)("get_updates_proxy")
app = builder.build()
client = app.bot._request[0]._client
assert client.timeout == httpx.Timeout(pool=3, connect=2, read=4, write=5)
assert client.limits == httpx.Limits(max_connections=1, max_keepalive_connections=1)
assert client.proxies == "proxy_url"
assert client.proxies == "get_updates_proxy"
assert client.http1 is True
assert client.http2 is False
def test_custom_socket_options(self, builder, monkeypatch, bot):
httpx_request_kwargs = []
httpx_request_init = HTTPXRequest.__init__
def init_transport(*args, **kwargs):
nonlocal httpx_request_kwargs
# This is called once for request and once for get_updates_request, so we make
# it a list
httpx_request_kwargs.append(kwargs.copy())
httpx_request_init(*args, **kwargs)
monkeypatch.setattr(HTTPXRequest, "__init__", init_transport)
builder.token(bot.token).build()
assert httpx_request_kwargs[0].get("socket_options") is None
assert httpx_request_kwargs[1].get("socket_options") is None
httpx_request_kwargs = []
ApplicationBuilder().token(bot.token).socket_options(((1, 2, 3),)).connection_pool_size(
"request"
).get_updates_socket_options(((4, 5, 6),)).get_updates_connection_pool_size(
"get_updates"
).build()
for kwargs in httpx_request_kwargs:
if kwargs.get("connection_pool_size") == "request":
assert kwargs.get("socket_options") == ((1, 2, 3),)
else:
assert kwargs.get("socket_options") == ((4, 5, 6),)
def test_custom_application_class(self, bot, builder):
class CustomApplication(Application):
def __init__(self, arg, **kwargs):
@@ -476,3 +567,48 @@ class TestApplicationBuilder:
assert app.job_queue is None
assert isinstance(app.update_queue, asyncio.Queue)
assert isinstance(app.updater, Updater)
def test_proxy_url_deprecation_warning(self, bot, builder, recwarn):
builder.token(bot.token).proxy_url("proxy_url")
assert len(recwarn) == 1
assert "`ApplicationBuilder.proxy_url` is deprecated" in str(recwarn[0].message)
assert recwarn[0].category is PTBDeprecationWarning
assert recwarn[0].filename == __file__, "wrong stacklevel"
def test_get_updates_proxy_url_deprecation_warning(self, bot, builder, recwarn):
builder.token(bot.token).get_updates_proxy_url("get_updates_proxy_url")
assert len(recwarn) == 1
assert "`ApplicationBuilder.get_updates_proxy_url` is deprecated" in str(
recwarn[0].message
)
assert recwarn[0].category is PTBDeprecationWarning
assert recwarn[0].filename == __file__, "wrong stacklevel"
@pytest.mark.parametrize(
("read_timeout", "timeout", "expected"),
[
(None, None, 0),
(1, None, 1),
(None, 1, 1),
(DEFAULT_NONE, None, 10),
(DEFAULT_NONE, 1, 11),
(1, 2, 3),
],
)
async def test_get_updates_read_timeout_value_passing(
self, bot, read_timeout, timeout, expected, monkeypatch, builder
):
# This test is a double check that ApplicationBuilder respects the changes of #3963 just
# like `Bot` does - see also the corresponding test in test_bot.py (same name)
caught_read_timeout = None
async def catch_timeouts(*args, **kwargs):
nonlocal caught_read_timeout
caught_read_timeout = kwargs.get("read_timeout")
return HTTPStatus.OK, b'{"ok": "True", "result": {}}'
monkeypatch.setattr(HTTPXRequest, "do_request", catch_timeouts)
bot = builder.get_updates_read_timeout(10).token(bot.token).build().bot
await bot.get_updates(read_timeout=read_timeout, timeout=timeout)
assert caught_read_timeout == expected
+20
View File
@@ -52,3 +52,23 @@ class TestHandler:
sh = SubclassHandler()
assert repr(sh) == "SubclassHandler[callback=TestHandler.test_repr.<locals>.some_func]"
def test_repr_no_qualname(self):
class ClassBasedCallback:
async def __call__(self, *args, **kwargs):
pass
def __repr__(self):
return "Repr of ClassBasedCallback"
class SubclassHandler(BaseHandler):
__slots__ = ()
def __init__(self):
super().__init__(callback=ClassBasedCallback())
def check_update(self, update: object):
pass
sh = SubclassHandler()
assert repr(sh) == "SubclassHandler[callback=Repr of ClassBasedCallback]"
+53
View File
@@ -1442,6 +1442,59 @@ class TestBasePersistence:
# This is the important part: the persistence is updated with `None` when the conv ends
assert papp.persistence.conversations == {"conv_1": {(1, 1): None}}
async def test_non_blocking_conversation_ends(self, bot):
papp = build_papp(token=bot.token, update_interval=100)
event = asyncio.Event()
async def callback(_, __):
await event.wait()
return HandlerStates.END
conversation = ConversationHandler(
entry_points=[
TrackingConversationHandler.build_handler(HandlerStates.END, callback=callback)
],
states={},
fallbacks=[],
persistent=True,
name="conv",
block=False,
)
papp.add_handler(conversation)
async with papp:
await papp.start()
assert papp.persistence.updated_conversations == {}
await papp.process_update(
TrackingConversationHandler.build_update(HandlerStates.END, 1)
)
assert papp.persistence.updated_conversations == {}
papp.persistence.reset_tracking()
event.set()
await asyncio.sleep(0.01)
await papp.update_persistence()
# On shutdown, persisted data should include the END state b/c that's what the
# pending state is being resolved to
assert papp.persistence.updated_conversations == {"conv": {(1, 1): 1}}
assert papp.persistence.conversations == {"conv": {(1, 1): HandlerStates.END}}
await papp.stop()
async with papp:
# On the next restart/persistence loading the ConversationHandler should resolve
# the stored END state to None …
assert papp.persistence.conversations == {"conv": {(1, 1): HandlerStates.END}}
# … and the update should be accepted by the entry point again
assert conversation.check_update(
TrackingConversationHandler.build_update(HandlerStates.END, 1)
)
await papp.update_persistence()
assert papp.persistence.conversations == {"conv": {(1, 1): None}}
async def test_conversation_timeout(self, bot):
# high update_interval so that we can instead manually call it
papp = build_papp(token=bot.token, update_interval=150)
+62
View File
@@ -2423,3 +2423,65 @@ class TestFilters:
),
)
assert filters.ATTACHMENT.check_update(up)
def test_filters_mention_no_entities(self, update):
update.message.text = "test"
assert not filters.Mention("@test").check_update(update)
assert not filters.Mention(123456).check_update(update)
assert not filters.Mention("123456").check_update(update)
assert not filters.Mention(User(1, "first_name", False)).check_update(update)
assert not filters.Mention(
["@test", 123456, "123456", User(1, "first_name", False)]
).check_update(update)
def test_filters_mention_type_mention(self, update):
update.message.text = "@test1 @test2 user"
update.message.entities = [
MessageEntity(MessageEntity.MENTION, 0, 6),
MessageEntity(MessageEntity.MENTION, 7, 6),
]
user_no_username = User(123456, "first_name", False)
user_wrong_username = User(123456, "first_name", False, username="wrong")
user_1 = User(111, "first_name", False, username="test1")
user_2 = User(222, "first_name", False, username="test2")
for username in ("@test1", "@test2"):
assert filters.Mention(username).check_update(update)
assert filters.Mention({username}).check_update(update)
for user in (user_1, user_2):
assert filters.Mention(user).check_update(update)
assert filters.Mention({user}).check_update(update)
assert not filters.Mention(
["@test3", 123, user_no_username, user_wrong_username]
).check_update(update)
def test_filters_mention_type_text_mention(self, update):
user_1 = User(111, "first_name", False, username="test1")
user_2 = User(222, "first_name", False, username="test2")
user_no_username = User(123456, "first_name", False)
user_wrong_username = User(123456, "first_name", False, username="wrong")
update.message.text = "test1 test2 user"
update.message.entities = [
MessageEntity(MessageEntity.TEXT_MENTION, 0, 5, user=user_1),
MessageEntity(MessageEntity.TEXT_MENTION, 6, 5, user=user_2),
]
for username in ("@test1", "@test2"):
assert filters.Mention(username).check_update(update)
assert filters.Mention({username}).check_update(update)
for user in (user_1, user_2):
assert filters.Mention(user).check_update(update)
assert filters.Mention({user}).check_update(update)
for user_id in (111, 222):
assert filters.Mention(user_id).check_update(update)
assert filters.Mention({user_id}).check_update(update)
assert not filters.Mention(
["@test3", 123, user_no_username, user_wrong_username]
).check_update(update)
+14 -2
View File
@@ -25,7 +25,7 @@ import time
import pytest
from telegram.ext import ApplicationBuilder, CallbackContext, ContextTypes, Job, JobQueue
from telegram.ext import ApplicationBuilder, CallbackContext, ContextTypes, Defaults, Job, JobQueue
from telegram.warnings import PTBUserWarning
from tests.auxil.envvars import GITHUB_ACTION, TEST_WITH_OPT_DEPS
from tests.auxil.pytest_classes import make_bot
@@ -106,6 +106,15 @@ class TestJobQueue:
self.job_time = 0
self.received_error = None
def test_scheduler_configuration(self, job_queue, timezone, bot):
# Unfortunately, we can't really test the executor setting explicitly without relying
# on protected attributes. However, this should be tested enough implicitly via all the
# other tests in here
assert job_queue.scheduler_configuration["timezone"] is UTC
tz_app = ApplicationBuilder().defaults(Defaults(tzinfo=timezone)).token(bot.token).build()
assert tz_app.job_queue.scheduler_configuration["timezone"] is timezone
async def job_run_once(self, context):
if (
isinstance(context, CallbackContext)
@@ -578,7 +587,7 @@ class TestJobQueue:
assert rec.name == "telegram.ext.Application"
assert "No error handlers are registered" in rec.getMessage()
async def test_custom_context(self, bot, job_queue):
async def test_custom_context(self, bot):
application = (
ApplicationBuilder()
.token(bot.token)
@@ -589,6 +598,7 @@ class TestJobQueue:
)
.build()
)
job_queue = JobQueue()
job_queue.set_application(application)
async def callback(context):
@@ -599,9 +609,11 @@ class TestJobQueue:
type(context.bot_data),
)
await job_queue.start()
job_queue.run_once(callback, 0.1)
await asyncio.sleep(0.15)
assert self.result == (CustomContext, None, None, int)
await job_queue.stop()
async def test_attribute_error(self):
job = Job(self.job_run_once)
+34 -1
View File
@@ -331,6 +331,39 @@ class TestUpdater:
assert log_found
async def test_polling_mark_updates_as_read_timeout(self, monkeypatch, updater, caplog):
timeout_event = asyncio.Event()
async def get_updates(*args, **kwargs):
await asyncio.sleep(0)
if timeout_event.is_set():
raise TimedOut("TestMessage")
return []
monkeypatch.setattr(updater.bot, "get_updates", get_updates)
async with updater:
await updater.start_polling()
with caplog.at_level(logging.ERROR):
timeout_event.set()
await updater.stop()
assert len(caplog.records) >= 1
log_found = False
for record in caplog.records:
if not record.getMessage().startswith(
"Error while calling `get_updates` one more time"
):
continue
assert record.name == "telegram.ext.Updater"
assert record.exc_info[0] is TimedOut
assert str(record.exc_info[1]) == "TestMessage"
log_found = True
break
assert log_found
async def test_polling_mark_updates_as_read_failure(self, monkeypatch, updater, caplog):
async def get_updates(*args, **kwargs):
await asyncio.sleep(0)
@@ -376,7 +409,7 @@ class TestUpdater:
expected = {
"timeout": 10,
"read_timeout": 2,
"read_timeout": DEFAULT_NONE,
"write_timeout": DEFAULT_NONE,
"connect_timeout": DEFAULT_NONE,
"pool_timeout": DEFAULT_NONE,
+153 -14
View File
@@ -28,6 +28,7 @@ from typing import Any, Callable, Coroutine, Tuple
import httpx
import pytest
from httpx import AsyncHTTPTransport
from telegram._utils.defaultvalue import DEFAULT_NONE
from telegram.error import (
@@ -41,7 +42,10 @@ from telegram.error import (
TelegramError,
TimedOut,
)
from telegram.request import BaseRequest, RequestData
from telegram.request._httpxrequest import HTTPXRequest
from telegram.request._requestparameter import RequestParameter
from telegram.warnings import PTBDeprecationWarning
from tests.auxil.envvars import TEST_WITH_OPT_DEPS
from tests.auxil.slots import mro_slots
@@ -78,7 +82,7 @@ async def httpx_request():
class TestNoSocksHTTP2WithoutRequest:
async def test_init(self, bot):
with pytest.raises(RuntimeError, match=r"python-telegram-bot\[socks\]"):
HTTPXRequest(proxy_url="socks5://foo")
HTTPXRequest(proxy="socks5://foo")
with pytest.raises(RuntimeError, match=r"python-telegram-bot\[http2\]"):
HTTPXRequest(http_version="2")
@@ -117,7 +121,7 @@ class TestRequestWithoutRequest:
# Make sure that other exceptions are forwarded
with pytest.raises(ImportError, match=r"Other Error Message"):
HTTPXRequest(proxy_url="socks5://foo")
HTTPXRequest(proxy="socks5://foo")
def test_slot_behaviour(self):
inst = HTTPXRequest()
@@ -329,7 +333,7 @@ class TestRequestWithoutRequest:
assert await httpx_request.retrieve(None, None) == server_response
async def test_timeout_propagation(self, monkeypatch, httpx_request):
async def test_timeout_propagation_to_do_request(self, monkeypatch, httpx_request):
async def make_assertion(*args, **kwargs):
self.test_flag = (
kwargs.get("read_timeout"),
@@ -341,7 +345,7 @@ class TestRequestWithoutRequest:
monkeypatch.setattr(httpx_request, "do_request", make_assertion)
await httpx_request.post("url", "method")
await httpx_request.post("url", None)
assert self.test_flag == (DEFAULT_NONE, DEFAULT_NONE, DEFAULT_NONE, DEFAULT_NONE)
await httpx_request.post(
@@ -349,6 +353,73 @@ class TestRequestWithoutRequest:
)
assert self.test_flag == (1, 2, 3, 4)
def test_read_timeout_not_implemented(self):
class SimpleRequest(BaseRequest):
async def do_request(self, *args, **kwargs):
raise httpx.ReadTimeout("read timeout")
async def initialize(self) -> None:
pass
async def shutdown(self) -> None:
pass
with pytest.raises(NotImplementedError):
SimpleRequest().read_timeout
@pytest.mark.parametrize("media", [True, False])
async def test_timeout_propagation_write_timeout(
self, monkeypatch, media, input_media_photo, recwarn # noqa: F811
):
class CustomRequest(BaseRequest):
async def initialize(self_) -> None:
pass
async def shutdown(self_) -> None:
pass
async def do_request(self_, *args, **kwargs) -> Tuple[int, bytes]:
self.test_flag = (
kwargs.get("read_timeout"),
kwargs.get("connect_timeout"),
kwargs.get("write_timeout"),
kwargs.get("pool_timeout"),
)
return HTTPStatus.OK, b'{"ok": "True", "result": {}}'
custom_request = CustomRequest()
data = {"string": "string", "int": 1, "float": 1.0}
if media:
data["media"] = input_media_photo
request_data = RequestData(
parameters=[RequestParameter.from_input(key, value) for key, value in data.items()],
)
# First make sure that custom timeouts are always respected
await custom_request.post(
"url", request_data, read_timeout=1, connect_timeout=2, write_timeout=3, pool_timeout=4
)
assert self.test_flag == (1, 2, 3, 4)
# Now also ensure that the default timeout for media requests is 20 seconds
await custom_request.post("url", request_data)
assert self.test_flag == (
DEFAULT_NONE,
DEFAULT_NONE,
20 if media else DEFAULT_NONE,
DEFAULT_NONE,
)
if media:
assert len(recwarn) == 1
assert "will default to `BaseRequest.DEFAULT_NONE` instead of 20" in str(
recwarn[0].message
)
assert recwarn[0].category is PTBDeprecationWarning
assert recwarn[0].filename == __file__
else:
assert len(recwarn) == 0
@pytest.mark.skipif(not TEST_WITH_OPT_DEPS, reason="No need to run this twice")
class TestHTTPXRequestWithoutRequest:
@@ -358,7 +429,9 @@ class TestHTTPXRequestWithoutRequest:
def _reset(self):
self.test_flag = None
def test_init(self, monkeypatch):
# We parametrize this to make sure that the legacy `proxy_url` argument is still supported
@pytest.mark.parametrize("proxy_argument", ["proxy", "proxy_url"])
def test_init(self, monkeypatch, proxy_argument):
@dataclass
class Client:
timeout: object
@@ -366,6 +439,7 @@ class TestHTTPXRequestWithoutRequest:
limits: object
http1: object
http2: object
transport: object = None
monkeypatch.setattr(httpx, "AsyncClient", Client)
@@ -378,20 +452,32 @@ class TestHTTPXRequestWithoutRequest:
assert request._client.http1 is True
assert not request._client.http2
request = HTTPXRequest(
connection_pool_size=42,
proxy_url="proxy_url",
connect_timeout=43,
read_timeout=44,
write_timeout=45,
pool_timeout=46,
)
assert request._client.proxies == "proxy_url"
kwargs = {
"connection_pool_size": 42,
proxy_argument: "proxy",
"connect_timeout": 43,
"read_timeout": 44,
"write_timeout": 45,
"pool_timeout": 46,
}
request = HTTPXRequest(**kwargs)
assert request._client.proxies == "proxy"
assert request._client.limits == httpx.Limits(
max_connections=42, max_keepalive_connections=42
)
assert request._client.timeout == httpx.Timeout(connect=43, read=44, write=45, pool=46)
def test_proxy_mutually_exclusive(self):
with pytest.raises(ValueError, match="mutually exclusive"):
HTTPXRequest(proxy="proxy", proxy_url="proxy_url")
def test_proxy_url_deprecation_warning(self, recwarn):
HTTPXRequest(proxy_url="http://127.0.0.1:3128")
assert len(recwarn) == 1
assert recwarn[0].category is PTBDeprecationWarning
assert "`proxy_url` is deprecated" in str(recwarn[0].message)
assert recwarn[0].filename == __file__, "incorrect stacklevel"
async def test_multiple_inits_and_shutdowns(self, monkeypatch):
self.test_flag = defaultdict(int)
@@ -618,6 +704,59 @@ class TestHTTPXRequestWithoutRequest:
assert exc_info.value.__cause__ is pool_timeout
@pytest.mark.parametrize("media", [True, False])
async def test_do_request_write_timeout(
self, monkeypatch, media, httpx_request, input_media_photo, recwarn # noqa: F811
):
async def request(_, **kwargs):
self.test_flag = kwargs.get("timeout")
return httpx.Response(HTTPStatus.OK, content=b'{"ok": "True", "result": {}}')
monkeypatch.setattr(httpx.AsyncClient, "request", request)
data = {"string": "string", "int": 1, "float": 1.0}
if media:
data["media"] = input_media_photo
request_data = RequestData(
parameters=[RequestParameter.from_input(key, value) for key, value in data.items()],
)
# First make sure that custom timeouts are always respected
await httpx_request.post(
"url", request_data, read_timeout=1, connect_timeout=2, write_timeout=3, pool_timeout=4
)
assert self.test_flag == httpx.Timeout(read=1, connect=2, write=3, pool=4)
# Now also ensure that the default timeout for media requests is 20 seconds
await httpx_request.post("url", request_data)
assert self.test_flag == httpx.Timeout(read=5, connect=5, write=20 if media else 5, pool=1)
# Just for double-checking, since warnings are issued for implementations of BaseRequest
# other than HTTPXRequest
assert len(recwarn) == 0
async def test_socket_opts(self, monkeypatch):
transport_kwargs = {}
transport_init = AsyncHTTPTransport.__init__
def init_transport(*args, **kwargs):
nonlocal transport_kwargs
transport_kwargs = kwargs.copy()
transport_init(*args, **kwargs)
monkeypatch.setattr(AsyncHTTPTransport, "__init__", init_transport)
HTTPXRequest()
assert "socket_options" not in transport_kwargs
transport_kwargs = {}
HTTPXRequest(socket_options=((1, 2, 3),))
assert transport_kwargs["socket_options"] == ((1, 2, 3),)
@pytest.mark.parametrize("read_timeout", [None, 1, 2, 3])
async def test_read_timeout_property(self, read_timeout):
assert HTTPXRequest(read_timeout=read_timeout).read_timeout == read_timeout
@pytest.mark.skipif(not TEST_WITH_OPT_DEPS, reason="No need to run this twice")
class TestHTTPXRequestWithRequest:
+68 -9
View File
@@ -26,7 +26,9 @@ import re
import socket
import time
from collections import defaultdict
from http import HTTPStatus
import httpx
import pytest
from telegram import (
@@ -78,7 +80,7 @@ from telegram.error import BadRequest, InvalidToken, NetworkError
from telegram.ext import ExtBot, InvalidCallbackData
from telegram.helpers import escape_markdown
from telegram.request import BaseRequest, HTTPXRequest, RequestData
from telegram.warnings import PTBUserWarning
from telegram.warnings import PTBDeprecationWarning, PTBUserWarning
from tests.auxil.bot_method_checks import check_defaults_handling
from tests.auxil.ci_bots import FALLBACKS
from tests.auxil.envvars import GITHUB_ACTION, TEST_WITH_OPT_DEPS
@@ -1362,14 +1364,14 @@ class TestBotWithoutRequest:
class OkException(BaseException):
pass
async def do_request(*args, **kwargs):
obj = kwargs.get("write_timeout")
if obj == 20:
async def request(*args, **kwargs):
timeout = kwargs.get("timeout")
if timeout.write == 20:
raise OkException
return 200, b'{"ok": true, "result": []}'
monkeypatch.setattr(bot.request, "do_request", do_request)
monkeypatch.setattr(httpx.AsyncClient, "request", request)
# Test file uploading
with pytest.raises(OkException):
@@ -2180,7 +2182,7 @@ class TestBotWithRequest:
)
assert message.reply_to_message is None
else:
with pytest.raises(BadRequest, match="message not found"):
with pytest.raises(BadRequest, match="Message to reply not found"):
await default_bot.send_poll(
chat_id,
question=question,
@@ -2237,7 +2239,7 @@ class TestBotWithRequest:
)
assert message.reply_to_message is None
else:
with pytest.raises(BadRequest, match="message not found"):
with pytest.raises(BadRequest, match="Message to reply not found"):
await default_bot.send_dice(
chat_id, reply_to_message_id=reply_to_message.message_id
)
@@ -2446,6 +2448,63 @@ class TestBotWithRequest:
if updates:
assert isinstance(updates[0], Update)
@pytest.mark.parametrize("bot_class", [Bot, ExtBot])
async def test_get_updates_read_timeout_deprecation_warning(
self, bot, recwarn, monkeypatch, bot_class
):
# Using the normal HTTPXRequest should not issue any warnings
await bot.get_updates()
assert len(recwarn) == 0
# Now let's test deprecation warning when using get_updates for other BaseRequest
# subclasses (we just monkeypatch the existing HTTPXRequest for this)
read_timeout = None
async def catch_timeouts(*args, **kwargs):
nonlocal read_timeout
read_timeout = kwargs.get("read_timeout")
return HTTPStatus.OK, b'{"ok": "True", "result": {}}'
monkeypatch.setattr(HTTPXRequest, "read_timeout", BaseRequest.read_timeout)
monkeypatch.setattr(HTTPXRequest, "do_request", catch_timeouts)
bot = bot_class(get_updates_request=HTTPXRequest(), token=bot.token)
await bot.get_updates()
assert len(recwarn) == 1
assert "does not override the property `read_timeout`" in str(recwarn[0].message)
assert recwarn[0].category is PTBDeprecationWarning
assert recwarn[0].filename == __file__, "wrong stacklevel"
assert read_timeout == 2
@pytest.mark.parametrize(
("read_timeout", "timeout", "expected"),
[
(None, None, 0),
(1, None, 1),
(None, 1, 1),
(DEFAULT_NONE, None, 10),
(DEFAULT_NONE, 1, 11),
(1, 2, 3),
],
)
async def test_get_updates_read_timeout_value_passing(
self, bot, read_timeout, timeout, expected, monkeypatch
):
caught_read_timeout = None
async def catch_timeouts(*args, **kwargs):
nonlocal caught_read_timeout
caught_read_timeout = kwargs.get("read_timeout")
return HTTPStatus.OK, b'{"ok": "True", "result": {}}'
monkeypatch.setattr(HTTPXRequest, "do_request", catch_timeouts)
bot = Bot(get_updates_request=HTTPXRequest(read_timeout=10), token=bot.token)
await bot.get_updates(read_timeout=read_timeout, timeout=timeout)
assert caught_read_timeout == expected
@pytest.mark.xdist_group("getUpdates_and_webhook")
@pytest.mark.parametrize("use_ip", [True, False])
# local file path as file_input is tested below in test_set_webhook_params
@@ -2570,7 +2629,7 @@ class TestBotWithRequest:
)
assert message.reply_to_message is None
else:
with pytest.raises(BadRequest, match="message not found"):
with pytest.raises(BadRequest, match="Message to reply not found"):
await default_bot.send_game(
chat_id, game_short_name, reply_to_message_id=reply_to_message.message_id
)
@@ -3053,7 +3112,7 @@ class TestBotWithRequest:
)
assert message.reply_to_message is None
else:
with pytest.raises(BadRequest, match="message not found"):
with pytest.raises(BadRequest, match="Message to reply not found"):
await default_bot.send_message(
chat_id, "test", reply_to_message_id=reply_to_message.message_id
)
+5 -6
View File
@@ -28,7 +28,7 @@ from bs4 import BeautifulSoup, PageElement, Tag
import telegram
from telegram._utils.defaultvalue import DefaultValue
from telegram._utils.types import DVInput, FileInput, ODVInput
from telegram._utils.types import FileInput, ODVInput
from telegram.ext import Defaults
from tests.auxil.envvars import RUN_TEST_OFFICIAL
@@ -433,11 +433,10 @@ def check_param_type(
# Special case for when the parameter is a default value parameter
for name, _ in inspect.getmembers(Defaults, lambda x: isinstance(x, property)):
if name in ptb_param.name: # no strict == since we have a param: `explanation_parse_mode`
# Check if it's DVInput or ODVInput
for param_type in [DVInput, ODVInput]:
parsed = param_type[mapped_type]
if ptb_annotation == parsed:
return True
# Check if it's ODVInput
parsed = ODVInput[mapped_type]
if (ptb_annotation | None) == parsed: # We have to add back None in our annotation
return True
return False
# Special case for send_* methods where we accept more types than the official API: