Skip to content

Commit 9f82bd7

Browse files
1st1fantix
andauthored
Port uvloop to Python 3.12 (#570)
* 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 <fantix.king@gmail.com>
1 parent 0687643 commit 9f82bd7

33 files changed

+468
-157
lines changed

.github/workflows/release.yml

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ jobs:
7575
strategy:
7676
matrix:
7777
os: [ubuntu-latest, macos-latest]
78-
cibw_python: ["cp37-*", "cp38-*", "cp39-*", "cp310-*", "cp311-*"]
78+
cibw_python: ["cp37-*", "cp38-*", "cp39-*", "cp310-*", "cp311-*", "cp312-*"]
7979
cibw_arch: ["x86_64", "aarch64", "universal2"]
8080
exclude:
8181
- os: ubuntu-latest
@@ -110,14 +110,11 @@ jobs:
110110
run: |
111111
brew install gnu-sed libtool autoconf automake
112112
113-
- uses: pypa/cibuildwheel@v2.9.0
113+
- uses: pypa/cibuildwheel@v2.16.2
114114
env:
115115
CIBW_BUILD_VERBOSITY: 1
116116
CIBW_BUILD: ${{ matrix.cibw_python }}
117117
CIBW_ARCHS: ${{ matrix.cibw_arch }}
118-
CIBW_TEST_EXTRAS: "test"
119-
CIBW_TEST_COMMAND: "python -m unittest discover -v {project}/tests"
120-
CIBW_TEST_COMMAND_WINDOWS: "python -m unittest discover -v {project}\\tests"
121118
CIBW_TEST_SKIP: "*universal2:arm64"
122119

123120
- uses: actions/upload-artifact@v3

.github/workflows/tests.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ jobs:
1414
runs-on: ${{ matrix.os }}
1515
strategy:
1616
matrix:
17-
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
17+
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"]
1818
os: [ubuntu-latest, macos-latest]
1919

2020
env:
@@ -58,7 +58,8 @@ jobs:
5858
make test
5959
6060
- name: Test (debug build)
61-
if: steps.release.outputs.version == 0
61+
# XXX Re-enable 3.12 once we migrate to Cython 3
62+
if: steps.release.outputs.version == 0 && matrix.python-version != '3.12'
6263
run: |
6364
make distclean && make debug && make test
6465

README.rst

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,27 @@ uvloop with::
5353
Using uvloop
5454
------------
5555

56+
As of uvloop 0.18, the preferred way of using it is via the
57+
``uvloop.run()`` helper function:
58+
59+
60+
.. code:: python
61+
62+
import uvloop
63+
64+
async def main():
65+
# Main entry-point.
66+
...
67+
68+
uvloop.run(main())
69+
70+
``uvloop.run()`` works by simply configuring ``asyncio.run()``
71+
to use uvloop, passing all of the arguments to it, such as ``debug``,
72+
e.g. ``uvloop.run(main(), debug=True)``.
73+
74+
With Python 3.11 and earlier the following alternative
75+
snippet can be used:
76+
5677
.. code:: python
5778
5879
import asyncio

pyproject.toml

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
[project]
2+
name = "uvloop"
3+
description = "Fast implementation of asyncio event loop on top of libuv"
4+
authors = [{name = "Yury Selivanov", email = "yury@magic.io"}]
5+
requires-python = '>=3.7.0'
6+
readme = "README.rst"
7+
license = {text = "MIT License"}
8+
dynamic = ["version"]
9+
keywords = [
10+
"asyncio",
11+
"networking",
12+
]
13+
classifiers = [
14+
"Development Status :: 5 - Production/Stable",
15+
"Framework :: AsyncIO",
16+
"Intended Audience :: Developers",
17+
"License :: OSI Approved :: Apache Software License",
18+
"License :: OSI Approved :: MIT License",
19+
"Operating System :: POSIX",
20+
"Operating System :: MacOS :: MacOS X",
21+
"Programming Language :: Python :: 3 :: Only",
22+
"Programming Language :: Python :: 3.7",
23+
"Programming Language :: Python :: 3.8",
24+
"Programming Language :: Python :: 3.9",
25+
"Programming Language :: Python :: 3.10",
26+
"Programming Language :: Python :: 3.11",
27+
"Programming Language :: Python :: 3.12",
28+
"Programming Language :: Python :: Implementation :: CPython",
29+
"Topic :: System :: Networking",
30+
]
31+
32+
[project.urls]
33+
github = "https://github.com/MagicStack/uvloop"
34+
35+
[project.optional-dependencies]
36+
test = [
37+
# pycodestyle is a dependency of flake8, but it must be frozen because
38+
# their combination breaks too often
39+
# (example breakage: https://gitlab.com/pycqa/flake8/issues/427)
40+
'aiohttp>=3.8.1; python_version < "3.12"',
41+
'aiohttp==3.9.0b0; python_version >= "3.12"',
42+
'flake8~=5.0',
43+
'psutil',
44+
'pycodestyle~=2.9.0',
45+
'pyOpenSSL~=23.0.0',
46+
'mypy>=0.800',
47+
'Cython(>=0.29.36,<0.30.0)',
48+
]
49+
docs = [
50+
'Sphinx~=4.1.2',
51+
'sphinxcontrib-asyncio~=0.3.0',
52+
'sphinx_rtd_theme~=0.5.2',
53+
]
54+
55+
[build-system]
56+
requires = [
57+
"setuptools>=60",
58+
"wheel",
59+
"Cython(>=0.29.36,<0.30.0)",
60+
]
61+
build-backend = "setuptools.build_meta"
62+
63+
[tool.setuptools]
64+
zip-safe = false
65+
packages = ["uvloop"]
66+
67+
[tool.cibuildwheel]
68+
build-frontend = "build"
69+
test-extras = "test"
70+
test-command = "python -m unittest discover -v {project}/tests"
71+
72+
[tool.pytest.ini_options]
73+
addopts = "--capture=no --assert=plain --strict-markers --tb=native --import-mode=importlib"
74+
testpaths = "tests"
75+
filterwarnings = "default"

pytest.ini

Lines changed: 0 additions & 4 deletions
This file was deleted.

setup.py

Lines changed: 1 addition & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -21,40 +21,7 @@
2121
from setuptools.command.sdist import sdist
2222

2323

24-
CYTHON_DEPENDENCY = 'Cython(>=0.29.32,<0.30.0)'
25-
26-
# Minimal dependencies required to test uvloop.
27-
TEST_DEPENDENCIES = [
28-
# pycodestyle is a dependency of flake8, but it must be frozen because
29-
# their combination breaks too often
30-
# (example breakage: https://gitlab.com/pycqa/flake8/issues/427)
31-
'aiohttp>=3.8.1',
32-
'flake8~=5.0',
33-
'psutil',
34-
'pycodestyle~=2.9.0',
35-
'pyOpenSSL~=23.0.0',
36-
'mypy>=0.800',
37-
CYTHON_DEPENDENCY,
38-
]
39-
40-
# Dependencies required to build documentation.
41-
DOC_DEPENDENCIES = [
42-
'Sphinx~=4.1.2',
43-
'sphinxcontrib-asyncio~=0.3.0',
44-
'sphinx_rtd_theme~=0.5.2',
45-
]
46-
47-
EXTRA_DEPENDENCIES = {
48-
'docs': DOC_DEPENDENCIES,
49-
'test': TEST_DEPENDENCIES,
50-
# Dependencies required to develop uvloop.
51-
'dev': [
52-
CYTHON_DEPENDENCY,
53-
'pytest>=3.6.0',
54-
] + DOC_DEPENDENCIES + TEST_DEPENDENCIES
55-
}
56-
57-
24+
CYTHON_DEPENDENCY = 'Cython(>=0.29.36,<0.30.0)'
5825
MACHINE = platform.machine()
5926
MODULES_CFLAGS = [os.getenv('UVLOOP_OPT_CFLAGS', '-O2')]
6027
_ROOT = pathlib.Path(__file__).parent
@@ -245,10 +212,6 @@ def build_extensions(self):
245212
super().build_extensions()
246213

247214

248-
with open(str(_ROOT / 'README.rst')) as f:
249-
readme = f.read()
250-
251-
252215
with open(str(_ROOT / 'uvloop' / '_version.py')) as f:
253216
for line in f:
254217
if line.startswith('__version__ ='):
@@ -268,16 +231,7 @@ def build_extensions(self):
268231

269232

270233
setup(
271-
name='uvloop',
272-
description='Fast implementation of asyncio event loop on top of libuv',
273-
long_description=readme,
274-
url='http://github.com/MagicStack/uvloop',
275-
license='MIT',
276-
author='Yury Selivanov',
277-
author_email='yury@magic.io',
278-
platforms=['macOS', 'POSIX'],
279234
version=VERSION,
280-
packages=['uvloop'],
281235
cmdclass={
282236
'sdist': uvloop_sdist,
283237
'build_ext': uvloop_build_ext
@@ -291,20 +245,5 @@ def build_extensions(self):
291245
extra_compile_args=MODULES_CFLAGS
292246
),
293247
],
294-
classifiers=[
295-
'Development Status :: 5 - Production/Stable',
296-
'Framework :: AsyncIO',
297-
'Programming Language :: Python :: 3 :: Only',
298-
'Programming Language :: Python :: 3.7',
299-
'Programming Language :: Python :: 3.8',
300-
'Programming Language :: Python :: 3.9',
301-
'Programming Language :: Python :: 3.10',
302-
'License :: OSI Approved :: Apache Software License',
303-
'License :: OSI Approved :: MIT License',
304-
'Intended Audience :: Developers',
305-
],
306-
include_package_data=True,
307-
extras_require=EXTRA_DEPENDENCIES,
308248
setup_requires=setup_requires,
309-
python_requires='>=3.7',
310249
)

tests/cython_helper.pyx

Lines changed: 0 additions & 5 deletions
This file was deleted.

tests/test_aiohttp.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
skip_tests = False
88

99
import asyncio
10+
import sys
1011
import unittest
1112
import weakref
1213

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

5051
def test_aiohttp_graceful_shutdown(self):
52+
if self.implementation == 'asyncio' and sys.version_info >= (3, 12, 0):
53+
# In Python 3.12.0, asyncio.Server.wait_closed() waits for all
54+
# existing connections to complete, before aiohttp sends
55+
# on_shutdown signals.
56+
# https://github.com/aio-libs/aiohttp/issues/7675#issuecomment-1752143748
57+
# https://github.com/python/cpython/pull/98582
58+
raise unittest.SkipTest('bug in aiohttp: #7675')
59+
5160
async def websocket_handler(request):
5261
ws = aiohttp.web.WebSocketResponse()
5362
await ws.prepare(request)
@@ -73,7 +82,13 @@ async def on_shutdown(app):
7382

7483
runner = aiohttp.web.AppRunner(app)
7584
self.loop.run_until_complete(runner.setup())
76-
site = aiohttp.web.TCPSite(runner, '0.0.0.0', '0')
85+
site = aiohttp.web.TCPSite(
86+
runner,
87+
'0.0.0.0',
88+
0,
89+
# https://github.com/aio-libs/aiohttp/pull/7188
90+
shutdown_timeout=0.1,
91+
)
7792
self.loop.run_until_complete(site.start())
7893
port = site._server.sockets[0].getsockname()[1]
7994

@@ -90,7 +105,7 @@ async def client():
90105
async def stop():
91106
await asyncio.sleep(0.1)
92107
try:
93-
await asyncio.wait_for(runner.cleanup(), timeout=0.1)
108+
await asyncio.wait_for(runner.cleanup(), timeout=0.5)
94109
finally:
95110
try:
96111
client_task.cancel()

tests/test_dealloc.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,12 @@ def test_dealloc_1(self):
3030
async def test():
3131
prog = '''\
3232
import uvloop
33-
import asyncio
3433
3534
async def foo():
3635
return 42
3736
3837
def main():
39-
uvloop.install()
40-
loop = asyncio.get_event_loop()
38+
loop = uvloop.new_event_loop()
4139
loop.set_debug(True)
4240
loop.run_until_complete(foo())
4341
# Do not close the loop on purpose: let __dealloc__ methods run.

tests/test_libuv_api.py

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,23 @@
11
from uvloop import _testbase as tb
22
from uvloop.loop import libuv_get_loop_t_ptr, libuv_get_version
3+
from uvloop.loop import _testhelper_unwrap_capsuled_pointer as unwrap
34

45

56
class Test_UV_libuv(tb.UVTestCase):
67
def test_libuv_get_loop_t_ptr(self):
7-
loop = self.new_loop()
8-
cap1 = libuv_get_loop_t_ptr(loop)
9-
cap2 = libuv_get_loop_t_ptr(loop)
10-
cap3 = libuv_get_loop_t_ptr(self.new_loop())
8+
loop1 = self.new_loop()
9+
cap1 = libuv_get_loop_t_ptr(loop1)
10+
cap2 = libuv_get_loop_t_ptr(loop1)
1111

12-
import pyximport
12+
loop2 = self.new_loop()
13+
cap3 = libuv_get_loop_t_ptr(loop2)
1314

14-
pyximport.install()
15-
16-
import cython_helper
17-
18-
self.assertTrue(cython_helper.capsule_equals(cap1, cap2))
19-
self.assertFalse(cython_helper.capsule_equals(cap1, cap3))
15+
try:
16+
self.assertEqual(unwrap(cap1), unwrap(cap2))
17+
self.assertNotEqual(unwrap(cap1), unwrap(cap3))
18+
finally:
19+
loop1.close()
20+
loop2.close()
2021

2122
def test_libuv_get_version(self):
2223
self.assertGreater(libuv_get_version(), 0)

tests/test_process.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -851,8 +851,7 @@ async def test():
851851
)
852852
await proc.communicate()
853853
854-
uvloop.install()
855-
asyncio.run(test())
854+
uvloop.run(test())
856855
857856
stdin, stdout, stderr = dups
858857
(r, w), = pipes

tests/test_runner.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import asyncio
2+
import unittest
3+
import uvloop
4+
5+
6+
class TestSourceCode(unittest.TestCase):
7+
8+
def test_uvloop_run_1(self):
9+
CNT = 0
10+
11+
async def main():
12+
nonlocal CNT
13+
CNT += 1
14+
15+
loop = asyncio.get_running_loop()
16+
17+
self.assertTrue(isinstance(loop, uvloop.Loop))
18+
self.assertTrue(loop.get_debug())
19+
20+
return 'done'
21+
22+
result = uvloop.run(main(), debug=True)
23+
24+
self.assertEqual(result, 'done')
25+
self.assertEqual(CNT, 1)
26+
27+
def test_uvloop_run_2(self):
28+
29+
async def main():
30+
pass
31+
32+
coro = main()
33+
with self.assertRaisesRegex(TypeError, ' a non-uvloop event loop'):
34+
uvloop.run(
35+
coro,
36+
loop_factory=asyncio.DefaultEventLoopPolicy().new_event_loop,
37+
)
38+
39+
coro.close()

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy