Skip to content

Commit 3da32b4

Browse files
committed
Merge PR MagicStack#638: Fix build for Python 3.14
2 parents 96b7ed3 + 93d25d2 commit 3da32b4

File tree

5 files changed

+110
-43
lines changed

5 files changed

+110
-43
lines changed

.github/workflows/release.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ jobs:
8383
- "cp311-*"
8484
- "cp312-*"
8585
- "cp313-*"
86+
- "cp314-*"
8687
cibw_arch: ["x86_64", "aarch64", "universal2"]
8788
exclude:
8889
- os: ubuntu-latest

.github/workflows/tests.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ jobs:
2222
- "3.11"
2323
- "3.12"
2424
- "3.13"
25+
- "3.14"
2526
os: [ubuntu-latest, macos-latest]
2627

2728
env:

pyproject.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
name = "uvloop"
33
description = "Fast implementation of asyncio event loop on top of libuv"
44
authors = [{name = "Yury Selivanov", email = "[email protected]"}]
5-
requires-python = '>=3.8.0'
5+
requires-python = '>=3.8.1'
66
readme = "README.rst"
77
license = {text = "MIT License"}
88
dynamic = ["version"]
@@ -38,9 +38,9 @@ test = [
3838
# their combination breaks too often
3939
# (example breakage: https://gitlab.com/pycqa/flake8/issues/427)
4040
'aiohttp>=3.10.5',
41-
'flake8~=5.0',
41+
'flake8~=6.1',
4242
'psutil',
43-
'pycodestyle~=2.9.0',
43+
'pycodestyle~=2.11.0',
4444
'pyOpenSSL~=23.0.0',
4545
'mypy>=0.800',
4646
]

uvloop/__init__.py

Lines changed: 104 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,19 @@
33
import sys as _sys
44
import warnings as _warnings
55

6-
from asyncio.events import BaseDefaultEventLoopPolicy as __BasePolicy
7-
86
from . import includes as __includes # NOQA
97
from .loop import Loop as __BaseLoop # NOQA
108
from ._version import __version__ # NOQA
119

1210

13-
__all__ = ('new_event_loop', 'install', 'EventLoopPolicy')
11+
__all__: _typing.Tuple[str, ...] = ('new_event_loop', 'run')
12+
_AbstractEventLoop = __asyncio.AbstractEventLoop
1413

1514

1615
_T = _typing.TypeVar("_T")
1716

1817

19-
class Loop(__BaseLoop, __asyncio.AbstractEventLoop): # type: ignore[misc]
18+
class Loop(__BaseLoop, _AbstractEventLoop): # type: ignore[misc]
2019
pass
2120

2221

@@ -25,18 +24,6 @@ def new_event_loop() -> Loop:
2524
return Loop()
2625

2726

28-
def install() -> None:
29-
"""A helper function to install uvloop policy."""
30-
if _sys.version_info[:2] >= (3, 12):
31-
_warnings.warn(
32-
'uvloop.install() is deprecated in favor of uvloop.run() '
33-
'starting with Python 3.12.',
34-
DeprecationWarning,
35-
stacklevel=1,
36-
)
37-
__asyncio.set_event_loop_policy(EventLoopPolicy())
38-
39-
4027
if _typing.TYPE_CHECKING:
4128
def run(
4229
main: _typing.Coroutine[_typing.Any, _typing.Any, _T],
@@ -114,7 +101,7 @@ async def wrapper():
114101
)
115102

116103

117-
def _cancel_all_tasks(loop: __asyncio.AbstractEventLoop) -> None:
104+
def _cancel_all_tasks(loop: _AbstractEventLoop) -> None:
118105
# Copied from python/cpython
119106

120107
to_cancel = __asyncio.all_tasks(loop)
@@ -139,30 +126,108 @@ def _cancel_all_tasks(loop: __asyncio.AbstractEventLoop) -> None:
139126
})
140127

141128

142-
class EventLoopPolicy(__BasePolicy):
143-
"""Event loop policy.
129+
_deprecated_names = ('install', 'EventLoopPolicy')
130+
131+
132+
if _sys.version_info[:2] < (3, 16):
133+
__all__ += _deprecated_names
134+
135+
136+
def __getattr__(name: str) -> _typing.Any:
137+
if name not in _deprecated_names:
138+
raise AttributeError(f"module 'uvloop' has no attribute '{name}'")
139+
elif _sys.version_info[:2] >= (3, 16):
140+
raise AttributeError(
141+
f"module 'uvloop' has no attribute '{name}' "
142+
f"(it was removed in Python 3.16, use uvloop.run() instead)"
143+
)
144+
145+
import threading
146+
147+
def install() -> None:
148+
"""A helper function to install uvloop policy.
149+
150+
This function is deprecated and will be removed in Python 3.16.
151+
Use `uvloop.run()` instead.
152+
"""
153+
if _sys.version_info[:2] >= (3, 12):
154+
_warnings.warn(
155+
'uvloop.install() is deprecated in favor of uvloop.run() '
156+
'starting with Python 3.12.',
157+
DeprecationWarning,
158+
stacklevel=1,
159+
)
160+
__asyncio.set_event_loop_policy(EventLoopPolicy())
161+
162+
class EventLoopPolicy(
163+
# This is to avoid a mypy error about AbstractEventLoopPolicy
164+
getattr(__asyncio, 'AbstractEventLoopPolicy') # type: ignore[misc]
165+
):
166+
"""Event loop policy for uvloop.
167+
168+
This class is deprecated and will be removed in Python 3.16.
169+
Use `uvloop.run()` instead.
170+
171+
>>> import asyncio
172+
>>> import uvloop
173+
>>> asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
174+
>>> asyncio.get_event_loop()
175+
<uvloop.Loop running=False closed=False debug=False>
176+
"""
177+
178+
def _loop_factory(self) -> Loop:
179+
return new_event_loop()
180+
181+
if _typing.TYPE_CHECKING:
182+
# EventLoopPolicy doesn't implement these, but since they are
183+
# marked as abstract in typeshed, we have to put them in so mypy
184+
# thinks the base methods are overridden. This is the same approach
185+
# taken for the Windows event loop policy classes in typeshed.
186+
def get_child_watcher(self) -> _typing.NoReturn:
187+
...
188+
189+
def set_child_watcher(
190+
self, watcher: _typing.Any
191+
) -> _typing.NoReturn:
192+
...
193+
194+
class _Local(threading.local):
195+
_loop: _typing.Optional[_AbstractEventLoop] = None
196+
197+
def __init__(self) -> None:
198+
self._local = self._Local()
199+
200+
def get_event_loop(self) -> _AbstractEventLoop:
201+
"""Get the event loop for the current context.
202+
203+
Returns an instance of EventLoop or raises an exception.
204+
"""
205+
if self._local._loop is None:
206+
raise RuntimeError(
207+
'There is no current event loop in thread %r.'
208+
% threading.current_thread().name
209+
)
144210

145-
The preferred way to make your application use uvloop:
211+
return self._local._loop
146212

147-
>>> import asyncio
148-
>>> import uvloop
149-
>>> asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
150-
>>> asyncio.get_event_loop()
151-
<uvloop.Loop running=False closed=False debug=False>
152-
"""
213+
def set_event_loop(
214+
self, loop: _typing.Optional[_AbstractEventLoop]
215+
) -> None:
216+
"""Set the event loop."""
217+
if loop is not None and not isinstance(loop, _AbstractEventLoop):
218+
raise TypeError(
219+
f"loop must be an instance of AbstractEventLoop or None, "
220+
f"not '{type(loop).__name__}'"
221+
)
222+
self._local._loop = loop
153223

154-
def _loop_factory(self) -> Loop:
155-
return new_event_loop()
224+
def new_event_loop(self) -> Loop:
225+
"""Create a new event loop.
156226
157-
if _typing.TYPE_CHECKING:
158-
# EventLoopPolicy doesn't implement these, but since they are marked
159-
# as abstract in typeshed, we have to put them in so mypy thinks
160-
# the base methods are overridden. This is the same approach taken
161-
# for the Windows event loop policy classes in typeshed.
162-
def get_child_watcher(self) -> _typing.NoReturn:
163-
...
227+
You must call set_event_loop() to make this the current event loop.
228+
"""
229+
return self._loop_factory()
164230

165-
def set_child_watcher(
166-
self, watcher: _typing.Any
167-
) -> _typing.NoReturn:
168-
...
231+
globals()['install'] = install
232+
globals()['EventLoopPolicy'] = EventLoopPolicy
233+
return globals()[name]

uvloop/includes/stdlib.pxi

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ cdef aio_isfuture = getattr(asyncio, 'isfuture', None)
4444
cdef aio_get_running_loop = getattr(asyncio, '_get_running_loop', None)
4545
cdef aio_set_running_loop = getattr(asyncio, '_set_running_loop', None)
4646
cdef aio_debug_wrapper = getattr(asyncio.coroutines, 'debug_wrapper', None)
47-
cdef aio_AbstractChildWatcher = asyncio.AbstractChildWatcher
47+
cdef aio_AbstractChildWatcher = getattr(asyncio, "AbstractChildWatcher", ())
4848
cdef aio_Transport = asyncio.Transport
4949
cdef aio_FlowControlMixin = asyncio.transports._FlowControlMixin
5050

0 commit comments

Comments
 (0)