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
143 changes: 104 additions & 39 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,108 @@ def _cancel_all_tasks(loop: __asyncio.AbstractEventLoop) -> None:
})


class EventLoopPolicy(__BasePolicy):
"""Event loop policy.
_deprecated_names = ('install', 'EventLoopPolicy')


if _sys.version_info[:2] < (3, 16):
__all__ += _deprecated_names


def __getattr__(name: str) -> _typing.Any:
if name not in _deprecated_names:
raise AttributeError(f"module 'uvloop' has no attribute '{name}'")
elif _sys.version_info[:2] >= (3, 16):
raise AttributeError(
f"module 'uvloop' has no attribute '{name}' "
f"(it was removed in Python 3.16, use uvloop.run() instead)"
)

import threading

def install() -> None:
"""A helper function to install uvloop policy.

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
)

The preferred way to make your application use uvloop:
return self._local._loop

>>> import asyncio
>>> import uvloop
>>> asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
>>> asyncio.get_event_loop()
<uvloop.Loop running=False closed=False debug=False>
"""
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 None, "
f"not '{type(loop).__name__}'"
)
self._local._loop = loop

def _loop_factory(self) -> Loop:
return new_event_loop()
def new_event_loop(self) -> Loop:
"""Create a 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:
...
You must call set_event_loop() to make this the current event loop.
"""
return self._loop_factory()

def set_child_watcher(
self, watcher: _typing.Any
) -> _typing.NoReturn:
...
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