Skip to content

Commit

Permalink
Enabled tests on Python 3.13, disabled on Python 3.8 (#1589)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
JrooTJunior authored Oct 19, 2024
1 parent 1dbdcf0 commit 51beb48
Show file tree
Hide file tree
Showing 7 changed files with 86 additions and 51 deletions.
3 changes: 1 addition & 2 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -111,7 +111,6 @@ jobs:
- macos-latest
# - windows-latest
python-version:
- 'pypy3.8'
- 'pypy3.9'
- 'pypy3.10'

Expand Down
16 changes: 16 additions & 0 deletions CHANGES/1589.misc.rst
Original file line number Diff line number Diff line change
@@ -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.
18 changes: 9 additions & 9 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" },
Expand All @@ -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",
Expand All @@ -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",
Expand All @@ -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 = [
Expand Down Expand Up @@ -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
Expand All @@ -215,7 +215,7 @@ exclude = [
"scripts",
"*.egg-info",
]
target-version = "py310"
target-version = "py39"

[tool.ruff.lint]
select = [
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
17 changes: 10 additions & 7 deletions tests/test_api/test_client/test_session/test_aiohttp_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,13 +248,16 @@ async def test_context_manager(self):
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()
Expand Down
49 changes: 30 additions & 19 deletions tests/test_dispatcher/test_dispatcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -778,11 +778,14 @@ async def test_polling(self, bot: MockedBot, as_task: bool):
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:
Expand Down Expand Up @@ -852,15 +855,20 @@ async def test_start_polling(self, bot: MockedBot):
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)

Expand Down Expand Up @@ -916,11 +924,14 @@ async def _mock_updates(*_):
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()
Expand Down
17 changes: 10 additions & 7 deletions tests/test_utils/test_callback_answer.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,13 +198,16 @@ async def answer(*args, **kwargs):
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, {})

Expand Down
17 changes: 10 additions & 7 deletions tests/test_utils/test_chat_action.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,13 +96,16 @@ async def handler(event, data):
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"),
Expand Down

0 comments on commit 51beb48

Please sign in to comment.