Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ jobs:
- "cp311-*"
- "cp312-*"
- "cp313-*"
- "cp314-*"
cibw_arch: ["x86_64", "aarch64", "universal2"]
exclude:
- os: ubuntu-latest
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ jobs:
- "3.11"
- "3.12"
- "3.13"
- "3.14"
os: [ubuntu-latest, macos-latest]

env:
Expand Down
6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
name = "uvloop"
description = "Fast implementation of asyncio event loop on top of libuv"
authors = [{name = "Yury Selivanov", email = "[email protected]"}]
requires-python = '>=3.8.0'
requires-python = '>=3.8.1'
readme = "README.rst"
license = {text = "MIT License"}
dynamic = ["version"]
Expand Down Expand Up @@ -38,9 +38,9 @@ test = [
# their combination breaks too often
# (example breakage: https://gitlab.com/pycqa/flake8/issues/427)
'aiohttp>=3.10.5',
'flake8~=5.0',
'flake8~=6.1',
'psutil',
'pycodestyle~=2.9.0',
'pycodestyle~=2.11.0',
'pyOpenSSL~=23.0.0',
'mypy>=0.800',
]
Expand Down
140 changes: 100 additions & 40 deletions uvloop/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,19 @@
import sys as _sys
import warnings as _warnings

from asyncio.events import BaseDefaultEventLoopPolicy as __BasePolicy

from . import includes as __includes # NOQA
from .loop import Loop as __BaseLoop # NOQA
from ._version import __version__ # NOQA


__all__ = ('new_event_loop', 'install', 'EventLoopPolicy')
__all__: _typing.Tuple[str, ...] = ('new_event_loop', 'run')
_AbstractEventLoop = __asyncio.AbstractEventLoop


_T = _typing.TypeVar("_T")


class Loop(__BaseLoop, __asyncio.AbstractEventLoop): # type: ignore[misc]
class Loop(__BaseLoop, _AbstractEventLoop): # type: ignore[misc]
pass


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


def install() -> None:
"""A helper function to install uvloop policy."""
if _sys.version_info[:2] >= (3, 12):
_warnings.warn(
'uvloop.install() is deprecated in favor of uvloop.run() '
'starting with Python 3.12.',
DeprecationWarning,
stacklevel=1,
)
__asyncio.set_event_loop_policy(EventLoopPolicy())


if _typing.TYPE_CHECKING:
def run(
main: _typing.Coroutine[_typing.Any, _typing.Any, _T],
Expand Down Expand Up @@ -114,7 +101,7 @@ async def wrapper():
)


def _cancel_all_tasks(loop: __asyncio.AbstractEventLoop) -> None:
def _cancel_all_tasks(loop: _AbstractEventLoop) -> None:
# Copied from python/cpython

to_cancel = __asyncio.all_tasks(loop)
Expand All @@ -139,30 +126,103 @@ def _cancel_all_tasks(loop: __asyncio.AbstractEventLoop) -> None:
})


class EventLoopPolicy(__BasePolicy):
"""Event loop policy.
if _sys.version_info[:2] < (3, 16):
__all__ += ('install', 'EventLoopPolicy')

The preferred way to make your application use uvloop:
def __getattr__(name: str) -> _typing.Any:
if name not in __all__:
raise AttributeError(f"module 'uvloop' has no attribute '{name}'")

>>> import asyncio
>>> import uvloop
>>> asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
>>> asyncio.get_event_loop()
<uvloop.Loop running=False closed=False debug=False>
"""
import threading

def _loop_factory(self) -> Loop:
return new_event_loop()
def install() -> None:
"""A helper function to install uvloop policy.

if _typing.TYPE_CHECKING:
# EventLoopPolicy doesn't implement these, but since they are marked
# as abstract in typeshed, we have to put them in so mypy thinks
# the base methods are overridden. This is the same approach taken
# for the Windows event loop policy classes in typeshed.
def get_child_watcher(self) -> _typing.NoReturn:
...

def set_child_watcher(
self, watcher: _typing.Any
) -> _typing.NoReturn:
...
This function is deprecated and will be removed in Python 3.16.
Use `uvloop.run()` instead.
"""
if _sys.version_info[:2] >= (3, 12):
_warnings.warn(
'uvloop.install() is deprecated in favor of uvloop.run() '
'starting with Python 3.12.',
DeprecationWarning,
stacklevel=1,
)
__asyncio.set_event_loop_policy(EventLoopPolicy())

class EventLoopPolicy(
# This is to avoid a mypy error about AbstractEventLoopPolicy
getattr(__asyncio, 'AbstractEventLoopPolicy') # type: ignore[misc]
):
"""Event loop policy for uvloop.

This class is deprecated and will be removed in Python 3.16.
Use `uvloop.run()` instead.

>>> import asyncio
>>> import uvloop
>>> asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
>>> asyncio.get_event_loop()
<uvloop.Loop running=False closed=False debug=False>
"""

def _loop_factory(self) -> Loop:
return new_event_loop()

if _typing.TYPE_CHECKING:
# EventLoopPolicy doesn't implement these, but since they are
# marked as abstract in typeshed, we have to put them in so
# mypy thinks the base methods are overridden. This is the same
# approach taken for the Windows event loop policy classes in
# typeshed.
def get_child_watcher(self) -> _typing.NoReturn:
...

def set_child_watcher(
self, watcher: _typing.Any
) -> _typing.NoReturn:
...

class _Local(threading.local):
_loop: _typing.Optional[_AbstractEventLoop] = None

def __init__(self) -> None:
self._local = self._Local()

def get_event_loop(self) -> _AbstractEventLoop:
"""Get the event loop for the current context.

Returns an instance of EventLoop or raises an exception.
"""
if self._local._loop is None:
raise RuntimeError(
'There is no current event loop in thread %r.'
% threading.current_thread().name
)

return self._local._loop

def set_event_loop(
self, loop: _typing.Optional[_AbstractEventLoop]
) -> None:
"""Set the event loop."""
if loop is not None and not isinstance(
loop, _AbstractEventLoop
):
raise TypeError(
f"loop must be an instance of AbstractEventLoop or "
f"None, not '{type(loop).__name__}'"
)
self._local._loop = loop

def new_event_loop(self) -> Loop:
"""Create a new event loop.

You must call set_event_loop() to make this the current event
loop.
"""
return self._loop_factory()

globals()['install'] = install
globals()['EventLoopPolicy'] = EventLoopPolicy
return globals()[name]
2 changes: 1 addition & 1 deletion uvloop/includes/stdlib.pxi
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ cdef aio_isfuture = getattr(asyncio, 'isfuture', None)
cdef aio_get_running_loop = getattr(asyncio, '_get_running_loop', None)
cdef aio_set_running_loop = getattr(asyncio, '_set_running_loop', None)
cdef aio_debug_wrapper = getattr(asyncio.coroutines, 'debug_wrapper', None)
cdef aio_AbstractChildWatcher = asyncio.AbstractChildWatcher
cdef aio_AbstractChildWatcher = getattr(asyncio, "AbstractChildWatcher", ())
cdef aio_Transport = asyncio.Transport
cdef aio_FlowControlMixin = asyncio.transports._FlowControlMixin

Expand Down