diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c7b540e1c4..819c61c5d1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,7 +10,7 @@ jobs: strategy: fail-fast: false matrix: - python: ['3.5', '3.6', '3.7', '3.8'] + python: ['3.6', '3.7', '3.8'] arch: ['x86', 'x64'] lsp: [''] extra_name: [''] @@ -46,7 +46,7 @@ jobs: strategy: fail-fast: false matrix: - python: ['3.5', '3.6', '3.7', '3.8'] + python: ['3.6', '3.7', '3.8'] check_docs: ['0'] check_formatting: ['0'] extra_name: [''] @@ -79,7 +79,7 @@ jobs: strategy: fail-fast: false matrix: - python: ['3.5', '3.6', '3.7', '3.8'] + python: ['3.6', '3.7', '3.8'] steps: - name: Checkout uses: actions/checkout@v2 diff --git a/.travis.yml b/.travis.yml index 685b4a8e93..576f0b9ae5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,13 +21,7 @@ jobs: env: - "JOB_NAME='Ubuntu 19.10, full VM'" - "VM_IMAGE=https://cloud-images.ubuntu.com/eoan/current/eoan-server-cloudimg-amd64.img" - # 3.5.0 and 3.5.1 have different __aiter__ semantics than all - # other versions, so we need to test them specially. Travis's - # newer images only provide 3.5.2+, so we have to request the old - # 'trusty' images. - - python: 3.5.0 - dist: trusty - - python: 3.5-dev + - python: 3.6.1 # earliest 3.6 version available on Travis - python: 3.6-dev - python: 3.7-dev - python: 3.8-dev diff --git a/README.rst b/README.rst index 4fe3ee5ebe..b1dae85404 100644 --- a/README.rst +++ b/README.rst @@ -102,7 +102,7 @@ demonstration of implementing the "Happy Eyeballs" algorithm in an older library versus Trio. **Cool, but will it work on my system?** Probably! As long as you have -some kind of Python 3.5-or-better (CPython or the latest PyPy3 are +some kind of Python 3.6-or-better (CPython or the latest PyPy3 are both fine), and are using Linux, macOS, or Windows, then Trio should absolutely work. *BSD and illumos likely work too, but we don't have testing infrastructure for them. And all of our dependencies are pure diff --git a/ci.sh b/ci.sh index e5ad7f8875..23d10210c9 100755 --- a/ci.sh +++ b/ci.sh @@ -55,10 +55,6 @@ if [ "$AGENT_OS" = "Windows_NT" ]; then pydir="$PWD/pyinstall/${PYTHON_PKG}" export PATH="${pydir}/tools:${pydir}/tools/scripts:$PATH" - - # Fix an issue with the nuget python 3.5 packages - # https://github.com/python-trio/trio/pull/827#issuecomment-457433940 - rm -f "${pydir}/tools/pyvenv.cfg" || true fi ### Travis + macOS ### diff --git a/docs-requirements.txt b/docs-requirements.txt index cdf0c30996..31e88d374b 100644 --- a/docs-requirements.txt +++ b/docs-requirements.txt @@ -22,7 +22,7 @@ outcome==1.0.1 # via -r docs-requirements.in packaging==20.3 # via sphinx pygments==2.6.1 # via sphinx pyparsing==2.4.7 # via packaging -pytz==2019.3 # via babel +pytz==2020.1 # via babel requests==2.23.0 # via sphinx six==1.14.0 # via packaging sniffio==1.1.0 # via -r docs-requirements.in diff --git a/docs/source/index.rst b/docs/source/index.rst index 4f20067785..6e4f2f78e1 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -44,7 +44,7 @@ chance to give feedback about any compatibility-breaking changes. Vital statistics: * Supported environments: Linux, macOS, or Windows running some kind of Python - 3.5-or-better (either CPython or PyPy3 is fine). \*BSD and + 3.6-or-better (either CPython or PyPy3 is fine). \*BSD and illumos likely work too, but are untested. * Install: ``python3 -m pip install -U trio`` (or on Windows, maybe diff --git a/docs/source/tutorial.rst b/docs/source/tutorial.rst index 3b9255b236..e435966f3b 100644 --- a/docs/source/tutorial.rst +++ b/docs/source/tutorial.rst @@ -94,7 +94,7 @@ Okay, ready? Let's get started. Before you begin ---------------- -1. Make sure you're using Python 3.5 or newer. +1. Make sure you're using Python 3.6 or newer. 2. ``python3 -m pip install --upgrade trio`` (or on Windows, maybe ``py -3 -m pip install --upgrade trio`` – `details @@ -312,7 +312,7 @@ runs: >>>> # but forcing a garbage collection gives us a warning: >>>> import gc >>>> gc.collect() - /home/njs/pypy-3.5-nightly/lib-python/3/importlib/_bootstrap.py:191: RuntimeWarning: coroutine 'sleep' was never awaited + /home/njs/pypy-3.8-nightly/lib-python/3/importlib/_bootstrap.py:191: RuntimeWarning: coroutine 'sleep' was never awaited if _module_locks.get(name) is wr: # XXX PyPy fix? 0 >>>> diff --git a/newsfragments/75.removal.rst b/newsfragments/75.removal.rst new file mode 100644 index 0000000000..b239384710 --- /dev/null +++ b/newsfragments/75.removal.rst @@ -0,0 +1 @@ +Remove support for Python 3.5. diff --git a/setup.cfg b/setup.cfg index e6579a4f07..90b7a189bb 100644 --- a/setup.cfg +++ b/setup.cfg @@ -3,3 +3,4 @@ xfail_strict = true faulthandler_timeout=60 markers = redistributors_should_skip: tests that should be skipped by downstream redistributors +junit_family = xunit2 diff --git a/setup.py b/setup.py index 9ef41cb3e0..3d9ba620a8 100644 --- a/setup.py +++ b/setup.py @@ -44,7 +44,7 @@ Vital statistics: * Supported environments: Linux, macOS, or Windows running some kind of Python - 3.5-or-better (either CPython or PyPy3 is fine). \\*BSD and illumos likely + 3.6-or-better (either CPython or PyPy3 is fine). \\*BSD and illumos likely work too, but are not tested. * Install: ``python3 -m pip install -U trio`` (or on Windows, maybe @@ -93,7 +93,7 @@ # This means, just install *everything* you see under trio/, even if it # doesn't look like a source file, so long as it appears in MANIFEST.in: include_package_data=True, - python_requires=">=3.5", + python_requires=">=3.6", keywords=["async", "io", "networking", "trio"], classifiers=[ "Development Status :: 3 - Alpha", @@ -107,8 +107,9 @@ "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "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", "Topic :: System :: Networking", "Framework :: Trio", ], diff --git a/trio/__init__.py b/trio/__init__.py index 679c8509be..1ad0e25362 100644 --- a/trio/__init__.py +++ b/trio/__init__.py @@ -169,10 +169,3 @@ __name__ + ".subprocess", _deprecated_subprocess_reexports.__dict__ ) del fixup_module_metadata - -import sys -if sys.version_info < (3, 6): - _deprecate.warn_deprecated( - "Support for Python 3.5", "0.14", issue=75, instead="Python 3.6+" - ) -del sys diff --git a/trio/_abc.py b/trio/_abc.py index 88c2ff1f70..8064e71169 100644 --- a/trio/_abc.py +++ b/trio/_abc.py @@ -1,6 +1,5 @@ from abc import ABCMeta, abstractmethod from typing import Generic, TypeVar -from ._util import aiter_compat import trio @@ -414,7 +413,6 @@ async def receive_some(self, max_bytes=None): """ - @aiter_compat def __aiter__(self): return self @@ -629,7 +627,6 @@ async def receive(self) -> ReceiveType: """ - @aiter_compat def __aiter__(self): return self diff --git a/trio/_core/_entry_queue.py b/trio/_core/_entry_queue.py index 97b1c56fa4..6833cc886f 100644 --- a/trio/_core/_entry_queue.py +++ b/trio/_core/_entry_queue.py @@ -178,9 +178,7 @@ def run_sync_soon(self, sync_fn, *args, idempotent=False): If ``idempotent=True``, then ``sync_fn`` and ``args`` must be hashable, and Trio will make a best-effort attempt to discard any call submission which is equal to an already-pending call. Trio - will make an attempt to process these in first-in first-out order, - but no guarantees. (Currently processing is FIFO on CPython 3.6 and - PyPy, but not CPython 3.5.) + will process these in first-in first-out order. Any ordering guarantees apply separately to ``idempotent=False`` and ``idempotent=True`` calls; there's no rule for how calls in the diff --git a/trio/_core/_run.py b/trio/_core/_run.py index c1ed65ed25..10606c710a 100644 --- a/trio/_core/_run.py +++ b/trio/_core/_run.py @@ -60,7 +60,7 @@ def _public(fn): # On 3.7+, Context.run() is implemented in C and doesn't show up in -# tracebacks. On 3.6 and earlier, we use the contextvars backport, which is +# tracebacks. On 3.6, we use the contextvars backport, which is # currently implemented in Python and adds 1 frame to tracebacks. So this # function is a super-overkill version of "0 if sys.version_info >= (3, 7) # else 1". But if Context.run ever changes, we'll be ready! @@ -1256,12 +1256,9 @@ def _return_value_looks_like_wrong_library(value): # The protocol for detecting an asyncio Future-like object if getattr(value, "_asyncio_future_blocking", None) is not None: return True - # asyncio.Future doesn't have _asyncio_future_blocking until - # 3.5.3. We don't want to import asyncio, but this janky check - # should work well enough for our purposes. And it also catches - # tornado Futures and twisted Deferreds. By the time we're calling - # this function, we already know something has gone wrong, so a - # heuristic is pretty safe. + # This janky check catches tornado Futures and twisted Deferreds. + # By the time we're calling this function, we already know + # something has gone wrong, so a heuristic is pretty safe. if value.__class__.__name__ in ("Future", "Deferred"): return True return False @@ -1909,7 +1906,7 @@ def run_impl(runner, async_fn, args): try: # We used to unwrap the Outcome object here and send/throw its # contents in directly, but it turns out that .throw() is - # buggy, at least on CPython 3.6 and earlier: + # buggy, at least on CPython 3.6: # https://bugs.python.org/issue29587 # https://bugs.python.org/issue29590 # So now we send in the Outcome object and unwrap it on the diff --git a/trio/_core/_unbounded_queue.py b/trio/_core/_unbounded_queue.py index f5e2dda5c3..abe4d60c56 100644 --- a/trio/_core/_unbounded_queue.py +++ b/trio/_core/_unbounded_queue.py @@ -1,7 +1,6 @@ import attr from .. import _core -from .._util import aiter_compat from .._deprecate import deprecated __all__ = ["UnboundedQueue"] @@ -146,7 +145,6 @@ def statistics(self): tasks_waiting=self._lot.statistics().tasks_waiting ) - @aiter_compat def __aiter__(self): return self diff --git a/trio/_core/_windows_cffi.py b/trio/_core/_windows_cffi.py index e2b95a9113..58845b86f4 100644 --- a/trio/_core/_windows_cffi.py +++ b/trio/_core/_windows_cffi.py @@ -1,10 +1,6 @@ import cffi import re import enum -try: - from enum import IntFlag -except ImportError: # python 3.5 - from enum import IntEnum as IntFlag ################################################################ # Functions and types @@ -264,7 +260,7 @@ class FileFlags(enum.IntEnum): TRUNCATE_EXISTING = 5 -class AFDPollFlags(IntFlag): +class AFDPollFlags(enum.IntFlag): # These are drawn from a combination of: # https://github.com/piscisaureus/wepoll/blob/master/src/afd.h # https://github.com/reactos/reactos/blob/master/sdk/include/reactos/drivers/afd/shared.h @@ -289,7 +285,7 @@ class WSAIoctls(enum.IntEnum): SIO_BSP_HANDLE_SELECT = 0x4800001C -class CompletionModes(IntFlag): +class CompletionModes(enum.IntFlag): FILE_SKIP_COMPLETION_PORT_ON_SUCCESS = 0x1 FILE_SKIP_SET_EVENT_ON_HANDLE = 0x2 diff --git a/trio/_core/tests/test_ki.py b/trio/_core/tests/test_ki.py index e0d3b97c50..a63407484a 100644 --- a/trio/_core/tests/test_ki.py +++ b/trio/_core/tests/test_ki.py @@ -176,11 +176,30 @@ async def agen_unprotected2(): finally: assert not _core.currently_ki_protected() + # Native async generators + @_core.enable_ki_protection + async def agen_protected3(): + assert _core.currently_ki_protected() + try: + yield + finally: + assert _core.currently_ki_protected() + + @_core.disable_ki_protection + async def agen_unprotected3(): + assert not _core.currently_ki_protected() + try: + yield + finally: + assert not _core.currently_ki_protected() + for agen_fn in [ agen_protected1, agen_protected2, + agen_protected3, agen_unprotected1, agen_unprotected2, + agen_unprotected3, ]: async for _ in agen_fn(): # noqa assert not _core.currently_ki_protected() @@ -504,9 +523,7 @@ def test_ki_wakes_us_up(): # # which contains the desired sequence. # - # Affected version of CPython include: - # - all versions of 3.5 (fix will not be backported) - # - 3.6.1 and earlier + # Affected version of CPython include 3.6.1 and earlier. # It's fixed in 3.6.2 and 3.7+ # # PyPy was never affected. diff --git a/trio/_core/tests/test_run.py b/trio/_core/tests/test_run.py index 3593793fe1..fa399d93b4 100644 --- a/trio/_core/tests/test_run.py +++ b/trio/_core/tests/test_run.py @@ -14,13 +14,11 @@ import outcome import sniffio import pytest -from async_generator import async_generator from .tutil import slow, check_sequence_matches, gc_collect_harder from ... import _core from ..._threads import to_thread_run_sync from ..._timeouts import sleep, fail_after -from ..._util import aiter_compat from ...testing import ( wait_all_tasks_blocked, Sequencer, @@ -38,9 +36,7 @@ async def sleep_forever(): # Some of our tests need to leak coroutines, and thus trigger the # "RuntimeWarning: coroutine '...' was never awaited" message. This context # manager should be used anywhere this happens to hide those messages, because -# (a) when expected they're clutter, (b) on CPython 3.5.x where x < 3, this -# warning can trigger a segfault if we run with warnings turned into errors: -# https://bugs.python.org/issue27811 +# when expected they're clutter. @contextmanager def ignore_coroutine_never_awaited_warnings(): with warnings.catch_warnings(): @@ -1380,13 +1376,7 @@ def cb(x): for i in range(100): token.run_sync_soon(cb, i, idempotent=True) await wait_all_tasks_blocked() - if ( - sys.version_info < (3, 6) - and platform.python_implementation() == "CPython" - ): - # no order guarantees - record.sort() - # Otherwise, we guarantee FIFO + # We guarantee FIFO assert record == list(range(100)) @@ -1762,9 +1752,8 @@ def generator_based_coro(): # pragma: no cover bad_call(len, [1, 2, 3]) assert "appears to be synchronous" in str(excinfo.value) - @async_generator async def async_gen(arg): # pragma: no cover - pass + yield with pytest.raises(TypeError) as excinfo: bad_call(async_gen, 0) @@ -2049,7 +2038,6 @@ def __init__(self, *largs): async def _accumulate(self, f, items, i): items[i] = await f() - @aiter_compat def __aiter__(self): return self @@ -2389,23 +2377,10 @@ def test_async_function_implemented_in_C(): # These used to crash because we'd try to mutate the coroutine object's # cr_frame, but C functions don't have Python frames. - ns = {"_core": _core} - try: - exec( - dedent( - """ - async def agen_fn(record): - assert not _core.currently_ki_protected() - record.append("the generator ran") - yield - """ - ), - ns, - ) - except SyntaxError: - pytest.skip("Requires Python 3.6+") - else: - agen_fn = ns["agen_fn"] + async def agen_fn(record): + assert not _core.currently_ki_protected() + record.append("the generator ran") + yield run_record = [] agen = agen_fn(run_record) diff --git a/trio/_deprecated_ssl_reexports.py b/trio/_deprecated_ssl_reexports.py index f86077e3fe..35d22f49f4 100644 --- a/trio/_deprecated_ssl_reexports.py +++ b/trio/_deprecated_ssl_reexports.py @@ -15,15 +15,10 @@ cert_time_to_seconds, CertificateError, create_default_context, DER_cert_to_PEM_cert, get_default_verify_paths, match_hostname, PEM_cert_to_DER_cert, Purpose, SSLEOFError, SSLError, SSLSyscallError, - SSLZeroReturnError + SSLZeroReturnError, AlertDescription, SSLErrorNumber, SSLSession, + VerifyFlags, VerifyMode, Options ) -# Added in python 3.6 -try: - from ssl import AlertDescription, SSLErrorNumber, SSLSession, VerifyFlags, VerifyMode, Options # noqa -except ImportError: - pass - # Added in python 3.7 try: from ssl import SSLCertVerificationError, TLSVersion # noqa diff --git a/trio/_file_io.py b/trio/_file_io.py index 32468af46b..10464589a9 100644 --- a/trio/_file_io.py +++ b/trio/_file_io.py @@ -2,7 +2,7 @@ import io from .abc import AsyncResource -from ._util import aiter_compat, async_wraps, fspath +from ._util import async_wraps import trio @@ -95,7 +95,6 @@ def __dir__(self): ) return attrs - @aiter_compat def __aiter__(self): return self @@ -159,10 +158,6 @@ async def open_file( :func:`trio.Path.open` """ - # python3.5 compat - if isinstance(file, trio.Path): - file = fspath(file) - _file = wrap_file( await trio.to_thread.run_sync( io.open, file, mode, buffering, encoding, errors, newline, closefd, diff --git a/trio/_highlevel_open_unix_stream.py b/trio/_highlevel_open_unix_stream.py index 59141ebc38..08eef0c135 100644 --- a/trio/_highlevel_open_unix_stream.py +++ b/trio/_highlevel_open_unix_stream.py @@ -1,3 +1,4 @@ +import os from contextlib import contextmanager import trio @@ -44,6 +45,6 @@ async def open_unix_socket(filename,): # possible location to connect to sock = socket(AF_UNIX, SOCK_STREAM) with close_on_error(sock): - await sock.connect(trio._util.fspath(filename)) + await sock.connect(os.fspath(filename)) return trio.SocketStream(sock) diff --git a/trio/_highlevel_ssl_helpers.py b/trio/_highlevel_ssl_helpers.py index 9b68e942f4..0df1665e5c 100644 --- a/trio/_highlevel_ssl_helpers.py +++ b/trio/_highlevel_ssl_helpers.py @@ -14,12 +14,8 @@ # if it's one we created, but not OK if it's one that was passed in... and # the one major protocol using NPN/ALPN is HTTP/2, which mandates that you use # a specially configured SSLContext anyway! I also thought maybe we could copy -# the given SSLContext and then mutate the copy, but it's no good: -# copy.copy(SSLContext) seems to succeed, but the state is not transferred! -# For example, with CPython 3.5, we have: -# ctx = ssl.create_default_context() -# assert ctx.check_hostname == True -# assert copy.copy(ctx).check_hostname == False +# the given SSLContext and then mutate the copy, but it's no good as SSLContext +# objects can't be copied: https://bugs.python.org/issue33023. # So... let's punt on that for now. Hopefully we'll be getting a new Python # TLS API soon and can revisit this then. async def open_ssl_over_tcp_stream( diff --git a/trio/_path.py b/trio/_path.py index bbadf9d874..2f34a38972 100644 --- a/trio/_path.py +++ b/trio/_path.py @@ -4,22 +4,11 @@ import pathlib import trio -from trio._util import async_wraps, fspath +from trio._util import async_wraps __all__ = ['Path'] -# python3.5 compat: __fspath__ does not exist in 3.5, so unwrap any trio.Path -# being passed to any wrapped method -def unwrap_paths(args): - new_args = [] - for arg in args: - if isinstance(arg, Path): - arg = arg._wrapped - new_args.append(arg) - return new_args - - # re-wrap return value from methods that return new instances of pathlib.Path def rewrap_path(value): if isinstance(value, pathlib.Path): @@ -30,7 +19,6 @@ def rewrap_path(value): def _forward_factory(cls, attr_name, attr): @wraps(attr) def wrapper(self, *args, **kwargs): - args = unwrap_paths(args) attr = getattr(self._wrapped, attr_name) value = attr(*args, **kwargs) return rewrap_path(value) @@ -69,7 +57,6 @@ async def wrapper(self, *args, **kwargs): def thread_wrapper_factory(cls, meth_name): @async_wraps(cls, cls._wraps, meth_name) async def wrapper(self, *args, **kwargs): - args = unwrap_paths(args) meth = getattr(self._wrapped, meth_name) func = partial(meth, *args, **kwargs) value = await trio.to_thread.run_sync(func) @@ -82,7 +69,6 @@ def classmethod_wrapper_factory(cls, meth_name): @classmethod @async_wraps(cls, cls._wraps, meth_name) async def wrapper(cls, *args, **kwargs): - args = unwrap_paths(args) meth = getattr(cls._wraps, meth_name) func = partial(meth, *args, **kwargs) value = await trio.to_thread.run_sync(func) @@ -168,8 +154,6 @@ class Path(metaclass=AsyncAutoWrapperType): _wrap_iter = ['glob', 'rglob', 'iterdir'] def __init__(self, *args): - args = unwrap_paths(args) - self._wrapped = pathlib.Path(*args) def __getattr__(self, name): @@ -185,7 +169,7 @@ def __repr__(self): return 'trio.Path({})'.format(repr(str(self))) def __fspath__(self): - return fspath(self._wrapped) + return os.fspath(self._wrapped) @wraps(pathlib.Path.open) async def open(self, *args, **kwargs): @@ -219,6 +203,4 @@ async def open(self, *args, **kwargs): # sense than inventing our own special docstring for this. del Path.absolute.__doc__ -# python3.5 compat -if hasattr(os, 'PathLike'): - os.PathLike.register(Path) +os.PathLike.register(Path) diff --git a/trio/_signals.py b/trio/_signals.py index 2ebb4a0a5a..2f2793c43e 100644 --- a/trio/_signals.py +++ b/trio/_signals.py @@ -3,9 +3,7 @@ from collections import OrderedDict import trio -from ._util import ( - signal_raise, aiter_compat, is_main_thread, ConflictDetector -) +from ._util import signal_raise, is_main_thread, ConflictDetector __all__ = ["open_signal_receiver"] @@ -96,7 +94,6 @@ def deliver_next(): def _pending_signal_count(self): return len(self._pending) - @aiter_compat def __aiter__(self): return self diff --git a/trio/_socket.py b/trio/_socket.py index 18403962d2..a3ffe0315f 100644 --- a/trio/_socket.py +++ b/trio/_socket.py @@ -7,7 +7,6 @@ import idna as _idna import trio -from ._util import fspath from . import _core @@ -296,13 +295,7 @@ def _sniff_sockopts_for_fileno(family, type, proto, fileno): # and then we'll throw it away and construct a new one with the correct metadata. if not _sys.platform == "linux": return family, type, proto - try: - from socket import SO_DOMAIN, SO_PROTOCOL - except ImportError: - # Only available on 3.6 and above: - SO_PROTOCOL = 38 - SO_DOMAIN = 39 - from socket import SOL_SOCKET, SO_TYPE + from socket import SO_DOMAIN, SO_PROTOCOL, SOL_SOCKET, SO_TYPE sockobj = _stdlib_socket.socket(family, type, proto, fileno=fileno) try: family = sockobj.getsockopt(SOL_SOCKET, SO_DOMAIN) @@ -512,7 +505,7 @@ async def _resolve_address(self, address, flags): elif self._sock.family == _stdlib_socket.AF_UNIX: await trio.hazmat.checkpoint() # unwrap path-likes - return fspath(address) + return _os.fspath(address) else: await trio.hazmat.checkpoint() diff --git a/trio/_sync.py b/trio/_sync.py index f34acd0858..e4af1be178 100644 --- a/trio/_sync.py +++ b/trio/_sync.py @@ -5,7 +5,6 @@ import trio -from ._util import aiter_compat from ._core import enable_ki_protection, ParkingLot from ._deprecate import deprecated diff --git a/trio/_util.py b/trio/_util.py index 9204b83349..b50a58036e 100644 --- a/trio/_util.py +++ b/trio/_util.py @@ -9,8 +9,6 @@ import typing as t import threading -import async_generator - # There's a dependency loop here... _core is allowed to use this file (in fact # it's the *only* file in the main trio/ package it's allowed to use), but # ConflictDetector needs checkpoint so it also has to import @@ -68,24 +66,6 @@ def signal_raise(signum): signal.pthread_kill(threading.get_ident(), signum) -# Decorator to handle the change to __aiter__ in 3.5.2 -if sys.version_info < (3, 5, 2): - - def aiter_compat(aiter_impl): - # de-sugar decorator to fix Python 3.8 coverage issue - # https://github.com/python-trio/trio/pull/784#issuecomment-446438407 - async def __aiter__(*args, **kwargs): - return aiter_impl(*args, **kwargs) - - __aiter__ = wraps(aiter_impl)(__aiter__) - - return __aiter__ -else: - - def aiter_compat(aiter_impl): - return aiter_impl - - # See: #461 as to why this is needed. # The gist is that threading.main_thread() has the capability to lie to us # if somebody else edits the threading ident cache to replace the main @@ -177,66 +157,6 @@ def fix_one(qualname, name, obj): fix_one(objname, objname, obj) -# os.fspath is defined on Python 3.6+ but we need to support Python 3.5 too -# This is why we provide our own implementation. On Python 3.6+ we use the -# StdLib's version and on Python 3.5 our own version. -# Our own implementation implementation is based on PEP 519 while it has also -# been adapted to work with pathlib objects on python 3.5 -# The input typehint is removed as there is no os.PathLike on 3.5. -# See: https://www.python.org/dev/peps/pep-0519/#os - - -def fspath(path) -> t.Union[str, bytes]: - """Return the path representation of a path-like object. - - Returns - ------- - - If str or bytes is passed in, it is returned unchanged. - - If the os.PathLike interface is implemented it is used to get the path - representation. - - If the python version is 3.5 or earlier and a pathlib object is passed, - the object's string representation is returned. - - Raises - ------ - - Regardless of the input, if the path representation (e.g. the value - returned from __fspath__) is not str or bytes, TypeError is raised. - - If the provided path is not str, bytes, pathlib.PurePath or os.PathLike, - TypeError is raised. - """ - if isinstance(path, (str, bytes)): - return path - # Work from the object's type to match method resolution of other magic - # methods. - path_type = type(path) - # On python 3.5, pathlib objects don't have the __fspath__ method, - # but we still want to get their string representation. - if issubclass(path_type, pathlib.PurePath): - return str(path) - try: - path_repr = path_type.__fspath__(path) - except AttributeError: - if hasattr(path_type, '__fspath__'): - raise - else: - raise TypeError( - "expected str, bytes or os.PathLike object, " - "not " + path_type.__name__ - ) - if isinstance(path_repr, (str, bytes)): - return path_repr - else: - raise TypeError( - "expected {}.__fspath__() to return str or bytes, " - "not {}".format(path_type.__name__, - type(path_repr).__name__) - ) - - -if hasattr(os, "fspath"): - fspath = os.fspath # noqa - - class generic_function: """Decorator that makes a function indexable, to communicate non-inferrable generic type parameters to a static type checker. @@ -266,7 +186,7 @@ def __getitem__(self, _): # If a new class inherits from any ABC, then the new class's metaclass has to # inherit from ABCMeta. If a new class inherits from typing.Generic, and -# you're using Python 3.6 or earlier, then the new class's metaclass has to +# you're using Python 3.6, then the new class's metaclass has to # inherit from typing.GenericMeta. Some of the classes that want to use Final # or NoPublicConstructor inherit from ABCs and generics, so Final has to # inherit from these metaclasses. Fortunately, GenericMeta inherits from diff --git a/trio/testing/_sequencer.py b/trio/testing/_sequencer.py index 118969f83d..05b7560eec 100644 --- a/trio/testing/_sequencer.py +++ b/trio/testing/_sequencer.py @@ -1,7 +1,7 @@ from collections import defaultdict import attr -from async_generator import async_generator, yield_, asynccontextmanager +from async_generator import asynccontextmanager from .. import _core from .. import _util @@ -61,7 +61,6 @@ async def main(): _broken = attr.ib(default=False, init=False) @asynccontextmanager - @async_generator async def __call__(self, position: int): if position in self._claimed: raise RuntimeError( @@ -84,6 +83,6 @@ async def __call__(self, position: int): if self._broken: raise RuntimeError("sequence broken!") try: - await yield_() + yield finally: self._sequence_points[position + 1].set() diff --git a/trio/tests/test_exports.py b/trio/tests/test_exports.py index 3650403179..e7d894c12d 100644 --- a/trio/tests/test_exports.py +++ b/trio/tests/test_exports.py @@ -64,10 +64,6 @@ def public_namespaces(module): # https://github.com/PyCQA/astroid/issues/681 "ignore:the imp module is deprecated.*:DeprecationWarning" ) -@pytest.mark.filterwarnings( - # Same as above, but on Python 3.5 - "ignore:the imp module is deprecated.*:PendingDeprecationWarning" -) @pytest.mark.parametrize("modname", NAMESPACES) @pytest.mark.parametrize("tool", ["pylint", "jedi"]) def test_static_tool_sees_all_symbols(tool, modname): diff --git a/trio/tests/test_file_io.py b/trio/tests/test_file_io.py index 8f96e75aac..fd4fa648b4 100644 --- a/trio/tests/test_file_io.py +++ b/trio/tests/test_file_io.py @@ -1,4 +1,5 @@ import io +import os import pytest from unittest import mock @@ -6,13 +7,12 @@ import trio from trio import _core -from trio._util import fspath from trio._file_io import AsyncIOWrapper, _FILE_SYNC_ATTRS, _FILE_ASYNC_METHODS @pytest.fixture def path(tmpdir): - return fspath(tmpdir.join('test')) + return os.fspath(tmpdir.join('test')) @pytest.fixture diff --git a/trio/tests/test_path.py b/trio/tests/test_path.py index 67d7c2957e..9bbbfd4df2 100644 --- a/trio/tests/test_path.py +++ b/trio/tests/test_path.py @@ -5,7 +5,6 @@ import trio from trio._path import AsyncAutoWrapperType as Type -from trio._util import fspath from trio._file_io import AsyncIOWrapper @@ -57,7 +56,7 @@ async def test_cmp_magic(cls_a, cls_b): assert not b == None # noqa -# upstream python3.5 bug: we should also test (pathlib.Path, trio.Path), but +# upstream python3.8 bug: we should also test (pathlib.Path, trio.Path), but # __*div__ does not properly raise NotImplementedError like the other comparison # magic, so trio.Path's implementation does not get dispatched cls_pairs = [ @@ -203,7 +202,7 @@ async def test_path_nonpath(): async def test_open_file_can_open_path(path): async with await trio.open_file(path, 'w') as f: - assert f.name == fspath(path) + assert f.name == os.fspath(path) async def test_globmethods(path): diff --git a/trio/tests/test_ssl.py b/trio/tests/test_ssl.py index 16b323e98b..9cdad56fe3 100644 --- a/trio/tests/test_ssl.py +++ b/trio/tests/test_ssl.py @@ -8,7 +8,7 @@ from OpenSSL import SSL import trustme -from async_generator import async_generator, yield_, asynccontextmanager +from async_generator import asynccontextmanager import trio from .. import _core @@ -138,7 +138,6 @@ def ssl_echo_serve_sync(sock, *, expect_fail=False): # (running in a thread). Useful for testing making connections with different # SSLContexts. @asynccontextmanager -@async_generator async def ssl_echo_server_raw(**kwargs): a, b = stdlib_socket.socketpair() async with trio.open_nursery() as nursery: @@ -151,19 +150,16 @@ async def ssl_echo_server_raw(**kwargs): partial(ssl_echo_serve_sync, b, **kwargs) ) - await yield_(SocketStream(tsocket.from_stdlib_socket(a))) + yield SocketStream(tsocket.from_stdlib_socket(a)) # Fixture that gives a properly set up SSLStream connected to a trio-test-1 # echo server (running in a thread) @asynccontextmanager -@async_generator async def ssl_echo_server(client_ctx, **kwargs): async with ssl_echo_server_raw(**kwargs) as sock: - await yield_( - SSLStream( - sock, client_ctx, server_hostname="trio-test-1.example.org" - ) + yield SSLStream( + sock, client_ctx, server_hostname="trio-test-1.example.org" ) @@ -175,13 +171,12 @@ def __init__(self, sleeper=None): ctx = SSL.Context(SSL.SSLv23_METHOD) # TLS 1.3 removes renegotiation support. Which is great for them, but # we still have to support versions before that, and that means we - # need to test renegotation support, which means we need to force this + # need to test renegotiation support, which means we need to force this # to use a lower version where this test server can trigger # renegotiations. Of course TLS 1.3 support isn't released yet, but # I'm told that this will work once it is. (And once it is we can - # remove the pragma: no cover too.) Alternatively, once we drop - # support for CPython 3.5 on macOS, then we could switch to using - # TLSv1_2_METHOD. + # remove the pragma: no cover too.) Alternatively, we could switch to + # using TLSv1_2_METHOD. # # Discussion: https://github.com/pyca/pyopenssl/issues/624 diff --git a/trio/tests/test_util.py b/trio/tests/test_util.py index 5815ae3e42..f0f25a2ce6 100644 --- a/trio/tests/test_util.py +++ b/trio/tests/test_util.py @@ -1,23 +1,14 @@ -import os -import pathlib import signal -import sys import pytest import trio from .. import _core from .._util import ( - signal_raise, ConflictDetector, fspath, is_main_thread, generic_function, - Final, NoPublicConstructor + signal_raise, ConflictDetector, is_main_thread, generic_function, Final, + NoPublicConstructor ) -from ..testing import wait_all_tasks_blocked, assert_checkpoints - - -def raise_(exc): - """ Raise provided exception. - Just a helper for raising exceptions from lambdas. """ - raise exc +from ..testing import wait_all_tasks_blocked def test_signal_raise(): @@ -82,86 +73,6 @@ def test_module_metadata_is_fixed_up(): assert trio.to_thread.run_sync.__qualname__ == "run_sync" -# define a concrete class implementing the PathLike protocol -# Since we want to have compatibility with Python 3.5 we need -# to define the base class on runtime. -BaseKlass = os.PathLike if hasattr(os, "PathLike") else object - - -class ConcretePathLike(BaseKlass): - """ Class implementing the file system path protocol.""" - def __init__(self, path=""): - self.path = path - - def __fspath__(self): - return self.path - - -class TestFspath: - - # based on: - # https://github.com/python/cpython/blob/da6c3da6c33c6bf794f741e348b9c6d86cc43ec5/Lib/test/test_os.py#L3527-L3571 - - @pytest.mark.parametrize( - "path", (b'hello', b'goodbye', b'some/path/and/file') - ) - def test_return_bytes(self, path): - assert path == fspath(path) - - @pytest.mark.parametrize( - "path", ('hello', 'goodbye', 'some/path/and/file') - ) - def test_return_string(self, path): - assert path == fspath(path) - - @pytest.mark.parametrize( - "path", (pathlib.Path("/home"), pathlib.Path("C:\\windows")) - ) - def test_handle_pathlib(self, path): - assert str(path) == fspath(path) - - @pytest.mark.parametrize("path", ("path/like/object", b"path/like/object")) - def test_handle_pathlike_protocol(self, path): - pathlike = ConcretePathLike(path) - assert path == fspath(pathlike) - if sys.version_info > (3, 6): - assert issubclass(ConcretePathLike, os.PathLike) - assert isinstance(pathlike, os.PathLike) - - def test_argument_required(self): - with pytest.raises(TypeError): - fspath() - - def test_throw_error_at_multiple_arguments(self): - with pytest.raises(TypeError): - fspath(1, 2) - - @pytest.mark.parametrize( - "klass", (23, object(), int, type, os, type("blah", (), {})()) - ) - def test_throw_error_at_non_pathlike(self, klass): - with pytest.raises(TypeError): - fspath(klass) - - @pytest.mark.parametrize( - "exception, method", - [ - (TypeError, 1), # __fspath__ is not callable - (TypeError, lambda x: 23 - ), # __fspath__ returns a value other than str or bytes - (Exception, lambda x: raise_(Exception) - ), # __fspath__raises a random exception - (AttributeError, lambda x: raise_(AttributeError) - ), # __fspath__ raises AttributeError - ] - ) - def test_bad_pathlike_implementation(self, exception, method): - klass = type('foo', (), {}) - klass.__fspath__ = method - with pytest.raises(exception): - fspath(klass()) - - async def test_is_main_thread(): assert is_main_thread()