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
This commit is contained in:
Alex Root Junior 2024-10-19 14:55:38 +03:00 committed by GitHub
parent 1dbdcf0516
commit 51beb48257
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 86 additions and 51 deletions

View file

@ -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'

16
CHANGES/1589.misc.rst Normal file
View file

@ -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 <https://devguide.python.org/versions/>`_.
.. 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 <https://visualstudio.microsoft.com/visual-cpp-build-tools/>`_ 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 <https://developer.apple.com/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.

View file

@ -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

View file

@ -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()

View file

@ -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()

View file

@ -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, {})

View file

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