Support of Py3.14 (#1730)
Some checks failed
Tests / tests (macos-latest, 3.10) (push) Has been cancelled
Tests / tests (macos-latest, 3.11) (push) Has been cancelled
Tests / tests (macos-latest, 3.12) (push) Has been cancelled
Tests / tests (macos-latest, 3.13) (push) Has been cancelled
Tests / tests (macos-latest, 3.14) (push) Has been cancelled
Tests / tests (ubuntu-latest, 3.10) (push) Has been cancelled
Tests / tests (ubuntu-latest, 3.11) (push) Has been cancelled
Tests / tests (ubuntu-latest, 3.12) (push) Has been cancelled
Tests / tests (ubuntu-latest, 3.13) (push) Has been cancelled
Tests / tests (ubuntu-latest, 3.14) (push) Has been cancelled
Tests / tests (windows-latest, 3.10) (push) Has been cancelled
Tests / tests (windows-latest, 3.11) (push) Has been cancelled
Tests / tests (windows-latest, 3.12) (push) Has been cancelled
Tests / tests (windows-latest, 3.13) (push) Has been cancelled
Tests / tests (windows-latest, 3.14) (push) Has been cancelled
Tests / pypy-tests (macos-latest, pypy3.10) (push) Has been cancelled
Tests / pypy-tests (macos-latest, pypy3.11) (push) Has been cancelled
Tests / pypy-tests (ubuntu-latest, pypy3.10) (push) Has been cancelled
Tests / pypy-tests (ubuntu-latest, pypy3.11) (push) Has been cancelled

* Py3.14 support
Bump .pre-commit-config.yaml
Bump `mongo` feature deps
Bump `proxy` feature dep
Bump `test` feature deps
Bump `dev` feature deps

Set `aiohttp` max version `<3.14`

Fix `test_isolation.py` tests
Fix `test_storages.py` tests

Add Py version limit `<3.15` (breaking changes possible)
Add new `uvloop` starter to `Dispatcher.run_polling`

Remove old `uvloop` `set_event_loop_policy`
Remove `pytest-lazy-fixture`

* Make `test` and `dev` features deps strong fixed

* Add 1730.feature.rst

* Remove unneeded `None` from `Dispatcher.run_polling`

* Fix `macos-latest-pypy3.11` test `test_aiohtt_server.py`

* Update `tests.yml`

* Update `tests.yml`
This commit is contained in:
Andrew 2025-10-11 00:16:50 +03:00 committed by GitHub
parent 0c6a705310
commit 4caf56814e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 77 additions and 64 deletions

View file

@ -33,6 +33,7 @@ jobs:
- "3.11"
- "3.12"
- "3.13"
- "3.14"
defaults:
# Windows sucks. Force use bash instead of PowerShell
@ -74,13 +75,13 @@ jobs:
if: ${{ env.IS_WINDOWS == 'false' }}
uses: shogo82148/actions-setup-redis@v1
with:
redis-version: 6
redis-version: "8"
- name: Setup mongodb
if: ${{ env.IS_UBUNTU == 'true' }}
uses: supercharge/mongodb-github-action@1.10.0
uses: supercharge/mongodb-github-action@1.12.0
with:
mongodb-version: "7.0"
mongodb-version: "8"
mongodb-username: mongo
mongodb-password: mongo
mongodb-port: 27017

View file

@ -1,6 +1,6 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
rev: v6.0.0
hooks:
- id: "trailing-whitespace"
- id: "check-case-conflict"
@ -20,6 +20,6 @@ repos:
files: &files '^(aiogram|tests|examples)'
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: 'v0.13.3'
rev: 'v0.14.0'
hooks:
- id: ruff

6
CHANGES/1730.feature.rst Normal file
View file

@ -0,0 +1,6 @@
This PR updates the codebase to support Python 3.14.
- Updated project dep `aiohttp`
- Updated development deps
- Fixed tests to support Py3.14
- Refactored `uvloop` using due to deprecation of `asyncio.set_event_loop_police`

View file

@ -1,6 +1,3 @@
import asyncio as _asyncio
from contextlib import suppress
from aiogram.dispatcher.flags import FlagGenerator
from . import enums, methods, types
@ -14,12 +11,6 @@ from .utils.magic_filter import MagicFilter
from .utils.text_decorations import html_decoration as html
from .utils.text_decorations import markdown_decoration as md
with suppress(ImportError):
import uvloop as _uvloop
_asyncio.set_event_loop_policy(_uvloop.EventLoopPolicy())
F = MagicFilter()
flags = FlagGenerator()

View file

@ -3,6 +3,7 @@ from __future__ import annotations
import asyncio
import contextvars
import signal
import sys
import warnings
from asyncio import CancelledError, Event, Future, Lock
from collections.abc import AsyncGenerator, Awaitable
@ -656,8 +657,7 @@ class Dispatcher(Router):
:return:
"""
with suppress(KeyboardInterrupt):
return asyncio.run(
self.start_polling(
coro = self.start_polling(
*bots,
**kwargs,
polling_timeout=polling_timeout,
@ -667,5 +667,18 @@ class Dispatcher(Router):
handle_signals=handle_signals,
close_bot_session=close_bot_session,
tasks_concurrency_limit=tasks_concurrency_limit,
),
)
try:
import uvloop
except ImportError:
return asyncio.run(coro)
else:
if sys.version_info >= (3, 11):
with asyncio.Runner(loop_factory=uvloop.new_event_loop) as runner:
return runner.run(coro)
else:
uvloop.install()
return asyncio.run(coro)

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.10"
requires-python = ">=3.10,<3.15"
license = "MIT"
authors = [
{ name = "Alex Root Junior", email = "jroot.junior@gmail.com" },
@ -34,6 +34,7 @@ classifiers = [
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Programming Language :: Python :: Implementation :: PyPy",
"Topic :: Software Development :: Libraries :: Application Frameworks",
"Topic :: Software Development :: Libraries :: Python Modules",
@ -41,7 +42,7 @@ classifiers = [
]
dependencies = [
"magic-filter>=1.0.12,<1.1",
"aiohttp>=3.9.0,<3.13",
"aiohttp>=3.9.0,<3.14",
"pydantic>=2.4.1,<2.13",
"aiofiles>=23.2.1,<24.2",
"certifi>=2023.7.22",
@ -62,11 +63,11 @@ redis = [
"redis[hiredis]>=6.2.0,<7",
]
mongo = [
"motor>=3.3.2,<3.7.0",
"pymongo>4.5,<4.11",
"motor>=3.3.2,<3.8",
"pymongo>4.5,<4.16",
]
proxy = [
"aiohttp-socks~=0.8.3",
"aiohttp-socks~=0.10.1",
]
i18n = [
"Babel>=2.13.0,<3",
@ -78,17 +79,15 @@ signature = [
"cryptography>=46.0.0",
]
test = [
"pytest~=7.4.2",
"pytest-html~=4.0.2",
"pytest-asyncio~=0.21.1",
"pytest-lazy-fixture~=0.6.3",
"pytest-mock~=3.12.0",
"pytest-mypy~=0.10.3",
"pytest-cov~=4.1.0",
"pytest-aiohttp~=1.0.5",
"aresponses~=2.1.6",
"pytz~=2025.2",
"pycryptodomex~=3.23.0",
"pytest==8.4.2",
"pytest-html==4.1.1",
"pytest-mock==3.15.1",
"pytest-mypy==1.0.1",
"pytest-cov==7.0.0",
"pytest-aiohttp==1.1.0",
"aresponses==3.0.0",
"pytz==2025.2",
"pycryptodomex==3.23.0",
]
docs = [
"Sphinx~=8.0.2",
@ -104,14 +103,14 @@ docs = [
"sphinxcontrib-towncrier~=0.4.0a0",
]
dev = [
"black~=25.9.0",
"isort~=6.1.0",
"ruff~=0.13.3",
"mypy~=1.10.1",
"toml~=0.10.2",
"pre-commit~=4.3.0",
"packaging~=24.1",
"motor-types~=1.0.0b4",
"black==25.9.0",
"isort==6.1.0",
"ruff==0.14.0",
"mypy==1.10.1",
"toml==0.10.2",
"pre-commit==4.3.0",
"packaging==25.0",
"motor-types==1.0.0b4",
]
[project.urls]

View file

@ -39,7 +39,7 @@ def pytest_configure(config):
config.addinivalue_line("markers", "redis: marked tests require redis connection to run")
config.addinivalue_line("markers", "mongo: marked tests require mongo connection to run")
if sys.platform == "win32":
if sys.platform == "win32" and sys.version_info < (3, 14):
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
else:
asyncio.set_event_loop_policy(asyncio.DefaultEventLoopPolicy())
@ -186,6 +186,16 @@ async def dispatcher():
await dp.emit_shutdown()
@pytest.fixture()
def storage(request):
return request.getfixturevalue(request.param)
@pytest.fixture()
def isolation(request):
return request.getfixturevalue(request.param)
# @pytest.fixture(scope="session")
# def event_loop_policy(request):
# if sys.platform == "win32":

View file

@ -8,11 +8,8 @@ from aiogram.fsm.storage.redis import RedisEventIsolation, RedisStorage
@pytest.mark.parametrize(
"isolation",
[
pytest.lazy_fixture("redis_isolation"),
pytest.lazy_fixture("lock_isolation"),
pytest.lazy_fixture("disabled_isolation"),
],
["redis_isolation", "lock_isolation", "disabled_isolation"],
indirect=True,
)
class TestIsolations:
async def test_lock(

View file

@ -8,12 +8,8 @@ from aiogram.fsm.storage.base import BaseStorage, StorageKey
@pytest.mark.parametrize(
"storage",
[
pytest.lazy_fixture("redis_storage"),
pytest.lazy_fixture("mongo_storage"),
pytest.lazy_fixture("pymongo_storage"),
pytest.lazy_fixture("memory_storage"),
],
["memory_storage", "redis_storage", "mongo_storage", "pymongo_storage"],
indirect=True,
)
class TestStorages:
async def test_set_state(self, storage: BaseStorage, storage_key: StorageKey):

View file

@ -185,8 +185,8 @@ class TestSimpleRequestHandler:
handler_event.clear()
resp = await self.make_reqest(client=client)
assert resp.status == 200
await asyncio.wait_for(handler_event.wait(), timeout=1)
await asyncio.wait_for(method_called_event.wait(), timeout=1)
await asyncio.wait_for(handler_event.wait(), timeout=3)
await asyncio.wait_for(method_called_event.wait(), timeout=3)
# Python 3.12 had some changes to asyncio which make it quite a bit faster. But
# probably because of that the assert_awaited call is consistently scheduled before the
# silent_call_request call - failing the test. So we wait for the method to be called