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"),