Skip to content

Commit

Permalink
Port uvloop to Python 3.12 (#570)
Browse files Browse the repository at this point in the history
* Bump Cython to 0.29.36
* Add missing new API -- timeout param to shutdown_default_executor
* Stop using the beloved, but now deprecated, 'IF'
* Explicitly qualify void-returning callback functions as `noexcept`.
* Fix test_libuv_get_loop_t_ptr to work under Python 3.12
* "Fix" the failing asyncio tests by adding a sleep() call
* Add 3.12 to CI scripts
* Stop configuring watchers for asyncio tests under 3.12+
* Add the new timeout parameter of shutdown_default_executor to typeshed
* Implement uvloop.run()
* Add pyproject.toml and update CI

Co-authored-by: Fantix King <[email protected]>
  • Loading branch information
1st1 and fantix authored Oct 10, 2023
1 parent 0687643 commit 9f82bd7
Show file tree
Hide file tree
Showing 33 changed files with 468 additions and 157 deletions.
7 changes: 2 additions & 5 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
cibw_python: ["cp37-*", "cp38-*", "cp39-*", "cp310-*", "cp311-*"]
cibw_python: ["cp37-*", "cp38-*", "cp39-*", "cp310-*", "cp311-*", "cp312-*"]
cibw_arch: ["x86_64", "aarch64", "universal2"]
exclude:
- os: ubuntu-latest
Expand Down Expand Up @@ -110,14 +110,11 @@ jobs:
run: |
brew install gnu-sed libtool autoconf automake
- uses: pypa/cibuildwheel@v2.9.0
- uses: pypa/cibuildwheel@v2.16.2
env:
CIBW_BUILD_VERBOSITY: 1
CIBW_BUILD: ${{ matrix.cibw_python }}
CIBW_ARCHS: ${{ matrix.cibw_arch }}
CIBW_TEST_EXTRAS: "test"
CIBW_TEST_COMMAND: "python -m unittest discover -v {project}/tests"
CIBW_TEST_COMMAND_WINDOWS: "python -m unittest discover -v {project}\\tests"
CIBW_TEST_SKIP: "*universal2:arm64"

- uses: actions/upload-artifact@v3
Expand Down
5 changes: 3 additions & 2 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"]
os: [ubuntu-latest, macos-latest]

env:
Expand Down Expand Up @@ -58,7 +58,8 @@ jobs:
make test
- name: Test (debug build)
if: steps.release.outputs.version == 0
# XXX Re-enable 3.12 once we migrate to Cython 3
if: steps.release.outputs.version == 0 && matrix.python-version != '3.12'
run: |
make distclean && make debug && make test
Expand Down
21 changes: 21 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,27 @@ uvloop with::
Using uvloop
------------

As of uvloop 0.18, the preferred way of using it is via the
``uvloop.run()`` helper function:


.. code:: python
import uvloop
async def main():
# Main entry-point.
...
uvloop.run(main())
``uvloop.run()`` works by simply configuring ``asyncio.run()``
to use uvloop, passing all of the arguments to it, such as ``debug``,
e.g. ``uvloop.run(main(), debug=True)``.

With Python 3.11 and earlier the following alternative
snippet can be used:

.. code:: python
import asyncio
Expand Down
75 changes: 75 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
[project]
name = "uvloop"
description = "Fast implementation of asyncio event loop on top of libuv"
authors = [{name = "Yury Selivanov", email = "[email protected]"}]
requires-python = '>=3.7.0'
readme = "README.rst"
license = {text = "MIT License"}
dynamic = ["version"]
keywords = [
"asyncio",
"networking",
]
classifiers = [
"Development Status :: 5 - Production/Stable",
"Framework :: AsyncIO",
"Intended Audience :: Developers",
"License :: OSI Approved :: Apache Software License",
"License :: OSI Approved :: MIT License",
"Operating System :: POSIX",
"Operating System :: MacOS :: MacOS X",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.7",
"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 :: Implementation :: CPython",
"Topic :: System :: Networking",
]

[project.urls]
github = "https://github.com/MagicStack/uvloop"

[project.optional-dependencies]
test = [
# pycodestyle is a dependency of flake8, but it must be frozen because
# their combination breaks too often
# (example breakage: https://gitlab.com/pycqa/flake8/issues/427)
'aiohttp>=3.8.1; python_version < "3.12"',
'aiohttp==3.9.0b0; python_version >= "3.12"',
'flake8~=5.0',
'psutil',
'pycodestyle~=2.9.0',
'pyOpenSSL~=23.0.0',
'mypy>=0.800',
'Cython(>=0.29.36,<0.30.0)',
]
docs = [
'Sphinx~=4.1.2',
'sphinxcontrib-asyncio~=0.3.0',
'sphinx_rtd_theme~=0.5.2',
]

[build-system]
requires = [
"setuptools>=60",
"wheel",
"Cython(>=0.29.36,<0.30.0)",
]
build-backend = "setuptools.build_meta"

[tool.setuptools]
zip-safe = false
packages = ["uvloop"]

[tool.cibuildwheel]
build-frontend = "build"
test-extras = "test"
test-command = "python -m unittest discover -v {project}/tests"

[tool.pytest.ini_options]
addopts = "--capture=no --assert=plain --strict-markers --tb=native --import-mode=importlib"
testpaths = "tests"
filterwarnings = "default"
4 changes: 0 additions & 4 deletions pytest.ini

This file was deleted.

63 changes: 1 addition & 62 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,40 +21,7 @@
from setuptools.command.sdist import sdist


CYTHON_DEPENDENCY = 'Cython(>=0.29.32,<0.30.0)'

# Minimal dependencies required to test uvloop.
TEST_DEPENDENCIES = [
# pycodestyle is a dependency of flake8, but it must be frozen because
# their combination breaks too often
# (example breakage: https://gitlab.com/pycqa/flake8/issues/427)
'aiohttp>=3.8.1',
'flake8~=5.0',
'psutil',
'pycodestyle~=2.9.0',
'pyOpenSSL~=23.0.0',
'mypy>=0.800',
CYTHON_DEPENDENCY,
]

# Dependencies required to build documentation.
DOC_DEPENDENCIES = [
'Sphinx~=4.1.2',
'sphinxcontrib-asyncio~=0.3.0',
'sphinx_rtd_theme~=0.5.2',
]

EXTRA_DEPENDENCIES = {
'docs': DOC_DEPENDENCIES,
'test': TEST_DEPENDENCIES,
# Dependencies required to develop uvloop.
'dev': [
CYTHON_DEPENDENCY,
'pytest>=3.6.0',
] + DOC_DEPENDENCIES + TEST_DEPENDENCIES
}


CYTHON_DEPENDENCY = 'Cython(>=0.29.36,<0.30.0)'
MACHINE = platform.machine()
MODULES_CFLAGS = [os.getenv('UVLOOP_OPT_CFLAGS', '-O2')]
_ROOT = pathlib.Path(__file__).parent
Expand Down Expand Up @@ -245,10 +212,6 @@ def build_extensions(self):
super().build_extensions()


with open(str(_ROOT / 'README.rst')) as f:
readme = f.read()


with open(str(_ROOT / 'uvloop' / '_version.py')) as f:
for line in f:
if line.startswith('__version__ ='):
Expand All @@ -268,16 +231,7 @@ def build_extensions(self):


setup(
name='uvloop',
description='Fast implementation of asyncio event loop on top of libuv',
long_description=readme,
url='http://github.com/MagicStack/uvloop',
license='MIT',
author='Yury Selivanov',
author_email='[email protected]',
platforms=['macOS', 'POSIX'],
version=VERSION,
packages=['uvloop'],
cmdclass={
'sdist': uvloop_sdist,
'build_ext': uvloop_build_ext
Expand All @@ -291,20 +245,5 @@ def build_extensions(self):
extra_compile_args=MODULES_CFLAGS
),
],
classifiers=[
'Development Status :: 5 - Production/Stable',
'Framework :: AsyncIO',
'Programming Language :: Python :: 3 :: Only',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
'License :: OSI Approved :: Apache Software License',
'License :: OSI Approved :: MIT License',
'Intended Audience :: Developers',
],
include_package_data=True,
extras_require=EXTRA_DEPENDENCIES,
setup_requires=setup_requires,
python_requires='>=3.7',
)
5 changes: 0 additions & 5 deletions tests/cython_helper.pyx

This file was deleted.

19 changes: 17 additions & 2 deletions tests/test_aiohttp.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
skip_tests = False

import asyncio
import sys
import unittest
import weakref

Expand Down Expand Up @@ -48,6 +49,14 @@ async def test():
self.loop.run_until_complete(runner.cleanup())

def test_aiohttp_graceful_shutdown(self):
if self.implementation == 'asyncio' and sys.version_info >= (3, 12, 0):
# In Python 3.12.0, asyncio.Server.wait_closed() waits for all
# existing connections to complete, before aiohttp sends
# on_shutdown signals.
# https://github.com/aio-libs/aiohttp/issues/7675#issuecomment-1752143748
# https://github.com/python/cpython/pull/98582
raise unittest.SkipTest('bug in aiohttp: #7675')

async def websocket_handler(request):
ws = aiohttp.web.WebSocketResponse()
await ws.prepare(request)
Expand All @@ -73,7 +82,13 @@ async def on_shutdown(app):

runner = aiohttp.web.AppRunner(app)
self.loop.run_until_complete(runner.setup())
site = aiohttp.web.TCPSite(runner, '0.0.0.0', '0')
site = aiohttp.web.TCPSite(
runner,
'0.0.0.0',
0,
# https://github.com/aio-libs/aiohttp/pull/7188
shutdown_timeout=0.1,
)
self.loop.run_until_complete(site.start())
port = site._server.sockets[0].getsockname()[1]

Expand All @@ -90,7 +105,7 @@ async def client():
async def stop():
await asyncio.sleep(0.1)
try:
await asyncio.wait_for(runner.cleanup(), timeout=0.1)
await asyncio.wait_for(runner.cleanup(), timeout=0.5)
finally:
try:
client_task.cancel()
Expand Down
4 changes: 1 addition & 3 deletions tests/test_dealloc.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,12 @@ def test_dealloc_1(self):
async def test():
prog = '''\
import uvloop
import asyncio
async def foo():
return 42
def main():
uvloop.install()
loop = asyncio.get_event_loop()
loop = uvloop.new_event_loop()
loop.set_debug(True)
loop.run_until_complete(foo())
# Do not close the loop on purpose: let __dealloc__ methods run.
Expand Down
23 changes: 12 additions & 11 deletions tests/test_libuv_api.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
from uvloop import _testbase as tb
from uvloop.loop import libuv_get_loop_t_ptr, libuv_get_version
from uvloop.loop import _testhelper_unwrap_capsuled_pointer as unwrap


class Test_UV_libuv(tb.UVTestCase):
def test_libuv_get_loop_t_ptr(self):
loop = self.new_loop()
cap1 = libuv_get_loop_t_ptr(loop)
cap2 = libuv_get_loop_t_ptr(loop)
cap3 = libuv_get_loop_t_ptr(self.new_loop())
loop1 = self.new_loop()
cap1 = libuv_get_loop_t_ptr(loop1)
cap2 = libuv_get_loop_t_ptr(loop1)

import pyximport
loop2 = self.new_loop()
cap3 = libuv_get_loop_t_ptr(loop2)

pyximport.install()

import cython_helper

self.assertTrue(cython_helper.capsule_equals(cap1, cap2))
self.assertFalse(cython_helper.capsule_equals(cap1, cap3))
try:
self.assertEqual(unwrap(cap1), unwrap(cap2))
self.assertNotEqual(unwrap(cap1), unwrap(cap3))
finally:
loop1.close()
loop2.close()

def test_libuv_get_version(self):
self.assertGreater(libuv_get_version(), 0)
3 changes: 1 addition & 2 deletions tests/test_process.py
Original file line number Diff line number Diff line change
Expand Up @@ -851,8 +851,7 @@ async def test():
)
await proc.communicate()
uvloop.install()
asyncio.run(test())
uvloop.run(test())
stdin, stdout, stderr = dups
(r, w), = pipes
Expand Down
39 changes: 39 additions & 0 deletions tests/test_runner.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import asyncio
import unittest
import uvloop


class TestSourceCode(unittest.TestCase):

def test_uvloop_run_1(self):
CNT = 0

async def main():
nonlocal CNT
CNT += 1

loop = asyncio.get_running_loop()

self.assertTrue(isinstance(loop, uvloop.Loop))
self.assertTrue(loop.get_debug())

return 'done'

result = uvloop.run(main(), debug=True)

self.assertEqual(result, 'done')
self.assertEqual(CNT, 1)

def test_uvloop_run_2(self):

async def main():
pass

coro = main()
with self.assertRaisesRegex(TypeError, ' a non-uvloop event loop'):
uvloop.run(
coro,
loop_factory=asyncio.DefaultEventLoopPolicy().new_event_loop,
)

coro.close()
Loading

0 comments on commit 9f82bd7

Please sign in to comment.