diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 480398a1f0..40af0960f5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,16 +18,18 @@ jobs: strategy: fail-fast: false matrix: - python: ['3.7', '3.8', '3.9', '3.10', 'pypy-3.8-nightly', 'pypy-3.9-nightly'] + # pypy-3.10 is failing, see https://github.com/python-trio/trio/issues/2678 + python: ['3.8', '3.9', '3.10', 'pypy-3.9-nightly'] #, 'pypy-3.10-nightly'] arch: ['x86', 'x64'] lsp: [''] lsp_extract_file: [''] extra_name: [''] exclude: - - python: 'pypy-3.8-nightly' - arch: 'x86' + # pypy does not release 32-bit binaries - python: 'pypy-3.9-nightly' arch: 'x86' + #- python: 'pypy-3.10-nightly' + # arch: 'x86' include: - python: '3.8' arch: 'x64' @@ -65,8 +67,8 @@ jobs: # and then finally an actual release version. actions/setup-python doesn't # support this for PyPy presently so we get no help there. # - # CPython -> 3.9.0-alpha - 3.9.X - # PyPy -> pypy-3.7 + # 'CPython' -> '3.9.0-alpha - 3.9.X' + # 'PyPy' -> 'pypy-3.9' python-version: ${{ fromJSON(format('["{0}", "{1}"]', format('{0}.0-alpha - {0}.X', matrix.python), matrix.python))[startsWith(matrix.python, 'pypy')] }} architecture: '${{ matrix.arch }}' cache: pip @@ -92,7 +94,7 @@ jobs: strategy: fail-fast: false matrix: - python: ['pypy-3.7', 'pypy-3.8', 'pypy-3.9', '3.7', '3.8', '3.9', '3.10', '3.11', '3.12-dev', 'pypy-3.8-nightly', 'pypy-3.9-nightly'] + python: ['pypy-3.9', 'pypy-3.10', '3.8', '3.9', '3.10', '3.11', '3.12-dev', 'pypy-3.9-nightly', 'pypy-3.10-nightly'] check_formatting: ['0'] extra_name: [''] include: @@ -143,7 +145,7 @@ jobs: strategy: fail-fast: false matrix: - python: ['3.7', '3.8', '3.9', '3.10', 'pypy-3.8-nightly', 'pypy-3.9-nightly'] + python: ['3.8', '3.9', '3.10', 'pypy-3.9-nightly', 'pypy-3.10-nightly'] continue-on-error: >- ${{ ( diff --git a/README.rst b/README.rst index 4e096eddf3..016823e1f5 100644 --- a/README.rst +++ b/README.rst @@ -92,8 +92,9 @@ 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.7-or-better (CPython or the latest PyPy3 are -both fine), and are using Linux, macOS, Windows, or FreeBSD, then Trio +some kind of Python 3.8-or-better (CPython or [currently maintained versions of +PyPy3](https://doc.pypy.org/en/latest/faq.html#which-python-versions-does-pypy-implement) +are both fine), and are using Linux, macOS, Windows, or FreeBSD, then Trio will work. Other environments might work too, but those are the ones we test on. And all of our dependencies are pure Python, except for CFFI on Windows, which has wheels available, so diff --git a/docs/source/index.rst b/docs/source/index.rst index 84d81880af..fc13227c3a 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -45,7 +45,7 @@ Vital statistics: * Supported environments: We test on - - Python: 3.7+ (CPython and PyPy) + - Python: 3.8+ (CPython and PyPy) - Windows, macOS, Linux (glibc and musl), FreeBSD Other environments might also work; give it a try and see. diff --git a/docs/source/reference-core.rst b/docs/source/reference-core.rst index 4f4f4d62b9..141e128026 100644 --- a/docs/source/reference-core.rst +++ b/docs/source/reference-core.rst @@ -974,12 +974,8 @@ work. What we need is something that's *like* a global variable, but that can have different values depending on which request handler is accessing it. -To solve this problem, Python 3.7 added a new module to the standard -library: :mod:`contextvars`. And not only does Trio have built-in -support for :mod:`contextvars`, but if you're using an earlier version -of Python, then Trio makes sure that a backported version of -:mod:`contextvars` is installed. So you can assume :mod:`contextvars` -is there and works regardless of what version of Python you're using. +To solve this problem, Python has a module in the standard +library: :mod:`contextvars`. Here's a toy example demonstrating how to use :mod:`contextvars`: @@ -1009,7 +1005,7 @@ Example output (yours may differ slightly): request 0: Request received finished For more information, read the -`contextvars docs `__. +`contextvars docs `__. .. _synchronization: diff --git a/docs/source/tutorial.rst b/docs/source/tutorial.rst index 19289ca991..0faffd119b 100644 --- a/docs/source/tutorial.rst +++ b/docs/source/tutorial.rst @@ -88,7 +88,7 @@ Okay, ready? Let's get started. Before you begin ---------------- -1. Make sure you're using Python 3.7 or newer. +1. Make sure you're using Python 3.8 or newer. 2. ``python3 -m pip install --upgrade trio`` (or on Windows, maybe ``py -3 -m pip install --upgrade trio`` – `details diff --git a/newsfragments/2668.removal.rst b/newsfragments/2668.removal.rst new file mode 100644 index 0000000000..512f681077 --- /dev/null +++ b/newsfragments/2668.removal.rst @@ -0,0 +1 @@ +Drop support for Python3.7 and PyPy3.7/3.8. diff --git a/newsfragments/README.rst b/newsfragments/README.rst index 349e67eec0..52dc0716bb 100644 --- a/newsfragments/README.rst +++ b/newsfragments/README.rst @@ -14,6 +14,7 @@ Each file should be named like ``..rst``, where deprecated features after an appropriate time, go in the ``deprecated`` category instead) * ``feature``: any new feature that doesn't qualify for ``headline`` +* ``removal``: removing support for old python versions, or other removals with no deprecation period. * ``bugfix`` * ``doc`` * ``deprecated`` diff --git a/notes-to-self/how-does-windows-so-reuseaddr-work.py b/notes-to-self/how-does-windows-so-reuseaddr-work.py index 4865ea17b3..d8d60d1d66 100644 --- a/notes-to-self/how-does-windows-so-reuseaddr-work.py +++ b/notes-to-self/how-does-windows-so-reuseaddr-work.py @@ -10,6 +10,7 @@ modes = ["default", "SO_REUSEADDR", "SO_EXCLUSIVEADDRUSE"] bind_types = ["wildcard", "specific"] + def sock(mode): s = socket.socket(family=socket.AF_INET) if mode == "SO_REUSEADDR": @@ -18,6 +19,7 @@ def sock(mode): s.setsockopt(socket.SOL_SOCKET, socket.SO_EXCLUSIVEADDRUSE, 1) return s + def bind(sock, bind_type): if bind_type == "wildcard": sock.bind(("0.0.0.0", 12345)) @@ -26,6 +28,7 @@ def bind(sock, bind_type): else: assert False + def table_entry(mode1, bind_type1, mode2, bind_type2): with sock(mode1) as sock1: bind(sock1, bind_type1) @@ -41,19 +44,22 @@ def table_entry(mode1, bind_type1, mode2, bind_type2): else: return "Success" -print(""" + +print( + """ second bind | """ -+ " | ".join(["%-19s" % mode for mode in modes]) + + " | ".join(["%-19s" % mode for mode in modes]) ) -print(""" """, end='') +print(""" """, end="") for mode in modes: - print(" | " + " | ".join(["%8s" % bind_type for bind_type in bind_types]), end='') + print(" | " + " | ".join(["%8s" % bind_type for bind_type in bind_types]), end="") -print(""" +print( + """ first bind -----------------------------------------------------------------""" -# default | wildcard | INUSE | Success | ACCESS | Success | INUSE | Success + # default | wildcard | INUSE | Success | ACCESS | Success | INUSE | Success ) for i, mode1 in enumerate(modes): @@ -63,6 +69,8 @@ def table_entry(mode1, bind_type1, mode2, bind_type2): for l, bind_type2 in enumerate(bind_types): entry = table_entry(mode1, bind_type1, mode2, bind_type2) row.append(entry) - #print(mode1, bind_type1, mode2, bind_type2, entry) - print("{:>19} | {:>8} | ".format(mode1, bind_type1) - + " | ".join(["%8s" % entry for entry in row])) + # print(mode1, bind_type1, mode2, bind_type2, entry) + print( + f"{mode1:>19} | {bind_type1:>8} | " + + " | ".join(["%8s" % entry for entry in row]) + ) diff --git a/notes-to-self/reopen-pipe.py b/notes-to-self/reopen-pipe.py index 910def397c..5e5b31e41f 100644 --- a/notes-to-self/reopen-pipe.py +++ b/notes-to-self/reopen-pipe.py @@ -3,12 +3,13 @@ import time import tempfile + def check_reopen(r1, w): try: print("Reopening read end") - r2 = os.open("/proc/self/fd/{}".format(r1), os.O_RDONLY) + r2 = os.open(f"/proc/self/fd/{r1}", os.O_RDONLY) - print("r1 is {}, r2 is {}".format(r1, r2)) + print(f"r1 is {r1}, r2 is {r2}") print("checking they both can receive from w...") @@ -36,11 +37,12 @@ def check_reopen(r1, w): def sleep_then_write(): time.sleep(1) os.write(w, b"c") + threading.Thread(target=sleep_then_write, daemon=True).start() assert os.read(r1, 1) == b"c" print("r1 definitely seems to be in blocking mode") except Exception as exc: - print("ERROR: {!r}".format(exc)) + print(f"ERROR: {exc!r}") print("-- testing anonymous pipe --") @@ -63,6 +65,6 @@ def sleep_then_write(): print("-- testing socketpair --") import socket + rs, ws = socket.socketpair() check_reopen(rs.fileno(), ws.fileno()) - diff --git a/notes-to-self/schedule-timing.py b/notes-to-self/schedule-timing.py index c3093066e2..176dcf9220 100644 --- a/notes-to-self/schedule-timing.py +++ b/notes-to-self/schedule-timing.py @@ -4,16 +4,18 @@ LOOPS = 0 RUNNING = True + async def reschedule_loop(depth): if depth == 0: global LOOPS while RUNNING: LOOPS += 1 await trio.sleep(0) - #await trio.lowlevel.cancel_shielded_checkpoint() + # await trio.lowlevel.cancel_shielded_checkpoint() else: await reschedule_loop(depth - 1) + async def report_loop(): global RUNNING try: @@ -25,13 +27,15 @@ async def report_loop(): end_count = LOOPS loops = end_count - start_count duration = end_time - start_time - print("{} loops/sec".format(loops / duration)) + print(f"{loops / duration} loops/sec") finally: RUNNING = False + async def main(): async with trio.open_nursery() as nursery: nursery.start_soon(reschedule_loop, 10) nursery.start_soon(report_loop) + trio.run(main) diff --git a/notes-to-self/socketpair-buffering.py b/notes-to-self/socketpair-buffering.py index dd3b1ad97d..5e77a709b7 100644 --- a/notes-to-self/socketpair-buffering.py +++ b/notes-to-self/socketpair-buffering.py @@ -32,6 +32,6 @@ except BlockingIOError: pass - print("setsockopt bufsize {}: {}".format(bufsize, i)) + print(f"setsockopt bufsize {bufsize}: {i}") a.close() b.close() diff --git a/notes-to-self/ssl-handshake/ssl-handshake.py b/notes-to-self/ssl-handshake/ssl-handshake.py index 81d875be6a..18a0e1a675 100644 --- a/notes-to-self/ssl-handshake/ssl-handshake.py +++ b/notes-to-self/ssl-handshake/ssl-handshake.py @@ -8,6 +8,7 @@ server_ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) server_ctx.load_cert_chain("trio-test-1.pem") + def _ssl_echo_serve_sync(sock): try: wrapped = server_ctx.wrap_socket(sock, server_side=True) @@ -20,16 +21,19 @@ def _ssl_echo_serve_sync(sock): except BrokenPipeError: pass + @contextmanager def echo_server_connection(): client_sock, server_sock = socket.socketpair() with client_sock, server_sock: t = threading.Thread( - target=_ssl_echo_serve_sync, args=(server_sock,), daemon=True) + target=_ssl_echo_serve_sync, args=(server_sock,), daemon=True + ) t.start() yield client_sock + class ManuallyWrappedSocket: def __init__(self, ctx, sock, **kwargs): self.incoming = ssl.MemoryBIO() @@ -82,21 +86,23 @@ def unwrap(self): def wrap_socket_via_wrap_socket(ctx, sock, **kwargs): return ctx.wrap_socket(sock, do_handshake_on_connect=False, **kwargs) + def wrap_socket_via_wrap_bio(ctx, sock, **kwargs): return ManuallyWrappedSocket(ctx, sock, **kwargs) for wrap_socket in [ - wrap_socket_via_wrap_socket, - wrap_socket_via_wrap_bio, + wrap_socket_via_wrap_socket, + wrap_socket_via_wrap_bio, ]: - print("\n--- checking {} ---\n".format(wrap_socket.__name__)) + print(f"\n--- checking {wrap_socket.__name__} ---\n") print("checking with do_handshake + correct hostname...") with echo_server_connection() as client_sock: client_ctx = ssl.create_default_context(cafile="trio-test-CA.pem") wrapped = wrap_socket( - client_ctx, client_sock, server_hostname="trio-test-1.example.org") + client_ctx, client_sock, server_hostname="trio-test-1.example.org" + ) wrapped.do_handshake() wrapped.sendall(b"x") assert wrapped.recv(1) == b"x" @@ -107,7 +113,8 @@ def wrap_socket_via_wrap_bio(ctx, sock, **kwargs): with echo_server_connection() as client_sock: client_ctx = ssl.create_default_context(cafile="trio-test-CA.pem") wrapped = wrap_socket( - client_ctx, client_sock, server_hostname="trio-test-2.example.org") + client_ctx, client_sock, server_hostname="trio-test-2.example.org" + ) try: wrapped.do_handshake() except Exception: @@ -119,7 +126,8 @@ def wrap_socket_via_wrap_bio(ctx, sock, **kwargs): with echo_server_connection() as client_sock: client_ctx = ssl.create_default_context(cafile="trio-test-CA.pem") wrapped = wrap_socket( - client_ctx, client_sock, server_hostname="trio-test-2.example.org") + client_ctx, client_sock, server_hostname="trio-test-2.example.org" + ) # We forgot to call do_handshake # But the hostname is wrong so something had better error out... sent = b"x" diff --git a/notes-to-self/sslobject.py b/notes-to-self/sslobject.py index cfac98676e..0692af319c 100644 --- a/notes-to-self/sslobject.py +++ b/notes-to-self/sslobject.py @@ -15,6 +15,7 @@ soutb = ssl.MemoryBIO() sso = server_ctx.wrap_bio(sinb, soutb, server_side=True) + @contextmanager def expect(etype): try: @@ -22,7 +23,8 @@ def expect(etype): except etype: pass else: - raise AssertionError("expected {}".format(etype)) + raise AssertionError(f"expected {etype}") + with expect(ssl.SSLWantReadError): cso.do_handshake() diff --git a/notes-to-self/thread-closure-bug-demo.py b/notes-to-self/thread-closure-bug-demo.py index 514636a1b4..b09a87fe5f 100644 --- a/notes-to-self/thread-closure-bug-demo.py +++ b/notes-to-self/thread-closure-bug-demo.py @@ -8,18 +8,21 @@ COUNT = 100 + def slow_tracefunc(frame, event, arg): # A no-op trace function that sleeps briefly to make us more likely to hit # the race condition. time.sleep(0.01) return slow_tracefunc + def run_with_slow_tracefunc(fn): # settrace() only takes effect when you enter a new frame, so we need this # little dance: sys.settrace(slow_tracefunc) return fn() + def outer(): x = 0 # We hide the done variable inside a list, because we want to use it to @@ -46,13 +49,14 @@ def traced_looper(): t.start() for i in range(COUNT): - print("after {} increments, x is {}".format(i, x)) + print(f"after {i} increments, x is {x}") x += 1 time.sleep(0.01) done[0] = True t.join() - print("Final discrepancy: {} (should be 0)".format(COUNT - x)) + print(f"Final discrepancy: {COUNT - x} (should be 0)") + outer() diff --git a/notes-to-self/thread-dispatch-bench.py b/notes-to-self/thread-dispatch-bench.py index 1625efae17..9afb4bbec8 100644 --- a/notes-to-self/thread-dispatch-bench.py +++ b/notes-to-self/thread-dispatch-bench.py @@ -10,11 +10,13 @@ COUNT = 10000 + def worker(in_q, out_q): while True: job = in_q.get() out_q.put(job()) + def main(): in_q = Queue() out_q = Queue() @@ -28,6 +30,7 @@ def main(): in_q.put(lambda: None) out_q.get() end = time.monotonic() - print("{:.2f} µs/job".format((end - start) / COUNT * 1e6)) + print(f"{(end - start) / COUNT * 1e6:.2f} µs/job") + main() diff --git a/notes-to-self/time-wait-windows-exclusiveaddruse.py b/notes-to-self/time-wait-windows-exclusiveaddruse.py index db3aaad08a..dcb4a27dd0 100644 --- a/notes-to-self/time-wait-windows-exclusiveaddruse.py +++ b/notes-to-self/time-wait-windows-exclusiveaddruse.py @@ -8,15 +8,17 @@ import socket from contextlib import contextmanager + @contextmanager def report_outcome(tagline): try: yield except OSError as exc: - print("{}: failed".format(tagline)) - print(" details: {!r}".format(exc)) + print(f"{tagline}: failed") + print(f" details: {exc!r}") else: - print("{}: succeeded".format(tagline)) + print(f"{tagline}: succeeded") + # Set up initial listening socket lsock = socket.socket() diff --git a/notes-to-self/time-wait.py b/notes-to-self/time-wait.py index e865a94982..08c71b0048 100644 --- a/notes-to-self/time-wait.py +++ b/notes-to-self/time-wait.py @@ -31,6 +31,7 @@ import attr + @attr.s(repr=False) class Options: listen1_early = attr.ib(default=None) @@ -49,9 +50,10 @@ def describe(self): for f in attr.fields(self.__class__): value = getattr(self, f.name) if value is not None: - info.append("{}={}".format(f.name, value)) + info.append(f"{f.name}={value}") return "Set/unset: {}".format(", ".join(info)) + def time_wait(options): print(options.describe()) @@ -60,7 +62,7 @@ def time_wait(options): listen0 = socket.socket() listen0.bind(("127.0.0.1", 0)) sockaddr = listen0.getsockname() - #print(" ", sockaddr) + # print(" ", sockaddr) listen0.close() listen1 = socket.socket() @@ -98,6 +100,7 @@ def time_wait(options): else: print(" -> ok") + time_wait(Options()) time_wait(Options(listen1_early=True, server=True, listen2=True)) time_wait(Options(listen1_early=True)) diff --git a/pyproject.toml b/pyproject.toml index cfb4060ee7..5b5e56ff2e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [tool.black] -target-version = ['py37'] +target-version = ['py38'] [tool.flake8] extend-ignore = ['D', 'E', 'W', 'F403', 'F405', 'F821', 'F822'] diff --git a/setup.py b/setup.py index 3f420f6c63..2917f7c12e 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.7-or-better (either CPython or PyPy3 is fine). \\*BSD and illumos likely + 3.8-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 @@ -96,7 +96,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.7", + python_requires=">=3.8", keywords=["async", "io", "networking", "trio"], classifiers=[ "Development Status :: 3 - Alpha", @@ -110,11 +110,11 @@ "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "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", "Topic :: System :: Networking", "Framework :: Trio", ], diff --git a/test-requirements.in b/test-requirements.in index 1e1e23c2b8..03997ad2e7 100644 --- a/test-requirements.in +++ b/test-requirements.in @@ -3,28 +3,25 @@ pytest >= 5.0 # for faulthandler in core coverage >= 7.2.5 async_generator >= 1.9 pyright -# ipython 7.x is the last major version supporting Python 3.7 -ipython < 7.35 # for the IPython traceback integration tests +ipython # for the IPython traceback integration tests pyOpenSSL >= 22.0.0 # for the ssl + DTLS tests trustme # for the ssl + DTLS tests pylint # for pylint finding all symbols tests jedi # for jedi code completion tests -cryptography>=36.0.0 # 35.0.0 is transitive but fails +cryptography>=41.0.0 # cryptography<41 segfaults on pypy3.10 # Tools black; implementation_name == "cpython" mypy; implementation_name == "cpython" types-pyOpenSSL; implementation_name == "cpython" -flake8 < 6.0.0 # 6.0.0 drops python 3.7 +flake8 flake8-pyproject astor # code generation pip-tools >= 6.13.0 # https://github.com/python-trio/trio/pull/654#issuecomment-420518745 -# typed_ast is deprecated as of 3.8, and straight up doesn't compile on 3.10-dev as of 2021-12-13 -typed_ast; implementation_name == "cpython" and python_version < "3.8" mypy-extensions; implementation_name == "cpython" -typing-extensions < 4.7.0 +typing-extensions # Trio's own dependencies cffi; os_name == "nt" @@ -34,9 +31,3 @@ idna outcome sniffio exceptiongroup >= 1.0.0rc9; python_version < "3.11" - -# isort 5.12.0 requires python 3.8 -isort < 5.12.0 - -# cryptography 40.0.2 (and presumably prior) segfaults on PyPy 3.7 -cryptography < 40.0.0 diff --git a/test-requirements.txt b/test-requirements.txt index 6babde688e..17fa0a099a 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -28,7 +28,7 @@ click==8.1.3 # pip-tools coverage==7.2.7 # via -r test-requirements.in -cryptography==39.0.2 +cryptography==41.0.1 # via # -r test-requirements.in # pyopenssl @@ -57,9 +57,7 @@ iniconfig==2.0.0 ipython==7.34.0 # via -r test-requirements.in isort==5.11.5 - # via - # -r test-requirements.in - # pylint + # via pylint jedi==0.18.2 # via # -r test-requirements.in diff --git a/trio/_channel.py b/trio/_channel.py index 7c8ff4660d..f77950c4e4 100644 --- a/trio/_channel.py +++ b/trio/_channel.py @@ -209,7 +209,7 @@ def abort_fn(_: RaiseCancelT) -> Abort: # Return type must be stringified or use a TypeVar @enable_ki_protection - def clone(self) -> "MemorySendChannel[SendType]": + def clone(self) -> MemorySendChannel[SendType]: """Clone this send channel object. This returns a new `MemorySendChannel` object, which acts as a @@ -352,7 +352,7 @@ def abort_fn(_: RaiseCancelT) -> Abort: return await trio.lowlevel.wait_task_rescheduled(abort_fn) # type: ignore[no-any-return] @enable_ki_protection - def clone(self) -> "MemoryReceiveChannel[ReceiveType]": + def clone(self) -> MemoryReceiveChannel[ReceiveType]: """Clone this receive channel object. This returns a new `MemoryReceiveChannel` object, which acts as a diff --git a/trio/_core/_run.py b/trio/_core/_run.py index 585dc4aa41..4f90889c5f 100644 --- a/trio/_core/_run.py +++ b/trio/_core/_run.py @@ -51,7 +51,7 @@ import contextvars # An unfortunate name collision here with trio._util.Final - from typing_extensions import Final as FinalT + from typing import Final as FinalT DEADLINE_HEAP_MIN_PRUNE_THRESHOLD: FinalT = 1000 @@ -1349,7 +1349,7 @@ def raise_cancel() -> NoReturn: class RunContext(threading.local): - runner: "Runner" + runner: Runner task: Task diff --git a/trio/_core/_tests/test_multierror.py b/trio/_core/_tests/test_multierror.py index 498f4d435b..ca98ebf982 100644 --- a/trio/_core/_tests/test_multierror.py +++ b/trio/_core/_tests/test_multierror.py @@ -475,9 +475,7 @@ def run_script(name, use_ipython=False): def check_simple_excepthook(completed, uses_ipython): assert_match_in_seq( [ - "in = (3, 8) - else "in ", + "in ", "MultiError", "--- 1 ---", "in exc1_fn", diff --git a/trio/_core/_tests/tutil.py b/trio/_core/_tests/tutil.py index f3a21364be..b3aa73fb7d 100644 --- a/trio/_core/_tests/tutil.py +++ b/trio/_core/_tests/tutil.py @@ -4,7 +4,6 @@ import os import socket as stdlib_socket import sys -import threading import warnings from contextlib import closing, contextmanager from typing import TYPE_CHECKING @@ -42,7 +41,7 @@ with s: try: s.bind(("::1", 0)) - except OSError: + except OSError: # pragma: no cover # since support for 3.7 was removed can_bind_ipv6 = False else: can_bind_ipv6 = True @@ -85,37 +84,13 @@ def _noop(*args, **kwargs): pass -if sys.version_info >= (3, 8): - - @contextmanager - def restore_unraisablehook(): - sys.unraisablehook, prev = sys.__unraisablehook__, sys.unraisablehook - try: - yield - finally: - sys.unraisablehook = prev - - @contextmanager - def disable_threading_excepthook(): - if sys.version_info >= (3, 10): - threading.excepthook, prev = threading.__excepthook__, threading.excepthook - else: - threading.excepthook, prev = _noop, threading.excepthook - - try: - yield - finally: - threading.excepthook = prev - -else: - - @contextmanager - def restore_unraisablehook(): # pragma: no cover - yield - - @contextmanager - def disable_threading_excepthook(): # pragma: no cover +@contextmanager +def restore_unraisablehook(): + sys.unraisablehook, prev = sys.__unraisablehook__, sys.unraisablehook + try: yield + finally: + sys.unraisablehook = prev # template is like: diff --git a/trio/_path.py b/trio/_path.py index bb81759ecf..67234e223d 100644 --- a/trio/_path.py +++ b/trio/_path.py @@ -246,14 +246,14 @@ async def open(self, *args, **kwargs): is_mount: Any owner: Any - if sys.version_info >= (3, 8) and sys.version_info < (3, 12): - link_to: Any if sys.version_info >= (3, 9): is_relative_to: Any with_stem: Any readlink: Any if sys.version_info >= (3, 10): hardlink_to: Any + if sys.version_info < (3, 12): + link_to: Any if sys.version_info >= (3, 12): is_junction: Any walk: Any diff --git a/trio/_socket.py b/trio/_socket.py index eaf0e04d15..659f844078 100644 --- a/trio/_socket.py +++ b/trio/_socket.py @@ -57,18 +57,6 @@ async def __aexit__( return False -################################################################ -# CONSTANTS -################################################################ - -try: - from socket import IPPROTO_IPV6 -except ImportError: - # Before Python 3.8, Windows is missing IPPROTO_IPV6 - # https://bugs.python.org/issue29515 - if sys.platform == "win32": # pragma: no branch - IPPROTO_IPV6 = 41 - ################################################################ # Overrides ################################################################ @@ -569,7 +557,7 @@ async def wait_writable(self) -> None: async def _resolve_address_nocp(self, address, *, local): if self.family == _stdlib_socket.AF_INET6: ipv6_v6only = self._sock.getsockopt( - IPPROTO_IPV6, _stdlib_socket.IPV6_V6ONLY + _stdlib_socket.IPPROTO_IPV6, _stdlib_socket.IPV6_V6ONLY ) else: ipv6_v6only = False diff --git a/trio/_tests/test_exports.py b/trio/_tests/test_exports.py index e51bbe31f5..20635b0022 100644 --- a/trio/_tests/test_exports.py +++ b/trio/_tests/test_exports.py @@ -267,7 +267,7 @@ def no_hidden(symbols): cache_json = json.loads(cache_file.read()) # skip a bunch of file-system activity (probably can un-memoize?) - @functools.lru_cache() + @functools.lru_cache def lookup_symbol(symbol): topname, *modname, name = symbol.split(".") version = next(cache.glob("3.*/")) diff --git a/trio/_tests/test_ssl.py b/trio/_tests/test_ssl.py index 2534c81260..f91cea8549 100644 --- a/trio/_tests/test_ssl.py +++ b/trio/_tests/test_ssl.py @@ -7,7 +7,6 @@ import threading from contextlib import asynccontextmanager, contextmanager from functools import partial -from typing import TYPE_CHECKING import pytest import trustme @@ -31,10 +30,6 @@ memory_stream_pair, ) -if TYPE_CHECKING: - from _pytest.mark import MarkDecorator - - # We have two different kinds of echo server fixtures we use for testing. The # first is a real server written using the stdlib ssl module and blocking # sockets. It runs in a thread and we talk to it over a real socketpair(), to @@ -63,12 +58,6 @@ TRIO_TEST_1_CERT.configure_cert(SERVER_CTX) -skip_on_broken_openssl: MarkDecorator = pytest.mark.skipif( - sys.version_info < (3, 8) and ssl.OPENSSL_VERSION_INFO[0] > 1, - reason="Python 3.7 does not work with OpenSSL versions higher than 1.X", -) - - # TLS 1.3 has a lot of changes from previous versions. So we want to run tests # with both TLS 1.3, and TLS 1.2. # "tls13" means that we're willing to negotiate TLS 1.3. Usually that's @@ -111,22 +100,6 @@ def ssl_echo_serve_sync(sock, *, expect_fail=False): wrapped.unwrap() except exceptions: pass - except ssl.SSLWantWriteError: # pragma: no cover - # Under unclear conditions, CPython sometimes raises - # SSLWantWriteError here. This is a bug (bpo-32219), - # but it's not our bug. Christian Heimes thinks - # it's fixed in 'recent' CPython versions so we fail - # the test for those and ignore it for earlier - # versions. - if ( - sys.implementation.name != "cpython" - or sys.version_info >= (3, 8) - ): - pytest.fail( - "still an issue on recent python versions " - "add a comment to " - "https://bugs.python.org/issue32219" - ) return wrapped.sendall(data) # This is an obscure workaround for an openssl bug. In server mode, in @@ -817,7 +790,6 @@ async def test_send_all_empty_string(client_ctx): await s.aclose() -@skip_on_broken_openssl @pytest.mark.parametrize("https_compatible", [False, True]) async def test_SSLStream_generic(client_ctx, https_compatible): async def stream_maker(): @@ -1033,7 +1005,6 @@ async def test_ssl_bad_shutdown(client_ctx): await server.aclose() -@skip_on_broken_openssl async def test_ssl_bad_shutdown_but_its_ok(client_ctx): client, server = ssl_memory_stream_pair( client_ctx, @@ -1098,7 +1069,6 @@ def close_hook(): assert transport_close_count == 1 -@skip_on_broken_openssl async def test_ssl_https_compatibility_disagreement(client_ctx): client, server = ssl_memory_stream_pair( client_ctx, @@ -1123,7 +1093,6 @@ async def receive_and_expect_error(): nursery.start_soon(receive_and_expect_error) -@skip_on_broken_openssl async def test_https_mode_eof_before_handshake(client_ctx): client, server = ssl_memory_stream_pair( client_ctx, diff --git a/trio/_unix_pipes.py b/trio/_unix_pipes.py index f4158eb27d..716550790e 100644 --- a/trio/_unix_pipes.py +++ b/trio/_unix_pipes.py @@ -10,7 +10,7 @@ from ._util import ConflictDetector, Final if TYPE_CHECKING: - from typing_extensions import Final as FinalType + from typing import Final as FinalType if os.name != "posix": # We raise an error here rather than gating the import in lowlevel.py diff --git a/trio/_util.py b/trio/_util.py index 0a0795fc15..b7b4403115 100644 --- a/trio/_util.py +++ b/trio/_util.py @@ -141,6 +141,7 @@ def _return_value_looks_like_wrong_library(value): # function. So we have to just call it and then check whether the # return value is a coroutine object. # Note: will not be necessary on python>=3.8, see https://bugs.python.org/issue34890 + # TODO: python3.7 support is now dropped, so the above can be addressed. if not isinstance(coro, collections.abc.Coroutine): # Give good error for: nursery.start_soon(func_returning_future) if _return_value_looks_like_wrong_library(coro): diff --git a/trio/socket.py b/trio/socket.py index 61ba48ad3b..a9e276c782 100644 --- a/trio/socket.py +++ b/trio/socket.py @@ -79,9 +79,6 @@ except ImportError: pass -# get names used by Trio that we define on our own -from ._socket import IPPROTO_IPV6 as IPPROTO_IPV6 - if _t.TYPE_CHECKING: IP_BIND_ADDRESS_NO_PORT: int else: @@ -297,6 +294,7 @@ IPPROTO_IPCOMP as IPPROTO_IPCOMP, IPPROTO_IPIP as IPPROTO_IPIP, IPPROTO_IPV4 as IPPROTO_IPV4, + IPPROTO_IPV6 as IPPROTO_IPV6, IPPROTO_L2TP as IPPROTO_L2TP, IPPROTO_MAX as IPPROTO_MAX, IPPROTO_MOBILE as IPPROTO_MOBILE,