From 51beb4825723c83947377738d9fd449aa2f7d746 Mon Sep 17 00:00:00 2001 From: Alex Root Junior Date: Sat, 19 Oct 2024 14:55:38 +0300 Subject: [PATCH] Enabled tests on Python 3.13, disabled on Python 3.8 (#1589) * Try to enable tests on Python 3.13 * Remove support for Python 3.8 and PyPy 3.8 Dropped Python 3.8 and PyPy 3.8 from the CI workflow and updated the minimum required Python version to 3.9 in pyproject.toml. Also updated dependencies and tools to align with the new minimum Python version. * Added changelog * Reformat code * Bump mypy python version --- .github/workflows/tests.yml | 3 +- CHANGES/1589.misc.rst | 16 ++++++ pyproject.toml | 18 +++---- .../test_session/test_aiohttp_session.py | 17 ++++--- tests/test_dispatcher/test_dispatcher.py | 49 ++++++++++++------- tests/test_utils/test_callback_answer.py | 17 ++++--- tests/test_utils/test_chat_action.py | 17 ++++--- 7 files changed, 86 insertions(+), 51 deletions(-) create mode 100644 CHANGES/1589.misc.rst diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 0d95be48..7c5292ef 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -29,11 +29,11 @@ jobs: - macos-latest - windows-latest python-version: - - '3.8' - '3.9' - '3.10' - '3.11' - '3.12' + - '3.13' defaults: # Windows sucks. Force use bash instead of PowerShell @@ -111,7 +111,6 @@ jobs: - macos-latest # - windows-latest python-version: - - 'pypy3.8' - 'pypy3.9' - 'pypy3.10' diff --git a/CHANGES/1589.misc.rst b/CHANGES/1589.misc.rst new file mode 100644 index 00000000..80c531c8 --- /dev/null +++ b/CHANGES/1589.misc.rst @@ -0,0 +1,16 @@ +Checked compatibility with Python 3.13 (added to the CI/CD processes), +so now aiogram is totally compatible with it. + +Dropped compatibility with Python 3.8 due to this version being `EOL `_. + +.. warning:: + + In some cases you will need to have the installed compiler (Rust or C++) + to install some of the dependencies to compile packages from source on `pip install` command. + + - If you are using Windows, you will need to have the `Visual Studio `_ installed. + - If you are using Linux, you will need to have the `build-essential` package installed. + - If you are using macOS, you will need to have the `Xcode `_ installed. + + When developers of this dependencies will release new versions with precompiled wheels for Windows, Linux and macOS, + this action will not be necessary anymore until the next version of the Python interpreter. diff --git a/pyproject.toml b/pyproject.toml index 03b3be31..d1d59a40 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "hatchling.build" name = "aiogram" description = 'Modern and fully asynchronous framework for Telegram Bot API' readme = "README.rst" -requires-python = ">=3.8" +requires-python = ">=3.9" license = "MIT" authors = [ { name = "Alex Root Junior", email = "jroot.junior@gmail.com" }, @@ -30,11 +30,11 @@ classifiers = [ "Typing :: Typed", "Intended Audience :: Developers", "Intended Audience :: System Administrators", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Software Development :: Libraries :: Application Frameworks", "Topic :: Software Development :: Libraries :: Python Modules", @@ -43,8 +43,7 @@ classifiers = [ dependencies = [ "magic-filter>=1.0.12,<1.1", "aiohttp>=3.9.0,<3.11", - "pydantic>=2.4.1,<2.9; python_version < '3.9'", # v2.9 breaks compatibility with Python 3.8 without any reason - "pydantic>=2.4.1,<2.10; python_version >= '3.9'", + "pydantic>=2.4.1,<2.10", "aiofiles>=23.2.1,<24.2", "certifi>=2023.7.22", "typing-extensions>=4.7.0,<=5.0", @@ -56,7 +55,8 @@ path = "aiogram/__meta__.py" [project.optional-dependencies] fast = [ - "uvloop>=0.17.0; (sys_platform == 'darwin' or sys_platform == 'linux') and platform_python_implementation != 'PyPy'", + "uvloop>=0.17.0; (sys_platform == 'darwin' or sys_platform == 'linux') and platform_python_implementation != 'PyPy' and python_version < '3.13'", + "uvloop>=0.21.0; (sys_platform == 'darwin' or sys_platform == 'linux') and platform_python_implementation != 'PyPy' and python_version >= '3.13'", "aiodns>=3.0.0", ] redis = [ @@ -198,7 +198,7 @@ view-cov = "google-chrome-stable reports/py{matrix:python}/coverage/index.html" [[tool.hatch.envs.test.matrix]] -python = ["38", "39", "310", "311", "312"] +python = ["39", "310", "311", "312", "313"] [tool.ruff] line-length = 99 @@ -215,7 +215,7 @@ exclude = [ "scripts", "*.egg-info", ] -target-version = "py310" +target-version = "py39" [tool.ruff.lint] select = [ @@ -275,7 +275,7 @@ exclude_lines = [ [tool.mypy] plugins = "pydantic.mypy" -python_version = "3.8" +python_version = "3.9" show_error_codes = true show_error_context = true pretty = true @@ -309,7 +309,7 @@ disallow_untyped_defs = true [tool.black] line-length = 99 -target-version = ['py38', 'py39', 'py310', 'py311'] +target-version = ['py39', 'py310', 'py311', 'py312', 'py313'] exclude = ''' ( \.eggs diff --git a/tests/test_api/test_client/test_session/test_aiohttp_session.py b/tests/test_api/test_client/test_session/test_aiohttp_session.py index 4d0e7153..e23abc13 100644 --- a/tests/test_api/test_client/test_session/test_aiohttp_session.py +++ b/tests/test_api/test_client/test_session/test_aiohttp_session.py @@ -248,13 +248,16 @@ class TestAiohttpSession: async with AiohttpSession() as session: assert isinstance(session, AsyncContextManager) - with patch( - "aiogram.client.session.aiohttp.AiohttpSession.create_session", - new_callable=AsyncMock, - ) as mocked_create_session, patch( - "aiogram.client.session.aiohttp.AiohttpSession.close", - new_callable=AsyncMock, - ) as mocked_close: + with ( + patch( + "aiogram.client.session.aiohttp.AiohttpSession.create_session", + new_callable=AsyncMock, + ) as mocked_create_session, + patch( + "aiogram.client.session.aiohttp.AiohttpSession.close", + new_callable=AsyncMock, + ) as mocked_close, + ): async with session as ctx: assert session == ctx mocked_close.assert_awaited_once() diff --git a/tests/test_dispatcher/test_dispatcher.py b/tests/test_dispatcher/test_dispatcher.py index 2e324a86..81bb6c2f 100644 --- a/tests/test_dispatcher/test_dispatcher.py +++ b/tests/test_dispatcher/test_dispatcher.py @@ -778,11 +778,14 @@ class TestDispatcher: async def _mock_updates(*_): yield Update(update_id=42) - with patch( - "aiogram.dispatcher.dispatcher.Dispatcher._process_update", new_callable=AsyncMock - ) as mocked_process_update, patch( - "aiogram.dispatcher.dispatcher.Dispatcher._listen_updates" - ) as patched_listen_updates: + with ( + patch( + "aiogram.dispatcher.dispatcher.Dispatcher._process_update", new_callable=AsyncMock + ) as mocked_process_update, + patch( + "aiogram.dispatcher.dispatcher.Dispatcher._listen_updates" + ) as patched_listen_updates, + ): patched_listen_updates.return_value = _mock_updates() await dispatcher._polling(bot=bot, handle_as_tasks=as_task) if as_task: @@ -852,15 +855,20 @@ class TestDispatcher: async def _mock_updates(*_): yield Update(update_id=42) - with patch( - "aiogram.dispatcher.dispatcher.Dispatcher._process_update", new_callable=AsyncMock - ) as mocked_process_update, patch( - "aiogram.dispatcher.router.Router.emit_startup", new_callable=AsyncMock - ) as mocked_emit_startup, patch( - "aiogram.dispatcher.router.Router.emit_shutdown", new_callable=AsyncMock - ) as mocked_emit_shutdown, patch( - "aiogram.dispatcher.dispatcher.Dispatcher._listen_updates" - ) as patched_listen_updates: + with ( + patch( + "aiogram.dispatcher.dispatcher.Dispatcher._process_update", new_callable=AsyncMock + ) as mocked_process_update, + patch( + "aiogram.dispatcher.router.Router.emit_startup", new_callable=AsyncMock + ) as mocked_emit_startup, + patch( + "aiogram.dispatcher.router.Router.emit_shutdown", new_callable=AsyncMock + ) as mocked_emit_shutdown, + patch( + "aiogram.dispatcher.dispatcher.Dispatcher._listen_updates" + ) as patched_listen_updates, + ): patched_listen_updates.return_value = _mock_updates() await dispatcher.start_polling(bot) @@ -916,11 +924,14 @@ class TestDispatcher: yield Update(update_id=42) await asyncio.sleep(1) - with patch( - "aiogram.dispatcher.dispatcher.Dispatcher._process_update", new_callable=AsyncMock - ) as mocked_process_update, patch( - "aiogram.dispatcher.dispatcher.Dispatcher._listen_updates", - return_value=_mock_updates(), + with ( + patch( + "aiogram.dispatcher.dispatcher.Dispatcher._process_update", new_callable=AsyncMock + ) as mocked_process_update, + patch( + "aiogram.dispatcher.dispatcher.Dispatcher._listen_updates", + return_value=_mock_updates(), + ), ): task = asyncio.ensure_future(dispatcher.start_polling(bot)) await running.wait() diff --git a/tests/test_utils/test_callback_answer.py b/tests/test_utils/test_callback_answer.py index cf641f46..8c0d76c2 100644 --- a/tests/test_utils/test_callback_answer.py +++ b/tests/test_utils/test_callback_answer.py @@ -198,13 +198,16 @@ class TestCallbackAnswerMiddleware: stack.append("answer") middleware = CallbackAnswerMiddleware() - with patch( - "aiogram.utils.callback_answer.CallbackAnswerMiddleware.construct_callback_answer", - new_callable=MagicMock, - side_effect=lambda **kwargs: CallbackAnswer(**{"answered": False, **properties}), - ), patch( - "aiogram.utils.callback_answer.CallbackAnswerMiddleware.answer", - new=answer, + with ( + patch( + "aiogram.utils.callback_answer.CallbackAnswerMiddleware.construct_callback_answer", + new_callable=MagicMock, + side_effect=lambda **kwargs: CallbackAnswer(**{"answered": False, **properties}), + ), + patch( + "aiogram.utils.callback_answer.CallbackAnswerMiddleware.answer", + new=answer, + ), ): await middleware(handler, event, {}) diff --git a/tests/test_utils/test_chat_action.py b/tests/test_utils/test_chat_action.py index 14048e00..d893b074 100644 --- a/tests/test_utils/test_chat_action.py +++ b/tests/test_utils/test_chat_action.py @@ -96,13 +96,16 @@ class TestChatActionMiddleware: handler1 = flags.chat_action(value)(handler) middleware = ChatActionMiddleware() - with patch( - "aiogram.utils.chat_action.ChatActionSender._run", - new_callable=AsyncMock, - ) as mocked_run, patch( - "aiogram.utils.chat_action.ChatActionSender._stop", - new_callable=AsyncMock, - ) as mocked_stop: + with ( + patch( + "aiogram.utils.chat_action.ChatActionSender._run", + new_callable=AsyncMock, + ) as mocked_run, + patch( + "aiogram.utils.chat_action.ChatActionSender._stop", + new_callable=AsyncMock, + ) as mocked_stop, + ): data = {"handler": HandlerObject(callback=handler1), "bot": bot} message = Message( chat=Chat(id=42, type="private", title="Test"),