Skip to content

Commit bf9fe64

Browse files
authored
drop python 3.7 support, drop pypy3.7-3.8, add pypy3.10 (except on windows) (#2668)
* drop support for Python 3.7, PyPy3.7 & 3.8. Updates documentation, CI, and code with help of `pyupgrade --py38-plus`.
1 parent 6f187fb commit bf9fe64

32 files changed

+116
-161
lines changed

.github/workflows/ci.yml

+9-7
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,18 @@ jobs:
1818
strategy:
1919
fail-fast: false
2020
matrix:
21-
python: ['3.7', '3.8', '3.9', '3.10', 'pypy-3.8-nightly', 'pypy-3.9-nightly']
21+
# pypy-3.10 is failing, see https://github.com/python-trio/trio/issues/2678
22+
python: ['3.8', '3.9', '3.10', 'pypy-3.9-nightly'] #, 'pypy-3.10-nightly']
2223
arch: ['x86', 'x64']
2324
lsp: ['']
2425
lsp_extract_file: ['']
2526
extra_name: ['']
2627
exclude:
27-
- python: 'pypy-3.8-nightly'
28-
arch: 'x86'
28+
# pypy does not release 32-bit binaries
2929
- python: 'pypy-3.9-nightly'
3030
arch: 'x86'
31+
#- python: 'pypy-3.10-nightly'
32+
# arch: 'x86'
3133
include:
3234
- python: '3.8'
3335
arch: 'x64'
@@ -65,8 +67,8 @@ jobs:
6567
# and then finally an actual release version. actions/setup-python doesn't
6668
# support this for PyPy presently so we get no help there.
6769
#
68-
# CPython -> 3.9.0-alpha - 3.9.X
69-
# PyPy -> pypy-3.7
70+
# 'CPython' -> '3.9.0-alpha - 3.9.X'
71+
# 'PyPy' -> 'pypy-3.9'
7072
python-version: ${{ fromJSON(format('["{0}", "{1}"]', format('{0}.0-alpha - {0}.X', matrix.python), matrix.python))[startsWith(matrix.python, 'pypy')] }}
7173
architecture: '${{ matrix.arch }}'
7274
cache: pip
@@ -92,7 +94,7 @@ jobs:
9294
strategy:
9395
fail-fast: false
9496
matrix:
95-
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']
97+
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']
9698
check_formatting: ['0']
9799
extra_name: ['']
98100
include:
@@ -143,7 +145,7 @@ jobs:
143145
strategy:
144146
fail-fast: false
145147
matrix:
146-
python: ['3.7', '3.8', '3.9', '3.10', 'pypy-3.8-nightly', 'pypy-3.9-nightly']
148+
python: ['3.8', '3.9', '3.10', 'pypy-3.9-nightly', 'pypy-3.10-nightly']
147149
continue-on-error: >-
148150
${{
149151
(

README.rst

+3-2
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,9 @@ demonstration of implementing the "Happy Eyeballs" algorithm in an
9292
older library versus Trio.
9393

9494
**Cool, but will it work on my system?** Probably! As long as you have
95-
some kind of Python 3.7-or-better (CPython or the latest PyPy3 are
96-
both fine), and are using Linux, macOS, Windows, or FreeBSD, then Trio
95+
some kind of Python 3.8-or-better (CPython or [currently maintained versions of
96+
PyPy3](https://doc.pypy.org/en/latest/faq.html#which-python-versions-does-pypy-implement)
97+
are both fine), and are using Linux, macOS, Windows, or FreeBSD, then Trio
9798
will work. Other environments might work too, but those
9899
are the ones we test on. And all of our dependencies are pure Python,
99100
except for CFFI on Windows, which has wheels available, so

docs/source/index.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ Vital statistics:
4545

4646
* Supported environments: We test on
4747

48-
- Python: 3.7+ (CPython and PyPy)
48+
- Python: 3.8+ (CPython and PyPy)
4949
- Windows, macOS, Linux (glibc and musl), FreeBSD
5050

5151
Other environments might also work; give it a try and see.

docs/source/reference-core.rst

+3-7
Original file line numberDiff line numberDiff line change
@@ -974,12 +974,8 @@ work. What we need is something that's *like* a global variable, but
974974
that can have different values depending on which request handler is
975975
accessing it.
976976

977-
To solve this problem, Python 3.7 added a new module to the standard
978-
library: :mod:`contextvars`. And not only does Trio have built-in
979-
support for :mod:`contextvars`, but if you're using an earlier version
980-
of Python, then Trio makes sure that a backported version of
981-
:mod:`contextvars` is installed. So you can assume :mod:`contextvars`
982-
is there and works regardless of what version of Python you're using.
977+
To solve this problem, Python has a module in the standard
978+
library: :mod:`contextvars`.
983979

984980
Here's a toy example demonstrating how to use :mod:`contextvars`:
985981

@@ -1009,7 +1005,7 @@ Example output (yours may differ slightly):
10091005
request 0: Request received finished
10101006
10111007
For more information, read the
1012-
`contextvars docs <https://docs.python.org/3.7/library/contextvars.html>`__.
1008+
`contextvars docs <https://docs.python.org/3/library/contextvars.html>`__.
10131009

10141010

10151011
.. _synchronization:

docs/source/tutorial.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ Okay, ready? Let's get started.
8888
Before you begin
8989
----------------
9090

91-
1. Make sure you're using Python 3.7 or newer.
91+
1. Make sure you're using Python 3.8 or newer.
9292

9393
2. ``python3 -m pip install --upgrade trio`` (or on Windows, maybe
9494
``py -3 -m pip install --upgrade trio`` – `details

newsfragments/2668.removal.rst

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Drop support for Python3.7 and PyPy3.7/3.8.

newsfragments/README.rst

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ Each file should be named like ``<ISSUE>.<TYPE>.rst``, where
1414
deprecated features after an appropriate time, go in the
1515
``deprecated`` category instead)
1616
* ``feature``: any new feature that doesn't qualify for ``headline``
17+
* ``removal``: removing support for old python versions, or other removals with no deprecation period.
1718
* ``bugfix``
1819
* ``doc``
1920
* ``deprecated``

notes-to-self/how-does-windows-so-reuseaddr-work.py

+17-9
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
modes = ["default", "SO_REUSEADDR", "SO_EXCLUSIVEADDRUSE"]
1111
bind_types = ["wildcard", "specific"]
1212

13+
1314
def sock(mode):
1415
s = socket.socket(family=socket.AF_INET)
1516
if mode == "SO_REUSEADDR":
@@ -18,6 +19,7 @@ def sock(mode):
1819
s.setsockopt(socket.SOL_SOCKET, socket.SO_EXCLUSIVEADDRUSE, 1)
1920
return s
2021

22+
2123
def bind(sock, bind_type):
2224
if bind_type == "wildcard":
2325
sock.bind(("0.0.0.0", 12345))
@@ -26,6 +28,7 @@ def bind(sock, bind_type):
2628
else:
2729
assert False
2830

31+
2932
def table_entry(mode1, bind_type1, mode2, bind_type2):
3033
with sock(mode1) as sock1:
3134
bind(sock1, bind_type1)
@@ -41,19 +44,22 @@ def table_entry(mode1, bind_type1, mode2, bind_type2):
4144
else:
4245
return "Success"
4346

44-
print("""
47+
48+
print(
49+
"""
4550
second bind
4651
| """
47-
+ " | ".join(["%-19s" % mode for mode in modes])
52+
+ " | ".join(["%-19s" % mode for mode in modes])
4853
)
4954

50-
print(""" """, end='')
55+
print(""" """, end="")
5156
for mode in modes:
52-
print(" | " + " | ".join(["%8s" % bind_type for bind_type in bind_types]), end='')
57+
print(" | " + " | ".join(["%8s" % bind_type for bind_type in bind_types]), end="")
5358

54-
print("""
59+
print(
60+
"""
5561
first bind -----------------------------------------------------------------"""
56-
# default | wildcard | INUSE | Success | ACCESS | Success | INUSE | Success
62+
# default | wildcard | INUSE | Success | ACCESS | Success | INUSE | Success
5763
)
5864

5965
for i, mode1 in enumerate(modes):
@@ -63,6 +69,8 @@ def table_entry(mode1, bind_type1, mode2, bind_type2):
6369
for l, bind_type2 in enumerate(bind_types):
6470
entry = table_entry(mode1, bind_type1, mode2, bind_type2)
6571
row.append(entry)
66-
#print(mode1, bind_type1, mode2, bind_type2, entry)
67-
print("{:>19} | {:>8} | ".format(mode1, bind_type1)
68-
+ " | ".join(["%8s" % entry for entry in row]))
72+
# print(mode1, bind_type1, mode2, bind_type2, entry)
73+
print(
74+
f"{mode1:>19} | {bind_type1:>8} | "
75+
+ " | ".join(["%8s" % entry for entry in row])
76+
)

notes-to-self/reopen-pipe.py

+6-4
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@
33
import time
44
import tempfile
55

6+
67
def check_reopen(r1, w):
78
try:
89
print("Reopening read end")
9-
r2 = os.open("/proc/self/fd/{}".format(r1), os.O_RDONLY)
10+
r2 = os.open(f"/proc/self/fd/{r1}", os.O_RDONLY)
1011

11-
print("r1 is {}, r2 is {}".format(r1, r2))
12+
print(f"r1 is {r1}, r2 is {r2}")
1213

1314
print("checking they both can receive from w...")
1415

@@ -36,11 +37,12 @@ def check_reopen(r1, w):
3637
def sleep_then_write():
3738
time.sleep(1)
3839
os.write(w, b"c")
40+
3941
threading.Thread(target=sleep_then_write, daemon=True).start()
4042
assert os.read(r1, 1) == b"c"
4143
print("r1 definitely seems to be in blocking mode")
4244
except Exception as exc:
43-
print("ERROR: {!r}".format(exc))
45+
print(f"ERROR: {exc!r}")
4446

4547

4648
print("-- testing anonymous pipe --")
@@ -63,6 +65,6 @@ def sleep_then_write():
6365

6466
print("-- testing socketpair --")
6567
import socket
68+
6669
rs, ws = socket.socketpair()
6770
check_reopen(rs.fileno(), ws.fileno())
68-

notes-to-self/schedule-timing.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,18 @@
44
LOOPS = 0
55
RUNNING = True
66

7+
78
async def reschedule_loop(depth):
89
if depth == 0:
910
global LOOPS
1011
while RUNNING:
1112
LOOPS += 1
1213
await trio.sleep(0)
13-
#await trio.lowlevel.cancel_shielded_checkpoint()
14+
# await trio.lowlevel.cancel_shielded_checkpoint()
1415
else:
1516
await reschedule_loop(depth - 1)
1617

18+
1719
async def report_loop():
1820
global RUNNING
1921
try:
@@ -25,13 +27,15 @@ async def report_loop():
2527
end_count = LOOPS
2628
loops = end_count - start_count
2729
duration = end_time - start_time
28-
print("{} loops/sec".format(loops / duration))
30+
print(f"{loops / duration} loops/sec")
2931
finally:
3032
RUNNING = False
3133

34+
3235
async def main():
3336
async with trio.open_nursery() as nursery:
3437
nursery.start_soon(reschedule_loop, 10)
3538
nursery.start_soon(report_loop)
3639

40+
3741
trio.run(main)

notes-to-self/socketpair-buffering.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,6 @@
3232
except BlockingIOError:
3333
pass
3434

35-
print("setsockopt bufsize {}: {}".format(bufsize, i))
35+
print(f"setsockopt bufsize {bufsize}: {i}")
3636
a.close()
3737
b.close()

notes-to-self/ssl-handshake/ssl-handshake.py

+15-7
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
server_ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
99
server_ctx.load_cert_chain("trio-test-1.pem")
1010

11+
1112
def _ssl_echo_serve_sync(sock):
1213
try:
1314
wrapped = server_ctx.wrap_socket(sock, server_side=True)
@@ -20,16 +21,19 @@ def _ssl_echo_serve_sync(sock):
2021
except BrokenPipeError:
2122
pass
2223

24+
2325
@contextmanager
2426
def echo_server_connection():
2527
client_sock, server_sock = socket.socketpair()
2628
with client_sock, server_sock:
2729
t = threading.Thread(
28-
target=_ssl_echo_serve_sync, args=(server_sock,), daemon=True)
30+
target=_ssl_echo_serve_sync, args=(server_sock,), daemon=True
31+
)
2932
t.start()
3033

3134
yield client_sock
3235

36+
3337
class ManuallyWrappedSocket:
3438
def __init__(self, ctx, sock, **kwargs):
3539
self.incoming = ssl.MemoryBIO()
@@ -82,21 +86,23 @@ def unwrap(self):
8286
def wrap_socket_via_wrap_socket(ctx, sock, **kwargs):
8387
return ctx.wrap_socket(sock, do_handshake_on_connect=False, **kwargs)
8488

89+
8590
def wrap_socket_via_wrap_bio(ctx, sock, **kwargs):
8691
return ManuallyWrappedSocket(ctx, sock, **kwargs)
8792

8893

8994
for wrap_socket in [
90-
wrap_socket_via_wrap_socket,
91-
wrap_socket_via_wrap_bio,
95+
wrap_socket_via_wrap_socket,
96+
wrap_socket_via_wrap_bio,
9297
]:
93-
print("\n--- checking {} ---\n".format(wrap_socket.__name__))
98+
print(f"\n--- checking {wrap_socket.__name__} ---\n")
9499

95100
print("checking with do_handshake + correct hostname...")
96101
with echo_server_connection() as client_sock:
97102
client_ctx = ssl.create_default_context(cafile="trio-test-CA.pem")
98103
wrapped = wrap_socket(
99-
client_ctx, client_sock, server_hostname="trio-test-1.example.org")
104+
client_ctx, client_sock, server_hostname="trio-test-1.example.org"
105+
)
100106
wrapped.do_handshake()
101107
wrapped.sendall(b"x")
102108
assert wrapped.recv(1) == b"x"
@@ -107,7 +113,8 @@ def wrap_socket_via_wrap_bio(ctx, sock, **kwargs):
107113
with echo_server_connection() as client_sock:
108114
client_ctx = ssl.create_default_context(cafile="trio-test-CA.pem")
109115
wrapped = wrap_socket(
110-
client_ctx, client_sock, server_hostname="trio-test-2.example.org")
116+
client_ctx, client_sock, server_hostname="trio-test-2.example.org"
117+
)
111118
try:
112119
wrapped.do_handshake()
113120
except Exception:
@@ -119,7 +126,8 @@ def wrap_socket_via_wrap_bio(ctx, sock, **kwargs):
119126
with echo_server_connection() as client_sock:
120127
client_ctx = ssl.create_default_context(cafile="trio-test-CA.pem")
121128
wrapped = wrap_socket(
122-
client_ctx, client_sock, server_hostname="trio-test-2.example.org")
129+
client_ctx, client_sock, server_hostname="trio-test-2.example.org"
130+
)
123131
# We forgot to call do_handshake
124132
# But the hostname is wrong so something had better error out...
125133
sent = b"x"

notes-to-self/sslobject.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,16 @@
1515
soutb = ssl.MemoryBIO()
1616
sso = server_ctx.wrap_bio(sinb, soutb, server_side=True)
1717

18+
1819
@contextmanager
1920
def expect(etype):
2021
try:
2122
yield
2223
except etype:
2324
pass
2425
else:
25-
raise AssertionError("expected {}".format(etype))
26+
raise AssertionError(f"expected {etype}")
27+
2628

2729
with expect(ssl.SSLWantReadError):
2830
cso.do_handshake()

notes-to-self/thread-closure-bug-demo.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,21 @@
88

99
COUNT = 100
1010

11+
1112
def slow_tracefunc(frame, event, arg):
1213
# A no-op trace function that sleeps briefly to make us more likely to hit
1314
# the race condition.
1415
time.sleep(0.01)
1516
return slow_tracefunc
1617

18+
1719
def run_with_slow_tracefunc(fn):
1820
# settrace() only takes effect when you enter a new frame, so we need this
1921
# little dance:
2022
sys.settrace(slow_tracefunc)
2123
return fn()
2224

25+
2326
def outer():
2427
x = 0
2528
# We hide the done variable inside a list, because we want to use it to
@@ -46,13 +49,14 @@ def traced_looper():
4649
t.start()
4750

4851
for i in range(COUNT):
49-
print("after {} increments, x is {}".format(i, x))
52+
print(f"after {i} increments, x is {x}")
5053
x += 1
5154
time.sleep(0.01)
5255

5356
done[0] = True
5457
t.join()
5558

56-
print("Final discrepancy: {} (should be 0)".format(COUNT - x))
59+
print(f"Final discrepancy: {COUNT - x} (should be 0)")
60+
5761

5862
outer()

0 commit comments

Comments
 (0)