From cdd2218fa3bb89eeec097bf0b7828897ef185aed Mon Sep 17 00:00:00 2001 From: Fantix King Date: Sun, 24 Jan 2021 19:59:20 -0600 Subject: [PATCH] Drop Python 3.5/3.6 support (#383) Fixes #381 --- .github/workflows/release.yml | 2 +- .github/workflows/tests.yml | 2 +- README.rst | 4 +- docs/dev/index.rst | 2 +- docs/index.rst | 2 +- docs/user/index.rst | 2 +- setup.py | 9 +-- tests/test_base.py | 39 ++++--------- tests/test_context.py | 33 +---------- tests/test_sockets.py | 4 -- tests/test_sourcecode.py | 2 - tests/test_tcp.py | 85 +++++---------------------- tests/test_udp.py | 81 ++++++++++--------------- tests/test_unix.py | 41 ++----------- uvloop/__init__.py | 1 - uvloop/_patch.py | 70 ---------------------- uvloop/_testbase.py | 17 ++---- uvloop/cbhandles.pyx | 40 ++++--------- uvloop/handles/process.pyx | 18 +----- uvloop/includes/compat.h | 45 +------------- uvloop/includes/stdlib.pxi | 1 + uvloop/loop.pyx | 107 ++++++++-------------------------- 22 files changed, 120 insertions(+), 487 deletions(-) delete mode 100644 uvloop/_patch.py diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 963f93e..f9594db 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -72,7 +72,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - python-version: [3.5, 3.6, 3.7, 3.8, 3.9] + python-version: [3.7, 3.8, 3.9] os: [ubuntu-20.04, macos-latest] arch: [x86_64, aarch64] exclude: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 3d2f65e..4a88998 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -14,7 +14,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - python-version: [3.5, 3.6, 3.7, 3.8, 3.9] + python-version: [3.7, 3.8, 3.9] os: [ubuntu-latest, macos-latest] steps: diff --git a/README.rst b/README.rst index dcb70f9..d62037a 100644 --- a/README.rst +++ b/README.rst @@ -39,7 +39,7 @@ about it. Installation ------------ -uvloop requires Python 3.5 or greater and is available on PyPI. +uvloop requires Python 3.7 or greater and is available on PyPI. Use pip to install it:: $ pip install uvloop @@ -72,7 +72,7 @@ manually creating an asyncio event loop: Building From Source -------------------- -To build uvloop, you'll need Python 3.5 or greater: +To build uvloop, you'll need Python 3.7 or greater: 1. Clone the repository: diff --git a/docs/dev/index.rst b/docs/dev/index.rst index 3ecf14a..5decc01 100644 --- a/docs/dev/index.rst +++ b/docs/dev/index.rst @@ -28,7 +28,7 @@ from the `libuv` Github repository. Build ----- -To build `uvloop`, you'll need ``Cython`` and Python 3.5. +To build `uvloop`, you'll need ``Cython`` and Python 3.7. .. note:: diff --git a/docs/index.rst b/docs/index.rst index 20ba9af..d66806f 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -14,7 +14,7 @@ uvloop `uvloop` is a fast, drop-in replacement of the built-in asyncio event loop. `uvloop` is released under the MIT license. -`uvloop` and asyncio, combined with the power of async/await in Python 3.5, +`uvloop` and asyncio, combined with the power of async/await in Python 3.7, makes it easier than ever to write high-performance networking code in Python. `uvloop` makes asyncio fast. In fact, it is at least 2x faster than nodejs, diff --git a/docs/user/index.rst b/docs/user/index.rst index 8cebfe6..b2e70f0 100644 --- a/docs/user/index.rst +++ b/docs/user/index.rst @@ -8,7 +8,7 @@ uvloop. Installation ------------ -`uvloop` is available from PyPI. It requires Python 3.5. +`uvloop` is available from PyPI. It requires Python 3.7. Use pip to install it. diff --git a/setup.py b/setup.py index f1f4a00..a2a6846 100644 --- a/setup.py +++ b/setup.py @@ -1,11 +1,8 @@ import sys vi = sys.version_info -if vi < (3, 5): - raise RuntimeError('uvloop requires Python 3.5 or greater') -if vi[:2] == (3, 6): - if vi.releaselevel == 'beta' and vi.serial < 3: - raise RuntimeError('uvloop requires Python 3.5 or 3.6b3 or greater') +if vi < (3, 7): + raise RuntimeError('uvloop requires Python 3.7 or greater') if sys.platform in ('win32', 'cygwin', 'cli'): raise RuntimeError('uvloop does not support Windows at the moment') @@ -310,8 +307,6 @@ setup( 'Development Status :: 5 - Production/Stable', 'Framework :: AsyncIO', 'Programming Language :: Python :: 3 :: Only', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', diff --git a/tests/test_base.py b/tests/test_base.py index 8ab4e8d..e46178a 100644 --- a/tests/test_base.py +++ b/tests/test_base.py @@ -345,9 +345,6 @@ class _TestBase: self.loop.run_until_complete(foo()) def test_run_until_complete_loop_orphan_future_close_loop(self): - if self.implementation == 'asyncio' and sys.version_info < (3, 6, 2): - raise unittest.SkipTest('unfixed asyncio') - async def foo(delay): await asyncio.sleep(delay) @@ -463,9 +460,7 @@ class _TestBase: self.loop.set_debug(True) - if hasattr(self.loop, 'get_exception_handler'): - # Available since Python 3.5.2 - self.assertIsNone(self.loop.get_exception_handler()) + self.assertIsNone(self.loop.get_exception_handler()) self.loop.set_exception_handler(handler) if hasattr(self.loop, 'get_exception_handler'): self.assertIs(self.loop.get_exception_handler(), handler) @@ -582,31 +577,19 @@ class _TestBase: self.loop.set_task_factory(None) self.assertIsNone(self.loop.get_task_factory()) - def _compile_agen(self, src): - try: - g = {} - exec(src, globals(), g) - except SyntaxError: - # Python < 3.6 - raise unittest.SkipTest() - else: - return g['waiter'] - def test_shutdown_asyncgens_01(self): finalized = list() if not hasattr(self.loop, 'shutdown_asyncgens'): raise unittest.SkipTest() - waiter = self._compile_agen( - '''async def waiter(timeout, finalized): - try: - await asyncio.sleep(timeout) - yield 1 - finally: - await asyncio.sleep(0) - finalized.append(1) - ''') + async def waiter(timeout, finalized): + try: + await asyncio.sleep(timeout) + yield 1 + finally: + await asyncio.sleep(0) + finalized.append(1) async def wait(): async for _ in waiter(1, finalized): @@ -641,13 +624,12 @@ class _TestBase: self.assertIn('asyncgen', context) logged += 1 - waiter = self._compile_agen('''async def waiter(timeout): + async def waiter(timeout): try: await asyncio.sleep(timeout) yield 1 finally: 1 / 0 - ''') async def wait(): async for _ in waiter(1): @@ -669,10 +651,9 @@ class _TestBase: if not hasattr(self.loop, 'shutdown_asyncgens'): raise unittest.SkipTest() - waiter = self._compile_agen('''async def waiter(): + async def waiter(): yield 1 yield 2 - ''') async def foo(): # We specifically want to hit _asyncgen_finalizer_hook diff --git a/tests/test_context.py b/tests/test_context.py index 0f340cb..4d3b12c 100644 --- a/tests/test_context.py +++ b/tests/test_context.py @@ -1,19 +1,14 @@ import asyncio +import contextvars import decimal import random -import sys -import unittest import weakref from uvloop import _testbase as tb -PY37 = sys.version_info >= (3, 7, 0) - - class _ContextBaseTests: - @unittest.skipUnless(PY37, 'requires Python 3.7') def test_task_decimal_context(self): async def fractions(t, precision, x, y): with decimal.localcontext() as ctx: @@ -37,9 +32,7 @@ class _ContextBaseTests: self.assertEqual(str(r2[0]), '0.333333') self.assertEqual(str(r2[1]), '0.111111') - @unittest.skipUnless(PY37, 'requires Python 3.7') def test_task_context_1(self): - import contextvars cvar = contextvars.ContextVar('cvar', default='nope') async def sub(): @@ -58,9 +51,7 @@ class _ContextBaseTests: task = self.loop.create_task(main()) self.loop.run_until_complete(task) - @unittest.skipUnless(PY37, 'requires Python 3.7') def test_task_context_2(self): - import contextvars cvar = contextvars.ContextVar('cvar', default='nope') async def main(): @@ -90,9 +81,7 @@ class _ContextBaseTests: self.assertEqual(cvar.get(), 'nope') - @unittest.skipUnless(PY37, 'requires Python 3.7') def test_task_context_3(self): - import contextvars cvar = contextvars.ContextVar('cvar', default=-1) # Run 100 Tasks in parallel, each modifying cvar. @@ -115,9 +104,7 @@ class _ContextBaseTests: self.assertEqual(cvar.get(), -1) - @unittest.skipUnless(PY37, 'requires Python 3.7') def test_task_context_4(self): - import contextvars cvar = contextvars.ContextVar('cvar', default='nope') class TrackMe: @@ -141,23 +128,7 @@ class _ContextBaseTests: class Test_UV_Context(_ContextBaseTests, tb.UVTestCase): - - @unittest.skipIf(PY37, 'requires Python <3.6') - def test_context_arg(self): - def cb(): - pass - - with self.assertRaisesRegex(NotImplementedError, - 'requires Python 3.7'): - self.loop.call_soon(cb, context=1) - - with self.assertRaisesRegex(NotImplementedError, - 'requires Python 3.7'): - self.loop.call_soon_threadsafe(cb, context=1) - - with self.assertRaisesRegex(NotImplementedError, - 'requires Python 3.7'): - self.loop.call_later(0.1, cb, context=1) + pass class Test_AIO_Context(_ContextBaseTests, tb.AIOTestCase): diff --git a/tests/test_sockets.py b/tests/test_sockets.py index 7d87571..51e3cdc 100644 --- a/tests/test_sockets.py +++ b/tests/test_sockets.py @@ -21,10 +21,6 @@ class _TestSockets: return buf def test_socket_connect_recv_send(self): - if self.is_asyncio_loop() and sys.version_info[:3] == (3, 5, 2): - # See https://github.com/python/asyncio/pull/366 for details. - raise unittest.SkipTest() - if sys.version_info[:3] >= (3, 8, 0): # @asyncio.coroutine is deprecated in 3.8 raise unittest.SkipTest() diff --git a/tests/test_sourcecode.py b/tests/test_sourcecode.py index 944232c..9ead3c8 100644 --- a/tests/test_sourcecode.py +++ b/tests/test_sourcecode.py @@ -10,8 +10,6 @@ def find_uvloop_root(): class TestFlake8(unittest.TestCase): - @unittest.skipIf(sys.version_info < (3, 6, 0), - "flake8 under 3.5 does not recognize f-strings in *.pyx") def test_flake8(self): edgepath = find_uvloop_root() config_path = os.path.join(edgepath, '.flake8') diff --git a/tests/test_tcp.py b/tests/test_tcp.py index 29281e7..33d806b 100644 --- a/tests/test_tcp.py +++ b/tests/test_tcp.py @@ -5,7 +5,6 @@ import os import select import socket import unittest.mock -import uvloop import ssl import sys import threading @@ -55,10 +54,6 @@ class MyBaseProto(asyncio.Protocol): class _TestTCP: def test_create_server_1(self): - if self.is_asyncio_loop() and sys.version_info[:3] == (3, 5, 2): - # See https://github.com/python/asyncio/pull/366 for details. - raise unittest.SkipTest() - CNT = 0 # number of clients that were successful TOTAL_CNT = 25 # total number of clients that test will create TIMEOUT = 5.0 # timeout for this test @@ -118,21 +113,14 @@ class _TestTCP: nonlocal CNT CNT = 0 - addrs = ('127.0.0.1', 'localhost') - if not isinstance(self.loop, uvloop.Loop): - # Hack to let tests run on Python 3.5.0 - # (asyncio doesn't support multiple hosts in 3.5.0) - addrs = '127.0.0.1' - srv = await asyncio.start_server( handle_client, - addrs, 0, + ('127.0.0.1', 'localhost'), 0, family=socket.AF_INET) srv_socks = srv.sockets self.assertTrue(srv_socks) - if self.has_start_serving(): - self.assertTrue(srv.is_serving()) + self.assertTrue(srv.is_serving()) addr = srv_socks[0].getsockname() @@ -149,8 +137,7 @@ class _TestTCP: for srv_sock in srv_socks: self.assertEqual(srv_sock.fileno(), -1) - if self.has_start_serving(): - self.assertFalse(srv.is_serving()) + self.assertFalse(srv.is_serving()) async def start_server_sock(): nonlocal CNT @@ -166,13 +153,11 @@ class _TestTCP: family=socket.AF_INET, sock=sock) - if self.PY37: - self.assertIs(srv.get_loop(), self.loop) + self.assertIs(srv.get_loop(), self.loop) srv_socks = srv.sockets self.assertTrue(srv_socks) - if self.has_start_serving(): - self.assertTrue(srv.is_serving()) + self.assertTrue(srv.is_serving()) tasks = [] for _ in range(TOTAL_CNT): @@ -187,8 +172,7 @@ class _TestTCP: for srv_sock in srv_socks: self.assertEqual(srv_sock.fileno(), -1) - if self.has_start_serving(): - self.assertFalse(srv.is_serving()) + self.assertFalse(srv.is_serving()) self.loop.run_until_complete(start_server()) self.assertEqual(CNT, TOTAL_CNT) @@ -213,8 +197,7 @@ class _TestTCP: srv_socks = srv.sockets self.assertTrue(srv_socks) - if self.has_start_serving(): - self.assertTrue(srv.is_serving()) + self.assertTrue(srv.is_serving()) host, port = srv_socks[0].getsockname() self.assertNotEqual(0, port) @@ -226,8 +209,7 @@ class _TestTCP: for srv_sock in srv_socks: self.assertEqual(srv_sock.fileno(), -1) - if self.has_start_serving(): - self.assertFalse(srv.is_serving()) + self.assertFalse(srv.is_serving()) self.loop.run_until_complete(start_server_ephemeral_ports()) @@ -267,11 +249,6 @@ class _TestTCP: raise unittest.SkipTest( 'The system does not support SO_REUSEPORT') - if sys.version_info[:3] < (3, 5, 1): - raise unittest.SkipTest( - 'asyncio in CPython 3.5.0 does not have the ' - 'reuse_port argument') - port = tb.find_free_port() async def runner(): @@ -338,9 +315,6 @@ class _TestTCP: self.loop.run_until_complete(test()) def test_create_server_8(self): - if self.implementation == 'asyncio' and not self.PY37: - raise unittest.SkipTest() - with self.assertRaisesRegex( ValueError, 'ssl_handshake_timeout is only meaningful'): self.loop.run_until_complete( @@ -348,9 +322,6 @@ class _TestTCP: lambda: None, host='::', port=0, ssl_handshake_timeout=10)) def test_create_server_9(self): - if not self.has_start_serving(): - raise unittest.SkipTest() - async def handle_client(reader, writer): pass @@ -375,9 +346,6 @@ class _TestTCP: self.loop.run_until_complete(start_server()) def test_create_server_10(self): - if not self.has_start_serving(): - raise unittest.SkipTest() - async def handle_client(reader, writer): pass @@ -578,9 +546,6 @@ class _TestTCP: self.loop.run_until_complete(client(srv.addr)) def test_create_connection_6(self): - if self.implementation == 'asyncio' and not self.PY37: - raise unittest.SkipTest() - with self.assertRaisesRegex( ValueError, 'ssl_handshake_timeout is only meaningful'): self.loop.run_until_complete( @@ -1093,9 +1058,6 @@ class Test_UV_TCP(_TestTCP, tb.UVTestCase): self.loop.run_until_complete(srv.wait_closed()) def test_connect_accepted_socket_ssl_args(self): - if self.implementation == 'asyncio' and not self.PY37: - raise unittest.SkipTest() - with self.assertRaisesRegex( ValueError, 'ssl_handshake_timeout is only meaningful'): with socket.socket() as s: @@ -1152,7 +1114,7 @@ class Test_UV_TCP(_TestTCP, tb.UVTestCase): proto.loop = loop extras = {} - if server_ssl and (self.implementation != 'asyncio' or self.PY37): + if server_ssl: extras = dict(ssl_handshake_timeout=SSL_HANDSHAKE_TIMEOUT) f = loop.create_task( @@ -1318,9 +1280,7 @@ class _TestSSL(tb.SSLTestCase): await fut async def start_server(): - extras = {} - if self.implementation != 'asyncio' or self.PY37: - extras = dict(ssl_handshake_timeout=SSL_HANDSHAKE_TIMEOUT) + extras = dict(ssl_handshake_timeout=SSL_HANDSHAKE_TIMEOUT) srv = await asyncio.start_server( handle_client, @@ -1383,9 +1343,7 @@ class _TestSSL(tb.SSLTestCase): sock.close() async def client(addr): - extras = {} - if self.implementation != 'asyncio' or self.PY37: - extras = dict(ssl_handshake_timeout=SSL_HANDSHAKE_TIMEOUT) + extras = dict(ssl_handshake_timeout=SSL_HANDSHAKE_TIMEOUT) reader, writer = await asyncio.open_connection( *addr, @@ -1517,10 +1475,7 @@ class _TestSSL(tb.SSLTestCase): max_clients=1, backlog=1) as srv: - exc_type = ssl.SSLError - if self.PY37: - exc_type = ssl.SSLCertVerificationError - with self.assertRaises(exc_type): + with self.assertRaises(ssl.SSLCertVerificationError): self.loop.run_until_complete(client(srv.addr)) def test_start_tls_wrong_args(self): @@ -2190,9 +2145,7 @@ class _TestSSL(tb.SSLTestCase): await fut async def start_server(): - extras = {} - if self.implementation != 'asyncio' or self.PY37: - extras = dict(ssl_handshake_timeout=SSL_HANDSHAKE_TIMEOUT) + extras = dict(ssl_handshake_timeout=SSL_HANDSHAKE_TIMEOUT) srv = await self.loop.create_server( server_protocol_factory, @@ -2282,9 +2235,7 @@ class _TestSSL(tb.SSLTestCase): conn.shutdown() async def client(addr): - extras = {} - if self.implementation != 'asyncio' or self.PY37: - extras = dict(ssl_handshake_timeout=SSL_HANDSHAKE_TIMEOUT) + extras = dict(ssl_handshake_timeout=SSL_HANDSHAKE_TIMEOUT) reader, writer = await asyncio.open_connection( *addr, @@ -2408,9 +2359,7 @@ class _TestSSL(tb.SSLTestCase): await fut async def start_server(): - extras = {} - if self.implementation != 'asyncio' or self.PY37: - extras['ssl_handshake_timeout'] = SSL_HANDSHAKE_TIMEOUT + extras = {'ssl_handshake_timeout': SSL_HANDSHAKE_TIMEOUT} if self.implementation != 'asyncio': # or self.PY38 extras['ssl_shutdown_timeout'] = 0.5 @@ -2473,9 +2422,7 @@ class _TestSSL(tb.SSLTestCase): sock.close() async def client(addr): - extras = {} - if self.implementation != 'asyncio' or self.PY37: - extras = dict(ssl_handshake_timeout=SSL_HANDSHAKE_TIMEOUT) + extras = dict(ssl_handshake_timeout=SSL_HANDSHAKE_TIMEOUT) reader, writer = await asyncio.open_connection( *addr, diff --git a/tests/test_udp.py b/tests/test_udp.py index d5e0c17..6a84a02 100644 --- a/tests/test_udp.py +++ b/tests/test_udp.py @@ -64,8 +64,7 @@ class _TestUDP: self.assertIs(server.transport, s_transport) extra = {} - if hasattr(socket, 'SO_REUSEPORT') and \ - sys.version_info[:3] >= (3, 5, 1): + if hasattr(socket, 'SO_REUSEPORT'): extra['reuse_port'] = True coro = self.loop.create_datagram_endpoint( @@ -179,21 +178,13 @@ class _TestUDP: assert False, 'Can not create socket.' with sock: - try: - f = self.loop.create_datagram_endpoint( - lambda: MyDatagramProto(loop=self.loop), sock=sock) - except TypeError as ex: - # asyncio in 3.5.0 doesn't have the 'sock' argument - if 'got an unexpected keyword argument' not in ex.args[0]: - raise - else: - tr, pr = self.loop.run_until_complete(f) - self.assertIsInstance(pr, MyDatagramProto) - tr.close() - self.loop.run_until_complete(pr.done) + f = self.loop.create_datagram_endpoint( + lambda: MyDatagramProto(loop=self.loop), sock=sock) + tr, pr = self.loop.run_until_complete(f) + self.assertIsInstance(pr, MyDatagramProto) + tr.close() + self.loop.run_until_complete(pr.done) - @unittest.skipIf(sys.version_info < (3, 5, 1), - "asyncio in 3.5.0 doesn't have the 'sock' argument") def test_create_datagram_endpoint_sock_unix_domain(self): class Proto(asyncio.DatagramProtocol): @@ -295,38 +286,32 @@ class _TestUDP: s1, s2 = socket.socketpair(socket.AF_UNIX, socket.SOCK_DGRAM, 0) with s1, s2: - try: - f = self.loop.create_datagram_endpoint( - lambda: Proto(loop=self.loop), sock=s1) - except TypeError as ex: - # asyncio in 3.5.0 doesn't have the 'sock' argument - if 'got an unexpected keyword argument' not in ex.args[0]: - raise + f = self.loop.create_datagram_endpoint( + lambda: Proto(loop=self.loop), sock=s1) + tr, pr = self.loop.run_until_complete(f) + self.assertIsInstance(pr, Proto) + + s2.send(b'hello, socketpair') + addr = self.loop.run_until_complete( + asyncio.wait_for(peername, 1)) + if sys.platform.startswith('linux'): + self.assertEqual(addr, None) else: - tr, pr = self.loop.run_until_complete(f) - self.assertIsInstance(pr, Proto) + self.assertEqual(addr, '') + self.assertEqual(pr.nbytes, 17) - s2.send(b'hello, socketpair') - addr = self.loop.run_until_complete( - asyncio.wait_for(peername, 1)) - if sys.platform.startswith('linux'): - self.assertEqual(addr, None) - else: - self.assertEqual(addr, '') - self.assertEqual(pr.nbytes, 17) + if not self.is_asyncio_loop(): + # asyncio doesn't support sendto(xx) on UDP sockets + # https://git.io/Jfqbw + data = b'from uvloop' + tr.sendto(data) + result = self.loop.run_until_complete(asyncio.wait_for( + self.loop.run_in_executor(None, s2.recv, 1024), + 1)) + self.assertEqual(data, result) - if not self.is_asyncio_loop(): - # asyncio doesn't support sendto(xx) on UDP sockets - # https://git.io/Jfqbw - data = b'from uvloop' - tr.sendto(data) - result = self.loop.run_until_complete(asyncio.wait_for( - self.loop.run_in_executor(None, s2.recv, 1024), - 1)) - self.assertEqual(data, result) - - tr.close() - self.loop.run_until_complete(pr.done) + tr.close() + self.loop.run_until_complete(pr.done) def _skip_create_datagram_endpoint_reuse_addr(self): if self.implementation == 'asyncio': @@ -336,8 +321,6 @@ class _TestUDP: raise unittest.SkipTest() if (3, 7, 0) <= sys.version_info < (3, 7, 6): raise unittest.SkipTest() - if sys.version_info < (3, 6, 10): - raise unittest.SkipTest() def test_create_datagram_endpoint_reuse_address_error(self): # bpo-37228: Ensure that explicit passing of `reuse_address=True` @@ -418,8 +401,8 @@ class Test_UV_UDP(_TestUDP, tb.UVTestCase): class Test_AIO_UDP(_TestUDP, tb.AIOTestCase): @unittest.skipUnless(tb.has_IPv6, 'no IPv6') @unittest.skipIf( - sys.version_info[:3] < (3, 6, 7) or sys.version_info[:3] == (3, 7, 0), - 'bpo-27500: bug fixed in Python 3.6.7, 3.7.1 and above.', + sys.version_info[:3] == (3, 7, 0), + 'bpo-27500: bug fixed in Python 3.7.1 and above.', ) def test_create_datagram_endpoint_addrs_ipv6(self): self._test_create_datagram_endpoint_addrs_ipv6() diff --git a/tests/test_unix.py b/tests/test_unix.py index be7252f..1c20635 100644 --- a/tests/test_unix.py +++ b/tests/test_unix.py @@ -66,8 +66,7 @@ class _TestUnix: try: srv_socks = srv.sockets self.assertTrue(srv_socks) - if self.has_start_serving(): - self.assertTrue(srv.is_serving()) + self.assertTrue(srv.is_serving()) tasks = [] for _ in range(TOTAL_CNT): @@ -83,8 +82,7 @@ class _TestUnix: for srv_sock in srv_socks: self.assertEqual(srv_sock.fileno(), -1) - if self.has_start_serving(): - self.assertFalse(srv.is_serving()) + self.assertFalse(srv.is_serving()) # asyncio doesn't cleanup the sock file self.assertTrue(os.path.exists(sock_name)) @@ -103,8 +101,7 @@ class _TestUnix: try: srv_socks = srv.sockets self.assertTrue(srv_socks) - if self.has_start_serving(): - self.assertTrue(srv.is_serving()) + self.assertTrue(srv.is_serving()) tasks = [] for _ in range(TOTAL_CNT): @@ -120,8 +117,7 @@ class _TestUnix: for srv_sock in srv_socks: self.assertEqual(srv_sock.fileno(), -1) - if self.has_start_serving(): - self.assertFalse(srv.is_serving()) + self.assertFalse(srv.is_serving()) # asyncio doesn't cleanup the sock file self.assertTrue(os.path.exists(sock_name)) @@ -160,9 +156,6 @@ class _TestUnix: self.loop.create_unix_server(object, sock_name)) def test_create_unix_server_3(self): - if self.implementation == 'asyncio' and not self.PY37: - raise unittest.SkipTest() - with self.assertRaisesRegex( ValueError, 'ssl_handshake_timeout is only meaningful'): self.loop.run_until_complete( @@ -371,30 +364,12 @@ class _TestUnix: (BrokenPipeError, ConnectionResetError)) def test_create_unix_connection_6(self): - if self.implementation == 'asyncio' and not self.PY37: - raise unittest.SkipTest() - with self.assertRaisesRegex( ValueError, 'ssl_handshake_timeout is only meaningful'): self.loop.run_until_complete( self.loop.create_unix_connection( lambda: None, path='/tmp/a', ssl_handshake_timeout=10)) - @unittest.skipUnless(sys.version_info < (3, 7), - 'Python version must be < 3.7') - def test_transport_unclosed_warning(self): - async def test(sock): - return await self.loop.create_unix_connection( - asyncio.Protocol, - None, - sock=sock) - - with self.assertWarnsRegex(ResourceWarning, 'unclosed'): - s1, s2 = socket.socketpair(socket.AF_UNIX) - with s1, s2: - self.loop.run_until_complete(test(s1)) - self.loop.close() - class Test_UV_Unix(_TestUnix, tb.UVTestCase): @@ -592,9 +567,7 @@ class _TestSSL(tb.SSLTestCase): await fut async def start_server(): - extras = {} - if self.implementation != 'asyncio' or self.PY37: - extras = dict(ssl_handshake_timeout=10.0) + extras = dict(ssl_handshake_timeout=10.0) with tempfile.TemporaryDirectory() as td: sock_name = os.path.join(td, 'sock') @@ -655,9 +628,7 @@ class _TestSSL(tb.SSLTestCase): sock.close() async def client(addr): - extras = {} - if self.implementation != 'asyncio' or self.PY37: - extras = dict(ssl_handshake_timeout=10.0) + extras = dict(ssl_handshake_timeout=10.0) reader, writer = await asyncio.open_unix_connection( addr, diff --git a/uvloop/__init__.py b/uvloop/__init__.py index 6dec2ba..eb53651 100644 --- a/uvloop/__init__.py +++ b/uvloop/__init__.py @@ -3,7 +3,6 @@ import asyncio as __asyncio from asyncio.events import BaseDefaultEventLoopPolicy as __BasePolicy from . import includes as __includes # NOQA -from . import _patch # NOQA from .loop import Loop as __BaseLoop # NOQA from ._version import __version__ # NOQA diff --git a/uvloop/_patch.py b/uvloop/_patch.py deleted file mode 100644 index c631b5a..0000000 --- a/uvloop/_patch.py +++ /dev/null @@ -1,70 +0,0 @@ -import asyncio -import sys - - -def _format_coroutine(coro): - if asyncio.iscoroutine(coro) \ - and not hasattr(coro, 'cr_code') \ - and not hasattr(coro, 'gi_code'): - - # Most likely a Cython coroutine - coro_name = '{}()'.format(coro.__qualname__ or coro.__name__) - - running = False - try: - running = coro.cr_running - except AttributeError: - try: - running = coro.gi_running - except AttributeError: - pass - - if running: - return '{} running'.format(coro_name) - else: - return coro_name - - return _old_format_coroutine(coro) - - -async def _wait_for_data(self, func_name): - """Wait until feed_data() or feed_eof() is called. - - If stream was paused, automatically resume it. - """ - if self._waiter is not None: - raise RuntimeError('%s() called while another coroutine is ' - 'already waiting for incoming data' % func_name) - - assert not self._eof, '_wait_for_data after EOF' - - # Waiting for data while paused will make deadlock, so prevent it. - if self._paused: - self._paused = False - self._transport.resume_reading() - - try: - create_future = self._loop.create_future - except AttributeError: - self._waiter = asyncio.Future(loop=self._loop) - else: - self._waiter = create_future() - - try: - await self._waiter - finally: - self._waiter = None - - -if sys.version_info < (3, 5, 3): - # This is needed to support Cython 'async def' coroutines. - from asyncio import coroutines - _old_format_coroutine = coroutines._format_coroutine - coroutines._format_coroutine = _format_coroutine - -if sys.version_info < (3, 5, 2): - # Fix a possible deadlock, improve performance. - from asyncio import streams - _old_wait_for_data = streams.StreamReader._wait_for_data - _wait_for_data.__module__ = _old_wait_for_data.__module__ - streams.StreamReader._wait_for_data = _wait_for_data diff --git a/uvloop/_testbase.py b/uvloop/_testbase.py index b706be5..3cc828c 100644 --- a/uvloop/_testbase.py +++ b/uvloop/_testbase.py @@ -13,7 +13,6 @@ import re import select import socket import ssl -import sys import tempfile import threading import time @@ -73,15 +72,10 @@ class BaseTestCase(unittest.TestCase, metaclass=BaseTestCaseMeta): async def wait_closed(self, obj): if not isinstance(obj, asyncio.StreamWriter): return - if sys.version_info >= (3, 7, 0): - try: - await obj.wait_closed() - except (BrokenPipeError, ConnectionError): - pass - - def has_start_serving(self): - return not (self.is_asyncio_loop() and - sys.version_info[:2] in [(3, 5), (3, 6)]) + try: + await obj.wait_closed() + except (BrokenPipeError, ConnectionError): + pass def is_asyncio_loop(self): return type(self.loop).__module__.startswith('asyncio.') @@ -102,9 +96,6 @@ class BaseTestCase(unittest.TestCase, metaclass=BaseTestCaseMeta): self.loop.set_exception_handler(self.loop_exception_handler) self.__unhandled_exceptions = [] - self.PY37 = sys.version_info[:2] >= (3, 7) - self.PY36 = sys.version_info[:2] >= (3, 6) - def tearDown(self): self.loop.close() diff --git a/uvloop/cbhandles.pyx b/uvloop/cbhandles.pyx index bd2e78f..2e248dd 100644 --- a/uvloop/cbhandles.pyx +++ b/uvloop/cbhandles.pyx @@ -15,15 +15,9 @@ cdef class Handle: self._source_traceback = extract_stack() cdef inline _set_context(self, object context): - if PY37: - if context is None: - context = Context_CopyCurrent() - self.context = context - else: - if context is not None: - raise NotImplementedError( - '"context" argument requires Python 3.7') - self.context = None + if context is None: + context = Context_CopyCurrent() + self.context = context def __dealloc__(self): if UVLOOP_DEBUG and self.loop is not None: @@ -52,9 +46,8 @@ cdef class Handle: Py_INCREF(self) try: - if PY37: - assert self.context is not None - Context_Enter(self.context) + assert self.context is not None + Context_Enter(self.context) if cb_type == 1: callback = self.arg1 @@ -108,8 +101,7 @@ cdef class Handle: finally: context = self.context Py_DECREF(self) - if PY37: - Context_Exit(context) + Context_Exit(context) cdef _cancel(self): self._cancelled = 1 @@ -185,15 +177,9 @@ cdef class TimerHandle: self.loop._debug_cb_timer_handles_total += 1 self.loop._debug_cb_timer_handles_count += 1 - if PY37: - if context is None: - context = Context_CopyCurrent() - self.context = context - else: - if context is not None: - raise NotImplementedError( - '"context" argument requires Python 3.7') - self.context = None + if context is None: + context = Context_CopyCurrent() + self.context = context if loop._debug: self._debug_info = ( @@ -257,9 +243,8 @@ cdef class TimerHandle: if self.loop._debug: started = time_monotonic() try: - if PY37: - assert self.context is not None - Context_Enter(self.context) + assert self.context is not None + Context_Enter(self.context) if args is not None: callback(*args) @@ -288,8 +273,7 @@ cdef class TimerHandle: finally: context = self.context Py_DECREF(self) - if PY37: - Context_Exit(context) + Context_Exit(context) self._clear() # Public API diff --git a/uvloop/handles/process.pyx b/uvloop/handles/process.pyx index 89cd26f..a7e81df 100644 --- a/uvloop/handles/process.pyx +++ b/uvloop/handles/process.pyx @@ -252,14 +252,7 @@ cdef class UVProcess(UVHandle): self.options.flags |= uv.UV_PROCESS_DETACHED if cwd is not None: - try: - # Lookup __fspath__ manually, as os.fspath() isn't - # available on Python 3.5. - fspath = type(cwd).__fspath__ - except AttributeError: - pass - else: - cwd = fspath(cwd) + cwd = os_fspath(cwd) if isinstance(cwd, str): cwd = PyUnicode_EncodeFSDefault(cwd) @@ -283,14 +276,7 @@ cdef class UVProcess(UVHandle): self.__args = args.copy() for i in range(an): - arg = args[i] - try: - fspath = type(arg).__fspath__ - except AttributeError: - pass - else: - arg = fspath(arg) - + arg = os_fspath(args[i]) if isinstance(arg, str): self.__args[i] = PyUnicode_EncodeFSDefault(arg) elif not isinstance(arg, bytes): diff --git a/uvloop/includes/compat.h b/uvloop/includes/compat.h index eef3451..7ae39e7 100644 --- a/uvloop/includes/compat.h +++ b/uvloop/includes/compat.h @@ -54,27 +54,7 @@ MakeUnixSockPyAddr(struct sockaddr_un *addr) } -#if PY_VERSION_HEX < 0x03070000 - -PyObject * Context_CopyCurrent(void) { - PyErr_SetString(PyExc_NotImplementedError, - "\"contextvars\" support requires Python 3.7+"); - return NULL; -}; - -int Context_Enter(PyObject *ctx) { - PyErr_SetString(PyExc_NotImplementedError, - "\"contextvars\" support requires Python 3.7+"); - return -1; -} - -int Context_Exit(PyObject *ctx) { - PyErr_SetString(PyExc_NotImplementedError, - "\"contextvars\" support requires Python 3.7+"); - return -1; -} - -#elif PY_VERSION_HEX < 0x03070100 +#if PY_VERSION_HEX < 0x03070100 PyObject * Context_CopyCurrent(void) { return (PyObject *)PyContext_CopyCurrent(); @@ -103,26 +83,3 @@ int Context_Exit(PyObject *ctx) { } #endif - - -#if PY_VERSION_HEX < 0x03070000 - -void PyOS_BeforeFork(void) -{ - _PyImport_AcquireLock(); -} - -void PyOS_AfterFork_Parent(void) -{ - if (_PyImport_ReleaseLock() <= 0) { - Py_FatalError("failed releasing import lock after fork"); - } -} - - -void PyOS_AfterFork_Child(void) -{ - PyOS_AfterFork(); -} - -#endif diff --git a/uvloop/includes/stdlib.pxi b/uvloop/includes/stdlib.pxi index 1debe71..7fd8ac9 100644 --- a/uvloop/includes/stdlib.pxi +++ b/uvloop/includes/stdlib.pxi @@ -111,6 +111,7 @@ cdef os_pipe = os.pipe cdef os_read = os.read cdef os_remove = os.remove cdef os_stat = os.stat +cdef os_fspath = os.fspath cdef stat_S_ISSOCK = stat.S_ISSOCK diff --git a/uvloop/loop.pyx b/uvloop/loop.pyx index fa68ca5..61191fb 100644 --- a/uvloop/loop.pyx +++ b/uvloop/loop.pyx @@ -49,8 +49,6 @@ include "errors.pyx" cdef: int PY39 = PY_VERSION_HEX >= 0x03090000 - int PY37 = PY_VERSION_HEX >= 0x03070000 - int PY36 = PY_VERSION_HEX >= 0x03060000 uint64_t MAX_SLEEP = 3600 * 24 * 365 * 100 @@ -177,13 +175,9 @@ cdef class Loop: self._coroutine_debug_set = False - if hasattr(sys, 'get_asyncgen_hooks'): - # Python >= 3.6 - # A weak set of all asynchronous generators that are - # being iterated by the loop. - self._asyncgens = weakref_WeakSet() - else: - self._asyncgens = None + # A weak set of all asynchronous generators that are + # being iterated by the loop. + self._asyncgens = weakref_WeakSet() # Set to True when `loop.shutdown_asyncgens` is called. self._asyncgens_shutdown_called = False @@ -1100,39 +1094,16 @@ cdef class Loop: if self._coroutine_debug_set == enabled: return - if PY37: - if enabled: - self._coroutine_origin_tracking_saved_depth = ( - sys.get_coroutine_origin_tracking_depth()) - sys.set_coroutine_origin_tracking_depth( - DEBUG_STACK_DEPTH) - else: - sys.set_coroutine_origin_tracking_depth( - self._coroutine_origin_tracking_saved_depth) - - self._coroutine_debug_set = enabled + if enabled: + self._coroutine_origin_tracking_saved_depth = ( + sys.get_coroutine_origin_tracking_depth()) + sys.set_coroutine_origin_tracking_depth( + DEBUG_STACK_DEPTH) else: - wrapper = aio_debug_wrapper - current_wrapper = sys_get_coroutine_wrapper() + sys.set_coroutine_origin_tracking_depth( + self._coroutine_origin_tracking_saved_depth) - if enabled: - if current_wrapper not in (None, wrapper): - _warn_with_source( - "loop.set_debug(True): cannot set debug coroutine " - "wrapper; another wrapper is already set %r" % - current_wrapper, RuntimeWarning, self) - else: - sys_set_coroutine_wrapper(wrapper) - self._coroutine_debug_set = True - else: - if current_wrapper not in (None, wrapper): - _warn_with_source( - "loop.set_debug(False): cannot unset debug coroutine " - "wrapper; another wrapper was set %r" % - current_wrapper, RuntimeWarning, self) - else: - sys_set_coroutine_wrapper(None) - self._coroutine_debug_set = False + self._coroutine_debug_set = enabled def _get_backend_id(self): """This method is used by uvloop tests and is not part of the API.""" @@ -1352,16 +1323,14 @@ cdef class Loop: # This is how asyncio loop behaves. mode = uv.UV_RUN_NOWAIT self._set_coroutine_debug(self._debug) - if self._asyncgens is not None: - old_agen_hooks = sys.get_asyncgen_hooks() - sys.set_asyncgen_hooks(firstiter=self._asyncgen_firstiter_hook, - finalizer=self._asyncgen_finalizer_hook) + old_agen_hooks = sys.get_asyncgen_hooks() + sys.set_asyncgen_hooks(firstiter=self._asyncgen_firstiter_hook, + finalizer=self._asyncgen_finalizer_hook) try: self._run(mode) finally: self._set_coroutine_debug(False) - if self._asyncgens is not None: - sys.set_asyncgen_hooks(*old_agen_hooks) + sys.set_asyncgen_hooks(*old_agen_hooks) def close(self): """Close the event loop. @@ -1509,11 +1478,8 @@ cdef class Loop: if sl > 2: flowinfo = sockaddr[2] if flowinfo < 0 or flowinfo > 0xfffff: - if PY37: - msg = 'getnameinfo(): flowinfo must be 0-1048575.' - else: - msg = 'getsockaddrarg: flowinfo must be 0-1048575.' - raise OverflowError(msg) + raise OverflowError( + 'getnameinfo(): flowinfo must be 0-1048575.') else: flowinfo = 0 @@ -2095,14 +2061,7 @@ cdef class Loop: 'path and sock can not be specified at the same time') orig_path = path - try: - # Lookup __fspath__ manually, as os.fspath() isn't - # available on Python 3.5. - fspath = type(path).__fspath__ - except AttributeError: - pass - else: - path = fspath(path) + path = os_fspath(path) if isinstance(path, str): path = PyUnicode_EncodeFSDefault(path) @@ -2221,14 +2180,7 @@ cdef class Loop: raise ValueError( 'path and sock can not be specified at the same time') - try: - # Lookup __fspath__ manually, as os.fspath() isn't - # available on Python 3.5. - fspath = type(path).__fspath__ - except AttributeError: - pass - else: - path = fspath(path) + path = os_fspath(path) if isinstance(path, str): path = PyUnicode_EncodeFSDefault(path) @@ -2850,13 +2802,13 @@ cdef class Loop: if (hasattr(callback, '__self__') and isinstance(callback.__self__, aio_AbstractChildWatcher)): - _warn_with_source( + warnings_warn( "!!! asyncio is trying to install its ChildWatcher for " "SIGCHLD signal !!!\n\nThis is probably because a uvloop " "instance is used with asyncio.set_event_loop(). " "The correct way to use uvloop is to install its policy: " "`asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())`" - "\n\n", RuntimeWarning, self) + "\n\n", RuntimeWarning, source=self) # TODO: ideally we should always raise an error here, # but that would be a backwards incompatible change, @@ -3107,10 +3059,10 @@ cdef class Loop: def _asyncgen_firstiter_hook(self, agen): if self._asyncgens_shutdown_called: - _warn_with_source( + warnings_warn( "asynchronous generator {!r} was scheduled after " "loop.shutdown_asyncgens() call".format(agen), - ResourceWarning, self) + ResourceWarning, source=self) self._asyncgens.add(agen) @@ -3119,9 +3071,7 @@ cdef class Loop: """Shutdown all active asynchronous generators.""" self._asyncgens_shutdown_called = True - if self._asyncgens is None or not len(self._asyncgens): - # If Python version is <3.6 or we don't have any asynchronous - # generators alive. + if not len(self._asyncgens): return closing_agens = list(self._asyncgens) @@ -3304,19 +3254,12 @@ cdef __install_pymem(): cdef _set_signal_wakeup_fd(fd): - if PY37 and fd >= 0: + if fd >= 0: return signal_set_wakeup_fd(fd, warn_on_full_buffer=False) else: return signal_set_wakeup_fd(fd) -cdef _warn_with_source(msg, cls, source): - if PY36: - warnings_warn(msg, cls, source=source) - else: - warnings_warn(msg, cls) - - # Helpers for tests @cython.iterable_coroutine