From 585b362ad4773c3121408ac2efb9b090fc3ca0f6 Mon Sep 17 00:00:00 2001 From: Harshil <37377066+harshil21@users.noreply.github.com> Date: Sat, 11 May 2024 06:06:44 -0400 Subject: [PATCH 01/10] Add py 3.13 to test matrix and adjust tests --- .github/workflows/unit_tests.yml | 2 +- pyproject.toml | 2 +- requirements-opts.txt | 2 + setup.py | 1 + telegram/ext/_application.py | 70 +++++++++++++++++--------------- tests/auxil/pytest_classes.py | 6 ++- tests/conftest.py | 6 +-- tests/ext/test_application.py | 62 +++++++++++++++++++++++----- tests/request/test_request.py | 19 +++------ tests/test_bot.py | 4 +- tests/test_message.py | 6 ++- 11 files changed, 114 insertions(+), 66 deletions(-) diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index fffe4573ddb..92c0affe786 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -20,7 +20,7 @@ jobs: runs-on: ${{matrix.os}} strategy: matrix: - python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] + python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13.0-beta.1'] os: [ubuntu-latest, windows-latest, macos-latest] fail-fast: False steps: diff --git a/pyproject.toml b/pyproject.toml index cd69c757871..ffbefb03e8f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ # BLACK: [tool.black] line-length = 99 -target-version = ['py38', 'py39', 'py310', 'py311'] +target-version = ['py38', 'py39', 'py310', 'py311', 'py312', 'py313'] # ISORT: [tool.isort] # black config diff --git a/requirements-opts.txt b/requirements-opts.txt index 05ac0d8c718..bb1a1eef326 100644 --- a/requirements-opts.txt +++ b/requirements-opts.txt @@ -13,6 +13,8 @@ httpx[socks] # socks httpx[http2] # http2 cryptography!=3.4,!=3.4.1,!=3.4.2,!=3.4.3,>=39.0.1 # passport +# temporary! Remove after cffi releases 1.17.0 +cffi @ git+https://github.com/python-cffi/cffi@d7f750b1b1c5ea4da5aa537b9baba0e01b0ce843 aiolimiter~=1.1.0 # rate-limiter!ext # tornado is rather stable, but let's not allow the next mayor release without prior testing diff --git a/setup.py b/setup.py index d62ad39ed32..6831bea2889 100644 --- a/setup.py +++ b/setup.py @@ -112,6 +112,7 @@ def get_setup_kwargs(raw: bool = False) -> Dict[str, Any]: "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", ], "python_requires": ">=3.8", } diff --git a/telegram/ext/_application.py b/telegram/ext/_application.py index 8c657d57b34..5aa77686790 100644 --- a/telegram/ext/_application.py +++ b/telegram/ext/_application.py @@ -251,40 +251,44 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica """ __slots__ = ( - "__create_task_tasks", - "__update_fetcher_task", - "__update_persistence_event", - "__update_persistence_lock", - "__update_persistence_task", - # Allowing '__weakref__' creation here since we need it for the JobQueue - # Uncomment if necessary - currently the __weakref__ slot is already created - # in the AsyncContextManager base class - # "__weakref__", - "_chat_data", - "_chat_ids_to_be_deleted_in_persistence", - "_chat_ids_to_be_updated_in_persistence", - "_conversation_handler_conversations", - "_initialized", - "_job_queue", - "_running", - "_update_processor", - "_user_data", - "_user_ids_to_be_deleted_in_persistence", - "_user_ids_to_be_updated_in_persistence", - "bot", - "bot_data", - "chat_data", - "context_types", - "error_handlers", - "handlers", - "persistence", - "post_init", - "post_shutdown", - "post_stop", - "update_queue", - "updater", - "user_data", + ( # noqa: RUF005 + "__create_task_tasks", + "__update_fetcher_task", + "__update_persistence_event", + "__update_persistence_lock", + "__update_persistence_task", + "_chat_data", + "_chat_ids_to_be_deleted_in_persistence", + "_chat_ids_to_be_updated_in_persistence", + "_conversation_handler_conversations", + "_initialized", + "_job_queue", + "_running", + "_update_processor", + "_user_data", + "_user_ids_to_be_deleted_in_persistence", + "_user_ids_to_be_updated_in_persistence", + "bot", + "bot_data", + "chat_data", + "context_types", + "error_handlers", + "handlers", + "persistence", + "post_init", + "post_shutdown", + "post_stop", + "update_queue", + "updater", + "user_data", + ) + + ("__weakref__",) + if sys.version_info >= (3, 13) + else () ) + # Allowing '__weakref__' creation here since we need it for the JobQueue + # Currently the __weakref__ slot is already created + # in the AsyncContextManager base class for pythons < 3.13 def __init__( self: "Application[BT, CCT, UD, CD, BD, JQ]", diff --git a/tests/auxil/pytest_classes.py b/tests/auxil/pytest_classes.py index 5586a8ea0b7..1b976b02e6c 100644 --- a/tests/auxil/pytest_classes.py +++ b/tests/auxil/pytest_classes.py @@ -21,7 +21,7 @@ pytest framework. A common change is to allow monkeypatching of the class members by not enforcing slots in the subclasses.""" from telegram import Bot, Message, User -from telegram.ext import Application, ExtBot +from telegram.ext import Application, ExtBot, Updater from tests.auxil.ci_bots import BOT_INFO_PROVIDER from tests.auxil.constants import PRIVATE_KEY from tests.auxil.envvars import TEST_WITH_OPT_DEPS @@ -89,6 +89,10 @@ class PytestMessage(Message): pass +class PytestUpdater(Updater): + pass + + def make_bot(bot_info=None, **kwargs): """ Tests are executed on tg.ext.ExtBot, as that class only extends the functionality of tg.bot diff --git a/tests/conftest.py b/tests/conftest.py index 213bcff4a23..a9ef3e68641 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -20,6 +20,7 @@ import datetime import logging import sys +from pathlib import Path from typing import Dict, List from uuid import uuid4 @@ -291,6 +292,5 @@ def timezone(tzinfo): @pytest.fixture() -def tmp_file(tmp_path): - with tmp_path / uuid4().hex as file: - yield file +def tmp_file(tmp_path) -> Path: + return tmp_path / uuid4().hex diff --git a/tests/ext/test_application.py b/tests/ext/test_application.py index 9057dcecaca..5f3e14e72df 100644 --- a/tests/ext/test_application.py +++ b/tests/ext/test_application.py @@ -60,7 +60,7 @@ from tests.auxil.build_messages import make_message_update from tests.auxil.files import PROJECT_ROOT_PATH from tests.auxil.networking import send_webhook_message -from tests.auxil.pytest_classes import make_bot +from tests.auxil.pytest_classes import PytestApplication, PytestUpdater, make_bot from tests.auxil.slots import mro_slots @@ -1580,7 +1580,13 @@ def thread_target(): async def post_init(app: Application) -> None: events.append("post_init") - app = Application.builder().bot(one_time_bot).post_init(post_init).build() + app = ( + Application.builder() + .application_class(PytestApplication) + .updater(PytestUpdater(one_time_bot, asyncio.Queue())) + .post_init(post_init) + .build() + ) app.bot._unfreeze() monkeypatch.setattr(app.bot, "get_updates", get_updates) monkeypatch.setattr( @@ -1623,7 +1629,13 @@ def thread_target(): async def post_shutdown(app: Application) -> None: events.append("post_shutdown") - app = Application.builder().bot(one_time_bot).post_shutdown(post_shutdown).build() + app = ( + Application.builder() + .application_class(PytestApplication) + .updater(PytestUpdater(one_time_bot, asyncio.Queue())) + .post_shutdown(post_shutdown) + .build() + ) app.bot._unfreeze() monkeypatch.setattr(app.bot, "get_updates", get_updates) monkeypatch.setattr( @@ -1649,7 +1661,7 @@ async def post_shutdown(app: Application) -> None: platform.system() == "Windows", reason="Can't send signals without stopping whole process on windows", ) - def test_run_polling_post_stop(self, bot, monkeypatch): + def test_run_polling_post_stop(self, one_time_bot, monkeypatch): events = [] async def get_updates(*args, **kwargs): @@ -1670,7 +1682,13 @@ def thread_target(): async def post_stop(app: Application) -> None: events.append("post_stop") - app = Application.builder().token(bot.token).post_stop(post_stop).build() + app = ( + Application.builder() + .application_class(PytestApplication) + .updater(PytestUpdater(one_time_bot, asyncio.Queue())) + .post_stop(post_stop) + .build() + ) app.bot._unfreeze() monkeypatch.setattr(app.bot, "get_updates", get_updates) monkeypatch.setattr(app, "stop", call_after(app.stop, lambda _: events.append("stop"))) @@ -1862,7 +1880,13 @@ def thread_target(): async def post_init(app: Application) -> None: events.append("post_init") - app = Application.builder().bot(one_time_bot).post_init(post_init).build() + app = ( + Application.builder() + .post_init(post_init) + .application_class(PytestApplication) + .updater(PytestUpdater(one_time_bot, asyncio.Queue())) + .build() + ) app.bot._unfreeze() monkeypatch.setattr(app.bot, "set_webhook", set_webhook) monkeypatch.setattr(app.bot, "delete_webhook", delete_webhook) @@ -1922,7 +1946,13 @@ def thread_target(): async def post_shutdown(app: Application) -> None: events.append("post_shutdown") - app = Application.builder().bot(one_time_bot).post_shutdown(post_shutdown).build() + app = ( + Application.builder() + .application_class(PytestApplication) + .updater(PytestUpdater(one_time_bot, asyncio.Queue())) + .post_shutdown(post_shutdown) + .build() + ) app.bot._unfreeze() monkeypatch.setattr(app.bot, "set_webhook", set_webhook) monkeypatch.setattr(app.bot, "delete_webhook", delete_webhook) @@ -1959,7 +1989,7 @@ async def post_shutdown(app: Application) -> None: platform.system() == "Windows", reason="Can't send signals without stopping whole process on windows", ) - def test_run_webhook_post_stop(self, bot, monkeypatch): + def test_run_webhook_post_stop(self, one_time_bot, monkeypatch): events = [] async def delete_webhook(*args, **kwargs): @@ -1986,7 +2016,13 @@ def thread_target(): async def post_stop(app: Application) -> None: events.append("post_stop") - app = Application.builder().token(bot.token).post_stop(post_stop).build() + app = ( + Application.builder() + .application_class(PytestApplication) + .updater(PytestUpdater(one_time_bot, asyncio.Queue())) + .post_stop(post_stop) + .build() + ) app.bot._unfreeze() monkeypatch.setattr(app.bot, "set_webhook", set_webhook) monkeypatch.setattr(app.bot, "delete_webhook", delete_webhook) @@ -2343,7 +2379,13 @@ async def task(app): app.create_task(task(app)) - app = ApplicationBuilder().bot(one_time_bot).post_init(post_init).build() + app = ( + ApplicationBuilder() + .application_class(PytestApplication) + .updater(PytestUpdater(one_time_bot, asyncio.Queue())) + .post_init(post_init) + .build() + ) monkeypatch.setattr(app.bot, "get_updates", get_updates) monkeypatch.setattr(app.bot, "set_webhook", set_webhook) monkeypatch.setattr(app.bot, "delete_webhook", delete_webhook) diff --git a/tests/request/test_request.py b/tests/request/test_request.py index 47e7d2125f6..c2dbd1e1e34 100644 --- a/tests/request/test_request.py +++ b/tests/request/test_request.py @@ -47,6 +47,7 @@ from telegram.request._requestparameter import RequestParameter from telegram.warnings import PTBDeprecationWarning from tests.auxil.envvars import TEST_WITH_OPT_DEPS +from tests.auxil.networking import NonchalantHttpxRequest from tests.auxil.slots import mro_slots # We only need mixed_rqs fixture, but it uses the others, so pytest needs us to import them as well @@ -72,7 +73,7 @@ async def make_assertion(*args, **kwargs): @pytest.fixture() async def httpx_request(): - async with HTTPXRequest() as rq: + async with NonchalantHttpxRequest() as rq: yield rq @@ -130,15 +131,13 @@ def test_slot_behaviour(self): assert getattr(inst, at, "err") != "err", f"got extra slot '{at}'" assert len(mro_slots(inst)) == len(set(mro_slots(inst))), "duplicate slot" - async def test_context_manager(self, monkeypatch): + async def test_context_manager(self, monkeypatch, httpx_request): async def initialize(): self.test_flag = ["initialize"] async def shutdown(): self.test_flag.append("stop") - httpx_request = HTTPXRequest() - monkeypatch.setattr(httpx_request, "initialize", initialize) monkeypatch.setattr(httpx_request, "shutdown", shutdown) @@ -147,15 +146,13 @@ async def shutdown(): assert self.test_flag == ["initialize", "stop"] - async def test_context_manager_exception_on_init(self, monkeypatch): + async def test_context_manager_exception_on_init(self, monkeypatch, httpx_request): async def initialize(): raise RuntimeError("initialize") async def shutdown(): self.test_flag = "stop" - httpx_request = HTTPXRequest() - monkeypatch.setattr(httpx_request, "initialize", initialize) monkeypatch.setattr(httpx_request, "shutdown", shutdown) @@ -538,15 +535,13 @@ async def test_do_request_after_shutdown(self, httpx_request): with pytest.raises(RuntimeError, match="not initialized"): await httpx_request.do_request(url="url", method="GET") - async def test_context_manager(self, monkeypatch): + async def test_context_manager(self, monkeypatch, httpx_request): async def initialize(): self.test_flag = ["initialize"] async def aclose(*args): self.test_flag.append("stop") - httpx_request = HTTPXRequest() - monkeypatch.setattr(httpx_request, "initialize", initialize) monkeypatch.setattr(httpx.AsyncClient, "aclose", aclose) @@ -555,15 +550,13 @@ async def aclose(*args): assert self.test_flag == ["initialize", "stop"] - async def test_context_manager_exception_on_init(self, monkeypatch): + async def test_context_manager_exception_on_init(self, monkeypatch, httpx_request): async def initialize(): raise RuntimeError("initialize") async def aclose(*args): self.test_flag = "stop" - httpx_request = HTTPXRequest() - monkeypatch.setattr(httpx_request, "initialize", initialize) monkeypatch.setattr(httpx.AsyncClient, "aclose", aclose) diff --git a/tests/test_bot.py b/tests/test_bot.py index 34f25e6ce39..07f4a63c83f 100644 --- a/tests/test_bot.py +++ b/tests/test_bot.py @@ -95,7 +95,7 @@ from tests.auxil.ci_bots import FALLBACKS from tests.auxil.envvars import GITHUB_ACTION, TEST_WITH_OPT_DEPS from tests.auxil.files import data_file -from tests.auxil.networking import expect_bad_request +from tests.auxil.networking import NonchalantHttpxRequest, expect_bad_request from tests.auxil.pytest_classes import PytestBot, PytestExtBot, make_bot from tests.auxil.slots import mro_slots @@ -251,7 +251,7 @@ async def initialize(*args, **kwargs): async def stop(*args, **kwargs): self.test_flag.append("stop") - temp_bot = PytestBot(token=bot.token) + temp_bot = PytestBot(token=bot.token, request=NonchalantHttpxRequest()) orig_stop = temp_bot.request.shutdown try: diff --git a/tests/test_message.py b/tests/test_message.py index e70b8f0668f..fcf23d24e8d 100644 --- a/tests/test_message.py +++ b/tests/test_message.py @@ -2594,7 +2594,9 @@ async def make_assertion(*args, **kwargs): async def test_default_do_quote( self, bot, message, default_quote, chat_type, expected, monkeypatch ): - message.set_bot(PytestExtBot(token=bot.token, defaults=Defaults(do_quote=default_quote))) + original_bot = message.get_bot() + temp_bot = PytestExtBot(token=bot.token, defaults=Defaults(do_quote=default_quote)) + message.set_bot(temp_bot) async def make_assertion(*_, **kwargs): reply_parameters = kwargs.get("reply_parameters") or ReplyParameters(message_id=False) @@ -2607,7 +2609,7 @@ async def make_assertion(*_, **kwargs): message.chat.type = chat_type assert await message.reply_text("test") finally: - message.get_bot()._defaults = None + message.set_bot(original_bot) async def test_edit_forum_topic(self, monkeypatch, message): async def make_assertion(*_, **kwargs): From a9884e5544cf9b5133f281a09c66d5917aaf453b Mon Sep 17 00:00:00 2001 From: Harshil <37377066+harshil21@users.noreply.github.com> Date: Sat, 11 May 2024 06:26:28 -0400 Subject: [PATCH 02/10] Possibly fix test suite for python < 3.13 --- requirements-opts.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-opts.txt b/requirements-opts.txt index bb1a1eef326..4e49fe7e374 100644 --- a/requirements-opts.txt +++ b/requirements-opts.txt @@ -14,7 +14,7 @@ httpx[socks] # socks httpx[http2] # http2 cryptography!=3.4,!=3.4.1,!=3.4.2,!=3.4.3,>=39.0.1 # passport # temporary! Remove after cffi releases 1.17.0 -cffi @ git+https://github.com/python-cffi/cffi@d7f750b1b1c5ea4da5aa537b9baba0e01b0ce843 +cffi @ git+https://github.com/python-cffi/cffi@d7f750b1b1c5ea4da5aa537b9baba0e01b0ce843 # passport aiolimiter~=1.1.0 # rate-limiter!ext # tornado is rather stable, but let's not allow the next mayor release without prior testing From 7abd956a6faf42bc1e1a66d11a314be6acb0c53c Mon Sep 17 00:00:00 2001 From: Harshil <37377066+harshil21@users.noreply.github.com> Date: Sat, 1 Jun 2024 14:40:59 -0400 Subject: [PATCH 03/10] bump to cffi 1.17.0rc1 and skip test_meta on py 3.13 --- requirements-opts.txt | 2 +- tests/test_meta.py | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/requirements-opts.txt b/requirements-opts.txt index 4e49fe7e374..17b37db6e71 100644 --- a/requirements-opts.txt +++ b/requirements-opts.txt @@ -14,7 +14,7 @@ httpx[socks] # socks httpx[http2] # http2 cryptography!=3.4,!=3.4.1,!=3.4.2,!=3.4.3,>=39.0.1 # passport # temporary! Remove after cffi releases 1.17.0 -cffi @ git+https://github.com/python-cffi/cffi@d7f750b1b1c5ea4da5aa537b9baba0e01b0ce843 # passport +cffi >= 1.17.0rc1 # passport aiolimiter~=1.1.0 # rate-limiter!ext # tornado is rather stable, but let's not allow the next mayor release without prior testing diff --git a/tests/test_meta.py b/tests/test_meta.py index fd698585dbb..f594848c901 100644 --- a/tests/test_meta.py +++ b/tests/test_meta.py @@ -17,6 +17,7 @@ # You should have received a copy of the GNU Lesser Public License # along with this program. If not, see [http://www.gnu.org/licenses/]. import os +import sys import pytest @@ -26,6 +27,11 @@ not env_var_2_bool(os.getenv("TEST_BUILD", "")), reason="TEST_BUILD not enabled" ) +# disable for python 3.13 and above +skip_disabled_py_3_13 = pytest.mark.skipif( + sys.version_info >= (3, 13), reason="Py 3.13 and above changes locals() which affects setup.py" +) + # To make the tests agnostic of the cwd @pytest.fixture(autouse=True) @@ -33,11 +39,13 @@ def _change_test_dir(request, monkeypatch): monkeypatch.chdir(request.config.rootdir) +@skip_disabled_py_3_13 @skip_disabled def test_build(): assert os.system("python setup.py bdist_dumb") == 0 # pragma: no cover +@skip_disabled_py_3_13 @skip_disabled def test_build_raw(): assert os.system("python setup_raw.py bdist_dumb") == 0 # pragma: no cover From bf3f9ad370242a8b234b8bccbcf2b1cfea119594 Mon Sep 17 00:00:00 2001 From: Harshil <37377066+harshil21@users.noreply.github.com> Date: Sat, 1 Jun 2024 15:01:33 -0400 Subject: [PATCH 04/10] pin cffi pre-release only to py 3.13 and above --- requirements-opts.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-opts.txt b/requirements-opts.txt index 17b37db6e71..c1d7950de41 100644 --- a/requirements-opts.txt +++ b/requirements-opts.txt @@ -14,7 +14,7 @@ httpx[socks] # socks httpx[http2] # http2 cryptography!=3.4,!=3.4.1,!=3.4.2,!=3.4.3,>=39.0.1 # passport # temporary! Remove after cffi releases 1.17.0 -cffi >= 1.17.0rc1 # passport +cffi >= 1.17.0rc1;python_version>"3.12" # passport aiolimiter~=1.1.0 # rate-limiter!ext # tornado is rather stable, but let's not allow the next mayor release without prior testing From fa136f3819b7c990a0812a3cf85f4c382bf267d8 Mon Sep 17 00:00:00 2001 From: Harshil <37377066+harshil21@users.noreply.github.com> Date: Sat, 1 Jun 2024 15:06:13 -0400 Subject: [PATCH 05/10] Add missing Application slot + some ruff fixes --- telegram/_chatfullinfo.py | 8 ++++---- telegram/_giveaway.py | 2 +- telegram/_inline/inputtextmessagecontent.py | 2 +- telegram/_update.py | 4 ++-- telegram/ext/_application.py | 1 + telegram/ext/_jobqueue.py | 2 +- 6 files changed, 10 insertions(+), 9 deletions(-) diff --git a/telegram/_chatfullinfo.py b/telegram/_chatfullinfo.py index 221b8f623cc..db9eb5fc342 100644 --- a/telegram/_chatfullinfo.py +++ b/telegram/_chatfullinfo.py @@ -490,10 +490,10 @@ def __init__( self.unrestrict_boost_count: Optional[int] = unrestrict_boost_count self.custom_emoji_sticker_set_name: Optional[str] = custom_emoji_sticker_set_name self.birthdate: Optional[Birthdate] = birthdate - self.personal_chat: Optional["Chat"] = personal_chat - self.business_intro: Optional["BusinessIntro"] = business_intro - self.business_location: Optional["BusinessLocation"] = business_location - self.business_opening_hours: Optional["BusinessOpeningHours"] = business_opening_hours + self.personal_chat: Optional[Chat] = personal_chat + self.business_intro: Optional[BusinessIntro] = business_intro + self.business_location: Optional[BusinessLocation] = business_location + self.business_opening_hours: Optional[BusinessOpeningHours] = business_opening_hours @classmethod def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["ChatFullInfo"]: diff --git a/telegram/_giveaway.py b/telegram/_giveaway.py index 3251898031d..ed6d4a28895 100644 --- a/telegram/_giveaway.py +++ b/telegram/_giveaway.py @@ -313,7 +313,7 @@ def __init__( self.winner_count: int = winner_count self.unclaimed_prize_count: Optional[int] = unclaimed_prize_count - self.giveaway_message: Optional["Message"] = giveaway_message + self.giveaway_message: Optional[Message] = giveaway_message self._id_attrs = ( self.winner_count, diff --git a/telegram/_inline/inputtextmessagecontent.py b/telegram/_inline/inputtextmessagecontent.py index 0e127ce70a7..475f9c5bb28 100644 --- a/telegram/_inline/inputtextmessagecontent.py +++ b/telegram/_inline/inputtextmessagecontent.py @@ -108,7 +108,7 @@ def __init__( # Optionals self.parse_mode: ODVInput[str] = parse_mode self.entities: Tuple[MessageEntity, ...] = parse_sequence_arg(entities) - self.link_preview_options: ODVInput["LinkPreviewOptions"] = parse_lpo_and_dwpp( + self.link_preview_options: ODVInput[LinkPreviewOptions] = parse_lpo_and_dwpp( disable_web_page_preview, link_preview_options ) diff --git a/telegram/_update.py b/telegram/_update.py index 784dea52aba..68ff52649d2 100644 --- a/telegram/_update.py +++ b/telegram/_update.py @@ -446,7 +446,7 @@ def __init__( ) self._effective_user: Optional[User] = None - self._effective_sender: Optional[Union["User", "Chat"]] = None + self._effective_sender: Optional[Union[User, Chat]] = None self._effective_chat: Optional[Chat] = None self._effective_message: Optional[Message] = None @@ -568,7 +568,7 @@ def effective_sender(self) -> Optional[Union["User", "Chat"]]: if self._effective_sender: return self._effective_sender - sender: Optional[Union["User", "Chat"]] = None + sender: Optional[Union[User, Chat]] = None if message := ( self.message diff --git a/telegram/ext/_application.py b/telegram/ext/_application.py index dee74460582..91540f7178c 100644 --- a/telegram/ext/_application.py +++ b/telegram/ext/_application.py @@ -257,6 +257,7 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica "__update_persistence_event", "__update_persistence_lock", "__update_persistence_task", + "__stop_running_marker", "_chat_data", "_chat_ids_to_be_deleted_in_persistence", "_chat_ids_to_be_updated_in_persistence", diff --git a/telegram/ext/_jobqueue.py b/telegram/ext/_jobqueue.py index 6edd5a892ea..b30b647a290 100644 --- a/telegram/ext/_jobqueue.py +++ b/telegram/ext/_jobqueue.py @@ -106,7 +106,7 @@ def __init__(self) -> None: self._application: Optional[weakref.ReferenceType[Application]] = None self._executor = AsyncIOExecutor() - self.scheduler: "AsyncIOScheduler" = AsyncIOScheduler(**self.scheduler_configuration) + self.scheduler: AsyncIOScheduler = AsyncIOScheduler(**self.scheduler_configuration) def __repr__(self) -> str: """Give a string representation of the JobQueue in the form ``JobQueue[application=...]``. From 946f2d6bbad7693e36d2b93a7cb7c525197a4b1d Mon Sep 17 00:00:00 2001 From: Harshil <37377066+harshil21@users.noreply.github.com> Date: Sat, 15 Jun 2024 21:23:55 -0400 Subject: [PATCH 06/10] bump to py3.13 beta 2 --- .github/workflows/unit_tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 8f0db60bfef..214eca12b30 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -19,7 +19,7 @@ jobs: runs-on: ${{matrix.os}} strategy: matrix: - python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13.0-beta.1'] + python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13.0-beta.2'] os: [ubuntu-latest, windows-latest, macos-latest] fail-fast: False steps: From 908b54f7ef927d2f28842d6573fa7cc1dc7240c0 Mon Sep 17 00:00:00 2001 From: Harshil <37377066+harshil21@users.noreply.github.com> Date: Sun, 16 Jun 2024 14:35:01 -0400 Subject: [PATCH 07/10] Review: add & move comment & revert using httpx_request fixture --- pyproject.toml | 1 + telegram/ext/_application.py | 6 +++--- tests/request/test_request.py | 16 ++++++++++++---- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 3789e8040bc..837ea84402c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -83,6 +83,7 @@ job-queue = [ ] passport = [ "cryptography!=3.4,!=3.4.1,!=3.4.2,!=3.4.3,>=39.0.1", + # cffi is a dependency of cryptography and needs a pre-release version to support python 3.13 "cffi >= 1.17.0rc1; python_version > '3.12'" ] rate-limiter = [ diff --git a/telegram/ext/_application.py b/telegram/ext/_application.py index 91540f7178c..4f623ed3695 100644 --- a/telegram/ext/_application.py +++ b/telegram/ext/_application.py @@ -283,13 +283,13 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica "updater", "user_data", ) + # Allowing '__weakref__' creation here since we need it for the JobQueue + # Currently the __weakref__ slot is already created + # in the AsyncContextManager base class for pythons < 3.13 + ("__weakref__",) if sys.version_info >= (3, 13) else () ) - # Allowing '__weakref__' creation here since we need it for the JobQueue - # Currently the __weakref__ slot is already created - # in the AsyncContextManager base class for pythons < 3.13 def __init__( self: "Application[BT, CCT, UD, CD, BD, JQ]", diff --git a/tests/request/test_request.py b/tests/request/test_request.py index c2dbd1e1e34..ecfb65ece36 100644 --- a/tests/request/test_request.py +++ b/tests/request/test_request.py @@ -131,13 +131,15 @@ def test_slot_behaviour(self): assert getattr(inst, at, "err") != "err", f"got extra slot '{at}'" assert len(mro_slots(inst)) == len(set(mro_slots(inst))), "duplicate slot" - async def test_context_manager(self, monkeypatch, httpx_request): + async def test_context_manager(self, monkeypatch): async def initialize(): self.test_flag = ["initialize"] async def shutdown(): self.test_flag.append("stop") + httpx_request = NonchalantHttpxRequest() + monkeypatch.setattr(httpx_request, "initialize", initialize) monkeypatch.setattr(httpx_request, "shutdown", shutdown) @@ -146,13 +148,15 @@ async def shutdown(): assert self.test_flag == ["initialize", "stop"] - async def test_context_manager_exception_on_init(self, monkeypatch, httpx_request): + async def test_context_manager_exception_on_init(self, monkeypatch): async def initialize(): raise RuntimeError("initialize") async def shutdown(): self.test_flag = "stop" + httpx_request = NonchalantHttpxRequest() + monkeypatch.setattr(httpx_request, "initialize", initialize) monkeypatch.setattr(httpx_request, "shutdown", shutdown) @@ -535,13 +539,15 @@ async def test_do_request_after_shutdown(self, httpx_request): with pytest.raises(RuntimeError, match="not initialized"): await httpx_request.do_request(url="url", method="GET") - async def test_context_manager(self, monkeypatch, httpx_request): + async def test_context_manager(self, monkeypatch): async def initialize(): self.test_flag = ["initialize"] async def aclose(*args): self.test_flag.append("stop") + httpx_request = NonchalantHttpxRequest() + monkeypatch.setattr(httpx_request, "initialize", initialize) monkeypatch.setattr(httpx.AsyncClient, "aclose", aclose) @@ -550,13 +556,15 @@ async def aclose(*args): assert self.test_flag == ["initialize", "stop"] - async def test_context_manager_exception_on_init(self, monkeypatch, httpx_request): + async def test_context_manager_exception_on_init(self, monkeypatch): async def initialize(): raise RuntimeError("initialize") async def aclose(*args): self.test_flag = "stop" + httpx_request = NonchalantHttpxRequest() + monkeypatch.setattr(httpx_request, "initialize", initialize) monkeypatch.setattr(httpx.AsyncClient, "aclose", aclose) From eca420ae4db56d104fa19d14a8cade2c709ca24d Mon Sep 17 00:00:00 2001 From: Harshil <37377066+harshil21@users.noreply.github.com> Date: Sun, 16 Jun 2024 14:42:56 -0400 Subject: [PATCH 08/10] bump python version in type completeness --- .github/workflows/type_completeness.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/type_completeness.yml b/.github/workflows/type_completeness.yml index 74087e3e8dd..4a98c0b30a8 100644 --- a/.github/workflows/type_completeness.yml +++ b/.github/workflows/type_completeness.yml @@ -18,12 +18,12 @@ jobs: - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: - python-version: 3.9 + python-version: 3.12 cache: 'pip' cache-dependency-path: '**/requirements*.txt' - name: Install Pyright run: | - python -W ignore -m pip install pyright~=1.1.316 + python -W ignore -m pip install pyright~=1.1.367 - name: Get PR Completeness # Must run before base completeness, as base completeness will checkout the base branch # And we can't go back to the PR branch after that in case the PR is coming from a fork From 4bf67d76e5e8ef764f27d36c1896c8225b168f47 Mon Sep 17 00:00:00 2001 From: Harshil <37377066+harshil21@users.noreply.github.com> Date: Mon, 17 Jun 2024 13:59:06 -0400 Subject: [PATCH 09/10] Update pyproject.toml Co-authored-by: Bibo-Joshi <22366557+Bibo-Joshi@users.noreply.github.com> --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 837ea84402c..13ae98395c0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -83,7 +83,7 @@ job-queue = [ ] passport = [ "cryptography!=3.4,!=3.4.1,!=3.4.2,!=3.4.3,>=39.0.1", - # cffi is a dependency of cryptography and needs a pre-release version to support python 3.13 + # cffi is a dependency of cryptography and added support for python 3.13 in 1.17.0rc1 "cffi >= 1.17.0rc1; python_version > '3.12'" ] rate-limiter = [ From a4d67ec571860b98757a877550f4293d7f636a07 Mon Sep 17 00:00:00 2001 From: Hinrich Mahler <22366557+Bibo-Joshi@users.noreply.github.com> Date: Mon, 17 Jun 2024 20:33:29 +0200 Subject: [PATCH 10/10] =?UTF-8?q?Fix=20type=20completeness=20check=20-=20n?= =?UTF-8?q?o=20idea=20why=20that=20works=20=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- telegram/ext/_jobqueue.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/telegram/ext/_jobqueue.py b/telegram/ext/_jobqueue.py index b30b647a290..6edd5a892ea 100644 --- a/telegram/ext/_jobqueue.py +++ b/telegram/ext/_jobqueue.py @@ -106,7 +106,7 @@ def __init__(self) -> None: self._application: Optional[weakref.ReferenceType[Application]] = None self._executor = AsyncIOExecutor() - self.scheduler: AsyncIOScheduler = AsyncIOScheduler(**self.scheduler_configuration) + self.scheduler: "AsyncIOScheduler" = AsyncIOScheduler(**self.scheduler_configuration) def __repr__(self) -> str: """Give a string representation of the JobQueue in the form ``JobQueue[application=...]``.