Date: Wed, 21 Aug 2024 12:40:10 +0900
Subject: [PATCH 19/79] Check for newsfragments (#3055)
* Check newsfragments using a custom Action
* Remove unnecessary pull request event
---------
Co-authored-by: CoolCat467 <52022020+CoolCat467@users.noreply.github.com>
---
.github/workflows/check-newsfragment.yml | 23 +++++++++++++++++++++++
1 file changed, 23 insertions(+)
create mode 100644 .github/workflows/check-newsfragment.yml
diff --git a/.github/workflows/check-newsfragment.yml b/.github/workflows/check-newsfragment.yml
new file mode 100644
index 0000000000..0aa78fcd3c
--- /dev/null
+++ b/.github/workflows/check-newsfragment.yml
@@ -0,0 +1,23 @@
+name: Check newsfragment
+
+on:
+ pull_request:
+ types: [labeled, unlabeled, opened, synchronize]
+ branches:
+ - main
+
+jobs:
+ check-newsfragment:
+ if: ${{ !contains(github.event.pull_request.labels.*.name, 'skip newsfragment') }}
+ runs-on: 'ubuntu-latest'
+
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Check newsfragments
+ run: |
+ if git diff --name-only origin/main | grep -v '/_tests/' | grep 'src/trio/'; then
+ git diff --name-only origin/main | grep 'newsfragments/' || exit 1
+ fi
From 57c95a553038e376be73455466196af2369c59c6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Filip=20=C5=A0t=C4=9Bdronsk=C3=BD?=
Date: Wed, 21 Aug 2024 06:40:47 +0200
Subject: [PATCH 20/79] Fix trio.socket.SocketType.bind to work with PathLike
arguments again (#3042)
* Fix trio.socket.SocketType.bind to work with PathLike arguments again
fixes #3041
* Test passing various types to trio.socket.SocketType.{bind,connect}
Tests #3041
* Add a newsfragment
* Appease pre-commit
---------
Co-authored-by: EXPLOSION
---
newsfragments/3041.bugfix.rst | 1 +
src/trio/_socket.py | 2 +-
src/trio/_tests/test_socket.py | 10 +++++++---
3 files changed, 9 insertions(+), 4 deletions(-)
create mode 100644 newsfragments/3041.bugfix.rst
diff --git a/newsfragments/3041.bugfix.rst b/newsfragments/3041.bugfix.rst
new file mode 100644
index 0000000000..b21851e5b3
--- /dev/null
+++ b/newsfragments/3041.bugfix.rst
@@ -0,0 +1 @@
+Allow sockets to bind any ``os.PathLike`` object.
diff --git a/src/trio/_socket.py b/src/trio/_socket.py
index 0a3bd1cba1..69a4d773d4 100644
--- a/src/trio/_socket.py
+++ b/src/trio/_socket.py
@@ -469,7 +469,7 @@ async def _resolve_address_nocp(
)
elif hasattr(_stdlib_socket, "AF_UNIX") and family == _stdlib_socket.AF_UNIX:
# unwrap path-likes
- assert isinstance(address, (str, bytes))
+ assert isinstance(address, (str, bytes, os.PathLike))
return os.fspath(address)
else:
return address
diff --git a/src/trio/_tests/test_socket.py b/src/trio/_tests/test_socket.py
index b98b3246e9..9397733c72 100644
--- a/src/trio/_tests/test_socket.py
+++ b/src/trio/_tests/test_socket.py
@@ -6,6 +6,7 @@
import socket as stdlib_socket
import sys
import tempfile
+from pathlib import Path
from socket import AddressFamily, SocketKind
from typing import TYPE_CHECKING, Any, Callable, List, Tuple, Union
@@ -1077,7 +1078,7 @@ async def test_unix_domain_socket() -> None:
# Bind has a special branch to use a thread, since it has to do filesystem
# traversal. Maybe connect should too? Not sure.
- async def check_AF_UNIX(path: str | bytes) -> None:
+ async def check_AF_UNIX(path: str | bytes | os.PathLike[str]) -> None:
with tsocket.socket(family=tsocket.AF_UNIX) as lsock:
await lsock.bind(path)
lsock.listen(10)
@@ -1091,8 +1092,11 @@ async def check_AF_UNIX(path: str | bytes) -> None:
# Can't use tmpdir fixture, because we can exceed the maximum AF_UNIX path
# length on macOS.
with tempfile.TemporaryDirectory() as tmpdir:
- path = f"{tmpdir}/sock"
- await check_AF_UNIX(path)
+ # Test passing various supported types as path
+ # Must use different filenames to prevent "address already in use"
+ await check_AF_UNIX(f"{tmpdir}/sock")
+ await check_AF_UNIX(Path(f"{tmpdir}/sock1"))
+ await check_AF_UNIX(os.fsencode(f"{tmpdir}/sock2"))
try:
cookie = os.urandom(20).hex().encode("ascii")
From fbb9d50dc7b678a5da99856f4e88a35c140839e3 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Wed, 21 Aug 2024 04:56:13 +0000
Subject: [PATCH 21/79] Dependency updates (#3068)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
---
docs-requirements.txt | 20 ++++++++++----------
test-requirements.txt | 26 +++++++++++++-------------
2 files changed, 23 insertions(+), 23 deletions(-)
diff --git a/docs-requirements.txt b/docs-requirements.txt
index 744355fea3..f08b168f1b 100644
--- a/docs-requirements.txt
+++ b/docs-requirements.txt
@@ -6,13 +6,13 @@ attrs==24.2.0
# via
# -r docs-requirements.in
# outcome
-babel==2.15.0
+babel==2.16.0
# via sphinx
beautifulsoup4==4.12.3
# via sphinx-codeautolink
certifi==2024.7.4
# via requests
-cffi==1.17.0 ; os_name == 'nt' or platform_python_implementation != 'PyPy'
+cffi==1.17.0 ; platform_python_implementation != 'PyPy' or os_name == 'nt'
# via
# -r docs-requirements.in
# cryptography
@@ -20,7 +20,7 @@ charset-normalizer==3.3.2
# via requests
click==8.1.7
# via towncrier
-colorama==0.4.6 ; platform_system == 'Windows' or sys_platform == 'win32'
+colorama==0.4.6 ; sys_platform == 'win32' or platform_system == 'Windows'
# via
# click
# sphinx
@@ -40,11 +40,11 @@ imagesize==1.4.1
# via sphinx
immutables==0.20
# via -r docs-requirements.in
-importlib-metadata==8.2.0 ; python_version < '3.10'
+importlib-metadata==8.4.0 ; python_full_version < '3.10'
# via
# sphinx
# towncrier
-importlib-resources==6.4.0 ; python_version < '3.10'
+importlib-resources==6.4.3 ; python_full_version < '3.10'
# via towncrier
jinja2==3.1.4
# via
@@ -57,13 +57,13 @@ outcome==1.3.0.post0
# via -r docs-requirements.in
packaging==24.1
# via sphinx
-pycparser==2.22 ; os_name == 'nt' or platform_python_implementation != 'PyPy'
+pycparser==2.22 ; platform_python_implementation != 'PyPy' or os_name == 'nt'
# via cffi
pygments==2.18.0
# via sphinx
pyopenssl==24.2.1
# via -r docs-requirements.in
-pytz==2024.1 ; python_version < '3.9'
+pytz==2024.1 ; python_full_version < '3.9'
# via babel
requests==2.32.3
# via sphinx
@@ -73,7 +73,7 @@ snowballstemmer==2.2.0
# via sphinx
sortedcontainers==2.4.0
# via -r docs-requirements.in
-soupsieve==2.5
+soupsieve==2.6
# via beautifulsoup4
sphinx==7.1.2
# via
@@ -108,13 +108,13 @@ sphinxcontrib-serializinghtml==1.1.5
# via sphinx
sphinxcontrib-trio==1.1.2
# via -r docs-requirements.in
-tomli==2.0.1 ; python_version < '3.11'
+tomli==2.0.1 ; python_full_version < '3.11'
# via towncrier
towncrier==24.7.1
# via -r docs-requirements.in
urllib3==2.2.2
# via requests
-zipp==3.19.2 ; python_version < '3.10'
+zipp==3.20.0 ; python_full_version < '3.10'
# via
# importlib-metadata
# importlib-resources
diff --git a/test-requirements.txt b/test-requirements.txt
index aba004aefe..5bbaf38f6d 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -12,13 +12,13 @@ attrs==24.2.0
# via
# -r test-requirements.in
# outcome
-babel==2.15.0
+babel==2.16.0
# via sphinx
black==24.8.0 ; implementation_name == 'cpython'
# via -r test-requirements.in
certifi==2024.7.4
# via requests
-cffi==1.17.0 ; os_name == 'nt' or platform_python_implementation != 'PyPy'
+cffi==1.17.0 ; platform_python_implementation != 'PyPy' or os_name == 'nt'
# via
# -r test-requirements.in
# cryptography
@@ -28,7 +28,7 @@ click==8.1.7 ; implementation_name == 'cpython'
# via black
codespell==2.3.0
# via -r test-requirements.in
-colorama==0.4.6 ; sys_platform == 'win32' or (implementation_name == 'cpython' and platform_system == 'Windows')
+colorama==0.4.6 ; (implementation_name != 'cpython' and sys_platform == 'win32') or (platform_system != 'Windows' and sys_platform == 'win32') or (implementation_name == 'cpython' and platform_system == 'Windows')
# via
# click
# pylint
@@ -46,7 +46,7 @@ dill==0.3.8
# via pylint
docutils==0.20.1
# via sphinx
-exceptiongroup==1.2.2 ; python_version < '3.11'
+exceptiongroup==1.2.2 ; python_full_version < '3.11'
# via
# -r test-requirements.in
# pytest
@@ -57,7 +57,7 @@ idna==3.7
# trustme
imagesize==1.4.1
# via sphinx
-importlib-metadata==8.2.0 ; python_version < '3.10'
+importlib-metadata==8.4.0 ; python_full_version < '3.10'
# via sphinx
iniconfig==2.0.0
# via pytest
@@ -97,7 +97,7 @@ platformdirs==4.2.2
# pylint
pluggy==1.5.0
# via pytest
-pycparser==2.22 ; os_name == 'nt' or platform_python_implementation != 'PyPy'
+pycparser==2.22 ; platform_python_implementation != 'PyPy' or os_name == 'nt'
# via cffi
pygments==2.18.0
# via sphinx
@@ -105,11 +105,11 @@ pylint==3.2.6
# via -r test-requirements.in
pyopenssl==24.2.1
# via -r test-requirements.in
-pyright==1.1.375
+pyright==1.1.376
# via -r test-requirements.in
pytest==8.3.2
# via -r test-requirements.in
-pytz==2024.1 ; python_version < '3.9'
+pytz==2024.1 ; python_full_version < '3.9'
# via babel
requests==2.32.3
# via sphinx
@@ -135,13 +135,13 @@ sphinxcontrib-qthelp==1.0.3
# via sphinx
sphinxcontrib-serializinghtml==1.1.5
# via sphinx
-tomli==2.0.1 ; python_version < '3.11'
+tomli==2.0.1 ; python_full_version < '3.11'
# via
# black
# mypy
# pylint
# pytest
-tomlkit==0.13.0
+tomlkit==0.13.2
# via pylint
trustme==1.1.0
# via -r test-requirements.in
@@ -153,7 +153,7 @@ types-docutils==0.21.0.20240724
# via -r test-requirements.in
types-pyopenssl==24.1.0.20240722 ; implementation_name == 'cpython'
# via -r test-requirements.in
-types-setuptools==71.1.0.20240806 ; implementation_name == 'cpython'
+types-setuptools==71.1.0.20240818 ; implementation_name == 'cpython'
# via types-cffi
typing-extensions==4.12.2
# via
@@ -164,7 +164,7 @@ typing-extensions==4.12.2
# pylint
urllib3==2.2.2
# via requests
-uv==0.2.34
+uv==0.3.0
# via -r test-requirements.in
-zipp==3.19.2 ; python_version < '3.10'
+zipp==3.20.0 ; python_full_version < '3.10'
# via importlib-metadata
From e3cfb239601b29dcec037422de8500eabe1f4623 Mon Sep 17 00:00:00 2001
From: CoolCat467 <52022020+CoolCat467@users.noreply.github.com>
Date: Wed, 21 Aug 2024 13:32:59 -0500
Subject: [PATCH 22/79] Switch to use `uv pip install` (#2957)
* Switch to use `uv`. Closes #2956
* Update ci.sh
-U => --upgrade, for legibility
* Revert changing wheel version to be lower
* Make sure running without a venv works
Also fix shellcheck reported issues
* Try to fix getting python executable on windows
* Make `PYTHON_PATH` work on both windows and linux
* Add comment about python path variable
* Detect if on Github CI and use system python if so
* Try invoking with `python -m uv`
* Install pinned uv version from the start
Windows CI is failing because uv is trying to change it's own executable while running
* Follow shellcheck suggestions
* Use constraints instead of grep
---------
Co-authored-by: John Litborn <11260241+jakkdl@users.noreply.github.com>
---
check.sh | 2 +-
ci.sh | 20 ++++++++++++--------
2 files changed, 13 insertions(+), 9 deletions(-)
diff --git a/check.sh b/check.sh
index d6efb8749a..6c3aac3884 100755
--- a/check.sh
+++ b/check.sh
@@ -112,7 +112,7 @@ if [ $EXIT_STATUS -ne 0 ]; then
Problems were found by static analysis (listed above).
To fix formatting and see remaining errors, run
- pip install -r test-requirements.txt
+ uv pip install -r test-requirements.txt
black src/trio
ruff check src/trio
./check.sh
diff --git a/ci.sh b/ci.sh
index 1134b57457..ef3dee55ca 100755
--- a/ci.sh
+++ b/ci.sh
@@ -37,25 +37,29 @@ python -c "import sys, struct, ssl; print('python:', sys.version); print('versio
echo "::endgroup::"
echo "::group::Install dependencies"
-python -m pip install -U pip build
+python -m pip install -U pip uv -c test-requirements.txt
python -m pip --version
+python -m uv --version
+
+python -m uv pip install build
python -m build
-python -m pip install dist/*.whl -c test-requirements.txt
+wheel_package=$(ls dist/*.whl)
+python -m uv pip install "trio @ $wheel_package" -c test-requirements.txt
if [ "$CHECK_FORMATTING" = "1" ]; then
- python -m pip install -r test-requirements.txt exceptiongroup
+ python -m uv pip install -r test-requirements.txt exceptiongroup
echo "::endgroup::"
source check.sh
else
# Actual tests
# expands to 0 != 1 if NO_TEST_REQUIREMENTS is not set, if set the `-0` has no effect
# https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_06_02
- if [ ${NO_TEST_REQUIREMENTS-0} == 1 ]; then
- python -m pip install pytest coverage -c test-requirements.txt
+ if [ "${NO_TEST_REQUIREMENTS-0}" == 1 ]; then
+ python -m uv pip install pytest coverage -c test-requirements.txt
flags="--skip-optional-imports"
else
- python -m pip install -r test-requirements.txt
+ python -m uv pip install -r test-requirements.txt
flags=""
fi
@@ -118,7 +122,7 @@ else
cd empty
INSTALLDIR=$(python -c "import os, trio; print(os.path.dirname(trio.__file__))")
- cp ../pyproject.toml $INSTALLDIR
+ cp ../pyproject.toml "$INSTALLDIR"
# get mypy tests a nice cache
MYPYPATH=".." mypy --config-file= --cache-dir=./.mypy_cache -c "import trio" >/dev/null 2>/dev/null || true
@@ -128,7 +132,7 @@ else
echo "::endgroup::"
echo "::group:: Run Tests"
- if COVERAGE_PROCESS_START=$(pwd)/../pyproject.toml coverage run --rcfile=../pyproject.toml -m pytest -ra --junitxml=../test-results.xml --run-slow ${INSTALLDIR} --verbose --durations=10 $flags; then
+ if COVERAGE_PROCESS_START=$(pwd)/../pyproject.toml coverage run --rcfile=../pyproject.toml -m pytest -ra --junitxml=../test-results.xml --run-slow "${INSTALLDIR}" --verbose --durations=10 $flags; then
PASSED=true
else
PASSED=false
From 50e09c0bffde4a237f531e40184b193f66d08d47 Mon Sep 17 00:00:00 2001
From: CoolCat467 <52022020+CoolCat467@users.noreply.github.com>
Date: Wed, 21 Aug 2024 13:43:33 -0500
Subject: [PATCH 23/79] Regenerate generated files automatically if any are
changed (#3050)
* Regenerate files automatically if any are changed
* Skip running `regenerate-files` on pre-commit.ci
* Update test to reflect the changes made
* Fix type annotation for test
* Do not check generated files, only source files
---
.pre-commit-config.yaml | 9 +++++++++
src/trio/_tests/tools/test_gen_exports.py | 12 ++++++++++--
src/trio/_tools/gen_exports.py | 6 +++++-
3 files changed, 24 insertions(+), 3 deletions(-)
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index f85695b5a8..aa83f1deb1 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -4,6 +4,7 @@ ci:
autoupdate_commit_msg: "[pre-commit.ci] pre-commit autoupdate"
autoupdate_schedule: weekly
submodules: false
+ skip: [regenerate-files]
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
@@ -33,3 +34,11 @@ repos:
rev: v2.3.0
hooks:
- id: codespell
+ - repo: local
+ hooks:
+ - id: regenerate-files
+ name: regenerate generated files
+ language: system
+ entry: python src/trio/_tools/gen_exports.py
+ pass_filenames: false
+ files: ^src\/trio\/_core\/(_run|(_i(o_(common|epoll|kqueue|windows)|nstrumentation)))\.py$
diff --git a/src/trio/_tests/tools/test_gen_exports.py b/src/trio/_tests/tools/test_gen_exports.py
index 19158451f7..d0b1f3697c 100644
--- a/src/trio/_tests/tools/test_gen_exports.py
+++ b/src/trio/_tests/tools/test_gen_exports.py
@@ -91,7 +91,9 @@ def test_create_pass_through_args() -> None:
@skip_lints
@pytest.mark.parametrize("imports", [IMPORT_1, IMPORT_2, IMPORT_3])
-def test_process(tmp_path: Path, imports: str) -> None:
+def test_process(
+ tmp_path: Path, imports: str, capsys: pytest.CaptureFixture[str]
+) -> None:
try:
import black # noqa: F401
# there's no dedicated CI run that has astor+isort, but lacks black.
@@ -106,7 +108,13 @@ def test_process(tmp_path: Path, imports: str) -> None:
with pytest.raises(SystemExit) as excinfo:
process([file], do_test=True)
assert excinfo.value.code == 1
- process([file], do_test=False)
+ captured = capsys.readouterr()
+ assert "Generated sources are outdated. Please regenerate." in captured.out
+ with pytest.raises(SystemExit) as excinfo:
+ process([file], do_test=False)
+ assert excinfo.value.code == 1
+ captured = capsys.readouterr()
+ assert "Regenerated sources successfully." in captured.out
assert genpath.exists()
process([file], do_test=True)
# But if we change the lookup path it notices
diff --git a/src/trio/_tools/gen_exports.py b/src/trio/_tools/gen_exports.py
index 4ecb29511e..b723bb229b 100755
--- a/src/trio/_tools/gen_exports.py
+++ b/src/trio/_tools/gen_exports.py
@@ -289,8 +289,9 @@ def process(files: Iterable[File], *, do_test: bool) -> None:
dirname, basename = os.path.split(file.path)
new_path = os.path.join(dirname, PREFIX + basename)
new_files[new_path] = new_source
+ matches_disk = matches_disk_files(new_files)
if do_test:
- if not matches_disk_files(new_files):
+ if not matches_disk:
print("Generated sources are outdated. Please regenerate.")
sys.exit(1)
else:
@@ -300,6 +301,9 @@ def process(files: Iterable[File], *, do_test: bool) -> None:
with open(new_path, "w", encoding="utf-8") as f:
f.write(new_source)
print("Regenerated sources successfully.")
+ if not matches_disk:
+ # With pre-commit integration, show that we edited files.
+ sys.exit(1)
# This is in fact run in CI, but only in the formatting check job, which
From 6f614ec9ac8f94e5996143b1c332fd89a57d68c9 Mon Sep 17 00:00:00 2001
From: EXPLOSION
Date: Thu, 22 Aug 2024 11:22:05 +0900
Subject: [PATCH 24/79] Update `uv`'s output when it gets bumped in autodeps
(#3069)
---
.github/workflows/autodeps.yml | 17 +++++++++++++----
1 file changed, 13 insertions(+), 4 deletions(-)
diff --git a/.github/workflows/autodeps.yml b/.github/workflows/autodeps.yml
index 0e0655c5aa..cfa044cf79 100644
--- a/.github/workflows/autodeps.yml
+++ b/.github/workflows/autodeps.yml
@@ -17,6 +17,7 @@ jobs:
issues: write
repository-projects: write
contents: write
+
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -24,6 +25,7 @@ jobs:
uses: actions/setup-python@v5
with:
python-version: "3.8"
+
- name: Bump dependencies
run: |
python -m pip install -U pip pre-commit
@@ -31,12 +33,19 @@ jobs:
uv pip compile --universal --python-version=3.8 --upgrade test-requirements.in -o test-requirements.txt
uv pip compile --universal --python-version=3.8 --upgrade docs-requirements.in -o docs-requirements.txt
pre-commit autoupdate --jobs 0
+
+ - name: Install new requirements
+ run: python -m pip install -r test-requirements.txt
+
+ # apply newer versions' formatting
- name: Black
+ run: black src/trio
+
+ - name: uv
run: |
- # The new dependencies may contain a new black version.
- # Commit any changes immediately.
- python -m pip install -r test-requirements.txt
- black src/trio
+ uv pip compile --universal --python-version=3.8 test-requirements.in -o test-requirements.txt
+ uv pip compile --universal --python-version=3.8 docs-requirements.in -o docs-requirements.txt
+
- name: Commit changes and create automerge PR
env:
GH_TOKEN: ${{ github.token }}
From 64e731dfce93e4907165096137aab1505ede5cd2 Mon Sep 17 00:00:00 2001
From: EXPLOSION
Date: Mon, 26 Aug 2024 00:53:35 +0900
Subject: [PATCH 25/79] Update sphinx (#3070)
* Bump what Python version `docs-requirements.txt` is for
* Update `__module__` before GenericAlias makes a copy
* Reference SSLStream correctly
* Explain >=2
* fix & extend comment on math.inf & types.FrameType workaround
---------
Co-authored-by: jakkdl
---
.github/workflows/autodeps.yml | 4 ++--
check.sh | 2 +-
docs-requirements.in | 3 ++-
docs-requirements.txt | 30 ++++++++----------------------
docs/source/conf.py | 8 +++++---
src/trio/_abc.py | 12 ++++++++++++
src/trio/_ssl.py | 4 ++++
7 files changed, 34 insertions(+), 29 deletions(-)
diff --git a/.github/workflows/autodeps.yml b/.github/workflows/autodeps.yml
index cfa044cf79..0e28de2687 100644
--- a/.github/workflows/autodeps.yml
+++ b/.github/workflows/autodeps.yml
@@ -31,7 +31,7 @@ jobs:
python -m pip install -U pip pre-commit
python -m pip install -r test-requirements.txt
uv pip compile --universal --python-version=3.8 --upgrade test-requirements.in -o test-requirements.txt
- uv pip compile --universal --python-version=3.8 --upgrade docs-requirements.in -o docs-requirements.txt
+ uv pip compile --universal --python-version=3.11 --upgrade docs-requirements.in -o docs-requirements.txt
pre-commit autoupdate --jobs 0
- name: Install new requirements
@@ -44,7 +44,7 @@ jobs:
- name: uv
run: |
uv pip compile --universal --python-version=3.8 test-requirements.in -o test-requirements.txt
- uv pip compile --universal --python-version=3.8 docs-requirements.in -o docs-requirements.txt
+ uv pip compile --universal --python-version=3.11 docs-requirements.in -o docs-requirements.txt
- name: Commit changes and create automerge PR
env:
diff --git a/check.sh b/check.sh
index 6c3aac3884..a4fb2e4e2a 100755
--- a/check.sh
+++ b/check.sh
@@ -81,7 +81,7 @@ echo "::group::Pip Compile - Tests"
uv pip compile --universal --python-version=3.8 test-requirements.in -o test-requirements.txt
echo "::endgroup::"
echo "::group::Pip Compile - Docs"
-uv pip compile --universal --python-version=3.8 docs-requirements.in -o docs-requirements.txt
+uv pip compile --universal --python-version=3.11 docs-requirements.in -o docs-requirements.txt
echo "::endgroup::"
if git status --porcelain | grep -q "requirements.txt"; then
diff --git a/docs-requirements.in b/docs-requirements.in
index c4695fc688..ce7c139b85 100644
--- a/docs-requirements.in
+++ b/docs-requirements.in
@@ -2,7 +2,8 @@
# sphinx 5.3 doesn't work with our _NoValue workaround
sphinx >= 6.0
jinja2
-sphinx_rtd_theme
+# >= is necessary to prevent `uv` from selecting a `Sphinx` version this does not support
+sphinx_rtd_theme >= 2
sphinxcontrib-jquery
sphinxcontrib-trio
towncrier
diff --git a/docs-requirements.txt b/docs-requirements.txt
index f08b168f1b..91b64e94c8 100644
--- a/docs-requirements.txt
+++ b/docs-requirements.txt
@@ -1,6 +1,6 @@
# This file was autogenerated by uv via the following command:
-# uv pip compile --universal --python-version=3.8 docs-requirements.in -o docs-requirements.txt
-alabaster==0.7.13
+# uv pip compile --universal --python-version=3.11 docs-requirements.in -o docs-requirements.txt
+alabaster==0.7.16
# via sphinx
attrs==24.2.0
# via
@@ -40,12 +40,6 @@ imagesize==1.4.1
# via sphinx
immutables==0.20
# via -r docs-requirements.in
-importlib-metadata==8.4.0 ; python_full_version < '3.10'
- # via
- # sphinx
- # towncrier
-importlib-resources==6.4.3 ; python_full_version < '3.10'
- # via towncrier
jinja2==3.1.4
# via
# -r docs-requirements.in
@@ -63,8 +57,6 @@ pygments==2.18.0
# via sphinx
pyopenssl==24.2.1
# via -r docs-requirements.in
-pytz==2024.1 ; python_full_version < '3.9'
- # via babel
requests==2.32.3
# via sphinx
sniffio==1.3.1
@@ -75,7 +67,7 @@ sortedcontainers==2.4.0
# via -r docs-requirements.in
soupsieve==2.6
# via beautifulsoup4
-sphinx==7.1.2
+sphinx==7.4.7
# via
# -r docs-requirements.in
# sphinx-codeautolink
@@ -89,11 +81,11 @@ sphinx-hoverxref==1.4.0
# via -r docs-requirements.in
sphinx-rtd-theme==2.0.0
# via -r docs-requirements.in
-sphinxcontrib-applehelp==1.0.4
+sphinxcontrib-applehelp==2.0.0
# via sphinx
-sphinxcontrib-devhelp==1.0.2
+sphinxcontrib-devhelp==2.0.0
# via sphinx
-sphinxcontrib-htmlhelp==2.0.1
+sphinxcontrib-htmlhelp==2.1.0
# via sphinx
sphinxcontrib-jquery==4.1
# via
@@ -102,19 +94,13 @@ sphinxcontrib-jquery==4.1
# sphinx-rtd-theme
sphinxcontrib-jsmath==1.0.1
# via sphinx
-sphinxcontrib-qthelp==1.0.3
+sphinxcontrib-qthelp==2.0.0
# via sphinx
-sphinxcontrib-serializinghtml==1.1.5
+sphinxcontrib-serializinghtml==2.0.0
# via sphinx
sphinxcontrib-trio==1.1.2
# via -r docs-requirements.in
-tomli==2.0.1 ; python_full_version < '3.11'
- # via towncrier
towncrier==24.7.1
# via -r docs-requirements.in
urllib3==2.2.2
# via requests
-zipp==3.20.0 ; python_full_version < '3.10'
- # via
- # importlib-metadata
- # importlib-resources
diff --git a/docs/source/conf.py b/docs/source/conf.py
index 7ea27de24b..a024a609ac 100755
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -243,12 +243,14 @@ def add_mapping(
# This has been removed in Py3.12, so add a link to the 3.11 version with deprecation warnings.
add_mapping("method", "pathlib", "Path.link_to", "3.11")
+
# defined in py:data in objects.inv, but sphinx looks for a py:class
+ # see https://github.com/sphinx-doc/sphinx/issues/10974
+ # to dump the objects.inv for the stdlib, you can run
+ # python -m sphinx.ext.intersphinx http://docs.python.org/3/objects.inv
add_mapping("class", "math", "inf")
- # `types.FrameType.__module__` is "builtins", so sphinx looks for
- # builtins.FrameType.
- # See https://github.com/sphinx-doc/sphinx/issues/11802
add_mapping("class", "types", "FrameType")
+
# new in py3.12, and need target because sphinx is unable to look up
# the module of the object if compiling on <3.12
if not hasattr(collections.abc, "Buffer"):
diff --git a/src/trio/_abc.py b/src/trio/_abc.py
index 20f1614cc6..cba058c4df 100644
--- a/src/trio/_abc.py
+++ b/src/trio/_abc.py
@@ -691,6 +691,14 @@ async def __anext__(self) -> ReceiveType:
raise StopAsyncIteration from None
+# these are necessary for Sphinx's :show-inheritance: with type args.
+# (this should be removed if possible)
+# see: https://github.com/python/cpython/issues/123250
+SendChannel.__module__ = SendChannel.__module__.replace("_abc", "abc")
+ReceiveChannel.__module__ = ReceiveChannel.__module__.replace("_abc", "abc")
+Listener.__module__ = Listener.__module__.replace("_abc", "abc")
+
+
class Channel(SendChannel[T], ReceiveChannel[T]):
"""A standard interface for interacting with bidirectional channels.
@@ -700,3 +708,7 @@ class Channel(SendChannel[T], ReceiveChannel[T]):
"""
__slots__ = ()
+
+
+# see above
+Channel.__module__ = Channel.__module__.replace("_abc", "abc")
diff --git a/src/trio/_ssl.py b/src/trio/_ssl.py
index 5bc37cf7dc..8d06297eaf 100644
--- a/src/trio/_ssl.py
+++ b/src/trio/_ssl.py
@@ -893,6 +893,10 @@ async def wait_send_all_might_not_block(self) -> None:
await self.transport_stream.wait_send_all_might_not_block()
+# this is necessary for Sphinx, see also `_abc.py`
+SSLStream.__module__ = SSLStream.__module__.replace("._ssl", "")
+
+
@final
class SSLListener(Listener[SSLStream[T_Stream]]):
"""A :class:`~trio.abc.Listener` for SSL/TLS-encrypted servers.
From 7c08af7b23e7da69ba9689a4fd30ea539c4aaae2 Mon Sep 17 00:00:00 2001
From: John Litborn <11260241+jakkdl@users.noreply.github.com>
Date: Mon, 26 Aug 2024 07:15:01 +0200
Subject: [PATCH 26/79] contributing.rst: add instructions for creating
virtualenv, update instructions for running w/ coverage (#3036)
* add instructions for creating virtualenv, update instructions for running with coverage
* fixes after review
* okay now instructions actually work
---
docs/source/contributing.rst | 35 +++++++++++++++++++++++++++++------
1 file changed, 29 insertions(+), 6 deletions(-)
diff --git a/docs/source/contributing.rst b/docs/source/contributing.rst
index f37f57d5dd..cace2943d2 100644
--- a/docs/source/contributing.rst
+++ b/docs/source/contributing.rst
@@ -176,6 +176,24 @@ This keeps us closer to the desired state where each open issue reflects some
work that still needs to be done.
+Environment
+~~~~~~~~~~~
+We strongly suggest using a virtual environment for managing dependencies,
+for example with `venv `__. So to
+set up your environment and install dependencies, you should run something like:
+
+.. code-block:: shell
+
+ cd path/to/trio/checkout/
+ python -m venv .venv # create virtual env in .venv
+ source .venv/bin/activate # activate it
+ pip install -e . # install trio, needed for pytest plugin
+ pip install -r test-requirements.txt # install test requirements
+
+you rarely need to recreate the virtual environment, but you need to re-activate it
+in future terminals. You might also need to re-install from test-requirements.txt if
+the versions in it get updated.
+
.. _pull-request-tests:
Tests
@@ -186,12 +204,11 @@ locally, you should run:
.. code-block:: shell
- cd path/to/trio/checkout/
- pip install -r test-requirements.txt # possibly using a virtualenv
- pytest trio
+ source .venv/bin/activate # if not already activated
+ pytest src
This doesn't try to be completely exhaustive – it only checks that
-things work on your machine, and it may skip some slow tests. But it's
+things work on your machine, and it will skip some slow tests. But it's
a good way to quickly check that things seem to be working, and we'll
automatically run the full test suite when your PR is submitted, so
you'll have a chance to see and fix any remaining issues then.
@@ -211,8 +228,14 @@ it being merely hard to fix). For example:
We use Codecov to track coverage, because it makes it easy to combine
coverage from running in different configurations. Running coverage
locally can be useful
-(``pytest --cov=PACKAGENAME --cov-report=html``), but don't be
-surprised if you get lower coverage than when looking at Codecov
+
+.. code-block:: shell
+
+ coverage run -m pytest
+ coverage combine
+ coverage report
+
+but don't be surprised if you get lower coverage than when looking at Codecov
reports, because there are some lines that are only executed on
Windows, or macOS, or PyPy, or CPython, or... you get the idea. After
you create a PR, Codecov will automatically report back with the
From cd196529352cb03d60facdc4c59758888f99ea1d Mon Sep 17 00:00:00 2001
From: Agnes Natasya
Date: Mon, 26 Aug 2024 13:30:01 +0800
Subject: [PATCH 27/79] move_on_ and fail_ functions accepts shield kwarg
(#3051)
* move_on_ and fail_ context managers accepts shield arg
* make it a kwarg
* news rst
* news rst
* better docstring and parametrize test
* undo
* black
* new line
* update news rst to issue number
* no need to explicitly link to docs
---------
Co-authored-by: EXPLOSION
---
docs/source/reference-core.rst | 3 +--
newsfragments/3052.feature.rst | 1 +
src/trio/_tests/test_timeouts.py | 45 +++++++++++++++++++++++++++++++-
src/trio/_timeouts.py | 26 ++++++++++++------
4 files changed, 64 insertions(+), 11 deletions(-)
create mode 100644 newsfragments/3052.feature.rst
diff --git a/docs/source/reference-core.rst b/docs/source/reference-core.rst
index 6808f930c6..392eac7d20 100644
--- a/docs/source/reference-core.rst
+++ b/docs/source/reference-core.rst
@@ -449,8 +449,7 @@ attribute to :data:`True`:
try:
await conn.send_hello_msg()
finally:
- with trio.move_on_after(CLEANUP_TIMEOUT) as cleanup_scope:
- cleanup_scope.shield = True
+ with trio.move_on_after(CLEANUP_TIMEOUT, shield=True) as cleanup_scope:
await conn.send_goodbye_msg()
So long as you're inside a scope with ``shield = True`` set, then
diff --git a/newsfragments/3052.feature.rst b/newsfragments/3052.feature.rst
new file mode 100644
index 0000000000..3d843b4feb
--- /dev/null
+++ b/newsfragments/3052.feature.rst
@@ -0,0 +1 @@
+`trio.move_on_at`, `trio.move_on_after`, `trio.fail_at` and `trio.fail_after` now accept *shield* as a keyword argument. If specified, it provides an initial value for the `~trio.CancelScope.shield` attribute of the `trio.CancelScope` object created by the context manager.
diff --git a/src/trio/_tests/test_timeouts.py b/src/trio/_tests/test_timeouts.py
index 98c3d18def..8b0f908ca7 100644
--- a/src/trio/_tests/test_timeouts.py
+++ b/src/trio/_tests/test_timeouts.py
@@ -1,5 +1,5 @@
import time
-from typing import Awaitable, Callable, TypeVar
+from typing import Awaitable, Callable, Protocol, TypeVar
import outcome
import pytest
@@ -75,6 +75,49 @@ async def sleep_3() -> None:
await check_takes_about(sleep_3, TARGET)
+class TimeoutScope(Protocol):
+ def __call__(self, seconds: float, *, shield: bool) -> trio.CancelScope: ...
+
+
+@pytest.mark.parametrize("scope", [move_on_after, fail_after])
+async def test_context_shields_from_outer(scope: TimeoutScope) -> None:
+ with _core.CancelScope() as outer, scope(TARGET, shield=True) as inner:
+ outer.cancel()
+ try:
+ await trio.lowlevel.checkpoint()
+ except trio.Cancelled:
+ pytest.fail("shield didn't work")
+ inner.shield = False
+ with pytest.raises(trio.Cancelled):
+ await trio.lowlevel.checkpoint()
+
+
+@slow
+async def test_move_on_after_moves_on_even_if_shielded() -> None:
+ async def task() -> None:
+ with _core.CancelScope() as outer, move_on_after(TARGET, shield=True):
+ outer.cancel()
+ # The outer scope is cancelled, but this task is protected by the
+ # shield, so it manages to get to sleep until deadline is met
+ await sleep_forever()
+
+ await check_takes_about(task, TARGET)
+
+
+@slow
+async def test_fail_after_fails_even_if_shielded() -> None:
+ async def task() -> None:
+ with pytest.raises(TooSlowError), _core.CancelScope() as outer, fail_after(
+ TARGET, shield=True
+ ):
+ outer.cancel()
+ # The outer scope is cancelled, but this task is protected by the
+ # shield, so it manages to get to sleep until deadline is met
+ await sleep_forever()
+
+ await check_takes_about(task, TARGET)
+
+
@slow
async def test_fail() -> None:
async def sleep_4() -> None:
diff --git a/src/trio/_timeouts.py b/src/trio/_timeouts.py
index 1d03b2f2e3..7bc985039b 100644
--- a/src/trio/_timeouts.py
+++ b/src/trio/_timeouts.py
@@ -7,12 +7,14 @@
import trio
-def move_on_at(deadline: float) -> trio.CancelScope:
+def move_on_at(deadline: float, *, shield: bool = False) -> trio.CancelScope:
"""Use as a context manager to create a cancel scope with the given
absolute deadline.
Args:
deadline (float): The deadline.
+ shield (bool): Initial value for the `~trio.CancelScope.shield` attribute
+ of the newly created cancel scope.
Raises:
ValueError: if deadline is NaN.
@@ -20,15 +22,17 @@ def move_on_at(deadline: float) -> trio.CancelScope:
"""
if math.isnan(deadline):
raise ValueError("deadline must not be NaN")
- return trio.CancelScope(deadline=deadline)
+ return trio.CancelScope(deadline=deadline, shield=shield)
-def move_on_after(seconds: float) -> trio.CancelScope:
+def move_on_after(seconds: float, *, shield: bool = False) -> trio.CancelScope:
"""Use as a context manager to create a cancel scope whose deadline is
set to now + *seconds*.
Args:
seconds (float): The timeout.
+ shield (bool): Initial value for the `~trio.CancelScope.shield` attribute
+ of the newly created cancel scope.
Raises:
ValueError: if timeout is less than zero or NaN.
@@ -36,7 +40,7 @@ def move_on_after(seconds: float) -> trio.CancelScope:
"""
if seconds < 0:
raise ValueError("timeout must be non-negative")
- return move_on_at(trio.current_time() + seconds)
+ return move_on_at(trio.current_time() + seconds, shield=shield)
async def sleep_forever() -> None:
@@ -96,7 +100,7 @@ class TooSlowError(Exception):
# workaround for PyCharm not being able to infer return type from @contextmanager
# see https://youtrack.jetbrains.com/issue/PY-36444/PyCharm-doesnt-infer-types-when-using-contextlib.contextmanager-decorator
-def fail_at(deadline: float) -> AbstractContextManager[trio.CancelScope]: # type: ignore[misc]
+def fail_at(deadline: float, *, shield: bool = False) -> AbstractContextManager[trio.CancelScope]: # type: ignore[misc]
"""Creates a cancel scope with the given deadline, and raises an error if it
is actually cancelled.
@@ -110,6 +114,8 @@ def fail_at(deadline: float) -> AbstractContextManager[trio.CancelScope]: # typ
Args:
deadline (float): The deadline.
+ shield (bool): Initial value for the `~trio.CancelScope.shield` attribute
+ of the newly created cancel scope.
Raises:
TooSlowError: if a :exc:`Cancelled` exception is raised in this scope
@@ -117,7 +123,7 @@ def fail_at(deadline: float) -> AbstractContextManager[trio.CancelScope]: # typ
ValueError: if deadline is NaN.
"""
- with move_on_at(deadline) as scope:
+ with move_on_at(deadline, shield=shield) as scope:
yield scope
if scope.cancelled_caught:
raise TooSlowError
@@ -127,7 +133,9 @@ def fail_at(deadline: float) -> AbstractContextManager[trio.CancelScope]: # typ
fail_at = contextmanager(fail_at)
-def fail_after(seconds: float) -> AbstractContextManager[trio.CancelScope]:
+def fail_after(
+ seconds: float, *, shield: bool = False
+) -> AbstractContextManager[trio.CancelScope]:
"""Creates a cancel scope with the given timeout, and raises an error if
it is actually cancelled.
@@ -140,6 +148,8 @@ def fail_after(seconds: float) -> AbstractContextManager[trio.CancelScope]:
Args:
seconds (float): The timeout.
+ shield (bool): Initial value for the `~trio.CancelScope.shield` attribute
+ of the newly created cancel scope.
Raises:
TooSlowError: if a :exc:`Cancelled` exception is raised in this scope
@@ -149,4 +159,4 @@ def fail_after(seconds: float) -> AbstractContextManager[trio.CancelScope]:
"""
if seconds < 0:
raise ValueError("timeout must be non-negative")
- return fail_at(trio.current_time() + seconds)
+ return fail_at(trio.current_time() + seconds, shield=shield)
From a5c375faa4296e9f7e162f7f1e1c1b99cb6f1f09 Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
<66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Mon, 26 Aug 2024 20:28:25 -0500
Subject: [PATCH 28/79] =?UTF-8?q?[pre-commit.ci]=20pre-commit=20autoupdate?=
=?UTF-8?q?=20ruff-pre-commit:=20v0.6.1=20=E2=86=92=20v0.6.2?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.6.1 → v0.6.2](https://github.com/astral-sh/ruff-pre-commit/compare/v0.6.1...v0.6.2)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
---
.pre-commit-config.yaml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index aa83f1deb1..6ea9215591 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -24,7 +24,7 @@ repos:
hooks:
- id: black
- repo: https://github.com/astral-sh/ruff-pre-commit
- rev: v0.6.1
+ rev: v0.6.2
hooks:
- id: ruff
types: [file]
From 4ae737ad3eb79c273cd6c1b093f6795701d06b81 Mon Sep 17 00:00:00 2001
From: EXPLOSION
Date: Thu, 29 Aug 2024 10:46:24 +0900
Subject: [PATCH 29/79] Stop testing PyPy's Python 3.9 (#3073)
---
.github/workflows/ci.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index f457740bdf..f89180d4bd 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -87,7 +87,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- python: ['pypy-3.9', 'pypy-3.10', '3.8', '3.9', '3.10', '3.11', '3.12', '3.13']
+ python: ['pypy-3.10', '3.8', '3.9', '3.10', '3.11', '3.12', '3.13']
check_formatting: ['0']
no_test_requirements: ['0']
extra_name: ['']
From b25c02a94e2defcb0fad32976b02218be1133bdf Mon Sep 17 00:00:00 2001
From: CoolCat467 <52022020+CoolCat467@users.noreply.github.com>
Date: Thu, 29 Aug 2024 10:41:27 -0500
Subject: [PATCH 30/79] Enable `flake8-commas`
---
docs/source/conf.py | 3 +-
notes-to-self/afd-lab.py | 2 +-
notes-to-self/blocking-read-hack.py | 4 +-
notes-to-self/file-read-latency.py | 2 +-
.../how-does-windows-so-reuseaddr-work.py | 6 +-
notes-to-self/manual-signal-handler.py | 5 +-
notes-to-self/proxy-benchmarks.py | 4 +-
notes-to-self/ssl-handshake/ssl-handshake.py | 16 +-
notes-to-self/wakeup-fd-racer.py | 2 +-
notes-to-self/win-waitable-timer.py | 2 +-
pyproject.toml | 1 +
src/trio/_abc.py | 4 +-
src/trio/_channel.py | 3 +-
src/trio/_core/_asyncgens.py | 15 +-
src/trio/_core/_concat_tb.py | 6 +-
src/trio/_core/_entry_queue.py | 2 +-
src/trio/_core/_generated_io_kqueue.py | 11 +-
src/trio/_core/_generated_io_windows.py | 19 ++-
src/trio/_core/_generated_run.py | 5 +-
src/trio/_core/_io_epoll.py | 4 +-
src/trio/_core/_io_kqueue.py | 10 +-
src/trio/_core/_io_windows.py | 85 +++++++----
src/trio/_core/_parking_lot.py | 5 +-
src/trio/_core/_run.py | 77 ++++++----
src/trio/_core/_tests/test_asyncgen.py | 11 +-
.../_core/_tests/test_exceptiongroup_gc.py | 3 +-
src/trio/_core/_tests/test_guest_mode.py | 18 ++-
src/trio/_core/_tests/test_io.py | 12 +-
src/trio/_core/_tests/test_parking_lot.py | 17 ++-
src/trio/_core/_tests/test_run.py | 92 +++++++----
src/trio/_core/_tests/test_unbounded_queue.py | 2 +-
src/trio/_core/_tests/test_windows.py | 29 +++-
src/trio/_core/_tests/type_tests/run.py | 4 +-
src/trio/_core/_thread_cache.py | 8 +-
src/trio/_core/_traps.py | 4 +-
src/trio/_core/_unbounded_queue.py | 3 +-
src/trio/_core/_wakeup_socketpair.py | 2 +-
src/trio/_core/_windows_cffi.py | 7 +-
src/trio/_dtls.py | 68 ++++++---
src/trio/_file_io.py | 14 +-
src/trio/_highlevel_open_tcp_listeners.py | 13 +-
src/trio/_highlevel_open_tcp_stream.py | 12 +-
src/trio/_highlevel_serve_listeners.py | 2 +-
src/trio/_highlevel_socket.py | 6 +-
src/trio/_highlevel_ssl_helpers.py | 14 +-
src/trio/_path.py | 2 +-
src/trio/_signals.py | 4 +-
src/trio/_socket.py | 144 +++++++++++++-----
src/trio/_ssl.py | 7 +-
src/trio/_subprocess.py | 28 ++--
src/trio/_subprocess_platform/kqueue.py | 5 +-
src/trio/_subprocess_platform/waitid.py | 7 +-
src/trio/_sync.py | 11 +-
src/trio/_tests/check_type_completeness.py | 6 +-
src/trio/_tests/module_with_deprecations.py | 5 +-
src/trio/_tests/test_channel.py | 6 +-
src/trio/_tests/test_contextvars.py | 2 +-
src/trio/_tests/test_deprecate.py | 11 +-
...deprecate_strict_exception_groups_false.py | 9 +-
src/trio/_tests/test_dtls.py | 41 +++--
src/trio/_tests/test_exports.py | 11 +-
src/trio/_tests/test_fakenet.py | 38 +++--
src/trio/_tests/test_file_io.py | 21 ++-
.../test_highlevel_open_tcp_listeners.py | 34 +++--
.../_tests/test_highlevel_open_tcp_stream.py | 31 ++--
.../_tests/test_highlevel_open_unix_stream.py | 6 +-
.../_tests/test_highlevel_serve_listeners.py | 12 +-
src/trio/_tests/test_highlevel_socket.py | 36 ++++-
src/trio/_tests/test_highlevel_ssl_helpers.py | 21 ++-
src/trio/_tests/test_path.py | 9 +-
src/trio/_tests/test_repl.py | 16 +-
src/trio/_tests/test_signals.py | 3 +-
src/trio/_tests/test_socket.py | 28 +++-
src/trio/_tests/test_ssl.py | 67 +++++---
src/trio/_tests/test_subprocess.py | 41 +++--
src/trio/_tests/test_sync.py | 4 +-
src/trio/_tests/test_testing.py | 11 +-
src/trio/_tests/test_testing_raisesgroup.py | 17 ++-
src/trio/_tests/test_threads.py | 14 +-
src/trio/_tests/test_wait_for_object.py | 14 +-
src/trio/_tests/tools/test_mypy_annotate.py | 2 +-
src/trio/_tests/type_tests/path.py | 3 +-
src/trio/_tests/type_tests/raisesgroup.py | 6 +-
src/trio/_threads.py | 31 ++--
src/trio/_tools/gen_exports.py | 9 +-
src/trio/_tools/mypy_annotate.py | 4 +-
src/trio/_unix_pipes.py | 8 +-
src/trio/_util.py | 15 +-
src/trio/_windows_pipes.py | 9 +-
src/trio/socket.py | 2 +-
src/trio/testing/_check_streams.py | 10 +-
src/trio/testing/_fake_net.py | 59 +++++--
src/trio/testing/_memory_streams.py | 12 +-
src/trio/testing/_raises_group.py | 48 +++---
src/trio/testing/_sequencer.py | 5 +-
src/trio/testing/_trio_test.py | 4 +-
96 files changed, 1082 insertions(+), 501 deletions(-)
diff --git a/docs/source/conf.py b/docs/source/conf.py
index a024a609ac..3a2b3d5f14 100755
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -132,7 +132,8 @@ def autodoc_process_signature(
# and insert the fully-qualified name.
signature = signature.replace("+E", "~trio.testing._raises_group.E")
signature = signature.replace(
- "+MatchE", "~trio.testing._raises_group.MatchE"
+ "+MatchE",
+ "~trio.testing._raises_group.MatchE",
)
if "DTLS" in name:
signature = signature.replace("SSL.Context", "OpenSSL.SSL.Context")
diff --git a/notes-to-self/afd-lab.py b/notes-to-self/afd-lab.py
index 600975482c..a95eb8bfd8 100644
--- a/notes-to-self/afd-lab.py
+++ b/notes-to-self/afd-lab.py
@@ -125,7 +125,7 @@ async def afd_poll(self, sock, flags, *, exclusive=0):
ffi.sizeof("AFD_POLL_INFO"),
ffi.NULL,
lpOverlapped,
- )
+ ),
)
except OSError as exc:
if exc.winerror != ErrorCodes.ERROR_IO_PENDING: # pragma: no cover
diff --git a/notes-to-self/blocking-read-hack.py b/notes-to-self/blocking-read-hack.py
index 56bcd03df9..688f103057 100644
--- a/notes-to-self/blocking-read-hack.py
+++ b/notes-to-self/blocking-read-hack.py
@@ -12,7 +12,9 @@ class BlockingReadTimeoutError(Exception):
async def blocking_read_with_timeout(
- fd, count, timeout # noqa: ASYNC109 # manual timeout
+ fd,
+ count,
+ timeout, # noqa: ASYNC109 # manual timeout
):
print("reading from fd", fd)
cancel_requested = False
diff --git a/notes-to-self/file-read-latency.py b/notes-to-self/file-read-latency.py
index e82b027a03..c01cd41bf2 100644
--- a/notes-to-self/file-read-latency.py
+++ b/notes-to-self/file-read-latency.py
@@ -23,5 +23,5 @@
seek = (end - between) / COUNT * 1e9
read = both - seek
print(
- f"{both:.2f} ns/(seek+read), {seek:.2f} ns/seek, estimate ~{read:.2f} ns/read"
+ f"{both:.2f} ns/(seek+read), {seek:.2f} ns/seek, estimate ~{read:.2f} ns/read",
)
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 70dd75e39f..f87e957adf 100644
--- a/notes-to-self/how-does-windows-so-reuseaddr-work.py
+++ b/notes-to-self/how-does-windows-so-reuseaddr-work.py
@@ -49,7 +49,7 @@ def table_entry(mode1, bind_type1, mode2, bind_type2):
"""
second bind
| """
- + " | ".join(["%-19s" % mode for mode in modes])
+ + " | ".join(["%-19s" % mode for mode in modes]),
)
print(""" """, end="")
@@ -58,7 +58,7 @@ def table_entry(mode1, bind_type1, mode2, bind_type2):
print(
"""
-first bind -----------------------------------------------------------------"""
+first bind -----------------------------------------------------------------""",
# default | wildcard | INUSE | Success | ACCESS | Success | INUSE | Success
)
@@ -72,5 +72,5 @@ def table_entry(mode1, bind_type1, mode2, bind_type2):
# print(mode1, bind_type1, mode2, bind_type2, entry)
print(
f"{mode1:>19} | {bind_type1:>8} | "
- + " | ".join(["%8s" % entry for entry in row])
+ + " | ".join(["%8s" % entry for entry in row]),
)
diff --git a/notes-to-self/manual-signal-handler.py b/notes-to-self/manual-signal-handler.py
index d865fa89ee..ead7f84d7d 100644
--- a/notes-to-self/manual-signal-handler.py
+++ b/notes-to-self/manual-signal-handler.py
@@ -11,11 +11,12 @@
"""
void* WINAPI GetProcAddress(void* hModule, char* lpProcName);
typedef void (*PyOS_sighandler_t)(int);
- """
+ """,
)
kernel32 = ffi.dlopen("kernel32.dll")
PyOS_getsig_ptr = kernel32.GetProcAddress(
- ffi.cast("void*", sys.dllhandle), b"PyOS_getsig"
+ ffi.cast("void*", sys.dllhandle),
+ b"PyOS_getsig",
)
PyOS_getsig = ffi.cast("PyOS_sighandler_t (*)(int)", PyOS_getsig_ptr)
diff --git a/notes-to-self/proxy-benchmarks.py b/notes-to-self/proxy-benchmarks.py
index d28909a347..830327cf48 100644
--- a/notes-to-self/proxy-benchmarks.py
+++ b/notes-to-self/proxy-benchmarks.py
@@ -54,7 +54,7 @@ def add_wrapper(cls, method):
f"""
def wrapper(self, *args, **kwargs):
return self._wrapped.{method}(*args, **kwargs)
- """
+ """,
)
ns = {}
exec(code, ns)
@@ -113,7 +113,7 @@ def setter(self, newval):
def deleter(self):
del self._wrapped.{attr}
- """
+ """,
)
ns = {}
exec(code, ns)
diff --git a/notes-to-self/ssl-handshake/ssl-handshake.py b/notes-to-self/ssl-handshake/ssl-handshake.py
index e906bc2a87..1665ce3331 100644
--- a/notes-to-self/ssl-handshake/ssl-handshake.py
+++ b/notes-to-self/ssl-handshake/ssl-handshake.py
@@ -27,7 +27,9 @@ 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()
@@ -101,7 +103,9 @@ 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-1.example.org"
+ client_ctx,
+ client_sock,
+ server_hostname="trio-test-1.example.org",
)
wrapped.do_handshake()
wrapped.sendall(b"x")
@@ -113,7 +117,9 @@ 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()
@@ -126,7 +132,9 @@ 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...
diff --git a/notes-to-self/wakeup-fd-racer.py b/notes-to-self/wakeup-fd-racer.py
index b56cbdc91c..97faa0dfdf 100644
--- a/notes-to-self/wakeup-fd-racer.py
+++ b/notes-to-self/wakeup-fd-racer.py
@@ -91,7 +91,7 @@ def main():
if duration < 2:
print(
f"Attempt {attempt}: OK, trying again "
- f"(select_calls = {select_calls}, drained = {drained})"
+ f"(select_calls = {select_calls}, drained = {drained})",
)
else:
print(f"Attempt {attempt}: FAILED, took {duration} seconds")
diff --git a/notes-to-self/win-waitable-timer.py b/notes-to-self/win-waitable-timer.py
index 43e6acd34a..b8d9af6cad 100644
--- a/notes-to-self/win-waitable-timer.py
+++ b/notes-to-self/win-waitable-timer.py
@@ -49,7 +49,7 @@
WORD wSecond;
WORD wMilliseconds;
} SYSTEMTIME, *PSYSTEMTIME, *LPSYSTEMTIME;
-"""
+""",
)
ffi.cdef(
diff --git a/pyproject.toml b/pyproject.toml
index 916e37360c..ff4aa34600 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -109,6 +109,7 @@ select = [
"ASYNC", # flake8-async
"B", # flake8-bugbear
"C4", # flake8-comprehensions
+ "COM", # flake8-commas
"E", # Error
"EXE", # flake8-executable
"F", # pyflakes
diff --git a/src/trio/_abc.py b/src/trio/_abc.py
index cba058c4df..306ee227fc 100644
--- a/src/trio/_abc.py
+++ b/src/trio/_abc.py
@@ -198,7 +198,9 @@ async def getaddrinfo(
@abstractmethod
async def getnameinfo(
- self, sockaddr: tuple[str, int] | tuple[str, int, int, int], flags: int
+ self,
+ sockaddr: tuple[str, int] | tuple[str, int, int, int],
+ flags: int,
) -> tuple[str, str]:
"""A custom implementation of :func:`~trio.socket.getnameinfo`.
diff --git a/src/trio/_channel.py b/src/trio/_channel.py
index f5ed4004d7..8a7dba140c 100644
--- a/src/trio/_channel.py
+++ b/src/trio/_channel.py
@@ -96,7 +96,8 @@ def _open_memory_channel(
# Need to use Tuple instead of tuple due to CI check running on 3.8
class open_memory_channel(Tuple["MemorySendChannel[T]", "MemoryReceiveChannel[T]"]):
def __new__( # type: ignore[misc] # "must return a subtype"
- cls, max_buffer_size: int | float # noqa: PYI041
+ cls,
+ max_buffer_size: int | float, # noqa: PYI041
) -> tuple[MemorySendChannel[T], MemoryReceiveChannel[T]]:
return _open_memory_channel(max_buffer_size)
diff --git a/src/trio/_core/_asyncgens.py b/src/trio/_core/_asyncgens.py
index 1a622dadfc..77f1c7eced 100644
--- a/src/trio/_core/_asyncgens.py
+++ b/src/trio/_core/_asyncgens.py
@@ -60,7 +60,8 @@ def firstiter(agen: AsyncGeneratorType[object, NoReturn]) -> None:
self.prev_hooks.firstiter(agen)
def finalize_in_trio_context(
- agen: AsyncGeneratorType[object, NoReturn], agen_name: str
+ agen: AsyncGeneratorType[object, NoReturn],
+ agen_name: str,
) -> None:
try:
runner.spawn_system_task(
@@ -85,7 +86,9 @@ def finalizer(agen: AsyncGeneratorType[object, NoReturn]) -> None:
if is_ours:
runner.entry_queue.run_sync_soon(
- finalize_in_trio_context, agen, agen_name
+ finalize_in_trio_context,
+ agen,
+ agen_name,
)
# Do this last, because it might raise an exception
@@ -123,7 +126,7 @@ def finalizer(agen: AsyncGeneratorType[object, NoReturn]) -> None:
raise RuntimeError(
f"Non-Trio async generator {agen_name!r} awaited something "
"during finalization; install a finalization hook to "
- "support this, or wrap it in 'async with aclosing(...):'"
+ "support this, or wrap it in 'async with aclosing(...):'",
)
self.prev_hooks = sys.get_asyncgen_hooks()
@@ -146,7 +149,7 @@ async def finalize_remaining(self, runner: _run.Runner) -> None:
# them was an asyncgen finalizer that snuck in under the wire.
runner.entry_queue.run_sync_soon(runner.reschedule, runner.init_task)
await _core.wait_task_rescheduled(
- lambda _: _core.Abort.FAILED # pragma: no cover
+ lambda _: _core.Abort.FAILED, # pragma: no cover
)
self.alive.update(self.trailing_needs_finalize)
self.trailing_needs_finalize.clear()
@@ -193,7 +196,9 @@ def close(self) -> None:
sys.set_asyncgen_hooks(*self.prev_hooks)
async def _finalize_one(
- self, agen: AsyncGeneratorType[object, NoReturn], name: object
+ self,
+ agen: AsyncGeneratorType[object, NoReturn],
+ name: object,
) -> None:
try:
# This shield ensures that finalize_asyncgen never exits
diff --git a/src/trio/_core/_concat_tb.py b/src/trio/_core/_concat_tb.py
index 497d37f8ad..2ddaf2e8e6 100644
--- a/src/trio/_core/_concat_tb.py
+++ b/src/trio/_core/_concat_tb.py
@@ -104,7 +104,8 @@ def controller(operation: tputil.ProxyOperation) -> Any | None: # type: ignore[
return operation.delegate() # Delegate is reverting to original behaviour
return cast(
- TracebackType, tputil.make_proxy(controller, type(base_tb), base_tb)
+ TracebackType,
+ tputil.make_proxy(controller, type(base_tb), base_tb),
) # Returns proxy to traceback
@@ -112,7 +113,8 @@ def controller(operation: tputil.ProxyOperation) -> Any | None: # type: ignore[
# `strict_exception_groups=False`. Once that is retired this function and its helper can
# be removed as well.
def concat_tb(
- head: TracebackType | None, tail: TracebackType | None
+ head: TracebackType | None,
+ tail: TracebackType | None,
) -> TracebackType | None:
# We have to use an iterative algorithm here, because in the worst case
# this might be a RecursionError stack that is by definition too deep to
diff --git a/src/trio/_core/_entry_queue.py b/src/trio/_core/_entry_queue.py
index 8305cd1629..8534bc07dc 100644
--- a/src/trio/_core/_entry_queue.py
+++ b/src/trio/_core/_entry_queue.py
@@ -77,7 +77,7 @@ async def kill_everything(exc: BaseException) -> NoReturn:
parent_nursery = _core.current_task().parent_nursery
if parent_nursery is None:
raise AssertionError(
- "Internal error: `parent_nursery` should never be `None`"
+ "Internal error: `parent_nursery` should never be `None`",
) from exc # pragma: no cover
parent_nursery.start_soon(kill_everything, exc)
diff --git a/src/trio/_core/_generated_io_kqueue.py b/src/trio/_core/_generated_io_kqueue.py
index e150fc21f9..eb230597b3 100644
--- a/src/trio/_core/_generated_io_kqueue.py
+++ b/src/trio/_core/_generated_io_kqueue.py
@@ -42,7 +42,8 @@ def current_kqueue() -> select.kqueue:
def monitor_kevent(
- ident: int, filter: int
+ ident: int,
+ filter: int,
) -> ContextManager[_core.UnboundedQueue[select.kevent]]:
"""TODO: these are implemented, but are currently more of a sketch than
anything real. See `#26
@@ -56,7 +57,9 @@ def monitor_kevent(
async def wait_kevent(
- ident: int, filter: int, abort_func: Callable[[RaiseCancelT], Abort]
+ ident: int,
+ filter: int,
+ abort_func: Callable[[RaiseCancelT], Abort],
) -> Abort:
"""TODO: these are implemented, but are currently more of a sketch than
anything real. See `#26
@@ -65,7 +68,9 @@ async def wait_kevent(
sys._getframe().f_locals[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
try:
return await GLOBAL_RUN_CONTEXT.runner.io_manager.wait_kevent(
- ident, filter, abort_func
+ ident,
+ filter,
+ abort_func,
)
except AttributeError:
raise RuntimeError("must be called from async context") from None
diff --git a/src/trio/_core/_generated_io_windows.py b/src/trio/_core/_generated_io_windows.py
index 72264f599f..1cad305a03 100644
--- a/src/trio/_core/_generated_io_windows.py
+++ b/src/trio/_core/_generated_io_windows.py
@@ -134,14 +134,17 @@ async def wait_overlapped(handle_: int | CData, lpOverlapped: CData | int) -> ob
sys._getframe().f_locals[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
try:
return await GLOBAL_RUN_CONTEXT.runner.io_manager.wait_overlapped(
- handle_, lpOverlapped
+ handle_,
+ lpOverlapped,
)
except AttributeError:
raise RuntimeError("must be called from async context") from None
async def write_overlapped(
- handle: int | CData, data: Buffer, file_offset: int = 0
+ handle: int | CData,
+ data: Buffer,
+ file_offset: int = 0,
) -> int:
"""TODO: these are implemented, but are currently more of a sketch than
anything real. See `#26
@@ -151,14 +154,18 @@ async def write_overlapped(
sys._getframe().f_locals[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
try:
return await GLOBAL_RUN_CONTEXT.runner.io_manager.write_overlapped(
- handle, data, file_offset
+ handle,
+ data,
+ file_offset,
)
except AttributeError:
raise RuntimeError("must be called from async context") from None
async def readinto_overlapped(
- handle: int | CData, buffer: Buffer, file_offset: int = 0
+ handle: int | CData,
+ buffer: Buffer,
+ file_offset: int = 0,
) -> int:
"""TODO: these are implemented, but are currently more of a sketch than
anything real. See `#26
@@ -168,7 +175,9 @@ async def readinto_overlapped(
sys._getframe().f_locals[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
try:
return await GLOBAL_RUN_CONTEXT.runner.io_manager.readinto_overlapped(
- handle, buffer, file_offset
+ handle,
+ buffer,
+ file_offset,
)
except AttributeError:
raise RuntimeError("must be called from async context") from None
diff --git a/src/trio/_core/_generated_run.py b/src/trio/_core/_generated_run.py
index ac3e0f39d6..b5957a134e 100644
--- a/src/trio/_core/_generated_run.py
+++ b/src/trio/_core/_generated_run.py
@@ -187,7 +187,10 @@ def spawn_system_task(
sys._getframe().f_locals[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
try:
return GLOBAL_RUN_CONTEXT.runner.spawn_system_task(
- async_fn, *args, name=name, context=context
+ async_fn,
+ *args,
+ name=name,
+ context=context,
)
except AttributeError:
raise RuntimeError("must be called from async context") from None
diff --git a/src/trio/_core/_io_epoll.py b/src/trio/_core/_io_epoll.py
index 29b378a014..5e05f0813f 100644
--- a/src/trio/_core/_io_epoll.py
+++ b/src/trio/_core/_io_epoll.py
@@ -205,7 +205,7 @@ class EpollIOManager:
_epoll: select.epoll = attrs.Factory(lambda: select.epoll())
# {fd: EpollWaiters}
_registered: defaultdict[int, EpollWaiters] = attrs.Factory(
- lambda: defaultdict(EpollWaiters)
+ lambda: defaultdict(EpollWaiters),
)
_force_wakeup: WakeupSocketpair = attrs.Factory(WakeupSocketpair)
_force_wakeup_fd: int | None = None
@@ -298,7 +298,7 @@ async def _epoll_wait(self, fd: int | _HasFileNo, attr_name: str) -> None:
waiters = self._registered[fd]
if getattr(waiters, attr_name) is not None:
raise _core.BusyResourceError(
- "another task is already reading / writing this fd"
+ "another task is already reading / writing this fd",
)
setattr(waiters, attr_name, _core.current_task())
self._update_registrations(fd)
diff --git a/src/trio/_core/_io_kqueue.py b/src/trio/_core/_io_kqueue.py
index 3d0aed7d35..13f95b22b2 100644
--- a/src/trio/_core/_io_kqueue.py
+++ b/src/trio/_core/_io_kqueue.py
@@ -43,7 +43,9 @@ class KqueueIOManager:
def __attrs_post_init__(self) -> None:
force_wakeup_event = select.kevent(
- self._force_wakeup.wakeup_sock, select.KQ_FILTER_READ, select.KQ_EV_ADD
+ self._force_wakeup.wakeup_sock,
+ select.KQ_FILTER_READ,
+ select.KQ_EV_ADD,
)
self._kqueue.control([force_wakeup_event], 0)
self._force_wakeup_fd = self._force_wakeup.wakeup_sock.fileno()
@@ -129,7 +131,7 @@ def monitor_kevent(
key = (ident, filter)
if key in self._registered:
raise _core.BusyResourceError(
- "attempt to register multiple listeners for same ident/filter pair"
+ "attempt to register multiple listeners for same ident/filter pair",
)
q = _core.UnboundedQueue[select.kevent]()
self._registered[key] = q
@@ -152,7 +154,7 @@ async def wait_kevent(
key = (ident, filter)
if key in self._registered:
raise _core.BusyResourceError(
- "attempt to register multiple listeners for same ident/filter pair"
+ "attempt to register multiple listeners for same ident/filter pair",
)
self._registered[key] = _core.current_task()
@@ -286,5 +288,5 @@ def notify_closing(self, fd: int | _HasFileNo) -> None:
# XX this is an interesting example of a case where being able
# to close a queue would be useful...
raise NotImplementedError(
- "can't close an fd that monitor_kevent is using"
+ "can't close an fd that monitor_kevent is using",
)
diff --git a/src/trio/_core/_io_windows.py b/src/trio/_core/_io_windows.py
index 99cb7c76be..0bc3b33378 100644
--- a/src/trio/_core/_io_windows.py
+++ b/src/trio/_core/_io_windows.py
@@ -303,7 +303,9 @@ def _check(success: T) -> T:
def _get_underlying_socket(
- sock: _HasFileNo | int | Handle, *, which: WSAIoctls = WSAIoctls.SIO_BASE_HANDLE
+ sock: _HasFileNo | int | Handle,
+ *,
+ which: WSAIoctls = WSAIoctls.SIO_BASE_HANDLE,
) -> Handle:
if hasattr(sock, "fileno"):
sock = sock.fileno()
@@ -354,7 +356,8 @@ def _get_base_socket(sock: _HasFileNo | int | Handle) -> Handle:
sock = sock.fileno()
sock = _handle(sock)
next_sock = _get_underlying_socket(
- sock, which=WSAIoctls.SIO_BSP_HANDLE_POLL
+ sock,
+ which=WSAIoctls.SIO_BSP_HANDLE_POLL,
)
if next_sock == sock:
# If BSP_HANDLE_POLL returns the same socket we already had,
@@ -366,7 +369,7 @@ def _get_base_socket(sock: _HasFileNo | int | Handle) -> Handle:
"return a different socket. Please file a bug at "
"https://github.com/python-trio/trio/issues/new, "
"and include the output of running: "
- "netsh winsock show catalog"
+ "netsh winsock show catalog",
) from ex
# Otherwise we've gotten at least one layer deeper, so
# loop back around to keep digging.
@@ -421,7 +424,7 @@ def __init__(self) -> None:
self._all_afd_handles: list[Handle] = []
self._iocp = _check(
- kernel32.CreateIoCompletionPort(INVALID_HANDLE_VALUE, ffi.NULL, 0, 0)
+ kernel32.CreateIoCompletionPort(INVALID_HANDLE_VALUE, ffi.NULL, 0, 0),
)
self._events = ffi.new("OVERLAPPED_ENTRY[]", MAX_EVENTS)
@@ -454,7 +457,8 @@ def __init__(self) -> None:
# LSPs can in theory override this, but we believe that it never
# actually happens in the wild (except Komodia)
select_handle = _get_underlying_socket(
- s, which=WSAIoctls.SIO_BSP_HANDLE_SELECT
+ s,
+ which=WSAIoctls.SIO_BSP_HANDLE_SELECT,
)
try:
# LSPs shouldn't override this...
@@ -472,7 +476,7 @@ def __init__(self) -> None:
"Please file a bug at "
"https://github.com/python-trio/trio/issues/new, "
"and include the output of running: "
- "netsh winsock show catalog"
+ "netsh winsock show catalog",
)
def close(self) -> None:
@@ -508,8 +512,11 @@ def force_wakeup(self) -> None:
assert self._iocp is not None
_check(
kernel32.PostQueuedCompletionStatus(
- self._iocp, 0, CKeys.FORCE_WAKEUP, ffi.NULL
- )
+ self._iocp,
+ 0,
+ CKeys.FORCE_WAKEUP,
+ ffi.NULL,
+ ),
)
def get_events(self, timeout: float) -> EventResult:
@@ -521,8 +528,13 @@ def get_events(self, timeout: float) -> EventResult:
assert self._iocp is not None
_check(
kernel32.GetQueuedCompletionStatusEx(
- self._iocp, self._events, MAX_EVENTS, received, milliseconds, 0
- )
+ self._iocp,
+ self._events,
+ MAX_EVENTS,
+ received,
+ milliseconds,
+ 0,
+ ),
)
except OSError as exc:
if exc.winerror != ErrorCodes.WAIT_TIMEOUT: # pragma: no cover
@@ -563,7 +575,8 @@ def process_events(self, received: EventResult) -> None:
overlapped = entry.lpOverlapped
transferred = entry.dwNumberOfBytesTransferred
info = CompletionKeyEventInfo(
- lpOverlapped=overlapped, dwNumberOfBytesTransferred=transferred
+ lpOverlapped=overlapped,
+ dwNumberOfBytesTransferred=transferred,
)
_core.reschedule(waiter, Value(info))
elif entry.lpCompletionKey == CKeys.LATE_CANCEL:
@@ -586,7 +599,7 @@ def process_events(self, received: EventResult) -> None:
exc = _core.TrioInternalError(
f"Failed to cancel overlapped I/O in {waiter.name} and didn't "
"receive the completion either. Did you forget to "
- "call register_with_iocp()?"
+ "call register_with_iocp()?",
)
# Raising this out of handle_io ensures that
# the user will see our message even if some
@@ -608,7 +621,8 @@ def process_events(self, received: EventResult) -> None:
overlapped = int(ffi.cast("uintptr_t", entry.lpOverlapped))
transferred = entry.dwNumberOfBytesTransferred
info = CompletionKeyEventInfo(
- lpOverlapped=overlapped, dwNumberOfBytesTransferred=transferred
+ lpOverlapped=overlapped,
+ dwNumberOfBytesTransferred=transferred,
)
queue.put_nowait(info)
@@ -622,8 +636,9 @@ def _register_with_iocp(self, handle_: int | CData, completion_key: int) -> None
# Ref: http://www.lenholgate.com/blog/2009/09/interesting-blog-posts-on-high-performance-servers.html
_check(
kernel32.SetFileCompletionNotificationModes(
- handle, CompletionModes.FILE_SKIP_SET_EVENT_ON_HANDLE
- )
+ handle,
+ CompletionModes.FILE_SKIP_SET_EVENT_ON_HANDLE,
+ ),
)
################################################################
@@ -637,8 +652,9 @@ def _refresh_afd(self, base_handle: Handle) -> None:
try:
_check(
kernel32.CancelIoEx(
- afd_group.handle, waiters.current_op.lpOverlapped
- )
+ afd_group.handle,
+ waiters.current_op.lpOverlapped,
+ ),
)
except OSError as exc:
if exc.winerror != ErrorCodes.ERROR_NOT_FOUND:
@@ -687,7 +703,7 @@ def _refresh_afd(self, base_handle: Handle) -> None:
ffi.sizeof("AFD_POLL_INFO"),
ffi.NULL,
lpOverlapped,
- )
+ ),
)
except OSError as exc:
if exc.winerror != ErrorCodes.ERROR_IO_PENDING:
@@ -813,7 +829,9 @@ def register_with_iocp(self, handle: int | CData) -> None:
@_public
async def wait_overlapped(
- self, handle_: int | CData, lpOverlapped: CData | int
+ self,
+ handle_: int | CData,
+ lpOverlapped: CData | int,
) -> object:
"""TODO: these are implemented, but are currently more of a sketch than
anything real. See `#26
@@ -825,7 +843,7 @@ async def wait_overlapped(
lpOverlapped = ffi.cast("LPOVERLAPPED", lpOverlapped)
if lpOverlapped in self._overlapped_waiters:
raise _core.BusyResourceError(
- "another task is already waiting on that lpOverlapped"
+ "another task is already waiting on that lpOverlapped",
)
task = _core.current_task()
self._overlapped_waiters[lpOverlapped] = task
@@ -852,8 +870,11 @@ def abort(raise_cancel_: RaiseCancelT) -> Abort:
# does, we'll assume the handle wasn't registered.
_check(
kernel32.PostQueuedCompletionStatus(
- self._iocp, 0, CKeys.LATE_CANCEL, lpOverlapped
- )
+ self._iocp,
+ 0,
+ CKeys.LATE_CANCEL,
+ lpOverlapped,
+ ),
)
# Keep the lpOverlapped referenced so its address
# doesn't get reused until our posted completion
@@ -863,7 +884,7 @@ def abort(raise_cancel_: RaiseCancelT) -> Abort:
self._posted_too_late_to_cancel.add(lpOverlapped)
else: # pragma: no cover
raise _core.TrioInternalError(
- "CancelIoEx failed with unexpected error"
+ "CancelIoEx failed with unexpected error",
) from exc
return _core.Abort.FAILED
@@ -888,7 +909,9 @@ def abort(raise_cancel_: RaiseCancelT) -> Abort:
return info
async def _perform_overlapped(
- self, handle: int | CData, submit_fn: Callable[[_Overlapped], None]
+ self,
+ handle: int | CData,
+ submit_fn: Callable[[_Overlapped], None],
) -> _Overlapped:
# submit_fn(lpOverlapped) submits some I/O
# it may raise an OSError with ERROR_IO_PENDING
@@ -909,7 +932,10 @@ async def _perform_overlapped(
@_public
async def write_overlapped(
- self, handle: int | CData, data: Buffer, file_offset: int = 0
+ self,
+ handle: int | CData,
+ data: Buffer,
+ file_offset: int = 0,
) -> int:
"""TODO: these are implemented, but are currently more of a sketch than
anything real. See `#26
@@ -930,7 +956,7 @@ def submit_write(lpOverlapped: _Overlapped) -> None:
len(cbuf),
ffi.NULL,
lpOverlapped,
- )
+ ),
)
lpOverlapped = await self._perform_overlapped(handle, submit_write)
@@ -939,7 +965,10 @@ def submit_write(lpOverlapped: _Overlapped) -> None:
@_public
async def readinto_overlapped(
- self, handle: int | CData, buffer: Buffer, file_offset: int = 0
+ self,
+ handle: int | CData,
+ buffer: Buffer,
+ file_offset: int = 0,
) -> int:
"""TODO: these are implemented, but are currently more of a sketch than
anything real. See `#26
@@ -959,7 +988,7 @@ def submit_read(lpOverlapped: _Overlapped) -> None:
len(cbuf),
ffi.NULL,
lpOverlapped,
- )
+ ),
)
lpOverlapped = await self._perform_overlapped(handle, submit_read)
diff --git a/src/trio/_core/_parking_lot.py b/src/trio/_core/_parking_lot.py
index c01fc2a9d8..916e6a6e96 100644
--- a/src/trio/_core/_parking_lot.py
+++ b/src/trio/_core/_parking_lot.py
@@ -182,7 +182,10 @@ def unpark_all(self) -> list[Task]:
@_core.enable_ki_protection
def repark(
- self, new_lot: ParkingLot, *, count: int | float = 1 # noqa: PYI041
+ self,
+ new_lot: ParkingLot,
+ *,
+ count: int | float = 1, # noqa: PYI041
) -> None:
"""Move parked tasks from one :class:`ParkingLot` object to another.
diff --git a/src/trio/_core/_run.py b/src/trio/_core/_run.py
index 093d3a202a..8921ed7f12 100644
--- a/src/trio/_core/_run.py
+++ b/src/trio/_core/_run.py
@@ -142,7 +142,7 @@ def function_with_unique_name_xyzzy() -> NoReturn:
raise
else: # pragma: no cover
raise TrioInternalError(
- "A ZeroDivisionError should have been raised, but it wasn't."
+ "A ZeroDivisionError should have been raised, but it wasn't.",
)
ctx = copy_context()
@@ -160,7 +160,7 @@ def function_with_unique_name_xyzzy() -> NoReturn:
else: # pragma: no cover
raise TrioInternalError(
f"The purpose of {function_with_unique_name_xyzzy.__name__} is "
- "to raise a ZeroDivisionError, but it didn't."
+ "to raise a ZeroDivisionError, but it didn't.",
)
@@ -219,7 +219,8 @@ def collapse_exception_group(
and NONSTRICT_EXCEPTIONGROUP_NOTE in getattr(excgroup, "__notes__", ())
):
exceptions[0].__traceback__ = concat_tb(
- excgroup.__traceback__, exceptions[0].__traceback__
+ excgroup.__traceback__,
+ exceptions[0].__traceback__,
)
return exceptions[0]
elif modified:
@@ -550,7 +551,7 @@ def __enter__(self) -> Self:
task = _core.current_task()
if self._has_been_entered:
raise RuntimeError(
- "Each CancelScope may only be used for a single 'with' block"
+ "Each CancelScope may only be used for a single 'with' block",
)
self._has_been_entered = True
if current_time() >= self._deadline:
@@ -564,7 +565,7 @@ def _close(self, exc: BaseException | None) -> BaseException | None:
if self._cancel_status is None:
new_exc = RuntimeError(
f"Cancel scope stack corrupted: attempted to exit {self!r} "
- "which had already been exited"
+ "which had already been exited",
)
new_exc.__context__ = exc
return new_exc
@@ -586,7 +587,7 @@ def _close(self, exc: BaseException | None) -> BaseException | None:
# without changing any state.
new_exc = RuntimeError(
f"Cancel scope stack corrupted: attempted to exit {self!r} "
- f"from unrelated {scope_task!r}\n{MISNESTING_ADVICE}"
+ f"from unrelated {scope_task!r}\n{MISNESTING_ADVICE}",
)
new_exc.__context__ = exc
return new_exc
@@ -598,7 +599,7 @@ def _close(self, exc: BaseException | None) -> BaseException | None:
# pass silently.
new_exc = RuntimeError(
f"Cancel scope stack corrupted: attempted to exit {self!r} "
- f"in {scope_task!r} that's still within its child {scope_task._cancel_status._scope!r}\n{MISNESTING_ADVICE}"
+ f"in {scope_task!r} that's still within its child {scope_task._cancel_status._scope!r}\n{MISNESTING_ADVICE}",
)
new_exc.__context__ = exc
exc = new_exc
@@ -932,7 +933,9 @@ async def __aenter__(self) -> Nursery:
self._scope = CancelScope()
self._scope.__enter__()
self._nursery = Nursery._create(
- current_task(), self._scope, self.strict_exception_groups
+ current_task(),
+ self._scope,
+ self.strict_exception_groups,
)
return self._nursery
@@ -970,7 +973,7 @@ async def __aexit__(
def __enter__(self) -> NoReturn:
raise RuntimeError(
- "use 'async with open_nursery(...)', not 'with open_nursery(...)'"
+ "use 'async with open_nursery(...)', not 'with open_nursery(...)'",
)
def __exit__(
@@ -1099,7 +1102,8 @@ def _child_finished(self, task: Task, outcome: Outcome[Any]) -> None:
self._check_nursery_closed()
async def _nested_child_finished(
- self, nested_child_exc: BaseException | None
+ self,
+ nested_child_exc: BaseException | None,
) -> BaseException | None:
# Returns ExceptionGroup instance (or any exception if the nursery is in loose mode
# and there is just one contained exception) if there are pending exceptions
@@ -1138,7 +1142,8 @@ def aborted(raise_cancel: _core.RaiseCancelT) -> Abort:
if not self._strict_exception_groups and len(self._pending_excs) == 1:
return self._pending_excs[0]
exception = BaseExceptionGroup(
- "Exceptions from Trio nursery", self._pending_excs
+ "Exceptions from Trio nursery",
+ self._pending_excs,
)
if not self._strict_exception_groups:
exception.add_note(NONSTRICT_EXCEPTIONGROUP_NOTE)
@@ -1255,7 +1260,10 @@ async def async_fn(arg1, arg2, *, task_status=trio.TASK_STATUS_IGNORED):
task_status: _TaskStatus[Any] = _TaskStatus(old_nursery, self)
thunk = functools.partial(async_fn, task_status=task_status)
task = GLOBAL_RUN_CONTEXT.runner.spawn_impl(
- thunk, args, old_nursery, name
+ thunk,
+ args,
+ old_nursery,
+ name,
)
task._eventual_parent_nursery = self
# Wait for either TaskStatus.started or an exception to
@@ -1266,7 +1274,7 @@ async def async_fn(arg1, arg2, *, task_status=trio.TASK_STATUS_IGNORED):
raise TrioInternalError(
"Internal nursery should not have multiple tasks. This can be "
'caused by the user managing to access the "old" nursery in '
- "`task_status` and spawning tasks in it."
+ "`task_status` and spawning tasks in it.",
) from exc
# If we get here, then the child either got reparented or exited
@@ -1557,7 +1565,8 @@ def guest_tick(self) -> None:
# Optimization: try to skip going into the thread if we can avoid it
events_outcome: Value[EventResult] | Error = capture(
- self.runner.io_manager.get_events, 0
+ self.runner.io_manager.get_events,
+ 0,
)
if timeout <= 0 or isinstance(events_outcome, Error) or events_outcome.value:
# No need to go into the thread
@@ -1696,7 +1705,9 @@ def current_root_task(self) -> Task | None:
@_public # Type-ignore due to use of Any here.
def reschedule( # type: ignore[misc]
- self, task: Task, next_send: Outcome[Any] = _NO_SEND
+ self,
+ task: Task,
+ next_send: Outcome[Any] = _NO_SEND,
) -> None:
"""Reschedule the given task with the given
:class:`outcome.Outcome`.
@@ -1789,7 +1800,11 @@ async def python_wrapper(orig_coro: Awaitable[RetT]) -> RetT:
# Set up the Task object
######
task = Task._create(
- coro=coro, parent_nursery=nursery, runner=self, name=name, context=context
+ coro=coro,
+ parent_nursery=nursery,
+ runner=self,
+ name=name,
+ context=context,
)
self.tasks.add(task)
@@ -1819,7 +1834,7 @@ def task_exited(self, task: Task, outcome: Outcome[Any]) -> None:
# traceback frame included
raise RuntimeError(
"Cancel scope stack corrupted: cancel scope surrounding "
- f"{task!r} was closed before the task exited\n{MISNESTING_ADVICE}"
+ f"{task!r} was closed before the task exited\n{MISNESTING_ADVICE}",
)
except RuntimeError as new_exc:
if isinstance(outcome, Error):
@@ -1932,7 +1947,10 @@ async def init(
async with open_nursery() as main_task_nursery:
try:
self.main_task = self.spawn_impl(
- async_fn, args, main_task_nursery, None
+ async_fn,
+ args,
+ main_task_nursery,
+ None,
)
except BaseException as exc:
self.main_task_outcome = Error(exc)
@@ -2424,7 +2442,8 @@ def my_done_callback(run_outcome):
# this time, so it shouldn't be possible to get an exception here,
# except for a TrioInternalError.
next_send = cast(
- EventResult, None
+ EventResult,
+ None,
) # First iteration must be `None`, every iteration after that is EventResult
for _tick in range(5): # expected need is 2 iterations + leave some wiggle room
if runner.system_nursery is not None:
@@ -2434,13 +2453,13 @@ def my_done_callback(run_outcome):
timeout = guest_state.unrolled_run_gen.send(next_send)
except StopIteration: # pragma: no cover
raise TrioInternalError(
- "Guest runner exited before system nursery was initialized"
+ "Guest runner exited before system nursery was initialized",
) from None
if timeout != 0: # pragma: no cover
guest_state.unrolled_run_gen.throw(
TrioInternalError(
- "Guest runner blocked before system nursery was initialized"
- )
+ "Guest runner blocked before system nursery was initialized",
+ ),
)
# next_send should be the return value of
# IOManager.get_events() if no I/O was waiting, which is
@@ -2454,8 +2473,8 @@ def my_done_callback(run_outcome):
guest_state.unrolled_run_gen.throw(
TrioInternalError(
"Guest runner yielded too many times before "
- "system nursery was initialized"
- )
+ "system nursery was initialized",
+ ),
)
guest_state.unrolled_run_next_send = Value(next_send)
@@ -2488,7 +2507,11 @@ def unrolled_run(
runner.instruments.call("before_run")
runner.clock.start_clock()
runner.init_task = runner.spawn_impl(
- runner.init, (async_fn, args), None, "", system_task=True
+ runner.init,
+ (async_fn, args),
+ None,
+ "",
+ system_task=True,
)
# You know how people talk about "event loops"? This 'while' loop right
@@ -2666,7 +2689,7 @@ def unrolled_run(
f"trio.run received unrecognized yield message {msg!r}. "
"Are you trying to use a library written for some "
"other framework like asyncio? That won't work "
- "without some kind of compatibility shim."
+ "without some kind of compatibility shim.",
)
# The foreign library probably doesn't adhere to our
# protocol of unwrapping whatever outcome gets sent in.
@@ -2690,7 +2713,7 @@ def unrolled_run(
warnings.warn(
RuntimeWarning(
"Trio guest run got abandoned without properly finishing... "
- "weird stuff might happen"
+ "weird stuff might happen",
),
stacklevel=1,
)
diff --git a/src/trio/_core/_tests/test_asyncgen.py b/src/trio/_core/_tests/test_asyncgen.py
index aa64a49d70..7f029771b3 100644
--- a/src/trio/_core/_tests/test_asyncgen.py
+++ b/src/trio/_core/_tests/test_asyncgen.py
@@ -43,7 +43,8 @@ async def example(cause: str) -> AsyncGenerator[int, None]:
async def async_main() -> None:
# GC'ed before exhausted
with pytest.warns(
- ResourceWarning, match="Async generator.*collected before.*exhausted"
+ ResourceWarning,
+ match="Async generator.*collected before.*exhausted",
):
assert await example("abandoned").asend(None) == 42
gc_collect_harder()
@@ -153,7 +154,8 @@ async def innermost() -> AsyncGenerator[int, None]:
record.append("innermost")
async def agen(
- label: int, inner: AsyncGenerator[int, None]
+ label: int,
+ inner: AsyncGenerator[int, None],
) -> AsyncGenerator[int, None]:
try:
yield await inner.asend(None)
@@ -197,7 +199,8 @@ def collect_at_opportune_moment(token: _core._entry_queue.TrioToken) -> None:
runner = _core._run.GLOBAL_RUN_CONTEXT.runner
assert runner.system_nursery is not None
if runner.system_nursery._closed and isinstance(
- runner.asyncgens.alive, weakref.WeakSet
+ runner.asyncgens.alive,
+ weakref.WeakSet,
):
saved.clear()
record.append("final collection")
@@ -235,7 +238,7 @@ async def async_main() -> None:
else: # pragma: no cover
pytest.fail(
"Didn't manage to hit the trailing_finalizer_asyncgens case "
- f"despite trying {_attempt} times"
+ f"despite trying {_attempt} times",
)
diff --git a/src/trio/_core/_tests/test_exceptiongroup_gc.py b/src/trio/_core/_tests/test_exceptiongroup_gc.py
index 8957a581a5..885ef68624 100644
--- a/src/trio/_core/_tests/test_exceptiongroup_gc.py
+++ b/src/trio/_core/_tests/test_exceptiongroup_gc.py
@@ -76,7 +76,8 @@ def test_concat_tb() -> None:
# Unclear if this can still fail, removing the `del` from _concat_tb.copy_tb does not seem
# to trigger it (on a platform where the `del` is executed)
@pytest.mark.skipif(
- sys.implementation.name != "cpython", reason="Only makes sense with refcounting GC"
+ sys.implementation.name != "cpython",
+ reason="Only makes sense with refcounting GC",
)
def test_ExceptionGroup_catch_doesnt_create_cyclic_garbage() -> None:
# https://github.com/python-trio/trio/pull/2063
diff --git a/src/trio/_core/_tests/test_guest_mode.py b/src/trio/_core/_tests/test_guest_mode.py
index 8972ec735a..1a8b230e78 100644
--- a/src/trio/_core/_tests/test_guest_mode.py
+++ b/src/trio/_core/_tests/test_guest_mode.py
@@ -62,7 +62,8 @@ def run_sync_soon_threadsafe(fn: Callable[[], object]) -> None:
nonlocal todo
if host_thread is threading.current_thread(): # pragma: no cover
crash = partial(
- pytest.fail, "run_sync_soon_threadsafe called from host thread"
+ pytest.fail,
+ "run_sync_soon_threadsafe called from host thread",
)
todo.put(("run", crash))
todo.put(("run", fn))
@@ -71,7 +72,8 @@ def run_sync_soon_not_threadsafe(fn: Callable[[], object]) -> None:
nonlocal todo
if host_thread is not threading.current_thread(): # pragma: no cover
crash = partial(
- pytest.fail, "run_sync_soon_not_threadsafe called from worker thread"
+ pytest.fail,
+ "run_sync_soon_not_threadsafe called from worker thread",
)
todo.put(("run", crash))
todo.put(("run", fn))
@@ -181,7 +183,9 @@ def after_start_never_runs() -> None: # pragma: no cover
# are raised out of start_guest_run, not out of the done_callback
with pytest.raises(trio.TrioInternalError):
trivial_guest_run(
- trio_main, clock=BadClock(), in_host_after_start=after_start_never_runs
+ trio_main,
+ clock=BadClock(),
+ in_host_after_start=after_start_never_runs,
)
@@ -331,7 +335,7 @@ async def sit_in_wait_all_tasks_blocked(watb_cscope: trio.CancelScope) -> None:
await trio.testing.wait_all_tasks_blocked(cushion=9999)
raise AssertionError( # pragma: no cover
"wait_all_tasks_blocked should *not* return normally, "
- "only by cancellation."
+ "only by cancellation.",
)
assert watb_cscope.cancelled_caught
@@ -486,7 +490,8 @@ async def trio_main() -> str:
raise AssertionError("should never be reached") # pragma: no cover
async def aio_pingpong(
- from_trio: asyncio.Queue[int], to_trio: MemorySendChannel[int]
+ from_trio: asyncio.Queue[int],
+ to_trio: MemorySendChannel[int],
) -> None:
print("aio_pingpong!")
@@ -525,7 +530,8 @@ async def aio_pingpong(
def test_guest_mode_internal_errors(
- monkeypatch: pytest.MonkeyPatch, recwarn: pytest.WarningsRecorder
+ monkeypatch: pytest.MonkeyPatch,
+ recwarn: pytest.WarningsRecorder,
) -> None:
with monkeypatch.context() as m:
diff --git a/src/trio/_core/_tests/test_io.py b/src/trio/_core/_tests/test_io.py
index acecc9d6c6..7060262edf 100644
--- a/src/trio/_core/_tests/test_io.py
+++ b/src/trio/_core/_tests/test_io.py
@@ -91,7 +91,9 @@ def fileno_wrapper(fileobj: stdlib_socket.socket) -> RetT:
@read_socket_test
@write_socket_test
async def test_wait_basic(
- socketpair: SocketPair, wait_readable: WaitSocket, wait_writable: WaitSocket
+ socketpair: SocketPair,
+ wait_readable: WaitSocket,
+ wait_writable: WaitSocket,
) -> None:
a, b = socketpair
@@ -215,7 +217,9 @@ async def writer() -> None:
@read_socket_test
@write_socket_test
async def test_socket_simultaneous_read_write(
- socketpair: SocketPair, wait_readable: WaitSocket, wait_writable: WaitSocket
+ socketpair: SocketPair,
+ wait_readable: WaitSocket,
+ wait_writable: WaitSocket,
) -> None:
record: list[str] = []
@@ -245,7 +249,9 @@ async def w_task(sock: stdlib_socket.socket) -> None:
@read_socket_test
@write_socket_test
async def test_socket_actual_streaming(
- socketpair: SocketPair, wait_readable: WaitSocket, wait_writable: WaitSocket
+ socketpair: SocketPair,
+ wait_readable: WaitSocket,
+ wait_writable: WaitSocket,
) -> None:
a, b = socketpair
diff --git a/src/trio/_core/_tests/test_parking_lot.py b/src/trio/_core/_tests/test_parking_lot.py
index 353c1ba45d..ed6a17012e 100644
--- a/src/trio/_core/_tests/test_parking_lot.py
+++ b/src/trio/_core/_tests/test_parking_lot.py
@@ -38,7 +38,8 @@ async def waiter(i: int, lot: ParkingLot) -> None:
assert len(record) == 6
check_sequence_matches(
- record, [{"sleep 0", "sleep 1", "sleep 2"}, {"wake 0", "wake 1", "wake 2"}]
+ record,
+ [{"sleep 0", "sleep 1", "sleep 2"}, {"wake 0", "wake 1", "wake 2"}],
)
async with _core.open_nursery() as nursery:
@@ -74,18 +75,23 @@ async def waiter(i: int, lot: ParkingLot) -> None:
lot.unpark(count=2)
await wait_all_tasks_blocked()
check_sequence_matches(
- record, ["sleep 0", "sleep 1", "sleep 2", {"wake 0", "wake 1"}]
+ record,
+ ["sleep 0", "sleep 1", "sleep 2", {"wake 0", "wake 1"}],
)
lot.unpark_all()
with pytest.raises(
- ValueError, match=r"^Cannot pop a non-integer number of tasks\.$"
+ ValueError,
+ match=r"^Cannot pop a non-integer number of tasks\.$",
):
lot.unpark(count=1.5)
async def cancellable_waiter(
- name: T, lot: ParkingLot, scopes: dict[T, _core.CancelScope], record: list[str]
+ name: T,
+ lot: ParkingLot,
+ scopes: dict[T, _core.CancelScope],
+ record: list[str],
) -> None:
with _core.CancelScope() as scope:
scopes[name] = scope
@@ -120,7 +126,8 @@ async def test_parking_lot_cancel() -> None:
assert len(record) == 6
check_sequence_matches(
- record, ["sleep 1", "sleep 2", "sleep 3", "cancelled 2", {"wake 1", "wake 3"}]
+ record,
+ ["sleep 1", "sleep 2", "sleep 3", "cancelled 2", {"wake 1", "wake 3"}],
)
diff --git a/src/trio/_core/_tests/test_run.py b/src/trio/_core/_tests/test_run.py
index ee823cb81a..e6ee5e869f 100644
--- a/src/trio/_core/_tests/test_run.py
+++ b/src/trio/_core/_tests/test_run.py
@@ -116,7 +116,7 @@ async def test_nursery_warn_use_async_with() -> None:
with on: # type: ignore
pass # pragma: no cover
excinfo.match(
- r"use 'async with open_nursery\(...\)', not 'with open_nursery\(...\)'"
+ r"use 'async with open_nursery\(...\)', not 'with open_nursery\(...\)'",
)
# avoid unawaited coro.
@@ -156,7 +156,8 @@ async def looper(whoami: str, record: list[tuple[str, int]]) -> None:
nursery.start_soon(looper, "b", record)
check_sequence_matches(
- record, [{("a", 0), ("b", 0)}, {("a", 1), ("b", 1)}, {("a", 2), ("b", 2)}]
+ record,
+ [{("a", 0), ("b", 0)}, {("a", 1), ("b", 1)}, {("a", 2), ("b", 2)}],
)
@@ -434,7 +435,10 @@ async def crasher() -> NoReturn:
# nursery block continue propagating to reach the
# outer scope.
with RaisesGroup(
- _core.Cancelled, _core.Cancelled, _core.Cancelled, KeyError
+ _core.Cancelled,
+ _core.Cancelled,
+ _core.Cancelled,
+ KeyError,
) as excinfo:
async with _core.open_nursery() as nursery:
# Two children that get cancelled by the nursery scope
@@ -769,7 +773,8 @@ async def task2() -> None:
nursery.cancel_scope.__exit__(None, None, None)
finally:
with pytest.raises(
- RuntimeError, match="which had already been exited"
+ RuntimeError,
+ match="which had already been exited",
) as exc_info:
await nursery_mgr.__aexit__(*sys.exc_info())
@@ -946,7 +951,7 @@ async def main() -> None:
# the second exceptiongroup is from the second nursery opened in Runner.init()
# the third exceptongroup is from the nursery defined in `system_task` above
assert RaisesGroup(RaisesGroup(RaisesGroup(KeyError, ValueError))).matches(
- excinfo.value.__cause__
+ excinfo.value.__cause__,
)
@@ -976,7 +981,7 @@ async def main() -> None:
# See explanation for triple-wrap in test_system_task_crash_ExceptionGroup
assert RaisesGroup(RaisesGroup(RaisesGroup(ValueError))).matches(
- excinfo.value.__cause__
+ excinfo.value.__cause__,
)
@@ -1126,13 +1131,18 @@ async def child() -> None:
await sleep_forever()
with RaisesGroup(
- Matcher(ValueError, "error text", lambda e: isinstance(e.__context__, KeyError))
+ Matcher(
+ ValueError,
+ "error text",
+ lambda e: isinstance(e.__context__, KeyError),
+ ),
):
async with _core.open_nursery() as nursery:
nursery.start_soon(child)
await wait_all_tasks_blocked()
_core.reschedule(
- not_none(child_task), outcome.Error(ValueError("error text"))
+ not_none(child_task),
+ outcome.Error(ValueError("error text")),
)
@@ -1195,13 +1205,14 @@ async def inner() -> None:
"^Unique Text$",
lambda e: isinstance(e.__context__, IndexError)
and isinstance(e.__context__.__context__, KeyError),
- )
+ ),
):
async with _core.open_nursery() as nursery:
nursery.start_soon(child)
await wait_all_tasks_blocked()
_core.reschedule(
- not_none(child_task), outcome.Error(ValueError("Unique Text"))
+ not_none(child_task),
+ outcome.Error(ValueError("Unique Text")),
)
@@ -1680,7 +1691,8 @@ async def async_gen(arg: T) -> AsyncGenerator[T, None]: # pragma: no cover
):
bad_call_run(f()) # type: ignore[arg-type]
with pytest.raises(
- TypeError, match="expected an async function but got an async generator"
+ TypeError,
+ match="expected an async function but got an async generator",
):
bad_call_run(async_gen, 0) # type: ignore
@@ -1690,7 +1702,7 @@ async def async_gen(arg: T) -> AsyncGenerator[T, None]: # pragma: no cover
bad_call_spawn(f()) # type: ignore[arg-type]
with RaisesGroup(
- Matcher(TypeError, "expected an async function but got an async generator")
+ Matcher(TypeError, "expected an async function but got an async generator"),
):
bad_call_spawn(async_gen, 0) # type: ignore
@@ -1768,7 +1780,9 @@ async def no_args() -> None: # pragma: no cover
await nursery.start(no_args)
async def sleep_then_start(
- seconds: int, *, task_status: _core.TaskStatus[int] = _core.TASK_STATUS_IGNORED
+ seconds: int,
+ *,
+ task_status: _core.TaskStatus[int] = _core.TASK_STATUS_IGNORED,
) -> None:
repr(task_status) # smoke test
await sleep(seconds)
@@ -1847,7 +1861,8 @@ async def just_started(
# but if the task does not execute any checkpoints, and exits, then start()
# doesn't raise Cancelled, since the task completed successfully.
async def started_with_no_checkpoint(
- *, task_status: _core.TaskStatus[None] = _core.TASK_STATUS_IGNORED
+ *,
+ task_status: _core.TaskStatus[None] = _core.TASK_STATUS_IGNORED,
) -> None:
task_status.started(None)
@@ -1861,7 +1876,8 @@ async def started_with_no_checkpoint(
# the child crashes after calling started(), the error can *still* come
# out of start()
async def raise_keyerror_after_started(
- *, task_status: _core.TaskStatus[None] = _core.TASK_STATUS_IGNORED
+ *,
+ task_status: _core.TaskStatus[None] = _core.TASK_STATUS_IGNORED,
) -> None:
task_status.started()
raise KeyError("whoopsiedaisy")
@@ -1924,7 +1940,8 @@ async def sleeping_children(
async with _core.open_nursery() as nursery:
target_nursery: _core.Nursery = await nursery.start(setup_nursery)
await target_nursery.start(
- sleeping_children, target_nursery.cancel_scope.cancel
+ sleeping_children,
+ target_nursery.cancel_scope.cancel,
)
# Cancelling the setup_nursery just *after* calling started()
@@ -2014,7 +2031,10 @@ def __init__(self, *largs: it) -> None:
self.nexts = [obj.__anext__ for obj in largs]
async def _accumulate(
- self, f: Callable[[], Awaitable[int]], items: list[int], i: int
+ self,
+ f: Callable[[], Awaitable[int]],
+ items: list[int],
+ i: int,
) -> None:
items[i] = await f()
@@ -2036,7 +2056,8 @@ async def __anext__(self) -> list[int]:
# We could also use RaisesGroup, but that's primarily meant as
# test infra, not as a runtime tool.
if len(e.exceptions) == 1 and isinstance(
- e.exceptions[0], StopAsyncIteration
+ e.exceptions[0],
+ StopAsyncIteration,
):
raise e.exceptions[0] from None
else: # pragma: no cover
@@ -2244,7 +2265,8 @@ async def detachable_coroutine(
nonlocal task, pdco_outcome
task = _core.current_task()
pdco_outcome = await outcome.acapture(
- _core.permanently_detach_coroutine_object, task_outcome
+ _core.permanently_detach_coroutine_object,
+ task_outcome,
)
await async_yield(yield_value)
@@ -2308,7 +2330,8 @@ def abort_fn(_: _core.RaiseCancelT) -> _core.Abort: # pragma: no cover
with pytest.raises(RuntimeError) as excinfo:
await _core.reattach_detached_coroutine_object(
- not_none(unrelated_task), None
+ not_none(unrelated_task),
+ None,
)
assert "does not match" in str(excinfo.value)
@@ -2409,7 +2432,8 @@ async def test_cancel_scope_deadline_duplicates() -> None:
# refer to this only seems to break test_cancel_scope_exit_doesnt_create_cyclic_garbage
# We're keeping it for now to cover Outcome and potential future refactoring
@pytest.mark.skipif(
- sys.implementation.name != "cpython", reason="Only makes sense with refcounting GC"
+ sys.implementation.name != "cpython",
+ reason="Only makes sense with refcounting GC",
)
async def test_simple_cancel_scope_usage_doesnt_create_cyclic_garbage() -> None:
# https://github.com/python-trio/trio/issues/1770
@@ -2448,7 +2472,8 @@ async def crasher() -> NoReturn:
@pytest.mark.skipif(
- sys.implementation.name != "cpython", reason="Only makes sense with refcounting GC"
+ sys.implementation.name != "cpython",
+ reason="Only makes sense with refcounting GC",
)
async def test_cancel_scope_exit_doesnt_create_cyclic_garbage() -> None:
# https://github.com/python-trio/trio/pull/2063
@@ -2460,7 +2485,7 @@ async def crasher() -> NoReturn:
old_flags = gc.get_debug()
try:
with RaisesGroup(
- Matcher(ValueError, "^this is a crash$")
+ Matcher(ValueError, "^this is a crash$"),
), _core.CancelScope() as outer:
async with _core.open_nursery() as nursery:
gc.collect()
@@ -2482,7 +2507,8 @@ async def crasher() -> NoReturn:
@pytest.mark.skipif(
- sys.implementation.name != "cpython", reason="Only makes sense with refcounting GC"
+ sys.implementation.name != "cpython",
+ reason="Only makes sense with refcounting GC",
)
async def test_nursery_cancel_doesnt_create_cyclic_garbage() -> None:
collected = False
@@ -2518,7 +2544,8 @@ def toggle_collected() -> None:
@pytest.mark.skipif(
- sys.implementation.name != "cpython", reason="Only makes sense with refcounting GC"
+ sys.implementation.name != "cpython",
+ reason="Only makes sense with refcounting GC",
)
async def test_locals_destroyed_promptly_on_cancel() -> None:
destroyed = False
@@ -2550,13 +2577,15 @@ def _create_kwargs(strictness: bool | None) -> dict[str, bool]:
@pytest.mark.filterwarnings(
- "ignore:.*strict_exception_groups=False:trio.TrioDeprecationWarning"
+ "ignore:.*strict_exception_groups=False:trio.TrioDeprecationWarning",
)
@pytest.mark.parametrize("run_strict", [True, False, None])
@pytest.mark.parametrize("open_nursery_strict", [True, False, None])
@pytest.mark.parametrize("multiple_exceptions", [True, False])
def test_setting_strict_exception_groups(
- run_strict: bool | None, open_nursery_strict: bool | None, multiple_exceptions: bool
+ run_strict: bool | None,
+ open_nursery_strict: bool | None,
+ multiple_exceptions: bool,
) -> None:
"""
Test default values and that nurseries can both inherit and override the global context
@@ -2593,7 +2622,7 @@ def run_main() -> None:
@pytest.mark.filterwarnings(
- "ignore:.*strict_exception_groups=False:trio.TrioDeprecationWarning"
+ "ignore:.*strict_exception_groups=False:trio.TrioDeprecationWarning",
)
@pytest.mark.parametrize("strict", [True, False, None])
async def test_nursery_collapse(strict: bool | None) -> None:
@@ -2635,7 +2664,7 @@ async def test_cancel_scope_no_cancellederror() -> None:
@pytest.mark.filterwarnings(
- "ignore:.*strict_exception_groups=False:trio.TrioDeprecationWarning"
+ "ignore:.*strict_exception_groups=False:trio.TrioDeprecationWarning",
)
@pytest.mark.parametrize("run_strict", [False, True])
@pytest.mark.parametrize("start_raiser_strict", [False, True, None])
@@ -2670,7 +2699,7 @@ async def raiser(*, task_status: _core.TaskStatus[None]) -> None:
async def start_raiser() -> None:
try:
async with _core.open_nursery(
- strict_exception_groups=start_raiser_strict
+ strict_exception_groups=start_raiser_strict,
) as nursery:
await nursery.start(raiser)
except BaseExceptionGroup as exc_group:
@@ -2680,7 +2709,8 @@ async def start_raiser() -> None:
# exception group raised by trio with a more specific one (subtype,
# different message, etc.).
raise BaseExceptionGroup(
- "start_raiser nursery custom message", exc_group.exceptions
+ "start_raiser nursery custom message",
+ exc_group.exceptions,
) from None
raise
diff --git a/src/trio/_core/_tests/test_unbounded_queue.py b/src/trio/_core/_tests/test_unbounded_queue.py
index 33eb41a5b3..e483ecd30a 100644
--- a/src/trio/_core/_tests/test_unbounded_queue.py
+++ b/src/trio/_core/_tests/test_unbounded_queue.py
@@ -8,7 +8,7 @@
from ...testing import assert_checkpoints, wait_all_tasks_blocked
pytestmark = pytest.mark.filterwarnings(
- "ignore:.*UnboundedQueue:trio.TrioDeprecationWarning"
+ "ignore:.*UnboundedQueue:trio.TrioDeprecationWarning",
)
diff --git a/src/trio/_core/_tests/test_windows.py b/src/trio/_core/_tests/test_windows.py
index 486a405590..e4a1bab615 100644
--- a/src/trio/_core/_tests/test_windows.py
+++ b/src/trio/_core/_tests/test_windows.py
@@ -54,7 +54,8 @@ def test_winerror(monkeypatch: pytest.MonkeyPatch) -> None:
mock.return_value = (12, "test error")
with pytest.raises(
- OSError, match=r"^\[WinError 12\] test error: 'file_1' -> 'file_2'$"
+ OSError,
+ match=r"^\[WinError 12\] test error: 'file_1' -> 'file_2'$",
) as exc:
raise_winerror(filename="file_1", filename2="file_2")
mock.assert_called_once_with()
@@ -66,7 +67,8 @@ def test_winerror(monkeypatch: pytest.MonkeyPatch) -> None:
# With an explicit number passed in, it overrides what getwinerror() returns.
with pytest.raises(
- OSError, match=r"^\[WinError 18\] test error: 'a/file' -> 'b/file'$"
+ OSError,
+ match=r"^\[WinError 18\] test error: 'a/file' -> 'b/file'$",
) as exc:
raise_winerror(18, filename="a/file", filename2="b/file")
mock.assert_called_once_with(18)
@@ -117,7 +119,8 @@ async def test_readinto_overlapped() -> None:
with tempfile.TemporaryDirectory() as tdir:
tfile = os.path.join(tdir, "numbers.txt")
with open( # noqa: ASYNC230 # This is a test, synchronous is ok
- tfile, "wb"
+ tfile,
+ "wb",
) as fp:
fp.write(data)
fp.flush()
@@ -141,7 +144,9 @@ async def test_readinto_overlapped() -> None:
async def read_region(start: int, end: int) -> None:
await _core.readinto_overlapped(
- handle, buffer_view[start:end], start
+ handle,
+ buffer_view[start:end],
+ start,
)
_core.register_with_iocp(handle)
@@ -184,7 +189,10 @@ async def main() -> None:
try:
async with _core.open_nursery() as nursery:
nursery.start_soon(
- _core.readinto_overlapped, read_handle, target, name="xyz"
+ _core.readinto_overlapped,
+ read_handle,
+ target,
+ name="xyz",
)
await wait_all_tasks_blocked()
nursery.cancel_scope.cancel()
@@ -241,7 +249,9 @@ def test_lsp_that_hooks_select_gives_good_error(
from .._windows_cffi import CData, WSAIoctls, _handle
def patched_get_underlying(
- sock: int | CData, *, which: int = WSAIoctls.SIO_BASE_HANDLE
+ sock: int | CData,
+ *,
+ which: int = WSAIoctls.SIO_BASE_HANDLE,
) -> CData:
if hasattr(sock, "fileno"): # pragma: no branch
sock = sock.fileno()
@@ -252,7 +262,8 @@ def patched_get_underlying(
monkeypatch.setattr(_io_windows, "_get_underlying_socket", patched_get_underlying)
with pytest.raises(
- RuntimeError, match="SIO_BASE_HANDLE and SIO_BSP_HANDLE_SELECT differ"
+ RuntimeError,
+ match="SIO_BASE_HANDLE and SIO_BSP_HANDLE_SELECT differ",
):
_core.run(sleep, 0)
@@ -269,7 +280,9 @@ def test_lsp_that_completely_hides_base_socket_gives_good_error(
from .._windows_cffi import CData, WSAIoctls, _handle
def patched_get_underlying(
- sock: int | CData, *, which: int = WSAIoctls.SIO_BASE_HANDLE
+ sock: int | CData,
+ *,
+ which: int = WSAIoctls.SIO_BASE_HANDLE,
) -> CData:
if hasattr(sock, "fileno"): # pragma: no branch
sock = sock.fileno()
diff --git a/src/trio/_core/_tests/type_tests/run.py b/src/trio/_core/_tests/type_tests/run.py
index c121ce6c7a..6d2124102b 100644
--- a/src/trio/_core/_tests/type_tests/run.py
+++ b/src/trio/_core/_tests/type_tests/run.py
@@ -29,7 +29,9 @@ async def foo_overloaded(arg: int | str) -> int | str:
v = trio.run(
- sleep_sort, (1, 3, 5, 2, 4), clock=trio.testing.MockClock(autojump_threshold=0)
+ sleep_sort,
+ (1, 3, 5, 2, 4),
+ clock=trio.testing.MockClock(autojump_threshold=0),
)
assert_type(v, "list[float]")
trio.run(sleep_sort, ["hi", "there"]) # type: ignore[arg-type]
diff --git a/src/trio/_core/_thread_cache.py b/src/trio/_core/_thread_cache.py
index d338ec1ee7..c612222697 100644
--- a/src/trio/_core/_thread_cache.py
+++ b/src/trio/_core/_thread_cache.py
@@ -23,7 +23,9 @@ def _to_os_thread_name(name: str) -> bytes:
# called once on import
def get_os_thread_name_func() -> Callable[[int | None, str], None] | None:
def namefunc(
- setname: Callable[[int, bytes], int], ident: int | None, name: str
+ setname: Callable[[int, bytes], int],
+ ident: int | None,
+ name: str,
) -> None:
# Thread.ident is None "if it has not been started". Unclear if that can happen
# with current usage.
@@ -33,7 +35,9 @@ def namefunc(
# namefunc on Mac also takes an ident, even if pthread_setname_np doesn't/can't use it
# so the caller don't need to care about platform.
def darwin_namefunc(
- setname: Callable[[bytes], int], ident: int | None, name: str
+ setname: Callable[[bytes], int],
+ ident: int | None,
+ name: str,
) -> None:
# I don't know if Mac can rename threads that hasn't been started, but default
# to no to be on the safe side.
diff --git a/src/trio/_core/_traps.py b/src/trio/_core/_traps.py
index 85e6b57306..27518406cb 100644
--- a/src/trio/_core/_traps.py
+++ b/src/trio/_core/_traps.py
@@ -213,13 +213,13 @@ async def permanently_detach_coroutine_object(
"""
if _run.current_task().child_nurseries:
raise RuntimeError(
- "can't permanently detach a coroutine object with open nurseries"
+ "can't permanently detach a coroutine object with open nurseries",
)
return await _async_yield(PermanentlyDetachCoroutineObject(final_outcome))
async def temporarily_detach_coroutine_object(
- abort_func: Callable[[RaiseCancelT], Abort]
+ abort_func: Callable[[RaiseCancelT], Abort],
) -> Any:
"""Temporarily detach the current coroutine object from the Trio
scheduler.
diff --git a/src/trio/_core/_unbounded_queue.py b/src/trio/_core/_unbounded_queue.py
index b9ebe484d7..b9e7974841 100644
--- a/src/trio/_core/_unbounded_queue.py
+++ b/src/trio/_core/_unbounded_queue.py
@@ -152,7 +152,8 @@ async def get_batch(self) -> list[T]:
def statistics(self) -> UnboundedQueueStatistics:
"""Return an :class:`UnboundedQueueStatistics` object containing debugging information."""
return UnboundedQueueStatistics(
- qsize=len(self._data), tasks_waiting=self._lot.statistics().tasks_waiting
+ qsize=len(self._data),
+ tasks_waiting=self._lot.statistics().tasks_waiting,
)
def __aiter__(self) -> Self:
diff --git a/src/trio/_core/_wakeup_socketpair.py b/src/trio/_core/_wakeup_socketpair.py
index fb821a23e7..ea4567017f 100644
--- a/src/trio/_core/_wakeup_socketpair.py
+++ b/src/trio/_core/_wakeup_socketpair.py
@@ -63,7 +63,7 @@ def wakeup_on_signals(self) -> None:
"running Trio in guest mode, then this might mean you "
"should set host_uses_signal_set_wakeup_fd=True. "
"Otherwise, file a bug on Trio and we'll help you figure "
- "out what's going on."
+ "out what's going on.",
),
stacklevel=1,
)
diff --git a/src/trio/_core/_windows_cffi.py b/src/trio/_core/_windows_cffi.py
index 244ea773c5..453b4beda3 100644
--- a/src/trio/_core/_windows_cffi.py
+++ b/src/trio/_core/_windows_cffi.py
@@ -210,7 +210,7 @@
# cribbed from pywincffi
# programmatically strips out those annotations MSDN likes, like _In_
REGEX_SAL_ANNOTATION = re.compile(
- r"\b(_In_|_Inout_|_Out_|_Outptr_|_Reserved_)(opt_)?\b"
+ r"\b(_In_|_Inout_|_Out_|_Outptr_|_Reserved_)(opt_)?\b",
)
LIB = REGEX_SAL_ANNOTATION.sub(" ", LIB)
@@ -253,7 +253,10 @@ def CreateEventA(
) -> Handle: ...
def SetFileCompletionNotificationModes(
- self, handle: Handle, flags: CompletionModes, /
+ self,
+ handle: Handle,
+ flags: CompletionModes,
+ /,
) -> int: ...
def PostQueuedCompletionStatus(
diff --git a/src/trio/_dtls.py b/src/trio/_dtls.py
index 31f7817e1c..62b01292a8 100644
--- a/src/trio/_dtls.py
+++ b/src/trio/_dtls.py
@@ -353,7 +353,9 @@ class OpaqueHandshakeMessage:
_AnyHandshakeMessage: TypeAlias = Union[
- HandshakeMessage, PseudoHandshakeMessage, OpaqueHandshakeMessage
+ HandshakeMessage,
+ PseudoHandshakeMessage,
+ OpaqueHandshakeMessage,
]
@@ -378,8 +380,10 @@ def decode_volley_trusted(
elif record.content_type in (ContentType.change_cipher_spec, ContentType.alert):
messages.append(
PseudoHandshakeMessage(
- record.version, record.content_type, record.payload
- )
+ record.version,
+ record.content_type,
+ record.payload,
+ ),
)
else:
assert record.content_type == ContentType.handshake
@@ -565,7 +569,11 @@ def _signable(*fields: bytes) -> bytes:
def _make_cookie(
- key: bytes, salt: bytes, tick: int, address: Any, client_hello_bits: bytes
+ key: bytes,
+ salt: bytes,
+ tick: int,
+ address: Any,
+ client_hello_bits: bytes,
) -> bytes:
assert len(salt) == SALT_BYTES
assert len(key) == KEY_BYTES
@@ -583,7 +591,10 @@ def _make_cookie(
def valid_cookie(
- key: bytes, cookie: bytes, address: Any, client_hello_bits: bytes
+ key: bytes,
+ cookie: bytes,
+ address: Any,
+ client_hello_bits: bytes,
) -> bool:
if len(cookie) > SALT_BYTES:
salt = cookie[:SALT_BYTES]
@@ -592,20 +603,28 @@ def valid_cookie(
cur_cookie = _make_cookie(key, salt, tick, address, client_hello_bits)
old_cookie = _make_cookie(
- key, salt, max(tick - 1, 0), address, client_hello_bits
+ key,
+ salt,
+ max(tick - 1, 0),
+ address,
+ client_hello_bits,
)
# I doubt using a short-circuiting 'or' here would leak any meaningful
# information, but why risk it when '|' is just as easy.
return hmac.compare_digest(cookie, cur_cookie) | hmac.compare_digest(
- cookie, old_cookie
+ cookie,
+ old_cookie,
)
else:
return False
def challenge_for(
- key: bytes, address: Any, epoch_seqno: int, client_hello_bits: bytes
+ key: bytes,
+ address: Any,
+ epoch_seqno: int,
+ client_hello_bits: bytes,
) -> bytes:
salt = os.urandom(SALT_BYTES)
tick = _current_cookie_tick()
@@ -641,7 +660,7 @@ def challenge_for(
payload = encode_handshake_fragment(hs)
packet = encode_record(
- Record(ContentType.handshake, ProtocolVersion.DTLS10, epoch_seqno, payload)
+ Record(ContentType.handshake, ProtocolVersion.DTLS10, epoch_seqno, payload),
)
return packet
@@ -666,7 +685,9 @@ def _read_loop(read_fn: Callable[[int], bytes]) -> bytes:
async def handle_client_hello_untrusted(
- endpoint: DTLSEndpoint, address: Any, packet: bytes
+ endpoint: DTLSEndpoint,
+ address: Any,
+ packet: bytes,
) -> None:
# it's trivial to write a simple function that directly calls this to
# get code coverage, but it should maybe:
@@ -686,7 +707,10 @@ async def handle_client_hello_untrusted(
if not valid_cookie(endpoint._listening_key, cookie, address, bits):
challenge_packet = challenge_for(
- endpoint._listening_key, address, epoch_seqno, bits
+ endpoint._listening_key,
+ address,
+ epoch_seqno,
+ bits,
)
try:
async with endpoint._send_lock:
@@ -746,7 +770,8 @@ async def handle_client_hello_untrusted(
async def dtls_receive_loop(
- endpoint_ref: ReferenceType[DTLSEndpoint], sock: SocketType
+ endpoint_ref: ReferenceType[DTLSEndpoint],
+ sock: SocketType,
) -> None:
try:
while True:
@@ -853,7 +878,7 @@ def __init__(
# support and isn't useful anyway -- especially for DTLS where it's equivalent
# to just performing a new handshake.
ctx.set_options(
- SSL.OP_NO_QUERY_MTU | SSL.OP_NO_RENEGOTIATION # type: ignore[attr-defined]
+ SSL.OP_NO_QUERY_MTU | SSL.OP_NO_RENEGOTIATION, # type: ignore[attr-defined]
)
self._ssl = SSL.Connection(ctx)
self._handshake_mtu = 0
@@ -877,7 +902,7 @@ def _set_replaced(self) -> None:
def _check_replaced(self) -> None:
if self._replaced:
raise trio.BrokenResourceError(
- "peer tore down this connection to start a new one"
+ "peer tore down this connection to start a new one",
)
# XX on systems where we can (maybe just Linux?) take advantage of the kernel's PMTU
@@ -931,7 +956,8 @@ async def aclose(self) -> None:
async def _send_volley(self, volley_messages: list[_AnyHandshakeMessage]) -> None:
packets = self._record_encoder.encode_volley(
- volley_messages, self._handshake_mtu
+ volley_messages,
+ self._handshake_mtu,
)
for packet in packets:
async with self.endpoint._send_lock:
@@ -1066,7 +1092,8 @@ def read_volley() -> list[_AnyHandshakeMessage]:
# PMTU estimate is wrong? Let's try dropping it to the minimum
# and hope that helps.
self._handshake_mtu = min(
- self._handshake_mtu, worst_case_mtu(self.endpoint.socket)
+ self._handshake_mtu,
+ worst_case_mtu(self.endpoint.socket),
)
async def send(self, data: bytes) -> None:
@@ -1082,7 +1109,8 @@ async def send(self, data: bytes) -> None:
self._ssl.write(data)
async with self.endpoint._send_lock:
await self.endpoint.socket.sendto(
- _read_loop(self._ssl.bio_read), self.peer_address
+ _read_loop(self._ssl.bio_read),
+ self.peer_address,
)
async def receive(self) -> bytes:
@@ -1226,7 +1254,9 @@ def _ensure_receive_loop(self) -> None:
# after we send our first packet.
if not self._receive_loop_spawned:
trio.lowlevel.spawn_system_task(
- dtls_receive_loop, weakref.ref(self), self.socket
+ dtls_receive_loop,
+ weakref.ref(self),
+ self.socket,
)
self._receive_loop_spawned = True
@@ -1318,7 +1348,7 @@ async def handler(dtls_channel):
except OSError:
# TODO: Write test that triggers this
raise RuntimeError( # pragma: no cover
- "DTLS socket must be bound before it can serve"
+ "DTLS socket must be bound before it can serve",
) from None
self._ensure_receive_loop()
# We do cookie verification ourselves, so tell OpenSSL not to worry about it.
diff --git a/src/trio/_file_io.py b/src/trio/_file_io.py
index ef867243f0..8038ff6234 100644
--- a/src/trio/_file_io.py
+++ b/src/trio/_file_io.py
@@ -465,8 +465,16 @@ async def open_file(
"""
_file = wrap_file(
await trio.to_thread.run_sync(
- io.open, file, mode, buffering, encoding, errors, newline, closefd, opener
- )
+ io.open,
+ file,
+ mode,
+ buffering,
+ encoding,
+ errors,
+ newline,
+ closefd,
+ opener,
+ ),
)
return _file
@@ -495,7 +503,7 @@ def has(attr: str) -> bool:
if not (has("close") and (has("read") or has("write"))):
raise TypeError(
f"{file} does not implement required duck-file methods: "
- "close and (read or write)"
+ "close and (read or write)",
)
return AsyncIOWrapper(file)
diff --git a/src/trio/_highlevel_open_tcp_listeners.py b/src/trio/_highlevel_open_tcp_listeners.py
index 80555be33e..2e71ca5543 100644
--- a/src/trio/_highlevel_open_tcp_listeners.py
+++ b/src/trio/_highlevel_open_tcp_listeners.py
@@ -112,7 +112,10 @@ async def open_tcp_listeners(
computed_backlog = _compute_backlog(backlog)
addresses = await tsocket.getaddrinfo(
- host, port, type=tsocket.SOCK_STREAM, flags=tsocket.AI_PASSIVE
+ host,
+ port,
+ type=tsocket.SOCK_STREAM,
+ flags=tsocket.AI_PASSIVE,
)
listeners = []
@@ -159,7 +162,8 @@ async def open_tcp_listeners(
"socket that that address could use"
)
raise OSError(errno.EAFNOSUPPORT, msg) from ExceptionGroup(
- msg, unsupported_address_families
+ msg,
+ unsupported_address_families,
)
return listeners
@@ -240,5 +244,8 @@ async def serve_tcp(
"""
listeners = await trio.open_tcp_listeners(port, host=host, backlog=backlog)
await trio.serve_listeners(
- handler, listeners, handler_nursery=handler_nursery, task_status=task_status
+ handler,
+ listeners,
+ handler_nursery=handler_nursery,
+ task_status=task_status,
)
diff --git a/src/trio/_highlevel_open_tcp_stream.py b/src/trio/_highlevel_open_tcp_stream.py
index d5c83da7c0..1e7f4332d3 100644
--- a/src/trio/_highlevel_open_tcp_stream.py
+++ b/src/trio/_highlevel_open_tcp_stream.py
@@ -141,7 +141,7 @@ def reorder_for_rfc_6555_section_5_4(
str,
Any,
]
- ]
+ ],
) -> None:
# RFC 6555 section 5.4 says that if getaddrinfo returns multiple address
# families (e.g. IPv4 and IPv6), then you should make sure that your first
@@ -346,14 +346,16 @@ async def attempt_connect(
# better job of it because it knows the remote IP/port.
with suppress(OSError, AttributeError):
sock.setsockopt(
- trio.socket.IPPROTO_IP, trio.socket.IP_BIND_ADDRESS_NO_PORT, 1
+ trio.socket.IPPROTO_IP,
+ trio.socket.IP_BIND_ADDRESS_NO_PORT,
+ 1,
)
try:
await sock.bind((local_address, 0))
except OSError:
raise OSError(
f"local_address={local_address!r} is incompatible "
- f"with remote address {sockaddr!r}"
+ f"with remote address {sockaddr!r}",
) from None
await sock.connect(sockaddr)
@@ -382,7 +384,9 @@ async def attempt_connect(
# workaround to check types until typing of nursery.start_soon improved
if TYPE_CHECKING:
await attempt_connect(
- (address_family, socket_type, proto), addr, attempt_failed
+ (address_family, socket_type, proto),
+ addr,
+ attempt_failed,
)
nursery.start_soon(
diff --git a/src/trio/_highlevel_serve_listeners.py b/src/trio/_highlevel_serve_listeners.py
index ec5a0efb3c..71521546d3 100644
--- a/src/trio/_highlevel_serve_listeners.py
+++ b/src/trio/_highlevel_serve_listeners.py
@@ -143,5 +143,5 @@ async def serve_listeners(
task_status.started(listeners)
raise AssertionError(
- "_serve_one_listener should never complete"
+ "_serve_one_listener should never complete",
) # pragma: no cover
diff --git a/src/trio/_highlevel_socket.py b/src/trio/_highlevel_socket.py
index 901e22f345..8d9e768074 100644
--- a/src/trio/_highlevel_socket.py
+++ b/src/trio/_highlevel_socket.py
@@ -76,7 +76,7 @@ def __init__(self, socket: SocketType):
self.socket = socket
self._send_conflict_detector = ConflictDetector(
- "another task is currently sending data on this SocketStream"
+ "another task is currently sending data on this SocketStream",
)
# Socket defaults:
@@ -167,12 +167,12 @@ def setsockopt(
if length is None:
if value is None:
raise TypeError(
- "invalid value for argument 'value', must not be None when specifying length"
+ "invalid value for argument 'value', must not be None when specifying length",
)
return self.socket.setsockopt(level, option, value)
if value is not None:
raise TypeError(
- f"invalid value for argument 'value': {value!r}, must be None when specifying optlen"
+ f"invalid value for argument 'value': {value!r}, must be None when specifying optlen",
)
return self.socket.setsockopt(level, option, value, length)
diff --git a/src/trio/_highlevel_ssl_helpers.py b/src/trio/_highlevel_ssl_helpers.py
index 03562c9edb..1239491a43 100644
--- a/src/trio/_highlevel_ssl_helpers.py
+++ b/src/trio/_highlevel_ssl_helpers.py
@@ -59,7 +59,9 @@ async def open_ssl_over_tcp_stream(
"""
tcp_stream = await trio.open_tcp_stream(
- host, port, happy_eyeballs_delay=happy_eyeballs_delay
+ host,
+ port,
+ happy_eyeballs_delay=happy_eyeballs_delay,
)
if ssl_context is None:
ssl_context = ssl.create_default_context()
@@ -68,7 +70,10 @@ async def open_ssl_over_tcp_stream(
ssl_context.options &= ~ssl.OP_IGNORE_UNEXPECTED_EOF
return trio.SSLStream(
- tcp_stream, ssl_context, server_hostname=host, https_compatible=https_compatible
+ tcp_stream,
+ ssl_context,
+ server_hostname=host,
+ https_compatible=https_compatible,
)
@@ -168,5 +173,8 @@ async def serve_ssl_over_tcp(
backlog=backlog,
)
await trio.serve_listeners(
- handler, listeners, handler_nursery=handler_nursery, task_status=task_status
+ handler,
+ listeners,
+ handler_nursery=handler_nursery,
+ task_status=task_status,
)
diff --git a/src/trio/_path.py b/src/trio/_path.py
index b9b5749c35..3663831a23 100644
--- a/src/trio/_path.py
+++ b/src/trio/_path.py
@@ -31,7 +31,7 @@
def _wraps_async(
- wrapped: Callable[..., Any]
+ wrapped: Callable[..., Any],
) -> Callable[[Callable[P, T]], Callable[P, Awaitable[T]]]:
def decorator(fn: Callable[P, T]) -> Callable[P, Awaitable[T]]:
async def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
diff --git a/src/trio/_signals.py b/src/trio/_signals.py
index f4d912808f..63ee176c1b 100644
--- a/src/trio/_signals.py
+++ b/src/trio/_signals.py
@@ -72,7 +72,7 @@ def __init__(self) -> None:
self._pending: OrderedDict[int, None] = OrderedDict()
self._lot = trio.lowlevel.ParkingLot()
self._conflict_detector = ConflictDetector(
- "only one task can iterate on a signal receiver at a time"
+ "only one task can iterate on a signal receiver at a time",
)
self._closed = False
@@ -170,7 +170,7 @@ def open_signal_receiver(
if not is_main_thread():
raise RuntimeError(
"Sorry, open_signal_receiver is only possible when running in "
- "Python interpreter's main thread"
+ "Python interpreter's main thread",
)
token = trio.lowlevel.current_trio_token()
queue = SignalReceiver()
diff --git a/src/trio/_socket.py b/src/trio/_socket.py
index 69a4d773d4..6dbe9c0c4b 100644
--- a/src/trio/_socket.py
+++ b/src/trio/_socket.py
@@ -62,7 +62,8 @@
#
class _try_sync:
def __init__(
- self, blocking_exc_override: Callable[[BaseException], bool] | None = None
+ self,
+ blocking_exc_override: Callable[[BaseException], bool] | None = None,
):
self._blocking_exc_override = blocking_exc_override
@@ -179,7 +180,11 @@ async def getaddrinfo(
flags: int = 0,
) -> list[
tuple[
- AddressFamily, SocketKind, int, str, tuple[str, int] | tuple[str, int, int, int]
+ AddressFamily,
+ SocketKind,
+ int,
+ str,
+ tuple[str, int] | tuple[str, int, int, int],
]
]:
"""Look up a numeric address given a name.
@@ -210,7 +215,12 @@ def numeric_only_failure(exc: BaseException) -> bool:
async with _try_sync(numeric_only_failure):
return _stdlib_socket.getaddrinfo(
- host, port, family, type, proto, flags | _NUMERIC_ONLY
+ host,
+ port,
+ family,
+ type,
+ proto,
+ flags | _NUMERIC_ONLY,
)
# That failed; it's a real hostname. We better use a thread.
#
@@ -245,7 +255,8 @@ def numeric_only_failure(exc: BaseException) -> bool:
async def getnameinfo(
- sockaddr: tuple[str, int] | tuple[str, int, int, int], flags: int
+ sockaddr: tuple[str, int] | tuple[str, int, int, int],
+ flags: int,
) -> tuple[str, str]:
"""Look up a name given a numeric address.
@@ -261,7 +272,10 @@ async def getnameinfo(
return await hr.getnameinfo(sockaddr, flags)
else:
return await trio.to_thread.run_sync(
- _stdlib_socket.getnameinfo, sockaddr, flags, abandon_on_cancel=True
+ _stdlib_socket.getnameinfo,
+ sockaddr,
+ flags,
+ abandon_on_cancel=True,
)
@@ -272,7 +286,9 @@ async def getprotobyname(name: str) -> int:
"""
return await trio.to_thread.run_sync(
- _stdlib_socket.getprotobyname, name, abandon_on_cancel=True
+ _stdlib_socket.getprotobyname,
+ name,
+ abandon_on_cancel=True,
)
@@ -357,7 +373,10 @@ def socket(
return sf.socket(family, type, proto)
else:
family, type, proto = _sniff_sockopts_for_fileno( # noqa: A001
- family, type, proto, fileno
+ family,
+ type,
+ proto,
+ fileno,
)
stdlib_socket = _stdlib_socket.socket(family, type, proto, fileno)
return from_stdlib_socket(stdlib_socket)
@@ -465,7 +484,7 @@ async def _resolve_address_nocp(
elif family == _stdlib_socket.AF_INET6:
if not isinstance(address, tuple) or not 2 <= len(address) <= 4:
raise ValueError(
- "address should be a (host, port, [flowinfo, [scopeid]]) tuple"
+ "address should be a (host, port, [flowinfo, [scopeid]]) tuple",
)
elif hasattr(_stdlib_socket, "AF_UNIX") and family == _stdlib_socket.AF_UNIX:
# unwrap path-likes
@@ -531,7 +550,7 @@ def __init__(self) -> None:
if type(self) is SocketType:
raise TypeError(
"SocketType is an abstract class; use trio.socket.socket if you "
- "want to construct a socket object"
+ "want to construct a socket object",
)
def detach(self) -> int:
@@ -553,7 +572,11 @@ def getsockopt(self, /, level: int, optname: int) -> int: ...
def getsockopt(self, /, level: int, optname: int, buflen: int) -> bytes: ...
def getsockopt(
- self, /, level: int, optname: int, buflen: int | None = None
+ self,
+ /,
+ level: int,
+ optname: int,
+ buflen: int | None = None,
) -> int | bytes:
raise NotImplementedError
@@ -562,7 +585,12 @@ def setsockopt(self, /, level: int, optname: int, value: int | Buffer) -> None:
@overload
def setsockopt(
- self, /, level: int, optname: int, value: None, optlen: int
+ self,
+ /,
+ level: int,
+ optname: int,
+ value: None,
+ optlen: int,
) -> None: ...
def setsockopt(
@@ -653,19 +681,27 @@ def recv(__self, __buflen: int, __flags: int = 0) -> Awaitable[bytes]:
raise NotImplementedError
def recv_into(
- __self, buffer: Buffer, nbytes: int = 0, flags: int = 0
+ __self,
+ buffer: Buffer,
+ nbytes: int = 0,
+ flags: int = 0,
) -> Awaitable[int]:
raise NotImplementedError
# return type of socket.socket.recvfrom in typeshed is tuple[bytes, Any]
def recvfrom(
- __self, __bufsize: int, __flags: int = 0
+ __self,
+ __bufsize: int,
+ __flags: int = 0,
) -> Awaitable[tuple[bytes, AddressFormat]]:
raise NotImplementedError
# return type of socket.socket.recvfrom_into in typeshed is tuple[bytes, Any]
def recvfrom_into(
- __self, buffer: Buffer, nbytes: int = 0, flags: int = 0
+ __self,
+ buffer: Buffer,
+ nbytes: int = 0,
+ flags: int = 0,
) -> Awaitable[tuple[int, AddressFormat]]:
raise NotImplementedError
@@ -698,7 +734,9 @@ def send(__self, __bytes: Buffer, __flags: int = 0) -> Awaitable[int]:
@overload
async def sendto(
- self, __data: Buffer, __address: tuple[object, ...] | str | Buffer
+ self,
+ __data: Buffer,
+ __address: tuple[object, ...] | str | Buffer,
) -> int: ...
@overload
@@ -748,7 +786,7 @@ def __init__(self, sock: _stdlib_socket.socket):
# For example, ssl.SSLSocket subclasses socket.socket, but we
# certainly don't want to blindly wrap one of those.
raise TypeError(
- f"expected object of type 'socket.socket', not '{type(sock).__name__}'"
+ f"expected object of type 'socket.socket', not '{type(sock).__name__}'",
)
self._sock = sock
self._sock.setblocking(False)
@@ -778,7 +816,11 @@ def getsockopt(self, /, level: int, optname: int) -> int: ...
def getsockopt(self, /, level: int, optname: int, buflen: int) -> bytes: ...
def getsockopt(
- self, /, level: int, optname: int, buflen: int | None = None
+ self,
+ /,
+ level: int,
+ optname: int,
+ buflen: int | None = None,
) -> int | bytes:
if buflen is None:
return self._sock.getsockopt(level, optname)
@@ -789,7 +831,12 @@ def setsockopt(self, /, level: int, optname: int, value: int | Buffer) -> None:
@overload
def setsockopt(
- self, /, level: int, optname: int, value: None, optlen: int
+ self,
+ /,
+ level: int,
+ optname: int,
+ value: None,
+ optlen: int,
) -> None: ...
def setsockopt(
@@ -803,12 +850,12 @@ def setsockopt(
if optlen is None:
if value is None:
raise TypeError(
- "invalid value for argument 'value', must not be None when specifying optlen"
+ "invalid value for argument 'value', must not be None when specifying optlen",
)
return self._sock.setsockopt(level, optname, value)
if value is not None:
raise TypeError(
- f"invalid value for argument 'value': {value!r}, must be None when specifying optlen"
+ f"invalid value for argument 'value': {value!r}, must be None when specifying optlen",
)
# Note: PyPy may crash here due to setsockopt only supporting
@@ -915,7 +962,8 @@ async def _resolve_address_nocp(
) -> AddressFormat:
if self.family == _stdlib_socket.AF_INET6:
ipv6_v6only = self._sock.getsockopt(
- _stdlib_socket.IPPROTO_IPV6, _stdlib_socket.IPV6_V6ONLY
+ _stdlib_socket.IPPROTO_IPV6,
+ _stdlib_socket.IPV6_V6ONLY,
)
else:
ipv6_v6only = False
@@ -977,7 +1025,8 @@ async def _nonblocking_helper(
################################################################
_accept = _make_simple_sock_method_wrapper(
- _stdlib_socket.socket.accept, _core.wait_readable
+ _stdlib_socket.socket.accept,
+ _core.wait_readable,
)
async def accept(self) -> tuple[SocketType, AddressFormat]:
@@ -1075,7 +1124,8 @@ def recv(__self, __buflen: int, __flags: int = 0) -> Awaitable[bytes]: ...
# this requires that we refrain from using `/` to specify pos-only
# args, or mypy thinks the signature differs from typeshed.
recv = _make_simple_sock_method_wrapper(
- _stdlib_socket.socket.recv, _core.wait_readable
+ _stdlib_socket.socket.recv,
+ _core.wait_readable,
)
################################################################
@@ -1085,11 +1135,15 @@ def recv(__self, __buflen: int, __flags: int = 0) -> Awaitable[bytes]: ...
if TYPE_CHECKING:
def recv_into(
- __self, buffer: Buffer, nbytes: int = 0, flags: int = 0
+ __self,
+ buffer: Buffer,
+ nbytes: int = 0,
+ flags: int = 0,
) -> Awaitable[int]: ...
recv_into = _make_simple_sock_method_wrapper(
- _stdlib_socket.socket.recv_into, _core.wait_readable
+ _stdlib_socket.socket.recv_into,
+ _core.wait_readable,
)
################################################################
@@ -1099,11 +1153,14 @@ def recv_into(
if TYPE_CHECKING:
# return type of socket.socket.recvfrom in typeshed is tuple[bytes, Any]
def recvfrom(
- __self, __bufsize: int, __flags: int = 0
+ __self,
+ __bufsize: int,
+ __flags: int = 0,
) -> Awaitable[tuple[bytes, AddressFormat]]: ...
recvfrom = _make_simple_sock_method_wrapper(
- _stdlib_socket.socket.recvfrom, _core.wait_readable
+ _stdlib_socket.socket.recvfrom,
+ _core.wait_readable,
)
################################################################
@@ -1113,11 +1170,15 @@ def recvfrom(
if TYPE_CHECKING:
# return type of socket.socket.recvfrom_into in typeshed is tuple[bytes, Any]
def recvfrom_into(
- __self, buffer: Buffer, nbytes: int = 0, flags: int = 0
+ __self,
+ buffer: Buffer,
+ nbytes: int = 0,
+ flags: int = 0,
) -> Awaitable[tuple[int, AddressFormat]]: ...
recvfrom_into = _make_simple_sock_method_wrapper(
- _stdlib_socket.socket.recvfrom_into, _core.wait_readable
+ _stdlib_socket.socket.recvfrom_into,
+ _core.wait_readable,
)
################################################################
@@ -1130,11 +1191,16 @@ def recvfrom_into(
if TYPE_CHECKING:
def recvmsg(
- __self, __bufsize: int, __ancbufsize: int = 0, __flags: int = 0
+ __self,
+ __bufsize: int,
+ __ancbufsize: int = 0,
+ __flags: int = 0,
) -> Awaitable[tuple[bytes, list[tuple[int, int, bytes]], int, Any]]: ...
recvmsg = _make_simple_sock_method_wrapper(
- _stdlib_socket.socket.recvmsg, _core.wait_readable, maybe_avail=True
+ _stdlib_socket.socket.recvmsg,
+ _core.wait_readable,
+ maybe_avail=True,
)
################################################################
@@ -1154,7 +1220,9 @@ def recvmsg_into(
) -> Awaitable[tuple[int, list[tuple[int, int, bytes]], int, Any]]: ...
recvmsg_into = _make_simple_sock_method_wrapper(
- _stdlib_socket.socket.recvmsg_into, _core.wait_readable, maybe_avail=True
+ _stdlib_socket.socket.recvmsg_into,
+ _core.wait_readable,
+ maybe_avail=True,
)
################################################################
@@ -1166,7 +1234,8 @@ def recvmsg_into(
def send(__self, __bytes: Buffer, __flags: int = 0) -> Awaitable[int]: ...
send = _make_simple_sock_method_wrapper(
- _stdlib_socket.socket.send, _core.wait_writable
+ _stdlib_socket.socket.send,
+ _core.wait_writable,
)
################################################################
@@ -1175,12 +1244,17 @@ def send(__self, __bytes: Buffer, __flags: int = 0) -> Awaitable[int]: ...
@overload
async def sendto(
- self, __data: Buffer, __address: tuple[object, ...] | str | Buffer
+ self,
+ __data: Buffer,
+ __address: tuple[object, ...] | str | Buffer,
) -> int: ...
@overload
async def sendto(
- self, __data: Buffer, __flags: int, __address: tuple[object, ...] | str | Buffer
+ self,
+ __data: Buffer,
+ __flags: int,
+ __address: tuple[object, ...] | str | Buffer,
) -> int: ...
@_wraps(_stdlib_socket.socket.sendto, assigned=(), updated=()) # type: ignore[misc]
diff --git a/src/trio/_ssl.py b/src/trio/_ssl.py
index 8d06297eaf..df1cbc37bc 100644
--- a/src/trio/_ssl.py
+++ b/src/trio/_ssl.py
@@ -376,10 +376,10 @@ def __init__(
# multiple concurrent calls to send_all/wait_send_all_might_not_block
# or to receive_some.
self._outer_send_conflict_detector = ConflictDetector(
- "another task is currently sending data on this SSLStream"
+ "another task is currently sending data on this SSLStream",
)
self._outer_recv_conflict_detector = ConflictDetector(
- "another task is currently receiving data on this SSLStream"
+ "another task is currently receiving data on this SSLStream",
)
self._estimated_receive_size = STARTING_RECEIVE_SIZE
@@ -615,7 +615,8 @@ async def _retry(
self._incoming.write_eof()
else:
self._estimated_receive_size = max(
- self._estimated_receive_size, len(data)
+ self._estimated_receive_size,
+ len(data),
)
self._incoming.write(data)
self._inner_recv_count += 1
diff --git a/src/trio/_subprocess.py b/src/trio/_subprocess.py
index 553e3d4885..90ab9cc16a 100644
--- a/src/trio/_subprocess.py
+++ b/src/trio/_subprocess.py
@@ -36,7 +36,10 @@
StrOrBytesPath: TypeAlias = Union[str, bytes, os.PathLike[str], os.PathLike[bytes]]
else:
StrOrBytesPath: TypeAlias = Union[
- str, bytes, "os.PathLike[str]", "os.PathLike[bytes]"
+ str,
+ bytes,
+ "os.PathLike[str]",
+ "os.PathLike[bytes]",
]
@@ -238,7 +241,7 @@ async def wait(self) -> int:
if self.poll() is None:
if self._pidfd is not None:
with contextlib.suppress(
- ClosedResourceError
+ ClosedResourceError,
): # something else (probably a call to poll) already closed the pidfd
await trio.lowlevel.wait_readable(self._pidfd.fileno())
else:
@@ -362,19 +365,19 @@ async def _open_process(
if options.get(key):
raise TypeError(
"trio.Process only supports communicating over "
- f"unbuffered byte streams; the '{key}' option is not supported"
+ f"unbuffered byte streams; the '{key}' option is not supported",
)
if os.name == "posix":
if isinstance(command, str) and not options.get("shell"):
raise TypeError(
"command must be a sequence (not a string) if shell=False "
- "on UNIX systems"
+ "on UNIX systems",
)
if not isinstance(command, str) and options.get("shell"):
raise TypeError(
"command must be a string (not a sequence) if shell=True "
- "on UNIX systems"
+ "on UNIX systems",
)
trio_stdin: ClosableSendStream | None = None
@@ -417,7 +420,7 @@ async def _open_process(
stdout=stdout,
stderr=stderr,
**options,
- )
+ ),
)
# We did not fail, so dismiss the stack for the trio ends
cleanup_on_fail.pop_all()
@@ -443,7 +446,7 @@ async def _posix_deliver_cancel(p: Process) -> None:
RuntimeWarning(
f"process {p!r} ignored SIGTERM for 5 seconds. "
"(Maybe you should pass a custom deliver_cancel?) "
- "Trying SIGKILL."
+ "Trying SIGKILL.",
),
stacklevel=1,
)
@@ -671,17 +674,17 @@ async def my_deliver_cancel(process):
raise ValueError(
"stdout=subprocess.PIPE is only valid with nursery.start, "
"since that's the only way to access the pipe; use nursery.start "
- "or pass the data you want to write directly"
+ "or pass the data you want to write directly",
)
if options.get("stdout") is subprocess.PIPE:
raise ValueError(
"stdout=subprocess.PIPE is only valid with nursery.start, "
- "since that's the only way to access the pipe"
+ "since that's the only way to access the pipe",
)
if options.get("stderr") is subprocess.PIPE:
raise ValueError(
"stderr=subprocess.PIPE is only valid with nursery.start, "
- "since that's the only way to access the pipe"
+ "since that's the only way to access the pipe",
)
if isinstance(stdin, (bytes, bytearray, memoryview)):
input_ = stdin
@@ -768,7 +771,10 @@ async def killer() -> None:
if proc.returncode and check:
raise subprocess.CalledProcessError(
- proc.returncode, proc.args, output=stdout, stderr=stderr
+ proc.returncode,
+ proc.args,
+ output=stdout,
+ stderr=stderr,
)
else:
assert proc.returncode is not None
diff --git a/src/trio/_subprocess_platform/kqueue.py b/src/trio/_subprocess_platform/kqueue.py
index fcf72650ee..2283bb5360 100644
--- a/src/trio/_subprocess_platform/kqueue.py
+++ b/src/trio/_subprocess_platform/kqueue.py
@@ -21,7 +21,10 @@ async def wait_child_exiting(process: _subprocess.Process) -> None:
def make_event(flags: int) -> select.kevent:
return select.kevent(
- process.pid, filter=select.KQ_FILTER_PROC, flags=flags, fflags=KQ_NOTE_EXIT
+ process.pid,
+ filter=select.KQ_FILTER_PROC,
+ flags=flags,
+ fflags=KQ_NOTE_EXIT,
)
try:
diff --git a/src/trio/_subprocess_platform/waitid.py b/src/trio/_subprocess_platform/waitid.py
index 44c8261074..ebf83b4802 100644
--- a/src/trio/_subprocess_platform/waitid.py
+++ b/src/trio/_subprocess_platform/waitid.py
@@ -40,7 +40,7 @@ def sync_wait_reapable(pid: int) -> None:
int pad[26];
} siginfo_t;
int waitid(int idtype, int id, siginfo_t* result, int options);
-"""
+""",
)
waitid_cffi = waitid_ffi.dlopen(None).waitid # type: ignore[attr-defined]
@@ -79,7 +79,10 @@ async def _waitid_system_task(pid: int, event: Event) -> None:
try:
await to_thread_run_sync(
- sync_wait_reapable, pid, abandon_on_cancel=True, limiter=waitid_limiter
+ sync_wait_reapable,
+ pid,
+ abandon_on_cancel=True,
+ limiter=waitid_limiter,
)
except OSError:
# If waitid fails, waitpid will fail too, so it still makes
diff --git a/src/trio/_sync.py b/src/trio/_sync.py
index 9f498e82a3..698716ea35 100644
--- a/src/trio/_sync.py
+++ b/src/trio/_sync.py
@@ -297,7 +297,7 @@ def acquire_on_behalf_of_nowait(self, borrower: Task | object) -> None:
"""
if borrower in self._borrowers:
raise RuntimeError(
- "this borrower is already holding one of this CapacityLimiter's tokens"
+ "this borrower is already holding one of this CapacityLimiter's tokens",
)
if len(self._borrowers) < self._total_tokens and not self._lot:
self._borrowers.add(borrower)
@@ -366,7 +366,7 @@ def release_on_behalf_of(self, borrower: Task | object) -> None:
"""
if borrower not in self._borrowers:
raise RuntimeError(
- "this borrower isn't holding any of this CapacityLimiter's tokens"
+ "this borrower isn't holding any of this CapacityLimiter's tokens",
)
self._borrowers.remove(borrower)
self._wake_waiters()
@@ -622,7 +622,9 @@ def statistics(self) -> LockStatistics:
"""
return LockStatistics(
- locked=self.locked(), owner=self._owner, tasks_waiting=len(self._lot)
+ locked=self.locked(),
+ owner=self._owner,
+ tasks_waiting=len(self._lot),
)
@@ -845,5 +847,6 @@ def statistics(self) -> ConditionStatistics:
"""
return ConditionStatistics(
- tasks_waiting=len(self._lot), lock_statistics=self._lock.statistics()
+ tasks_waiting=len(self._lot),
+ lock_statistics=self._lock.statistics(),
)
diff --git a/src/trio/_tests/check_type_completeness.py b/src/trio/_tests/check_type_completeness.py
index fa6ace074f..48e4df0bd9 100755
--- a/src/trio/_tests/check_type_completeness.py
+++ b/src/trio/_tests/check_type_completeness.py
@@ -111,7 +111,9 @@ def has_docstring_at_runtime(name: str) -> bool:
def check_type(
- platform: str, full_diagnostics_file: Path | None, expected_errors: list[object]
+ platform: str,
+ full_diagnostics_file: Path | None,
+ expected_errors: list[object],
) -> list[object]:
# convince isort we use the trio import
assert trio
@@ -144,7 +146,7 @@ def check_type(
if message.startswith("No docstring found for"):
continue
if message.startswith(
- "Type is missing type annotation and could be inferred differently by type checkers"
+ "Type is missing type annotation and could be inferred differently by type checkers",
):
continue
diff --git a/src/trio/_tests/module_with_deprecations.py b/src/trio/_tests/module_with_deprecations.py
index 73184d11e8..afe4187191 100644
--- a/src/trio/_tests/module_with_deprecations.py
+++ b/src/trio/_tests/module_with_deprecations.py
@@ -16,6 +16,9 @@
__deprecated_attributes__ = {
"dep1": _deprecate.DeprecatedAttribute("value1", "1.1", issue=1),
"dep2": _deprecate.DeprecatedAttribute(
- "value2", "1.2", issue=1, instead="instead-string"
+ "value2",
+ "1.2",
+ issue=1,
+ instead="instead-string",
),
}
diff --git a/src/trio/_tests/test_channel.py b/src/trio/_tests/test_channel.py
index 1271f6b765..07cc84a559 100644
--- a/src/trio/_tests/test_channel.py
+++ b/src/trio/_tests/test_channel.py
@@ -107,7 +107,8 @@ async def consumer(receive_channel: trio.MemoryReceiveChannel[int], i: int) -> N
async def test_close_basics() -> None:
async def send_block(
- s: trio.MemorySendChannel[None], expect: type[BaseException]
+ s: trio.MemorySendChannel[None],
+ expect: type[BaseException],
) -> None:
with pytest.raises(expect):
await s.send(None)
@@ -164,7 +165,8 @@ async def receive_block(r: trio.MemoryReceiveChannel[int]) -> None:
async def test_close_sync() -> None:
async def send_block(
- s: trio.MemorySendChannel[None], expect: type[BaseException]
+ s: trio.MemorySendChannel[None],
+ expect: type[BaseException],
) -> None:
with pytest.raises(expect):
await s.send(None)
diff --git a/src/trio/_tests/test_contextvars.py b/src/trio/_tests/test_contextvars.py
index ae0c25f876..63965e1e12 100644
--- a/src/trio/_tests/test_contextvars.py
+++ b/src/trio/_tests/test_contextvars.py
@@ -5,7 +5,7 @@
from .. import _core
trio_testing_contextvar: contextvars.ContextVar[str] = contextvars.ContextVar(
- "trio_testing_contextvar"
+ "trio_testing_contextvar",
)
diff --git a/src/trio/_tests/test_deprecate.py b/src/trio/_tests/test_deprecate.py
index fa5d7cbfef..1da1549d38 100644
--- a/src/trio/_tests/test_deprecate.py
+++ b/src/trio/_tests/test_deprecate.py
@@ -161,7 +161,10 @@ def new_hotness_method(self) -> str:
return "new hotness method"
old_hotness_method = deprecated_alias(
- "Alias.old_hotness_method", new_hotness_method, "3.21", issue=1
+ "Alias.old_hotness_method",
+ new_hotness_method,
+ "3.21",
+ issue=1,
)
@@ -272,5 +275,9 @@ def test_warning_class() -> None:
with pytest.warns(TrioDeprecationWarning):
warn_deprecated(
- "foo", "bar", issue=None, instead=None, use_triodeprecationwarning=True
+ "foo",
+ "bar",
+ issue=None,
+ instead=None,
+ use_triodeprecationwarning=True,
)
diff --git a/src/trio/_tests/test_deprecate_strict_exception_groups_false.py b/src/trio/_tests/test_deprecate_strict_exception_groups_false.py
index 317672bf23..ccf34d72ed 100644
--- a/src/trio/_tests/test_deprecate_strict_exception_groups_false.py
+++ b/src/trio/_tests/test_deprecate_strict_exception_groups_false.py
@@ -7,7 +7,8 @@
async def test_deprecation_warning_open_nursery() -> None:
with pytest.warns(
- trio.TrioDeprecationWarning, match="strict_exception_groups=False"
+ trio.TrioDeprecationWarning,
+ match="strict_exception_groups=False",
) as record:
async with trio.open_nursery(strict_exception_groups=False):
...
@@ -33,7 +34,8 @@ async def foo_loose_nursery() -> None:
def helper(fun: Callable[..., Awaitable[None]], num: int) -> None:
with pytest.warns(
- trio.TrioDeprecationWarning, match="strict_exception_groups=False"
+ trio.TrioDeprecationWarning,
+ match="strict_exception_groups=False",
) as record:
trio.run(fun, strict_exception_groups=False)
assert len(record) == num
@@ -52,7 +54,8 @@ async def trio_return(in_host: object) -> str:
return "ok"
with pytest.warns(
- trio.TrioDeprecationWarning, match="strict_exception_groups=False"
+ trio.TrioDeprecationWarning,
+ match="strict_exception_groups=False",
) as record:
trivial_guest_run(
trio_return,
diff --git a/src/trio/_tests/test_dtls.py b/src/trio/_tests/test_dtls.py
index d14edae25c..43ad273e57 100644
--- a/src/trio/_tests/test_dtls.py
+++ b/src/trio/_tests/test_dtls.py
@@ -38,7 +38,9 @@
parametrize_ipv6 = pytest.mark.parametrize(
- "ipv6", [False, pytest.param(True, marks=binds_ipv6)], ids=["ipv4", "ipv6"]
+ "ipv6",
+ [False, pytest.param(True, marks=binds_ipv6)],
+ ids=["ipv4", "ipv6"],
)
@@ -51,7 +53,10 @@ def endpoint(**kwargs: int | bool) -> DTLSEndpoint:
@asynccontextmanager
async def dtls_echo_server(
- *, autocancel: bool = True, mtu: int | None = None, ipv6: bool = False
+ *,
+ autocancel: bool = True,
+ mtu: int | None = None,
+ ipv6: bool = False,
) -> AsyncGenerator[tuple[DTLSEndpoint, tuple[str, int]], None]:
with endpoint(ipv6=ipv6) as server:
localhost = "::1" if ipv6 else "127.0.0.1"
@@ -62,7 +67,7 @@ async def echo_handler(dtls_channel: DTLSChannel) -> None:
print(
"echo handler started: "
f"server {dtls_channel.endpoint.socket.getsockname()!r} "
- f"client {dtls_channel.peer_address!r}"
+ f"client {dtls_channel.peer_address!r}",
)
if mtu is not None:
dtls_channel.set_ciphertext_mtu(mtu)
@@ -99,7 +104,8 @@ async def test_smoke(ipv6: bool) -> None:
assert await client_channel.receive() == b"goodbye"
with pytest.raises(
- ValueError, match="^openssl doesn't support sending empty DTLS packets$"
+ ValueError,
+ match="^openssl doesn't support sending empty DTLS packets$",
):
await client_channel.send(b"")
@@ -165,7 +171,7 @@ async def route_packet(packet: UDPPacket) -> None:
assert op == "deliver"
print(
f"{packet.source} -> {packet.destination}: delivered"
- f" {packet.payload.hex()}"
+ f" {packet.payload.hex()}",
)
fn.deliver_packet(packet)
break
@@ -225,7 +231,8 @@ async def handler(channel: DTLSChannel) -> None:
await server_nursery.start(server_endpoint.serve, server_ctx, handler)
client = client_endpoint.connect(
- server_endpoint.socket.getsockname(), client_ctx
+ server_endpoint.socket.getsockname(),
+ client_ctx,
)
async with trio.open_nursery() as nursery:
nursery.start_soon(client.send, b"from client")
@@ -372,9 +379,9 @@ async def test_server_socket_doesnt_crash_on_garbage(
frag_offset=0,
frag_len=10,
frag=bytes(10),
- )
+ ),
),
- )
+ ),
)
client_hello_extended = client_hello + b"\x00"
@@ -397,9 +404,9 @@ async def test_server_socket_doesnt_crash_on_garbage(
frag_offset=0,
frag_len=10,
frag=bytes(10),
- )
+ ),
),
- )
+ ),
)
client_hello_trailing_data_in_record = encode_record(
@@ -415,10 +422,10 @@ async def test_server_socket_doesnt_crash_on_garbage(
frag_offset=0,
frag_len=10,
frag=bytes(10),
- )
+ ),
)
+ b"\x00",
- )
+ ),
)
handshake_empty = encode_record(
@@ -427,7 +434,7 @@ async def test_server_socket_doesnt_crash_on_garbage(
version=ProtocolVersion.DTLS10,
epoch_seqno=0,
payload=b"",
- )
+ ),
)
client_hello_truncated_in_cookie = encode_record(
@@ -436,7 +443,7 @@ async def test_server_socket_doesnt_crash_on_garbage(
version=ProtocolVersion.DTLS10,
epoch_seqno=0,
payload=bytes(2 + 32 + 1) + b"\xff",
- )
+ ),
)
async with dtls_echo_server() as (_, address):
@@ -620,7 +627,8 @@ async def connecter() -> None:
# notices the timeout has expired
blackholed = False
await server_endpoint.socket.sendto(
- b"xxx", client_endpoint.socket.getsockname()
+ b"xxx",
+ client_endpoint.socket.getsockname(),
)
# now the client task should finish connecting and exit cleanly
@@ -682,7 +690,8 @@ def route_packet(packet: UDPPacket) -> None:
@parametrize_ipv6
async def test_handshake_handles_minimum_network_mtu(
- ipv6: bool, autojump_clock: trio.abc.Clock
+ ipv6: bool,
+ autojump_clock: trio.abc.Clock,
) -> None:
# Fake network that has the minimum allowable MTU for whatever protocol we're using.
fn = FakeNet()
diff --git a/src/trio/_tests/test_exports.py b/src/trio/_tests/test_exports.py
index 32a2666e48..034c4b7c8a 100644
--- a/src/trio/_tests/test_exports.py
+++ b/src/trio/_tests/test_exports.py
@@ -55,7 +55,7 @@ def _ensure_mypy_cache_updated() -> None:
"--no-error-summary",
"-c",
"import trio",
- ]
+ ],
)
assert not result[1] # stderr
assert not result[0] # stdout
@@ -72,7 +72,8 @@ def test_core_is_properly_reexported() -> None:
found = 0
for source in sources:
if symbol in dir(source) and getattr(source, symbol) is getattr(
- _core, symbol
+ _core,
+ symbol,
):
found += 1
print(symbol, found)
@@ -254,7 +255,9 @@ def no_underscores(symbols: Iterable[str]) -> set[str]:
@pytest.mark.parametrize("module_name", PUBLIC_MODULE_NAMES)
@pytest.mark.parametrize("tool", ["jedi", "mypy"])
def test_static_tool_sees_class_members(
- tool: str, module_name: str, tmp_path: Path
+ tool: str,
+ module_name: str,
+ tmp_path: Path,
) -> None:
module = PUBLIC_MODULES[PUBLIC_MODULE_NAMES.index(module_name)]
@@ -376,7 +379,7 @@ def lookup_symbol(symbol: str) -> dict[str, str]:
skip_if_optional_else_raise(error)
script = jedi.Script(
- f"from {module_name} import {class_name}; {class_name}."
+ f"from {module_name} import {class_name}; {class_name}.",
)
completions = script.complete()
static_names = no_hidden(c.name for c in completions) - ignore_names
diff --git a/src/trio/_tests/test_fakenet.py b/src/trio/_tests/test_fakenet.py
index bde6db0191..487b86514e 100644
--- a/src/trio/_tests/test_fakenet.py
+++ b/src/trio/_tests/test_fakenet.py
@@ -34,14 +34,16 @@ async def test_basic_udp() -> None:
assert port != 0
with pytest.raises(
- OSError, match=r"^\[\w+ \d+\] Invalid argument$"
+ OSError,
+ match=r"^\[\w+ \d+\] Invalid argument$",
) as exc: # Cannot rebind.
await s1.bind(("192.0.2.1", 0))
assert exc.value.errno == errno.EINVAL
# Cannot bind multiple sockets to the same address
with pytest.raises(
- OSError, match=r"^\[\w+ \d+\] (Address (already )?in use|Unknown error)$"
+ OSError,
+ match=r"^\[\w+ \d+\] (Address (already )?in use|Unknown error)$",
) as exc:
await s2.bind(("127.0.0.1", port))
assert exc.value.errno == errno.EADDRINUSE
@@ -131,7 +133,8 @@ async def test_recv_methods() -> None:
@pytest.mark.skipif(
- sys.platform == "win32", reason="functions not in socket on windows"
+ sys.platform == "win32",
+ reason="functions not in socket on windows",
)
async def test_nonwindows_functionality() -> None:
# mypy doesn't support a good way of aborting typechecking on different platforms
@@ -180,13 +183,15 @@ async def test_nonwindows_functionality() -> None:
assert addr == s1.getsockname()
with pytest.raises(
- AttributeError, match="^'FakeSocket' object has no attribute 'share'$"
+ AttributeError,
+ match="^'FakeSocket' object has no attribute 'share'$",
):
await s1.share(0) # type: ignore[attr-defined]
@pytest.mark.skipif(
- sys.platform != "win32", reason="windows-specific fakesocket testing"
+ sys.platform != "win32",
+ reason="windows-specific fakesocket testing",
)
async def test_windows_functionality() -> None:
# mypy doesn't support a good way of aborting typechecking on different platforms
@@ -196,11 +201,13 @@ async def test_windows_functionality() -> None:
s2 = trio.socket.socket(type=trio.socket.SOCK_DGRAM)
await s1.bind(("127.0.0.1", 0))
with pytest.raises(
- AttributeError, match="^'FakeSocket' object has no attribute 'sendmsg'$"
+ AttributeError,
+ match="^'FakeSocket' object has no attribute 'sendmsg'$",
):
await s1.sendmsg([b"jkl"], (), 0, s2.getsockname()) # type: ignore[attr-defined]
with pytest.raises(
- AttributeError, match="^'FakeSocket' object has no attribute 'recvmsg'$"
+ AttributeError,
+ match="^'FakeSocket' object has no attribute 'recvmsg'$",
):
s2.recvmsg(0) # type: ignore[attr-defined]
with pytest.raises(
@@ -224,28 +231,33 @@ async def test_not_implemented_functions() -> None:
# getsockopt
with pytest.raises(
- OSError, match=r"^FakeNet doesn't implement getsockopt\(\d, \d\)$"
+ OSError,
+ match=r"^FakeNet doesn't implement getsockopt\(\d, \d\)$",
):
s1.getsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY)
# setsockopt
with pytest.raises(
- NotImplementedError, match="^FakeNet always has IPV6_V6ONLY=True$"
+ NotImplementedError,
+ match="^FakeNet always has IPV6_V6ONLY=True$",
):
s1.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, False)
with pytest.raises(
- OSError, match=r"^FakeNet doesn't implement setsockopt\(\d+, \d+, \.\.\.\)$"
+ OSError,
+ match=r"^FakeNet doesn't implement setsockopt\(\d+, \d+, \.\.\.\)$",
):
s1.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, True)
with pytest.raises(
- OSError, match=r"^FakeNet doesn't implement setsockopt\(\d+, \d+, \.\.\.\)$"
+ OSError,
+ match=r"^FakeNet doesn't implement setsockopt\(\d+, \d+, \.\.\.\)$",
):
s1.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# set_inheritable
s1.set_inheritable(False)
with pytest.raises(
- NotImplementedError, match="^FakeNet can't make inheritable sockets$"
+ NotImplementedError,
+ match="^FakeNet can't make inheritable sockets$",
):
s1.set_inheritable(True)
@@ -274,7 +286,7 @@ async def test_init() -> None:
with pytest.raises(
NotImplementedError,
match=re.escape(
- f"FakeNet doesn't (yet) support type={trio.socket.SOCK_STREAM}"
+ f"FakeNet doesn't (yet) support type={trio.socket.SOCK_STREAM}",
),
):
s1 = trio.socket.socket()
diff --git a/src/trio/_tests/test_file_io.py b/src/trio/_tests/test_file_io.py
index cd02d4f768..138417ef40 100644
--- a/src/trio/_tests/test_file_io.py
+++ b/src/trio/_tests/test_file_io.py
@@ -59,13 +59,15 @@ def write(self) -> None: # pragma: no cover
def test_wrapped_property(
- async_file: AsyncIOWrapper[mock.Mock], wrapped: mock.Mock
+ async_file: AsyncIOWrapper[mock.Mock],
+ wrapped: mock.Mock,
) -> None:
assert async_file.wrapped is wrapped
def test_dir_matches_wrapped(
- async_file: AsyncIOWrapper[mock.Mock], wrapped: mock.Mock
+ async_file: AsyncIOWrapper[mock.Mock],
+ wrapped: mock.Mock,
) -> None:
attrs = _FILE_SYNC_ATTRS.union(_FILE_ASYNC_METHODS)
@@ -132,7 +134,8 @@ def test_type_stubs_match_lists() -> None:
def test_sync_attrs_forwarded(
- async_file: AsyncIOWrapper[mock.Mock], wrapped: mock.Mock
+ async_file: AsyncIOWrapper[mock.Mock],
+ wrapped: mock.Mock,
) -> None:
for attr_name in _FILE_SYNC_ATTRS:
if attr_name not in dir(async_file):
@@ -142,7 +145,8 @@ def test_sync_attrs_forwarded(
def test_sync_attrs_match_wrapper(
- async_file: AsyncIOWrapper[mock.Mock], wrapped: mock.Mock
+ async_file: AsyncIOWrapper[mock.Mock],
+ wrapped: mock.Mock,
) -> None:
for attr_name in _FILE_SYNC_ATTRS:
if attr_name in dir(async_file):
@@ -174,7 +178,8 @@ def test_async_methods_signature(async_file: AsyncIOWrapper[mock.Mock]) -> None:
async def test_async_methods_wrap(
- async_file: AsyncIOWrapper[mock.Mock], wrapped: mock.Mock
+ async_file: AsyncIOWrapper[mock.Mock],
+ wrapped: mock.Mock,
) -> None:
for meth_name in _FILE_ASYNC_METHODS:
if meth_name not in dir(async_file):
@@ -186,7 +191,8 @@ async def test_async_methods_wrap(
value = await meth(sentinel.argument, keyword=sentinel.keyword)
wrapped_meth.assert_called_once_with(
- sentinel.argument, keyword=sentinel.keyword
+ sentinel.argument,
+ keyword=sentinel.keyword,
)
assert value == wrapped_meth()
@@ -194,7 +200,8 @@ async def test_async_methods_wrap(
async def test_async_methods_match_wrapper(
- async_file: AsyncIOWrapper[mock.Mock], wrapped: mock.Mock
+ async_file: AsyncIOWrapper[mock.Mock],
+ wrapped: mock.Mock,
) -> None:
for meth_name in _FILE_ASYNC_METHODS:
if meth_name in dir(async_file):
diff --git a/src/trio/_tests/test_highlevel_open_tcp_listeners.py b/src/trio/_tests/test_highlevel_open_tcp_listeners.py
index 3196c9e533..6e2abf112a 100644
--- a/src/trio/_tests/test_highlevel_open_tcp_listeners.py
+++ b/src/trio/_tests/test_highlevel_open_tcp_listeners.py
@@ -160,7 +160,11 @@ def getsockopt(self, /, level: int, optname: int) -> int: ...
def getsockopt(self, /, level: int, optname: int, buflen: int) -> bytes: ...
def getsockopt(
- self, /, level: int, optname: int, buflen: int | None = None
+ self,
+ /,
+ level: int,
+ optname: int,
+ buflen: int | None = None,
) -> int | bytes:
if (level, optname) == (tsocket.SOL_SOCKET, tsocket.SO_ACCEPTCONN):
return True
@@ -171,7 +175,12 @@ def setsockopt(self, /, level: int, optname: int, value: int | Buffer) -> None:
@overload
def setsockopt(
- self, /, level: int, optname: int, value: None, optlen: int
+ self,
+ /,
+ level: int,
+ optname: int,
+ value: None,
+ optlen: int,
) -> None: ...
def setsockopt(
@@ -252,7 +261,9 @@ async def getaddrinfo(
]
async def getnameinfo(
- self, sockaddr: tuple[str, int] | tuple[str, int, int, int], flags: int
+ self,
+ sockaddr: tuple[str, int] | tuple[str, int, int, int],
+ flags: int,
) -> tuple[str, str]:
raise NotImplementedError()
@@ -268,8 +279,8 @@ async def test_open_tcp_listeners_multiple_host_cleanup_on_error() -> None:
(tsocket.AF_INET, "1.1.1.1"),
(tsocket.AF_INET, "2.2.2.2"),
(tsocket.AF_INET, "3.3.3.3"),
- ]
- )
+ ],
+ ),
)
with pytest.raises(FakeOSError):
@@ -313,14 +324,16 @@ async def handler(stream: SendStream) -> None:
[{tsocket.AF_INET}, {tsocket.AF_INET6}, {tsocket.AF_INET, tsocket.AF_INET6}],
)
async def test_open_tcp_listeners_some_address_families_unavailable(
- try_families: set[AddressFamily], fail_families: set[AddressFamily]
+ try_families: set[AddressFamily],
+ fail_families: set[AddressFamily],
) -> None:
fsf = FakeSocketFactory(
- 10, raise_on_family={family: errno.EAFNOSUPPORT for family in fail_families}
+ 10,
+ raise_on_family={family: errno.EAFNOSUPPORT for family in fail_families},
)
tsocket.set_custom_socket_factory(fsf)
tsocket.set_custom_hostname_resolver(
- FakeHostnameResolver([(family, "foo") for family in try_families])
+ FakeHostnameResolver([(family, "foo") for family in try_families]),
)
should_succeed = try_families - fail_families
@@ -352,7 +365,7 @@ async def test_open_tcp_listeners_socket_fails_not_afnosupport() -> None:
)
tsocket.set_custom_socket_factory(fsf)
tsocket.set_custom_hostname_resolver(
- FakeHostnameResolver([(tsocket.AF_INET, "foo"), (tsocket.AF_INET6, "bar")])
+ FakeHostnameResolver([(tsocket.AF_INET, "foo"), (tsocket.AF_INET6, "bar")]),
)
with pytest.raises(OSError, match="nope") as exc_info:
@@ -391,6 +404,7 @@ async def test_open_tcp_listeners_backlog_float_error() -> None:
tsocket.set_custom_socket_factory(fsf)
for should_fail in (0.0, 2.18, 3.14, 9.75):
with pytest.raises(
- TypeError, match=f"backlog must be an int or None, not {should_fail!r}"
+ TypeError,
+ match=f"backlog must be an int or None, not {should_fail!r}",
):
await open_tcp_listeners(0, backlog=should_fail) # type: ignore[arg-type]
diff --git a/src/trio/_tests/test_highlevel_open_tcp_stream.py b/src/trio/_tests/test_highlevel_open_tcp_stream.py
index ce1b1ac1de..81d2b403bb 100644
--- a/src/trio/_tests/test_highlevel_open_tcp_stream.py
+++ b/src/trio/_tests/test_highlevel_open_tcp_stream.py
@@ -156,12 +156,14 @@ async def test_local_address_real() -> None:
local_address = "127.0.0.2" if can_bind_127_0_0_2() else "127.0.0.1"
async with await open_tcp_stream(
- *listener.getsockname(), local_address=local_address
+ *listener.getsockname(),
+ local_address=local_address,
) as client_stream:
assert client_stream.socket.getsockname()[0] == local_address
if hasattr(trio.socket, "IP_BIND_ADDRESS_NO_PORT"):
assert client_stream.socket.getsockopt(
- trio.socket.IPPROTO_IP, trio.socket.IP_BIND_ADDRESS_NO_PORT
+ trio.socket.IPPROTO_IP,
+ trio.socket.IP_BIND_ADDRESS_NO_PORT,
)
server_sock, remote_addr = await listener.accept()
await client_stream.aclose()
@@ -172,13 +174,15 @@ async def test_local_address_real() -> None:
# Trying to connect to an ipv4 address with the ipv6 wildcard
# local_address should fail
with pytest.raises(
- OSError, match=r"^all attempts to connect* to *127\.0\.0\.\d:\d+ failed$"
+ OSError,
+ match=r"^all attempts to connect* to *127\.0\.0\.\d:\d+ failed$",
):
await open_tcp_stream(*listener.getsockname(), local_address="::")
# But the ipv4 wildcard address should work
async with await open_tcp_stream(
- *listener.getsockname(), local_address="0.0.0.0"
+ *listener.getsockname(),
+ local_address="0.0.0.0",
) as client_stream:
server_sock, remote_addr = await listener.accept()
server_sock.close()
@@ -318,7 +322,9 @@ async def getaddrinfo(
return [self._ip_to_gai_entry(ip) for ip in self.ip_order]
async def getnameinfo(
- self, sockaddr: tuple[str, int] | tuple[str, int, int, int], flags: int
+ self,
+ sockaddr: tuple[str, int] | tuple[str, int, int, int],
+ flags: int,
) -> tuple[str, str]:
raise NotImplementedError
@@ -391,7 +397,9 @@ async def test_one_host_slow_success(autojump_clock: MockClock) -> None:
async def test_one_host_quick_fail(autojump_clock: MockClock) -> None:
exc, scenario = await run_scenario(
- 82, [("1.2.3.4", 0.123, "error")], expect_error=OSError
+ 82,
+ [("1.2.3.4", 0.123, "error")],
+ expect_error=OSError,
)
assert isinstance(exc, OSError)
assert trio.current_time() == 0.123
@@ -399,7 +407,9 @@ async def test_one_host_quick_fail(autojump_clock: MockClock) -> None:
async def test_one_host_slow_fail(autojump_clock: MockClock) -> None:
exc, scenario = await run_scenario(
- 83, [("1.2.3.4", 100, "error")], expect_error=OSError
+ 83,
+ [("1.2.3.4", 100, "error")],
+ expect_error=OSError,
)
assert isinstance(exc, OSError)
assert trio.current_time() == 100
@@ -407,7 +417,9 @@ async def test_one_host_slow_fail(autojump_clock: MockClock) -> None:
async def test_one_host_failed_after_connect(autojump_clock: MockClock) -> None:
exc, scenario = await run_scenario(
- 83, [("1.2.3.4", 1, "postconnect_fail")], expect_error=KeyboardInterrupt
+ 83,
+ [("1.2.3.4", 1, "postconnect_fail")],
+ expect_error=KeyboardInterrupt,
)
assert isinstance(exc, KeyboardInterrupt)
@@ -532,7 +544,8 @@ async def test_all_fail(autojump_clock: MockClock) -> None:
subexceptions = (Matcher(OSError, match="^sorry$"),) * 4
assert RaisesGroup(
- *subexceptions, match="all attempts to connect to test.example.com:80 failed"
+ *subexceptions,
+ match="all attempts to connect to test.example.com:80 failed",
).matches(exc.__cause__)
assert trio.current_time() == (0.1 + 0.2 + 10)
diff --git a/src/trio/_tests/test_highlevel_open_unix_stream.py b/src/trio/_tests/test_highlevel_open_unix_stream.py
index 38c31b8a4a..4441d751d2 100644
--- a/src/trio/_tests/test_highlevel_open_unix_stream.py
+++ b/src/trio/_tests/test_highlevel_open_unix_stream.py
@@ -12,7 +12,8 @@
assert not TYPE_CHECKING or sys.platform != "win32"
skip_if_not_unix = pytest.mark.skipif(
- not hasattr(socket, "AF_UNIX"), reason="Needs unix socket support"
+ not hasattr(socket, "AF_UNIX"),
+ reason="Needs unix socket support",
)
@@ -79,6 +80,7 @@ async def test_open_unix_socket() -> None:
@pytest.mark.skipif(hasattr(socket, "AF_UNIX"), reason="Test for non-unix platforms")
async def test_error_on_no_unix() -> None:
with pytest.raises(
- RuntimeError, match="^Unix sockets are not supported on this platform$"
+ RuntimeError,
+ match="^Unix sockets are not supported on this platform$",
):
await open_unix_socket("")
diff --git a/src/trio/_tests/test_highlevel_serve_listeners.py b/src/trio/_tests/test_highlevel_serve_listeners.py
index 4909b90d04..6ecd42694f 100644
--- a/src/trio/_tests/test_highlevel_serve_listeners.py
+++ b/src/trio/_tests/test_highlevel_serve_listeners.py
@@ -95,7 +95,9 @@ async def do_tests(parent_nursery: Nursery) -> None:
async with trio.open_nursery() as nursery:
l2: list[MemoryListener] = await nursery.start(
- trio.serve_listeners, handler, listeners
+ trio.serve_listeners,
+ handler,
+ listeners,
)
assert l2 == listeners
# This is just split into another function because gh-136 isn't
@@ -123,7 +125,8 @@ def check_error(e: BaseException) -> bool:
async def test_serve_listeners_accept_capacity_error(
- autojump_clock: MockClock, caplog: pytest.LogCaptureFixture
+ autojump_clock: MockClock,
+ caplog: pytest.LogCaptureFixture,
) -> None:
listener = MemoryListener()
@@ -155,7 +158,8 @@ class Done(Exception):
pass
async def connection_watcher(
- *, task_status: TaskStatus[Nursery] = trio.TASK_STATUS_IGNORED
+ *,
+ task_status: TaskStatus[Nursery] = trio.TASK_STATUS_IGNORED,
) -> NoReturn:
async with trio.open_nursery() as nursery:
task_status.started(nursery)
@@ -173,7 +177,7 @@ async def connection_watcher(
handler,
[listener],
handler_nursery=handler_nursery,
- )
+ ),
)
for _ in range(10):
nursery.start_soon(listener.connect)
diff --git a/src/trio/_tests/test_highlevel_socket.py b/src/trio/_tests/test_highlevel_socket.py
index 976a3b5e04..735353ab7e 100644
--- a/src/trio/_tests/test_highlevel_socket.py
+++ b/src/trio/_tests/test_highlevel_socket.py
@@ -27,7 +27,8 @@ async def test_SocketStream_basics() -> None:
# DGRAM socket bad
with tsocket.socket(type=tsocket.SOCK_DGRAM) as sock:
with pytest.raises(
- ValueError, match="^SocketStream requires a SOCK_STREAM socket$"
+ ValueError,
+ match="^SocketStream requires a SOCK_STREAM socket$",
):
# TODO: does not raise an error?
SocketStream(sock)
@@ -155,7 +156,8 @@ async def test_SocketListener() -> None:
with tsocket.socket(type=tsocket.SOCK_DGRAM) as s:
await s.bind(("127.0.0.1", 0))
with pytest.raises(
- ValueError, match="^SocketListener requires a SOCK_STREAM socket$"
+ ValueError,
+ match="^SocketListener requires a SOCK_STREAM socket$",
) as excinfo:
SocketListener(s)
excinfo.match(r".*SOCK_STREAM")
@@ -166,7 +168,8 @@ async def test_SocketListener() -> None:
with tsocket.socket() as s:
await s.bind(("127.0.0.1", 0))
with pytest.raises(
- ValueError, match="^SocketListener requires a listening socket$"
+ ValueError,
+ match="^SocketListener requires a listening socket$",
) as excinfo:
SocketListener(s)
excinfo.match(r".*listen")
@@ -228,22 +231,39 @@ def getsockopt(self, /, level: int, optname: int) -> int: ...
@overload
def getsockopt( # noqa: F811
- self, /, level: int, optname: int, buflen: int
+ self,
+ /,
+ level: int,
+ optname: int,
+ buflen: int,
) -> bytes: ...
def getsockopt( # noqa: F811
- self, /, level: int, optname: int, buflen: int | None = None
+ self,
+ /,
+ level: int,
+ optname: int,
+ buflen: int | None = None,
) -> int | bytes:
return True
@overload
def setsockopt(
- self, /, level: int, optname: int, value: int | Buffer
+ self,
+ /,
+ level: int,
+ optname: int,
+ value: int | Buffer,
) -> None: ...
@overload
def setsockopt( # noqa: F811
- self, /, level: int, optname: int, value: None, optlen: int
+ self,
+ /,
+ level: int,
+ optname: int,
+ value: None,
+ optlen: int,
) -> None: ...
def setsockopt( # noqa: F811
@@ -276,7 +296,7 @@ async def accept(self) -> tuple[SocketType, object]:
OSError(errno.EFAULT, "attempt to write to read-only memory"),
OSError(errno.ENOBUFS, "out of buffers"),
fake_server_sock,
- ]
+ ],
)
listener = SocketListener(fake_listen_sock)
diff --git a/src/trio/_tests/test_highlevel_ssl_helpers.py b/src/trio/_tests/test_highlevel_ssl_helpers.py
index 53f687d7c3..ca23c333c7 100644
--- a/src/trio/_tests/test_highlevel_ssl_helpers.py
+++ b/src/trio/_tests/test_highlevel_ssl_helpers.py
@@ -82,8 +82,12 @@ async def test_open_ssl_over_tcp_stream_and_everything_else(
res: list[SSLListener[SocketListener]] = ( # type: ignore[type-var]
await nursery.start(
partial(
- serve_ssl_over_tcp, echo_handler, 0, SERVER_CTX, host="127.0.0.1"
- )
+ serve_ssl_over_tcp,
+ echo_handler,
+ 0,
+ SERVER_CTX,
+ host="127.0.0.1",
+ ),
)
)
(listener,) = res
@@ -105,7 +109,9 @@ async def test_open_ssl_over_tcp_stream_and_everything_else(
# We have the trust but not the hostname
# (checks custom ssl_context + hostname checking)
stream = await open_ssl_over_tcp_stream(
- "xyzzy.example.org", 80, ssl_context=client_ctx
+ "xyzzy.example.org",
+ 80,
+ ssl_context=client_ctx,
)
async with stream:
with pytest.raises(trio.BrokenResourceError):
@@ -113,7 +119,9 @@ async def test_open_ssl_over_tcp_stream_and_everything_else(
# This one should work!
stream = await open_ssl_over_tcp_stream(
- "trio-test-1.example.org", 80, ssl_context=client_ctx
+ "trio-test-1.example.org",
+ 80,
+ ssl_context=client_ctx,
)
async with stream:
assert isinstance(stream, trio.SSLStream)
@@ -149,7 +157,10 @@ async def test_open_ssl_over_tcp_listeners() -> None:
assert not listener._https_compatible
(listener,) = await open_ssl_over_tcp_listeners(
- 0, SERVER_CTX, host="127.0.0.1", https_compatible=True
+ 0,
+ SERVER_CTX,
+ host="127.0.0.1",
+ https_compatible=True,
)
async with listener:
assert listener._https_compatible
diff --git a/src/trio/_tests/test_path.py b/src/trio/_tests/test_path.py
index af29a0604b..bbe502afd6 100644
--- a/src/trio/_tests/test_path.py
+++ b/src/trio/_tests/test_path.py
@@ -98,11 +98,14 @@ async def test_div_magic(cls_a: PathOrStrType, cls_b: PathOrStrType) -> None:
@pytest.mark.parametrize(
- ("cls_a", "cls_b"), [(trio.Path, pathlib.Path), (trio.Path, trio.Path)]
+ ("cls_a", "cls_b"),
+ [(trio.Path, pathlib.Path), (trio.Path, trio.Path)],
)
@pytest.mark.parametrize("path", ["foo", "foo/bar/baz", "./foo"])
async def test_hash_magic(
- cls_a: EitherPathType, cls_b: EitherPathType, path: str
+ cls_a: EitherPathType,
+ cls_b: EitherPathType,
+ path: str,
) -> None:
a, b = cls_a(path), cls_b(path)
assert hash(a) == hash(b)
@@ -264,7 +267,7 @@ async def test_classmethods() -> None:
],
)
def test_wrapping_without_docstrings(
- wrapper: Callable[[Callable[[], None]], Callable[[], None]]
+ wrapper: Callable[[Callable[[], None]], Callable[[], None]],
) -> None:
@wrapper
def func_without_docstring() -> None: ... # pragma: no cover
diff --git a/src/trio/_tests/test_repl.py b/src/trio/_tests/test_repl.py
index fbfdb07a05..d585276b9d 100644
--- a/src/trio/_tests/test_repl.py
+++ b/src/trio/_tests/test_repl.py
@@ -76,7 +76,7 @@ async def test_basic_interaction(
# import works
"import sys",
"sys.stdout.write('hello stdout\\n')",
- ]
+ ],
)
monkeypatch.setattr(console, "raw_input", raw_input)
await trio._repl.run_repl(console)
@@ -89,7 +89,7 @@ async def test_system_exits_quit_interpreter(monkeypatch: pytest.MonkeyPatch) ->
raw_input = build_raw_input(
[
"raise SystemExit",
- ]
+ ],
)
monkeypatch.setattr(console, "raw_input", raw_input)
with pytest.raises(SystemExit):
@@ -115,7 +115,7 @@ async def test_KI_interrupts(
"",
"await f()",
"print('AFTER KeyboardInterrupt')",
- ]
+ ],
)
monkeypatch.setattr(console, "raw_input", raw_input)
await trio._repl.run_repl(console)
@@ -138,7 +138,7 @@ async def test_system_exits_in_exc_group(
"",
"raise BaseExceptionGroup('', [RuntimeError(), SystemExit()])",
"print('AFTER BaseExceptionGroup')",
- ]
+ ],
)
monkeypatch.setattr(console, "raw_input", raw_input)
await trio._repl.run_repl(console)
@@ -162,7 +162,7 @@ async def test_system_exits_in_nested_exc_group(
"raise BaseExceptionGroup(",
" '', [BaseExceptionGroup('', [RuntimeError(), SystemExit()])])",
"print('AFTER BaseExceptionGroup')",
- ]
+ ],
)
monkeypatch.setattr(console, "raw_input", raw_input)
await trio._repl.run_repl(console)
@@ -182,7 +182,7 @@ async def test_base_exception_captured(
# The statement after raise should still get executed
"raise BaseException",
"print('AFTER BaseException')",
- ]
+ ],
)
monkeypatch.setattr(console, "raw_input", raw_input)
await trio._repl.run_repl(console)
@@ -202,7 +202,7 @@ async def test_exc_group_captured(
# The statement after raise should still get executed
"raise ExceptionGroup('', [KeyError()])",
"print('AFTER ExceptionGroup')",
- ]
+ ],
)
monkeypatch.setattr(console, "raw_input", raw_input)
await trio._repl.run_repl(console)
@@ -224,7 +224,7 @@ async def test_base_exception_capture_from_coroutine(
# be executed
"await async_func_raises_base_exception()",
"print('AFTER BaseException')",
- ]
+ ],
)
monkeypatch.setattr(console, "raw_input", raw_input)
await trio._repl.run_repl(console)
diff --git a/src/trio/_tests/test_signals.py b/src/trio/_tests/test_signals.py
index 5e639652ef..fd4e32a4c3 100644
--- a/src/trio/_tests/test_signals.py
+++ b/src/trio/_tests/test_signals.py
@@ -43,7 +43,8 @@ async def test_open_signal_receiver() -> None:
async def test_open_signal_receiver_restore_handler_after_one_bad_signal() -> None:
orig = signal.getsignal(signal.SIGILL)
with pytest.raises(
- ValueError, match="(signal number out of range|invalid signal value)$"
+ ValueError,
+ match="(signal number out of range|invalid signal value)$",
):
with open_signal_receiver(signal.SIGILL, 1234567):
pass # pragma: no cover
diff --git a/src/trio/_tests/test_socket.py b/src/trio/_tests/test_socket.py
index 9397733c72..f2c2ee955e 100644
--- a/src/trio/_tests/test_socket.py
+++ b/src/trio/_tests/test_socket.py
@@ -56,7 +56,10 @@ def _frozenbind(self, *args: Any, **kwargs: Any) -> tuple[Any, ...]:
return frozenbound
def set(
- self, response: GetAddrInfoResponse | str, *args: Any, **kwargs: Any
+ self,
+ response: GetAddrInfoResponse | str,
+ *args: Any,
+ **kwargs: Any,
) -> None:
self._responses[self._frozenbind(*args, **kwargs)] = response
@@ -473,7 +476,8 @@ async def test_SocketType_shutdown() -> None:
],
)
async def test_SocketType_simple_server(
- address: str, socket_type: AddressFamily
+ address: str,
+ socket_type: AddressFamily,
) -> None:
# listen, bind, accept, connect, getpeername, getsockname
listener = tsocket.socket(socket_type)
@@ -557,7 +561,8 @@ def pad(addr: tuple[str | int, ...]) -> tuple[str | int, ...]:
return addr
def assert_eq(
- actual: tuple[str | int, ...], expected: tuple[str | int, ...]
+ actual: tuple[str | int, ...],
+ expected: tuple[str | int, ...],
) -> None:
assert pad(expected) == pad(actual)
@@ -589,7 +594,7 @@ async def res(
| tuple[str, str]
| tuple[str, str, int]
| tuple[str, str, int, int]
- )
+ ),
) -> Any:
return await sock._resolve_address_nocp(
args,
@@ -640,7 +645,8 @@ async def res(
# smoke test the basic functionality...
try:
netlink_sock = tsocket.socket(
- family=tsocket.AF_NETLINK, type=tsocket.SOCK_DGRAM
+ family=tsocket.AF_NETLINK,
+ type=tsocket.SOCK_DGRAM,
)
except (AttributeError, OSError):
pass
@@ -794,7 +800,9 @@ def connect(self, *args: Any, **kwargs: Any) -> None:
cancel_scope.cancel()
sock._sock = stdlib_socket.fromfd(
- self.detach(), self.family, self.type
+ self.detach(),
+ self.family,
+ self.type,
)
sock._sock.connect(*args, **kwargs)
# If connect *doesn't* raise, then pretend it did
@@ -843,7 +851,9 @@ async def test_resolve_address_exception_in_connect_closes_socket() -> None:
with tsocket.socket() as sock:
async def _resolve_address_nocp(
- self: Any, *args: Any, **kwargs: Any
+ self: Any,
+ *args: Any,
+ **kwargs: Any,
) -> None:
cancel_scope.cancel()
await _core.checkpoint()
@@ -992,7 +1002,9 @@ async def getaddrinfo(
return ("custom_gai", host, port, family, type, proto, flags)
async def getnameinfo(
- self, sockaddr: tuple[str, int] | tuple[str, int, int, int], flags: int
+ self,
+ sockaddr: tuple[str, int] | tuple[str, int, int, int],
+ flags: int,
) -> tuple[str, tuple[str, int] | tuple[str, int, int, int], int]:
return ("custom_gni", sockaddr, flags)
diff --git a/src/trio/_tests/test_ssl.py b/src/trio/_tests/test_ssl.py
index 8e780b2f9c..7197a47cc3 100644
--- a/src/trio/_tests/test_ssl.py
+++ b/src/trio/_tests/test_ssl.py
@@ -116,11 +116,15 @@ def client_ctx(request: pytest.FixtureRequest) -> ssl.SSLContext:
# The blocking socket server.
def ssl_echo_serve_sync(
- sock: stdlib_socket.socket, *, expect_fail: bool = False
+ sock: stdlib_socket.socket,
+ *,
+ expect_fail: bool = False,
) -> None:
try:
wrapped = SERVER_CTX.wrap_socket(
- sock, server_side=True, suppress_ragged_eofs=False
+ sock,
+ server_side=True,
+ suppress_ragged_eofs=False,
)
with wrapped:
wrapped.do_handshake()
@@ -175,7 +179,8 @@ async def ssl_echo_server_raw(**kwargs: Any) -> AsyncIterator[SocketStream]:
# nursery context manager to exit too.
with a, b:
nursery.start_soon(
- trio.to_thread.run_sync, partial(ssl_echo_serve_sync, b, **kwargs)
+ trio.to_thread.run_sync,
+ partial(ssl_echo_serve_sync, b, **kwargs),
)
yield SocketStream(tsocket.from_stdlib_socket(a))
@@ -185,7 +190,8 @@ async def ssl_echo_server_raw(**kwargs: Any) -> AsyncIterator[SocketStream]:
# echo server (running in a thread)
@asynccontextmanager # type: ignore[misc] # decorated contains Any
async def ssl_echo_server(
- client_ctx: SSLContext, **kwargs: Any
+ client_ctx: SSLContext,
+ **kwargs: Any,
) -> AsyncIterator[SSLStream[Stream]]:
async with ssl_echo_server_raw(**kwargs) as sock:
yield SSLStream(sock, client_ctx, server_hostname="trio-test-1.example.org")
@@ -239,10 +245,10 @@ def __init__(self, sleeper: None = None) -> None:
self._pending_cleartext = bytearray()
self._send_all_conflict_detector = ConflictDetector(
- "simultaneous calls to PyOpenSSLEchoStream.send_all"
+ "simultaneous calls to PyOpenSSLEchoStream.send_all",
)
self._receive_some_conflict_detector = ConflictDetector(
- "simultaneous calls to PyOpenSSLEchoStream.receive_some"
+ "simultaneous calls to PyOpenSSLEchoStream.receive_some",
)
if sleeper is None:
@@ -358,7 +364,10 @@ async def test_PyOpenSSLEchoStream_gives_resource_busy_errors() -> None:
# PyOpenSSLEchoStream will notice and complain.
async def do_test(
- func1: str, args1: tuple[object, ...], func2: str, args2: tuple[object, ...]
+ func1: str,
+ args1: tuple[object, ...],
+ func2: str,
+ args2: tuple[object, ...],
) -> None:
s = PyOpenSSLEchoStream()
with RaisesGroup(Matcher(_core.BusyResourceError, "simultaneous")):
@@ -369,14 +378,18 @@ async def do_test(
await do_test("send_all", (b"x",), "send_all", (b"x",))
await do_test("send_all", (b"x",), "wait_send_all_might_not_block", ())
await do_test(
- "wait_send_all_might_not_block", (), "wait_send_all_might_not_block", ()
+ "wait_send_all_might_not_block",
+ (),
+ "wait_send_all_might_not_block",
+ (),
)
await do_test("receive_some", (1,), "receive_some", (1,))
@contextmanager # type: ignore[misc] # decorated contains Any
def virtual_ssl_echo_server(
- client_ctx: SSLContext, **kwargs: Any
+ client_ctx: SSLContext,
+ **kwargs: Any,
) -> Iterator[SSLStream[PyOpenSSLEchoStream]]:
fakesock = PyOpenSSLEchoStream(**kwargs)
yield SSLStream(fakesock, client_ctx, server_hostname="trio-test-1.example.org")
@@ -401,7 +414,10 @@ def ssl_wrap_pair(
**client_kwargs,
)
server_ssl = SSLStream(
- server_transport, SERVER_CTX, server_side=True, **server_kwargs
+ server_transport,
+ SERVER_CTX,
+ server_side=True,
+ **server_kwargs,
)
return client_ssl, server_ssl
@@ -462,13 +478,16 @@ async def test_ssl_server_basics(client_ctx: SSLContext) -> None:
with a, b:
server_sock = tsocket.from_stdlib_socket(b)
server_transport = SSLStream(
- SocketStream(server_sock), SERVER_CTX, server_side=True
+ SocketStream(server_sock),
+ SERVER_CTX,
+ server_side=True,
)
assert server_transport.server_side
def client() -> None:
with client_ctx.wrap_socket(
- a, server_hostname="trio-test-1.example.org"
+ a,
+ server_hostname="trio-test-1.example.org",
) as client_sock:
client_sock.sendall(b"x")
assert client_sock.recv(1) == b"y"
@@ -612,7 +631,8 @@ async def test_renegotiation_simple(client_ctx: SSLContext) -> None:
@slow
async def test_renegotiation_randomized(
- mock_clock: MockClock, client_ctx: SSLContext
+ mock_clock: MockClock,
+ client_ctx: SSLContext,
) -> None:
# The only blocking things in this function are our random sleeps, so 0 is
# a good threshold.
@@ -716,7 +736,8 @@ async def sleeper_with_slow_wait_writable_and_expect(method: str) -> None:
await trio.sleep(1000)
with virtual_ssl_echo_server(
- client_ctx, sleeper=sleeper_with_slow_wait_writable_and_expect
+ client_ctx,
+ sleeper=sleeper_with_slow_wait_writable_and_expect,
) as s:
await send(b"x")
s.transport_stream.renegotiate()
@@ -747,7 +768,8 @@ async def do_wait_send_all_might_not_block(s: S) -> None:
await s.wait_send_all_might_not_block()
async def do_test(
- func1: Callable[[S], Awaitable[None]], func2: Callable[[S], Awaitable[None]]
+ func1: Callable[[S], Awaitable[None]],
+ func2: Callable[[S], Awaitable[None]],
) -> None:
s, _ = ssl_lockstep_stream_pair(client_ctx)
with RaisesGroup(Matcher(_core.BusyResourceError, "another task")):
@@ -835,7 +857,8 @@ async def test_send_all_empty_string(client_ctx: SSLContext) -> None:
@pytest.mark.parametrize("https_compatible", [False, True])
async def test_SSLStream_generic(
- client_ctx: SSLContext, https_compatible: bool
+ client_ctx: SSLContext,
+ https_compatible: bool,
) -> None:
async def stream_maker() -> tuple[
SSLStream[MemoryStapledStream],
@@ -1017,12 +1040,16 @@ async def test_ssl_over_ssl(client_ctx: SSLContext) -> None:
client_0, server_0 = memory_stream_pair()
client_1 = SSLStream(
- client_0, client_ctx, server_hostname="trio-test-1.example.org"
+ client_0,
+ client_ctx,
+ server_hostname="trio-test-1.example.org",
)
server_1 = SSLStream(server_0, SERVER_CTX, server_side=True)
client_2 = SSLStream(
- client_1, client_ctx, server_hostname="trio-test-1.example.org"
+ client_1,
+ client_ctx,
+ server_hostname="trio-test-1.example.org",
)
server_2 = SSLStream(server_1, SERVER_CTX, server_side=True)
@@ -1302,7 +1329,9 @@ async def setup(
transport_client = await open_tcp_stream(*listen_sock.getsockname())
ssl_client = SSLStream(
- transport_client, client_ctx, server_hostname="trio-test-1.example.org"
+ transport_client,
+ client_ctx,
+ server_hostname="trio-test-1.example.org",
)
return listen_sock, ssl_listener, ssl_client
diff --git a/src/trio/_tests/test_subprocess.py b/src/trio/_tests/test_subprocess.py
index 0a70e7a974..d65a07614a 100644
--- a/src/trio/_tests/test_subprocess.py
+++ b/src/trio/_tests/test_subprocess.py
@@ -131,7 +131,8 @@ async def test_basic(background_process: BackgroundProcessType) -> None:
await proc.wait()
assert proc.returncode == 1
assert repr(proc) == "".format(
- EXIT_FALSE, "exited with status 1"
+ EXIT_FALSE,
+ "exited with status 1",
)
@@ -173,7 +174,7 @@ async def test_multi_wait(background_process: BackgroundProcessType) -> None:
COPY_STDIN_TO_STDOUT_AND_BACKWARD_TO_STDERR = python(
"data = sys.stdin.buffer.read(); "
"sys.stdout.buffer.write(data); "
- "sys.stderr.buffer.write(data[::-1])"
+ "sys.stderr.buffer.write(data[::-1])",
)
@@ -234,7 +235,7 @@ async def test_interactive(background_process: BackgroundProcessType) -> None:
" request = int(line.strip())\n"
" print(str(idx * 2) * request)\n"
" print(str(idx * 2 + 1) * request * 2, file=sys.stderr)\n"
- " idx += 1\n"
+ " idx += 1\n",
),
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
@@ -246,7 +247,9 @@ async def expect(idx: int, request: int) -> None:
async with _core.open_nursery() as nursery:
async def drain_one(
- stream: ReceiveStream, count: int, digit: int
+ stream: ReceiveStream,
+ count: int,
+ digit: int,
) -> None:
while count > 0:
result = await stream.receive_some(count)
@@ -291,7 +294,10 @@ async def test_run() -> None:
data = bytes(random.randint(0, 255) for _ in range(2**18))
result = await run_process(
- CAT, stdin=data, capture_stdout=True, capture_stderr=True
+ CAT,
+ stdin=data,
+ capture_stdout=True,
+ capture_stderr=True,
)
assert result.args == CAT
assert result.returncode == 0
@@ -325,7 +331,8 @@ async def test_run() -> None:
with pytest.raises(ValueError, match=pipe_stdout_error):
await run_process(CAT, stdout=subprocess.PIPE)
with pytest.raises(
- ValueError, match=pipe_stdout_error.replace("stdout", "stderr", 1)
+ ValueError,
+ match=pipe_stdout_error.replace("stdout", "stderr", 1),
):
await run_process(CAT, stderr=subprocess.PIPE)
with pytest.raises(
@@ -350,7 +357,10 @@ async def test_run_check() -> None:
assert excinfo.value.stdout is None
result = await run_process(
- cmd, capture_stdout=True, capture_stderr=True, check=False
+ cmd,
+ capture_stdout=True,
+ capture_stderr=True,
+ check=False,
)
assert result.args == cmd
assert result.stdout == b""
@@ -361,7 +371,8 @@ async def test_run_check() -> None:
@skip_if_fbsd_pipes_broken
async def test_run_with_broken_pipe() -> None:
result = await run_process(
- [sys.executable, "-c", "import sys; sys.stdin.close()"], stdin=b"x" * 131072
+ [sys.executable, "-c", "import sys; sys.stdin.close()"],
+ stdin=b"x" * 131072,
)
assert result.returncode == 0
assert result.stdout is result.stderr is None
@@ -404,7 +415,9 @@ async def test_stderr_stdout(background_process: BackgroundProcessType) -> None:
# this one hits the branch where stderr=STDOUT but stdout
# is not redirected
async with background_process(
- CAT, stdin=subprocess.PIPE, stderr=subprocess.STDOUT
+ CAT,
+ stdin=subprocess.PIPE,
+ stderr=subprocess.STDOUT,
) as proc:
assert proc.stdout is None
assert proc.stderr is None
@@ -452,7 +465,8 @@ async def test_errors() -> None:
@background_process_param
async def test_signals(background_process: BackgroundProcessType) -> None:
async def test_one_signal(
- send_it: Callable[[Process], None], signum: signal.Signals | None
+ send_it: Callable[[Process], None],
+ signum: signal.Signals | None,
) -> None:
with move_on_after(1.0) as scope:
async with background_process(SLEEP(3600)) as proc:
@@ -557,7 +571,7 @@ async def custom_deliver_cancel(proc: Process) -> None:
async with _core.open_nursery() as nursery:
nursery.start_soon(
- partial(run_process, SLEEP(9999), deliver_cancel=custom_deliver_cancel)
+ partial(run_process, SLEEP(9999), deliver_cancel=custom_deliver_cancel),
)
await wait_all_tasks_blocked()
nursery.cancel_scope.cancel()
@@ -573,7 +587,7 @@ async def custom_deliver_cancel(proc: Process) -> None:
async def do_stuff() -> None:
async with _core.open_nursery() as nursery:
nursery.start_soon(
- partial(run_process, SLEEP(9999), deliver_cancel=custom_deliver_cancel)
+ partial(run_process, SLEEP(9999), deliver_cancel=custom_deliver_cancel),
)
await wait_all_tasks_blocked()
nursery.cancel_scope.cancel()
@@ -601,7 +615,8 @@ def broken_terminate(self: Process) -> NoReturn:
@pytest.mark.skipif(not posix, reason="posix only")
async def test_warn_on_cancel_SIGKILL_escalation(
- autojump_clock: MockClock, monkeypatch: pytest.MonkeyPatch
+ autojump_clock: MockClock,
+ monkeypatch: pytest.MonkeyPatch,
) -> None:
monkeypatch.setattr(Process, "terminate", lambda *args: None)
diff --git a/src/trio/_tests/test_sync.py b/src/trio/_tests/test_sync.py
index e4d04202cb..caf3f04f5b 100644
--- a/src/trio/_tests/test_sync.py
+++ b/src/trio/_tests/test_sync.py
@@ -494,7 +494,9 @@ def release(self) -> None:
]
generic_lock_test = pytest.mark.parametrize(
- "lock_factory", lock_factories, ids=lock_factory_names
+ "lock_factory",
+ lock_factories,
+ ids=lock_factory_names,
)
LockLike: TypeAlias = Union[
diff --git a/src/trio/_tests/test_testing.py b/src/trio/_tests/test_testing.py
index 0f2778dc15..07ab6a5a3a 100644
--- a/src/trio/_tests/test_testing.py
+++ b/src/trio/_tests/test_testing.py
@@ -393,7 +393,9 @@ def close_hook() -> None:
record.append("close_hook")
mss2 = MemorySendStream(
- send_all_hook, wait_send_all_might_not_block_hook, close_hook
+ send_all_hook,
+ wait_send_all_might_not_block_hook,
+ close_hook,
)
assert mss2.send_all_hook is send_all_hook
@@ -670,10 +672,13 @@ async def check(listener: SocketListener) -> None:
def test_trio_test() -> None:
async def busy_kitchen(
- *, mock_clock: object, autojump_clock: object
+ *,
+ mock_clock: object,
+ autojump_clock: object,
) -> None: ... # pragma: no cover
with pytest.raises(ValueError, match="^too many clocks spoil the broth!$"):
trio_test(busy_kitchen)(
- mock_clock=MockClock(), autojump_clock=MockClock(autojump_threshold=0)
+ mock_clock=MockClock(),
+ autojump_clock=MockClock(autojump_threshold=0),
)
diff --git a/src/trio/_tests/test_testing_raisesgroup.py b/src/trio/_tests/test_testing_raisesgroup.py
index 1e96d38e52..f877b5bd0c 100644
--- a/src/trio/_tests/test_testing_raisesgroup.py
+++ b/src/trio/_tests/test_testing_raisesgroup.py
@@ -22,7 +22,7 @@ def test_raises_group() -> None:
with pytest.raises(
ValueError,
match=wrap_escape(
- f'Invalid argument "{TypeError()!r}" must be exception type, Matcher, or RaisesGroup.'
+ f'Invalid argument "{TypeError()!r}" must be exception type, Matcher, or RaisesGroup.',
),
):
RaisesGroup(TypeError())
@@ -94,7 +94,8 @@ def test_flatten_subgroups() -> None:
raise ExceptionGroup("", (ExceptionGroup("", (ValueError(),)),))
with RaisesGroup(RaisesGroup(ValueError, flatten_subgroups=True)):
raise ExceptionGroup(
- "", (ExceptionGroup("", (ExceptionGroup("", (ValueError(),)),)),)
+ "",
+ (ExceptionGroup("", (ExceptionGroup("", (ValueError(),)),)),),
)
with pytest.raises(ExceptionGroup):
with RaisesGroup(RaisesGroup(ValueError, flatten_subgroups=True)):
@@ -116,7 +117,8 @@ def test_catch_unwrapped_exceptions() -> None:
# expecting multiple unwrapped exceptions is not possible
with pytest.raises(
- ValueError, match="^You cannot specify multiple exceptions with"
+ ValueError,
+ match="^You cannot specify multiple exceptions with",
):
RaisesGroup(SyntaxError, ValueError, allow_unwrapped=True) # type: ignore[call-overload]
# if users want one of several exception types they need to use a Matcher
@@ -245,7 +247,8 @@ def check_message(message: str, body: RaisesGroup[Any]) -> None:
check_message("ExceptionGroup(ValueError)", RaisesGroup(ValueError))
# multiple exceptions
check_message(
- "ExceptionGroup(ValueError, ValueError)", RaisesGroup(ValueError, ValueError)
+ "ExceptionGroup(ValueError, ValueError)",
+ RaisesGroup(ValueError, ValueError),
)
# nested
check_message(
@@ -265,7 +268,8 @@ def check_message(message: str, body: RaisesGroup[Any]) -> None:
# BaseExceptionGroup
check_message(
- "BaseExceptionGroup(KeyboardInterrupt)", RaisesGroup(KeyboardInterrupt)
+ "BaseExceptionGroup(KeyboardInterrupt)",
+ RaisesGroup(KeyboardInterrupt),
)
# BaseExceptionGroup with type inside Matcher
check_message(
@@ -286,7 +290,8 @@ def check_message(message: str, body: RaisesGroup[Any]) -> None:
def test_matcher() -> None:
with pytest.raises(
- ValueError, match="^You must specify at least one parameter to match on.$"
+ ValueError,
+ match="^You must specify at least one parameter to match on.$",
):
Matcher() # type: ignore[call-overload]
with pytest.raises(
diff --git a/src/trio/_tests/test_threads.py b/src/trio/_tests/test_threads.py
index b4a5842ff0..f2f1cf7815 100644
--- a/src/trio/_tests/test_threads.py
+++ b/src/trio/_tests/test_threads.py
@@ -336,7 +336,8 @@ def g() -> NoReturn:
raise ValueError(threading.current_thread())
with pytest.raises(
- ValueError, match=r"^$"
+ ValueError,
+ match=r"^$",
) as excinfo:
await to_thread_run_sync(g)
print(excinfo.value.args)
@@ -404,7 +405,8 @@ async def child(q: stdlib_queue.Queue[None], abandon_on_cancel: bool) -> None:
# handled gracefully. (Requires that the thread result machinery be prepared
# for call_soon to raise RunFinishedError.)
def test_run_in_worker_thread_abandoned(
- capfd: pytest.CaptureFixture[str], monkeypatch: pytest.MonkeyPatch
+ capfd: pytest.CaptureFixture[str],
+ monkeypatch: pytest.MonkeyPatch,
) -> None:
monkeypatch.setattr(_core._thread_cache, "IDLE_TIMEOUT", 0.01)
@@ -444,7 +446,9 @@ async def child() -> None:
@pytest.mark.parametrize("cancel", [False, True])
@pytest.mark.parametrize("use_default_limiter", [False, True])
async def test_run_in_worker_thread_limiter(
- MAX: int, cancel: bool, use_default_limiter: bool
+ MAX: int,
+ cancel: bool,
+ use_default_limiter: bool,
) -> None:
# This test is a bit tricky. The goal is to make sure that if we set
# limiter=CapacityLimiter(MAX), then in fact only MAX threads are ever
@@ -647,7 +651,7 @@ async def async_fn() -> None: # pragma: no cover
trio_test_contextvar: contextvars.ContextVar[str] = contextvars.ContextVar(
- "trio_test_contextvar"
+ "trio_test_contextvar",
)
@@ -879,7 +883,7 @@ async def agen(token: _core.TrioToken | None) -> AsyncGenerator[None, None]:
with _core.CancelScope(shield=True):
try:
await to_thread_run_sync(
- partial(from_thread_run, sleep, 0, trio_token=token)
+ partial(from_thread_run, sleep, 0, trio_token=token),
)
except _core.RunFinishedError:
record.append("finished")
diff --git a/src/trio/_tests/test_wait_for_object.py b/src/trio/_tests/test_wait_for_object.py
index 54bbb77567..58f2198fa6 100644
--- a/src/trio/_tests/test_wait_for_object.py
+++ b/src/trio/_tests/test_wait_for_object.py
@@ -81,7 +81,9 @@ async def test_WaitForMultipleObjects_sync_slow() -> None:
t0 = _core.current_time()
async with _core.open_nursery() as nursery:
nursery.start_soon(
- trio.to_thread.run_sync, WaitForMultipleObjects_sync, handle1
+ trio.to_thread.run_sync,
+ WaitForMultipleObjects_sync,
+ handle1,
)
await _timeouts.sleep(TIMEOUT)
# If we would comment the line below, the above thread will be stuck,
@@ -98,7 +100,10 @@ async def test_WaitForMultipleObjects_sync_slow() -> None:
t0 = _core.current_time()
async with _core.open_nursery() as nursery:
nursery.start_soon(
- trio.to_thread.run_sync, WaitForMultipleObjects_sync, handle1, handle2
+ trio.to_thread.run_sync,
+ WaitForMultipleObjects_sync,
+ handle1,
+ handle2,
)
await _timeouts.sleep(TIMEOUT)
kernel32.SetEvent(handle1)
@@ -114,7 +119,10 @@ async def test_WaitForMultipleObjects_sync_slow() -> None:
t0 = _core.current_time()
async with _core.open_nursery() as nursery:
nursery.start_soon(
- trio.to_thread.run_sync, WaitForMultipleObjects_sync, handle1, handle2
+ trio.to_thread.run_sync,
+ WaitForMultipleObjects_sync,
+ handle1,
+ handle2,
)
await _timeouts.sleep(TIMEOUT)
kernel32.SetEvent(handle2)
diff --git a/src/trio/_tests/tools/test_mypy_annotate.py b/src/trio/_tests/tools/test_mypy_annotate.py
index 1289e2ad48..09a57ce745 100644
--- a/src/trio/_tests/tools/test_mypy_annotate.py
+++ b/src/trio/_tests/tools/test_mypy_annotate.py
@@ -119,7 +119,7 @@ def test_endtoend(
monkeypatch.setattr(sys, "stdin", io.StringIO(inp_text))
mypy_annotate.main(
- ["--dumpfile", str(result_file), "--platform", "SomePlatform"]
+ ["--dumpfile", str(result_file), "--platform", "SomePlatform"],
)
std = capsys.readouterr()
diff --git a/src/trio/_tests/type_tests/path.py b/src/trio/_tests/type_tests/path.py
index 15d25ae954..5c7300fab5 100644
--- a/src/trio/_tests/type_tests/path.py
+++ b/src/trio/_tests/type_tests/path.py
@@ -113,7 +113,8 @@ async def async_attrs(path: trio.Path) -> None:
assert_type(await path.unlink(missing_ok=True), None)
assert_type(await path.write_bytes(b"123"), int)
assert_type(
- await path.write_text("hello", encoding="utf32le", errors="ignore"), int
+ await path.write_text("hello", encoding="utf32le", errors="ignore"),
+ int,
)
diff --git a/src/trio/_tests/type_tests/raisesgroup.py b/src/trio/_tests/type_tests/raisesgroup.py
index fe4053ebc5..e637ace076 100644
--- a/src/trio/_tests/type_tests/raisesgroup.py
+++ b/src/trio/_tests/type_tests/raisesgroup.py
@@ -64,7 +64,8 @@ def check_matches_with_different_exception_type() -> None:
# This should probably raise some type error somewhere, since
# ValueError != KeyboardInterrupt
e: BaseExceptionGroup[KeyboardInterrupt] = BaseExceptionGroup(
- "", (KeyboardInterrupt(),)
+ "",
+ (KeyboardInterrupt(),),
)
if RaisesGroup(ValueError).matches(e):
assert_type(e, BaseExceptionGroup[ValueError])
@@ -192,7 +193,8 @@ def check_nested_raisesgroups_contextmanager() -> None:
def check_nested_raisesgroups_matches() -> None:
"""Check nested RaisesGroups with .matches"""
exc: ExceptionGroup[ExceptionGroup[ValueError]] = ExceptionGroup(
- "", (ExceptionGroup("", (ValueError(),)),)
+ "",
+ (ExceptionGroup("", (ValueError(),)),),
)
# has the same problems as check_nested_raisesgroups_contextmanager
if RaisesGroup(RaisesGroup(ValueError)).matches(exc):
diff --git a/src/trio/_threads.py b/src/trio/_threads.py
index 0f0e585003..4cd460078a 100644
--- a/src/trio/_threads.py
+++ b/src/trio/_threads.py
@@ -150,10 +150,12 @@ class Run(Generic[RetT]):
afn: Callable[..., Awaitable[RetT]]
args: tuple[object, ...]
context: contextvars.Context = attrs.field(
- init=False, factory=contextvars.copy_context
+ init=False,
+ factory=contextvars.copy_context,
)
queue: stdlib_queue.SimpleQueue[outcome.Outcome[RetT]] = attrs.field(
- init=False, factory=stdlib_queue.SimpleQueue
+ init=False,
+ factory=stdlib_queue.SimpleQueue,
)
@disable_ki_protection
@@ -190,11 +192,13 @@ def run_in_system_nursery(self, token: TrioToken) -> None:
def in_trio_thread() -> None:
try:
trio.lowlevel.spawn_system_task(
- self.run_system, name=self.afn, context=self.context
+ self.run_system,
+ name=self.afn,
+ context=self.context,
)
except RuntimeError: # system nursery is closed
self.queue.put_nowait(
- outcome.Error(trio.RunFinishedError("system nursery is closed"))
+ outcome.Error(trio.RunFinishedError("system nursery is closed")),
)
token.run_sync_soon(in_trio_thread)
@@ -205,10 +209,12 @@ class RunSync(Generic[RetT]):
fn: Callable[..., RetT]
args: tuple[object, ...]
context: contextvars.Context = attrs.field(
- init=False, factory=contextvars.copy_context
+ init=False,
+ factory=contextvars.copy_context,
)
queue: stdlib_queue.SimpleQueue[outcome.Outcome[RetT]] = attrs.field(
- init=False, factory=stdlib_queue.SimpleQueue
+ init=False,
+ factory=stdlib_queue.SimpleQueue,
)
@disable_ki_protection
@@ -220,7 +226,7 @@ def unprotected_fn(self) -> RetT:
ret.close()
raise TypeError(
"Trio expected a synchronous function, but {!r} appears to be "
- "asynchronous".format(getattr(self.fn, "__qualname__", self.fn))
+ "asynchronous".format(getattr(self.fn, "__qualname__", self.fn)),
)
return ret
@@ -386,7 +392,7 @@ def worker_fn() -> RetT:
ret.close()
raise TypeError(
"Trio expected a sync function, but {!r} appears to be "
- "asynchronous".format(getattr(sync_fn, "__qualname__", sync_fn))
+ "asynchronous".format(getattr(sync_fn, "__qualname__", sync_fn)),
)
return ret
@@ -441,7 +447,7 @@ def abort(raise_cancel: RaiseCancelT) -> trio.lowlevel.Abort:
msg_from_thread.run_sync()
else: # pragma: no cover, internal debugging guard TODO: use assert_never
raise TypeError(
- f"trio.to_thread.run_sync received unrecognized thread message {msg_from_thread!r}."
+ f"trio.to_thread.run_sync received unrecognized thread message {msg_from_thread!r}.",
)
del msg_from_thread
@@ -477,14 +483,15 @@ def from_thread_check_cancelled() -> None:
raise_cancel = PARENT_TASK_DATA.cancel_register[0]
except AttributeError:
raise RuntimeError(
- "this thread wasn't created by Trio, can't check for cancellation"
+ "this thread wasn't created by Trio, can't check for cancellation",
) from None
if raise_cancel is not None:
raise_cancel()
def _send_message_to_trio(
- trio_token: TrioToken | None, message_to_trio: Run[RetT] | RunSync[RetT]
+ trio_token: TrioToken | None,
+ message_to_trio: Run[RetT] | RunSync[RetT],
) -> RetT:
"""Shared logic of from_thread functions"""
token_provided = trio_token is not None
@@ -494,7 +501,7 @@ def _send_message_to_trio(
trio_token = PARENT_TASK_DATA.token
except AttributeError:
raise RuntimeError(
- "this thread wasn't created by Trio, pass kwarg trio_token=..."
+ "this thread wasn't created by Trio, pass kwarg trio_token=...",
) from None
elif not isinstance(trio_token, TrioToken):
raise RuntimeError("Passed kwarg trio_token is not of type TrioToken")
diff --git a/src/trio/_tools/gen_exports.py b/src/trio/_tools/gen_exports.py
index b723bb229b..8e243be72b 100755
--- a/src/trio/_tools/gen_exports.py
+++ b/src/trio/_tools/gen_exports.py
@@ -211,7 +211,7 @@ def gen_public_wrappers_source(file: File) -> str:
if "import sys" not in file.imports: # pragma: no cover
header.append("import sys\n")
header.append(
- f'\nassert not TYPE_CHECKING or sys.platform=="{file.platform}"\n'
+ f'\nassert not TYPE_CHECKING or sys.platform=="{file.platform}"\n',
)
generated = ["".join(header)]
@@ -310,10 +310,13 @@ def process(files: Iterable[File], *, do_test: bool) -> None:
# doesn't collect coverage.
def main() -> None: # pragma: no cover
parser = argparse.ArgumentParser(
- description="Generate python code for public api wrappers"
+ description="Generate python code for public api wrappers",
)
parser.add_argument(
- "--test", "-t", action="store_true", help="test if code is still up to date"
+ "--test",
+ "-t",
+ action="store_true",
+ help="test if code is still up to date",
)
parsed_args = parser.parse_args()
diff --git a/src/trio/_tools/mypy_annotate.py b/src/trio/_tools/mypy_annotate.py
index 6bd20f401c..5acb9b993c 100644
--- a/src/trio/_tools/mypy_annotate.py
+++ b/src/trio/_tools/mypy_annotate.py
@@ -86,7 +86,9 @@ def main(argv: list[str]) -> None:
"""Look for error messages, and convert the format."""
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
- "--dumpfile", help="File to write pickled messages to.", required=True
+ "--dumpfile",
+ help="File to write pickled messages to.",
+ required=True,
)
parser.add_argument(
"--platform",
diff --git a/src/trio/_unix_pipes.py b/src/trio/_unix_pipes.py
index 34340d2b36..a95f761bcc 100644
--- a/src/trio/_unix_pipes.py
+++ b/src/trio/_unix_pipes.py
@@ -121,10 +121,10 @@ class FdStream(Stream):
def __init__(self, fd: int) -> None:
self._fd_holder = _FdHolder(fd)
self._send_conflict_detector = ConflictDetector(
- "another task is using this stream for send"
+ "another task is using this stream for send",
)
self._receive_conflict_detector = ConflictDetector(
- "another task is using this stream for receive"
+ "another task is using this stream for receive",
)
async def send_all(self, data: bytes) -> None:
@@ -147,7 +147,7 @@ async def send_all(self, data: bytes) -> None:
except OSError as e:
if e.errno == errno.EBADF:
raise trio.ClosedResourceError(
- "file was already closed"
+ "file was already closed",
) from None
else:
raise trio.BrokenResourceError from e
@@ -182,7 +182,7 @@ async def receive_some(self, max_bytes: int | None = None) -> bytes:
except OSError as exc:
if exc.errno == errno.EBADF:
raise trio.ClosedResourceError(
- "file was already closed"
+ "file was already closed",
) from None
else:
raise trio.BrokenResourceError from exc
diff --git a/src/trio/_util.py b/src/trio/_util.py
index 48994204a7..3af8b5cfde 100644
--- a/src/trio/_util.py
+++ b/src/trio/_util.py
@@ -154,7 +154,7 @@ def _return_value_looks_like_wrong_library(value: object) -> bool:
"Instead, you want (notice the parentheses!):\n"
"\n"
f" trio.run({async_fn.__name__}, ...) # correct!\n"
- f" nursery.start_soon({async_fn.__name__}, ...) # correct!"
+ f" nursery.start_soon({async_fn.__name__}, ...) # correct!",
) from None
# Give good error for: nursery.start_soon(future)
@@ -163,7 +163,7 @@ def _return_value_looks_like_wrong_library(value: object) -> bool:
"Trio was expecting an async function, but instead it got "
f"{async_fn!r} – are you trying to use a library written for "
"asyncio/twisted/tornado or similar? That won't work "
- "without some sort of compatibility shim."
+ "without some sort of compatibility shim.",
) from None
raise
@@ -183,19 +183,19 @@ def _return_value_looks_like_wrong_library(value: object) -> bool:
raise TypeError(
f"Trio got unexpected {coro!r} – are you trying to use a "
"library written for asyncio/twisted/tornado or similar? "
- "That won't work without some sort of compatibility shim."
+ "That won't work without some sort of compatibility shim.",
)
if inspect.isasyncgen(coro):
raise TypeError(
"start_soon expected an async function but got an async "
- f"generator {coro!r}"
+ f"generator {coro!r}",
)
# Give good error for: nursery.start_soon(some_sync_fn)
raise TypeError(
"Trio expected an async function, but {!r} appears to be "
- "synchronous".format(getattr(async_fn, "__qualname__", async_fn))
+ "synchronous".format(getattr(async_fn, "__qualname__", async_fn)),
)
return coro
@@ -253,7 +253,8 @@ def decorator(func: CallT) -> CallT:
def fixup_module_metadata(
- module_name: str, namespace: collections.abc.Mapping[str, object]
+ module_name: str,
+ namespace: collections.abc.Mapping[str, object],
) -> None:
seen_ids: set[int] = set()
@@ -370,7 +371,7 @@ class SomeClass(metaclass=NoPublicConstructor):
def __call__(cls, *args: object, **kwargs: object) -> None:
raise TypeError(
- f"{cls.__module__}.{cls.__qualname__} has no public constructor"
+ f"{cls.__module__}.{cls.__qualname__} has no public constructor",
)
def _create(cls: type[T], *args: object, **kwargs: object) -> T:
diff --git a/src/trio/_windows_pipes.py b/src/trio/_windows_pipes.py
index 43592807b8..e1eea1e72d 100644
--- a/src/trio/_windows_pipes.py
+++ b/src/trio/_windows_pipes.py
@@ -49,7 +49,7 @@ class PipeSendStream(SendStream):
def __init__(self, handle: int) -> None:
self._handle_holder = _HandleHolder(handle)
self._conflict_detector = ConflictDetector(
- "another task is currently using this pipe"
+ "another task is currently using this pipe",
)
async def send_all(self, data: bytes) -> None:
@@ -93,7 +93,7 @@ class PipeReceiveStream(ReceiveStream):
def __init__(self, handle: int) -> None:
self._handle_holder = _HandleHolder(handle)
self._conflict_detector = ConflictDetector(
- "another task is currently using this pipe"
+ "another task is currently using this pipe",
)
async def receive_some(self, max_bytes: int | None = None) -> bytes:
@@ -112,12 +112,13 @@ async def receive_some(self, max_bytes: int | None = None) -> bytes:
buffer = bytearray(max_bytes)
try:
size = await _core.readinto_overlapped(
- self._handle_holder.handle, buffer
+ self._handle_holder.handle,
+ buffer,
)
except BrokenPipeError:
if self._handle_holder.closed:
raise _core.ClosedResourceError(
- "another task closed this pipe"
+ "another task closed this pipe",
) from None
# Windows raises BrokenPipeError on one end of a pipe
diff --git a/src/trio/socket.py b/src/trio/socket.py
index e38501fb60..617f0382c0 100644
--- a/src/trio/socket.py
+++ b/src/trio/socket.py
@@ -29,7 +29,7 @@
_name: getattr(_stdlib_socket, _name)
for _name in _stdlib_socket.__all__ # type: ignore
if _name.isupper() and _name not in _bad_symbols
- }
+ },
)
# import the overwrites
diff --git a/src/trio/testing/_check_streams.py b/src/trio/testing/_check_streams.py
index c54c99c1fe..335c9c1ea5 100644
--- a/src/trio/testing/_check_streams.py
+++ b/src/trio/testing/_check_streams.py
@@ -57,7 +57,8 @@ async def __aexit__(
# on pytest, as the check_* functions are publicly exported.
@contextmanager
def _assert_raises(
- expected_exc: type[BaseException], wrapped: bool = False
+ expected_exc: type[BaseException],
+ wrapped: bool = False,
) -> Generator[None, None, None]:
__tracebackhide__ = True
try:
@@ -174,7 +175,8 @@ async def simple_check_wait_send_all_might_not_block(
async with _core.open_nursery() as nursery:
nursery.start_soon(
- simple_check_wait_send_all_might_not_block, nursery.cancel_scope
+ simple_check_wait_send_all_might_not_block,
+ nursery.cancel_scope,
)
nursery.start_soon(do_receive_some, 1)
@@ -467,7 +469,9 @@ async def flipped_clogged_stream_maker() -> tuple[Stream, Stream]:
test_data = i.to_bytes(DUPLEX_TEST_SIZE, "little")
async def sender(
- s: Stream, data: bytes | bytearray | memoryview, seed: int
+ s: Stream,
+ data: bytes | bytearray | memoryview,
+ seed: int,
) -> None:
r = random.Random(seed)
m = memoryview(data)
diff --git a/src/trio/testing/_fake_net.py b/src/trio/testing/_fake_net.py
index f8589f3a9c..ed0f6980f7 100644
--- a/src/trio/testing/_fake_net.py
+++ b/src/trio/testing/_fake_net.py
@@ -99,7 +99,8 @@ def as_python_sockaddr(self) -> tuple[str, int] | tuple[str, int, int, int]:
@classmethod
def from_python_sockaddr(
- cls: type[T_UDPEndpoint], sockaddr: tuple[str, int] | tuple[str, int, int, int]
+ cls: type[T_UDPEndpoint],
+ sockaddr: tuple[str, int] | tuple[str, int, int, int],
) -> T_UDPEndpoint:
ip, port = sockaddr[:2]
return cls(ip=ipaddress.ip_address(ip), port=port)
@@ -120,7 +121,9 @@ class UDPPacket:
# not used/tested anywhere
def reply(self, payload: bytes) -> UDPPacket: # pragma: no cover
return UDPPacket(
- source=self.destination, destination=self.source, payload=payload
+ source=self.destination,
+ destination=self.source,
+ payload=payload,
)
@@ -156,7 +159,9 @@ async def getaddrinfo(
raise NotImplementedError("FakeNet doesn't do fake DNS yet")
async def getnameinfo(
- self, sockaddr: tuple[str, int] | tuple[str, int, int, int], flags: int
+ self,
+ sockaddr: tuple[str, int] | tuple[str, int, int, int],
+ flags: int,
) -> tuple[str, str]:
raise NotImplementedError("FakeNet doesn't do fake DNS yet")
@@ -256,7 +261,10 @@ def close(self) -> None:
self._packet_receiver.close()
async def _resolve_address_nocp(
- self, address: object, *, local: bool
+ self,
+ address: object,
+ *,
+ local: bool,
) -> tuple[str, int]:
return await trio._socket._resolve_address_nocp( # type: ignore[no-any-return]
self.type,
@@ -360,7 +368,7 @@ async def _recvmsg_into(
raise NotImplementedError(
"The code will most likely hang if you try to receive on a fakesocket "
"without a binding. If that is not the case, or you explicitly want to "
- "test that, remove this warning."
+ "test that, remove this warning.",
)
self._check_closed()
@@ -399,11 +407,13 @@ def getpeername(self) -> tuple[str, int] | tuple[str, int, int, int]:
self._check_closed()
if self._binding is not None:
assert hasattr(
- self._binding, "remote"
+ self._binding,
+ "remote",
), "This method seems to assume that self._binding has a remote UDPEndpoint"
if self._binding.remote is not None: # pragma: no cover
assert isinstance(
- self._binding.remote, UDPEndpoint
+ self._binding.remote,
+ UDPEndpoint,
), "Self._binding.remote should be a UDPEndpoint"
return self._binding.remote.as_python_sockaddr()
_fake_err(errno.ENOTCONN)
@@ -415,7 +425,11 @@ def getsockopt(self, /, level: int, optname: int) -> int: ...
def getsockopt(self, /, level: int, optname: int, buflen: int) -> bytes: ...
def getsockopt(
- self, /, level: int, optname: int, buflen: int | None = None
+ self,
+ /,
+ level: int,
+ optname: int,
+ buflen: int | None = None,
) -> int | bytes:
self._check_closed()
raise OSError(f"FakeNet doesn't implement getsockopt({level}, {optname})")
@@ -425,7 +439,12 @@ def setsockopt(self, /, level: int, optname: int, value: int | Buffer) -> None:
@overload
def setsockopt(
- self, /, level: int, optname: int, value: None, optlen: int
+ self,
+ /,
+ level: int,
+ optname: int,
+ value: None,
+ optlen: int,
) -> None: ...
def setsockopt(
@@ -466,7 +485,9 @@ async def send(self, data: Buffer, flags: int = 0) -> int:
@overload
async def sendto(
- self, __data: Buffer, __address: tuple[object, ...] | str | Buffer
+ self,
+ __data: Buffer,
+ __address: tuple[object, ...] | str | Buffer,
) -> int: ...
@overload
@@ -503,21 +524,31 @@ async def recvfrom(self, bufsize: int, flags: int = 0) -> tuple[bytes, Any]:
return data, address
async def recvfrom_into(
- self, buf: Buffer, nbytes: int = 0, flags: int = 0
+ self,
+ buf: Buffer,
+ nbytes: int = 0,
+ flags: int = 0,
) -> tuple[int, Any]:
if nbytes != 0 and nbytes != memoryview(buf).nbytes:
raise NotImplementedError("partial recvfrom_into")
got_nbytes, ancdata, msg_flags, address = await self._recvmsg_into(
- [buf], 0, flags
+ [buf],
+ 0,
+ flags,
)
return got_nbytes, address
async def _recvmsg(
- self, bufsize: int, ancbufsize: int = 0, flags: int = 0
+ self,
+ bufsize: int,
+ ancbufsize: int = 0,
+ flags: int = 0,
) -> tuple[bytes, list[tuple[int, int, bytes]], int, Any]:
buf = bytearray(bufsize)
got_nbytes, ancdata, msg_flags, address = await self._recvmsg_into(
- [buf], ancbufsize, flags
+ [buf],
+ ancbufsize,
+ flags,
)
return (bytes(buf[:got_nbytes]), ancdata, msg_flags, address)
diff --git a/src/trio/testing/_memory_streams.py b/src/trio/testing/_memory_streams.py
index c9d430a9e6..26ddde8565 100644
--- a/src/trio/testing/_memory_streams.py
+++ b/src/trio/testing/_memory_streams.py
@@ -29,7 +29,7 @@ def __init__(self) -> None:
self._closed = False
self._lot = _core.ParkingLot()
self._fetch_lock = _util.ConflictDetector(
- "another task is already fetching data"
+ "another task is already fetching data",
)
# This object treats "close" as being like closing the send side of a
@@ -115,7 +115,7 @@ def __init__(
close_hook: SyncHook | None = None,
):
self._conflict_detector = _util.ConflictDetector(
- "another task is using this stream"
+ "another task is using this stream",
)
self._outgoing = _UnboundedByteQueue()
self.send_all_hook = send_all_hook
@@ -225,7 +225,7 @@ def __init__(
close_hook: SyncHook | None = None,
):
self._conflict_detector = _util.ConflictDetector(
- "another task is using this stream"
+ "another task is using this stream",
)
self._incoming = _UnboundedByteQueue()
self._closed = False
@@ -356,7 +356,7 @@ async def async_pump_from_send_stream_to_recv_stream() -> None:
def _make_stapled_pair(
- one_way_pair: Callable[[], tuple[SendStreamT, ReceiveStreamT]]
+ one_way_pair: Callable[[], tuple[SendStreamT, ReceiveStreamT]],
) -> tuple[
StapledStream[SendStreamT, ReceiveStreamT],
StapledStream[SendStreamT, ReceiveStreamT],
@@ -461,10 +461,10 @@ def __init__(self) -> None:
self._receiver_waiting = False
self._waiters = _core.ParkingLot()
self._send_conflict_detector = _util.ConflictDetector(
- "another task is already sending"
+ "another task is already sending",
)
self._receive_conflict_detector = _util.ConflictDetector(
- "another task is already receiving"
+ "another task is already receiving",
)
def _something_happened(self) -> None:
diff --git a/src/trio/testing/_raises_group.py b/src/trio/testing/_raises_group.py
index f96dcb2351..627663ffb5 100644
--- a/src/trio/testing/_raises_group.py
+++ b/src/trio/testing/_raises_group.py
@@ -28,7 +28,10 @@
from typing_extensions import TypeGuard, TypeVar
MatchE = TypeVar(
- "MatchE", bound=BaseException, default=BaseException, covariant=True
+ "MatchE",
+ bound=BaseException,
+ default=BaseException,
+ covariant=True,
)
else:
from typing import TypeVar
@@ -49,12 +52,14 @@ class _ExceptionInfo(Generic[MatchE]):
_excinfo: tuple[type[MatchE], MatchE, types.TracebackType] | None
def __init__(
- self, excinfo: tuple[type[MatchE], MatchE, types.TracebackType] | None
+ self,
+ excinfo: tuple[type[MatchE], MatchE, types.TracebackType] | None,
):
self._excinfo = excinfo
def fill_unfilled(
- self, exc_info: tuple[type[MatchE], MatchE, types.TracebackType]
+ self,
+ exc_info: tuple[type[MatchE], MatchE, types.TracebackType],
) -> None:
"""Fill an unfilled ExceptionInfo created with ``for_later()``."""
assert self._excinfo is None, "ExceptionInfo was already filled"
@@ -92,7 +97,7 @@ def tb(self) -> types.TracebackType:
def exconly(self, tryshort: bool = False) -> str:
raise NotImplementedError(
- "This is a helper method only available if you use RaisesGroup with the pytest package installed"
+ "This is a helper method only available if you use RaisesGroup with the pytest package installed",
)
def errisinstance(
@@ -100,7 +105,7 @@ def errisinstance(
exc: builtins.type[BaseException] | tuple[builtins.type[BaseException], ...],
) -> bool:
raise NotImplementedError(
- "This is a helper method only available if you use RaisesGroup with the pytest package installed"
+ "This is a helper method only available if you use RaisesGroup with the pytest package installed",
)
def getrepr(
@@ -114,7 +119,7 @@ def getrepr(
chain: bool = True,
) -> ReprExceptionInfo | ExceptionChainRepr:
raise NotImplementedError(
- "This is a helper method only available if you use RaisesGroup with the pytest package installed"
+ "This is a helper method only available if you use RaisesGroup with the pytest package installed",
)
@@ -139,7 +144,7 @@ def _stringify_exception(exc: BaseException) -> str:
[
getattr(exc, "message", str(exc)),
*getattr(exc, "__notes__", []),
- ]
+ ],
)
@@ -195,7 +200,7 @@ def __init__(
raise ValueError("You must specify at least one parameter to match on.")
if exception_type is not None and not issubclass(exception_type, BaseException):
raise ValueError(
- f"exception_type {exception_type} must be a subclass of BaseException"
+ f"exception_type {exception_type} must be a subclass of BaseException",
)
self.exception_type = exception_type
self.match: Pattern[str] | None
@@ -224,11 +229,13 @@ def matches(self, exception: BaseException) -> TypeGuard[MatchE]:
"""
if self.exception_type is not None and not isinstance(
- exception, self.exception_type
+ exception,
+ self.exception_type,
):
return False
if self.match is not None and not re.search(
- self.match, _stringify_exception(exception)
+ self.match,
+ _stringify_exception(exception),
):
return False
# If exception_type is None check() accepts BaseException.
@@ -242,7 +249,7 @@ def __str__(self) -> str:
if (match := self.match) is not None:
# If no flags were specified, discard the redundant re.compile() here.
reqs.append(
- f"match={match.pattern if match.flags == _regex_no_flags else match!r}"
+ f"match={match.pattern if match.flags == _regex_no_flags else match!r}",
)
if self.check is not None:
reqs.append(f"check={self.check!r}")
@@ -403,13 +410,13 @@ def __init__(
"You cannot specify multiple exceptions with `allow_unwrapped=True.`"
" If you want to match one of multiple possible exceptions you should"
" use a `Matcher`."
- " E.g. `Matcher(check=lambda e: isinstance(e, (...)))`"
+ " E.g. `Matcher(check=lambda e: isinstance(e, (...)))`",
)
if allow_unwrapped and isinstance(exception, RaisesGroup):
raise ValueError(
"`allow_unwrapped=True` has no effect when expecting a `RaisesGroup`."
" You might want it in the expected `RaisesGroup`, or"
- " `flatten_subgroups=True` if you don't care about the structure."
+ " `flatten_subgroups=True` if you don't care about the structure.",
)
if allow_unwrapped and (match is not None or check is not None):
raise ValueError(
@@ -418,7 +425,7 @@ def __init__(
" exception you should use a `Matcher` object. If you want to match/check"
" the exceptiongroup when the exception *is* wrapped you need to"
" do e.g. `if isinstance(exc.value, ExceptionGroup):"
- " assert RaisesGroup(...).matches(exc.value)` afterwards."
+ " assert RaisesGroup(...).matches(exc.value)` afterwards.",
)
# verify `expected_exceptions` and set `self.is_baseexceptiongroup`
@@ -429,7 +436,7 @@ def __init__(
"You cannot specify a nested structure inside a RaisesGroup with"
" `flatten_subgroups=True`. The parameter will flatten subgroups"
" in the raised exceptiongroup before matching, which would never"
- " match a nested structure."
+ " match a nested structure.",
)
self.is_baseexceptiongroup |= exc.is_baseexceptiongroup
elif isinstance(exc, Matcher):
@@ -439,14 +446,15 @@ def __init__(
continue
# Matcher __init__ assures it's a subclass of BaseException
self.is_baseexceptiongroup |= not issubclass(
- exc.exception_type, Exception
+ exc.exception_type,
+ Exception,
)
elif isinstance(exc, type) and issubclass(exc, BaseException):
self.is_baseexceptiongroup |= not issubclass(exc, Exception)
else:
raise ValueError(
f'Invalid argument "{exc!r}" must be exception type, Matcher, or'
- " RaisesGroup."
+ " RaisesGroup.",
)
def __enter__(self) -> ExceptionInfo[BaseExceptionGroup[E]]:
@@ -454,7 +462,8 @@ def __enter__(self) -> ExceptionInfo[BaseExceptionGroup[E]]:
return self.excinfo
def _unroll_exceptions(
- self, exceptions: Sequence[BaseException]
+ self,
+ exceptions: Sequence[BaseException],
) -> Sequence[BaseException]:
"""Used if `flatten_subgroups=True`."""
res: list[BaseException] = []
@@ -498,7 +507,8 @@ def matches(
return False
if self.match_expr is not None and not re.search(
- self.match_expr, _stringify_exception(exc_val)
+ self.match_expr,
+ _stringify_exception(exc_val),
):
return False
if self.check is not None and not self.check(exc_val):
diff --git a/src/trio/testing/_sequencer.py b/src/trio/testing/_sequencer.py
index 9c01e902f2..32171cb2a2 100644
--- a/src/trio/testing/_sequencer.py
+++ b/src/trio/testing/_sequencer.py
@@ -55,7 +55,8 @@ async def main():
"""
_sequence_points: defaultdict[int, Event] = attrs.field(
- factory=lambda: defaultdict(Event), init=False
+ factory=lambda: defaultdict(Event),
+ init=False,
)
_claimed: set[int] = attrs.field(factory=set, init=False)
_broken: bool = attrs.field(default=False, init=False)
@@ -75,7 +76,7 @@ async def __call__(self, position: int) -> AsyncIterator[None]:
for event in self._sequence_points.values():
event.set()
raise RuntimeError(
- "Sequencer wait cancelled -- sequence broken"
+ "Sequencer wait cancelled -- sequence broken",
) from None
else:
if self._broken:
diff --git a/src/trio/testing/_trio_test.py b/src/trio/testing/_trio_test.py
index a57c0ee4c7..226e559196 100644
--- a/src/trio/testing/_trio_test.py
+++ b/src/trio/testing/_trio_test.py
@@ -42,7 +42,9 @@ def wrapper(*args: ArgsT.args, **kwargs: ArgsT.kwargs) -> RetT:
raise ValueError("too many clocks spoil the broth!")
instruments = [i for i in kwargs.values() if isinstance(i, Instrument)]
return _core.run(
- partial(fn, *args, **kwargs), clock=clock, instruments=instruments
+ partial(fn, *args, **kwargs),
+ clock=clock,
+ instruments=instruments,
)
return wrapper
From 03906eab61c8e9f7672d9d0df2cbc2554b8c8c60 Mon Sep 17 00:00:00 2001
From: CoolCat467 <52022020+CoolCat467@users.noreply.github.com>
Date: Thu, 29 Aug 2024 10:42:28 -0500
Subject: [PATCH 31/79] Functional changes to `gen_exports`
---
src/trio/_tools/gen_exports.py | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/src/trio/_tools/gen_exports.py b/src/trio/_tools/gen_exports.py
index 8e243be72b..524a65f90d 100755
--- a/src/trio/_tools/gen_exports.py
+++ b/src/trio/_tools/gen_exports.py
@@ -191,6 +191,11 @@ def run_linters(file: File, source: str) -> str:
print(response)
sys.exit(1)
+ success, response = run_black(file, response)
+ if not success:
+ print(response)
+ sys.exit(1)
+
return response
From 810534df4c148235060010ef8cadeeac2e6c0222 Mon Sep 17 00:00:00 2001
From: CoolCat467 <52022020+CoolCat467@users.noreply.github.com>
Date: Thu, 29 Aug 2024 10:44:13 -0500
Subject: [PATCH 32/79] Add enabling flake8-commas commit to ignore revs
---
.git-blame-ignore-revs | 2 ++
1 file changed, 2 insertions(+)
diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs
index 1d3079ad5a..001d83c4a1 100644
--- a/.git-blame-ignore-revs
+++ b/.git-blame-ignore-revs
@@ -1,2 +1,4 @@
# sorting all imports with isort
933f77b96f0092e1baab4474a9208fc2e379aa32
+# enabling ruff's flake8-commas rule
+b25c02a94e2defcb0fad32976b02218be1133bdf
From 30babafa732799d6730fe5936c5ddbe8f453ad12 Mon Sep 17 00:00:00 2001
From: CoolCat467 <52022020+CoolCat467@users.noreply.github.com>
Date: Thu, 29 Aug 2024 10:47:33 -0500
Subject: [PATCH 33/79] Black changes that got missed somehow
---
src/trio/_tests/test_timeouts.py | 3 ++-
src/trio/_tests/tools/test_gen_exports.py | 4 +++-
src/trio/_timeouts.py | 4 +++-
3 files changed, 8 insertions(+), 3 deletions(-)
diff --git a/src/trio/_tests/test_timeouts.py b/src/trio/_tests/test_timeouts.py
index 8b0f908ca7..b2486c7d2b 100644
--- a/src/trio/_tests/test_timeouts.py
+++ b/src/trio/_tests/test_timeouts.py
@@ -108,7 +108,8 @@ async def task() -> None:
async def test_fail_after_fails_even_if_shielded() -> None:
async def task() -> None:
with pytest.raises(TooSlowError), _core.CancelScope() as outer, fail_after(
- TARGET, shield=True
+ TARGET,
+ shield=True,
):
outer.cancel()
# The outer scope is cancelled, but this task is protected by the
diff --git a/src/trio/_tests/tools/test_gen_exports.py b/src/trio/_tests/tools/test_gen_exports.py
index d0b1f3697c..669df968e0 100644
--- a/src/trio/_tests/tools/test_gen_exports.py
+++ b/src/trio/_tests/tools/test_gen_exports.py
@@ -92,7 +92,9 @@ def test_create_pass_through_args() -> None:
@skip_lints
@pytest.mark.parametrize("imports", [IMPORT_1, IMPORT_2, IMPORT_3])
def test_process(
- tmp_path: Path, imports: str, capsys: pytest.CaptureFixture[str]
+ tmp_path: Path,
+ imports: str,
+ capsys: pytest.CaptureFixture[str],
) -> None:
try:
import black # noqa: F401
diff --git a/src/trio/_timeouts.py b/src/trio/_timeouts.py
index 7bc985039b..b20a05fb68 100644
--- a/src/trio/_timeouts.py
+++ b/src/trio/_timeouts.py
@@ -134,7 +134,9 @@ def fail_at(deadline: float, *, shield: bool = False) -> AbstractContextManager[
def fail_after(
- seconds: float, *, shield: bool = False
+ seconds: float,
+ *,
+ shield: bool = False,
) -> AbstractContextManager[trio.CancelScope]:
"""Creates a cancel scope with the given timeout, and raises an error if
it is actually cancelled.
From 65a3dcba3172e6ade284aaf186320b17b4b80d7f Mon Sep 17 00:00:00 2001
From: EXPLOSION
Date: Fri, 30 Aug 2024 13:31:26 +0900
Subject: [PATCH 34/79] Test PyPy 3.10 on both Windows and MacOS (#3074)
---
.github/workflows/ci.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index f89180d4bd..074943ec63 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -18,7 +18,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- python: ['3.8', '3.9', '3.10', '3.11', '3.12']
+ python: ['pypy-3.10', '3.8', '3.9', '3.10', '3.11', '3.12']
arch: ['x86', 'x64']
lsp: ['']
lsp_extract_file: ['']
@@ -143,7 +143,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- python: ['3.8', '3.9', '3.10', '3.11', '3.12']
+ python: ['pypy-3.10', '3.8', '3.9', '3.10', '3.11', '3.12']
continue-on-error: >-
${{
(
From 51c8f9c0e5c83846c5bc296ba207948c0f4b1423 Mon Sep 17 00:00:00 2001
From: EXPLOSION
Date: Fri, 30 Aug 2024 13:31:44 +0900
Subject: [PATCH 35/79] Enable mypy and disable jedi on PyPy (#3075)
---
src/trio/_tests/test_exports.py | 8 +++-----
test-requirements.in | 10 +++++-----
test-requirements.txt | 14 +++++++-------
3 files changed, 15 insertions(+), 17 deletions(-)
diff --git a/src/trio/_tests/test_exports.py b/src/trio/_tests/test_exports.py
index 32a2666e48..1db92af5f8 100644
--- a/src/trio/_tests/test_exports.py
+++ b/src/trio/_tests/test_exports.py
@@ -172,8 +172,6 @@ def no_underscores(symbols: Iterable[str]) -> set[str]:
elif tool == "mypy":
if not RUN_SLOW: # pragma: no cover
pytest.skip("use --run-slow to check against mypy")
- if sys.implementation.name != "cpython":
- pytest.skip("mypy not installed in tests on pypy")
cache = Path.cwd() / ".mypy_cache"
@@ -266,10 +264,10 @@ def no_hidden(symbols: Iterable[str]) -> set[str]:
if (not symbol.startswith("_")) or symbol.startswith("__")
}
- if tool == "mypy":
- if sys.implementation.name != "cpython":
- pytest.skip("mypy not installed in tests on pypy")
+ if tool == "jedi" and sys.implementation.name != "cpython":
+ pytest.skip("jedi does not support pypy")
+ if tool == "mypy":
cache = Path.cwd() / ".mypy_cache"
_ensure_mypy_cache_updated()
diff --git a/test-requirements.in b/test-requirements.in
index af8a751b13..227a25afc0 100644
--- a/test-requirements.in
+++ b/test-requirements.in
@@ -6,22 +6,22 @@ pyright
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
+jedi; implementation_name == "cpython" # for jedi code completion tests
cryptography>=41.0.0 # cryptography<41 segfaults on pypy3.10
# Tools
black; implementation_name == "cpython"
-mypy; implementation_name == "cpython"
-types-pyOpenSSL; implementation_name == "cpython" # and annotations
+mypy
ruff >= 0.4.3
astor # code generation
uv >= 0.2.24
codespell
# https://github.com/python-trio/trio/pull/654#issuecomment-420518745
-mypy-extensions; implementation_name == "cpython"
+mypy-extensions
typing-extensions
-types-cffi; implementation_name == "cpython"
+types-cffi
+types-pyOpenSSL
# annotations in doc files
types-docutils
sphinx
diff --git a/test-requirements.txt b/test-requirements.txt
index 5bbaf38f6d..65c3d98cc2 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -63,7 +63,7 @@ iniconfig==2.0.0
# via pytest
isort==5.13.2
# via pylint
-jedi==0.19.1
+jedi==0.19.1 ; implementation_name == 'cpython'
# via -r test-requirements.in
jinja2==3.1.4
# via sphinx
@@ -71,9 +71,9 @@ markupsafe==2.1.5
# via jinja2
mccabe==0.7.0
# via pylint
-mypy==1.11.1 ; implementation_name == 'cpython'
+mypy==1.11.1
# via -r test-requirements.in
-mypy-extensions==1.0.0 ; implementation_name == 'cpython'
+mypy-extensions==1.0.0
# via
# -r test-requirements.in
# black
@@ -87,7 +87,7 @@ packaging==24.1
# black
# pytest
# sphinx
-parso==0.8.4
+parso==0.8.4 ; implementation_name == 'cpython'
# via jedi
pathspec==0.12.1 ; implementation_name == 'cpython'
# via black
@@ -145,15 +145,15 @@ tomlkit==0.13.2
# via pylint
trustme==1.1.0
# via -r test-requirements.in
-types-cffi==1.16.0.20240331 ; implementation_name == 'cpython'
+types-cffi==1.16.0.20240331
# via
# -r test-requirements.in
# types-pyopenssl
types-docutils==0.21.0.20240724
# via -r test-requirements.in
-types-pyopenssl==24.1.0.20240722 ; implementation_name == 'cpython'
+types-pyopenssl==24.1.0.20240722
# via -r test-requirements.in
-types-setuptools==71.1.0.20240818 ; implementation_name == 'cpython'
+types-setuptools==71.1.0.20240818
# via types-cffi
typing-extensions==4.12.2
# via
From 510aaa1685588371cacb453728a4b27d7186d674 Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Sat, 31 Aug 2024 13:52:29 +0200
Subject: [PATCH 36/79] remove unneeded type:ignore with mypy 1.11
---
src/trio/_tests/test_timeouts.py | 7 ++-----
1 file changed, 2 insertions(+), 5 deletions(-)
diff --git a/src/trio/_tests/test_timeouts.py b/src/trio/_tests/test_timeouts.py
index b47859857b..83bcd6dd3e 100644
--- a/src/trio/_tests/test_timeouts.py
+++ b/src/trio/_tests/test_timeouts.py
@@ -1,5 +1,5 @@
import time
-from typing import Awaitable, Callable, ContextManager, Protocol, TypeVar
+from typing import Awaitable, Callable, Protocol, TypeVar
import outcome
import pytest
@@ -170,10 +170,7 @@ async def test_timeouts_raise_value_error() -> None:
):
await fun(val)
- cm: Callable[[float], ContextManager[_core.CancelScope]]
- # mypy resolves the tuple as containing `Callable[[float], object]`, failing to see
- # that both callables are compatible with returning `Contextmanager[CancelScope]`
- for cm, val in ( # type: ignore[assignment]
+ for cm, val in (
(fail_after, -1),
(fail_after, nan),
(fail_at, nan),
From 51b2dffb916ae513e1c44b1afc243cdda072e690 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Sun, 1 Sep 2024 00:40:01 +0000
Subject: [PATCH 37/79] Dependency updates (#3077)
---
.pre-commit-config.yaml | 2 +-
docs-requirements.txt | 6 +++---
test-requirements.txt | 18 +++++++++---------
3 files changed, 13 insertions(+), 13 deletions(-)
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 6ea9215591..9300a7152e 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -24,7 +24,7 @@ repos:
hooks:
- id: black
- repo: https://github.com/astral-sh/ruff-pre-commit
- rev: v0.6.2
+ rev: v0.6.3
hooks:
- id: ruff
types: [file]
diff --git a/docs-requirements.txt b/docs-requirements.txt
index 91b64e94c8..aaf3fad2c7 100644
--- a/docs-requirements.txt
+++ b/docs-requirements.txt
@@ -10,7 +10,7 @@ babel==2.16.0
# via sphinx
beautifulsoup4==4.12.3
# via sphinx-codeautolink
-certifi==2024.7.4
+certifi==2024.8.30
# via requests
cffi==1.17.0 ; platform_python_implementation != 'PyPy' or os_name == 'nt'
# via
@@ -32,7 +32,7 @@ docutils==0.20.1
# sphinx-rtd-theme
exceptiongroup==1.2.2
# via -r docs-requirements.in
-idna==3.7
+idna==3.8
# via
# -r docs-requirements.in
# requests
@@ -100,7 +100,7 @@ sphinxcontrib-serializinghtml==2.0.0
# via sphinx
sphinxcontrib-trio==1.1.2
# via -r docs-requirements.in
-towncrier==24.7.1
+towncrier==24.8.0
# via -r docs-requirements.in
urllib3==2.2.2
# via requests
diff --git a/test-requirements.txt b/test-requirements.txt
index 65c3d98cc2..47aea97d8f 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -16,7 +16,7 @@ babel==2.16.0
# via sphinx
black==24.8.0 ; implementation_name == 'cpython'
# via -r test-requirements.in
-certifi==2024.7.4
+certifi==2024.8.30
# via requests
cffi==1.17.0 ; platform_python_implementation != 'PyPy' or os_name == 'nt'
# via
@@ -50,7 +50,7 @@ exceptiongroup==1.2.2 ; python_full_version < '3.11'
# via
# -r test-requirements.in
# pytest
-idna==3.7
+idna==3.8
# via
# -r test-requirements.in
# requests
@@ -71,7 +71,7 @@ markupsafe==2.1.5
# via jinja2
mccabe==0.7.0
# via pylint
-mypy==1.11.1
+mypy==1.11.2
# via -r test-requirements.in
mypy-extensions==1.0.0
# via
@@ -101,11 +101,11 @@ pycparser==2.22 ; platform_python_implementation != 'PyPy' or os_name == 'nt'
# via cffi
pygments==2.18.0
# via sphinx
-pylint==3.2.6
+pylint==3.2.7
# via -r test-requirements.in
pyopenssl==24.2.1
# via -r test-requirements.in
-pyright==1.1.376
+pyright==1.1.378
# via -r test-requirements.in
pytest==8.3.2
# via -r test-requirements.in
@@ -113,7 +113,7 @@ pytz==2024.1 ; python_full_version < '3.9'
# via babel
requests==2.32.3
# via sphinx
-ruff==0.6.1
+ruff==0.6.3
# via -r test-requirements.in
sniffio==1.3.1
# via -r test-requirements.in
@@ -153,7 +153,7 @@ types-docutils==0.21.0.20240724
# via -r test-requirements.in
types-pyopenssl==24.1.0.20240722
# via -r test-requirements.in
-types-setuptools==71.1.0.20240818
+types-setuptools==74.0.0.20240831
# via types-cffi
typing-extensions==4.12.2
# via
@@ -164,7 +164,7 @@ typing-extensions==4.12.2
# pylint
urllib3==2.2.2
# via requests
-uv==0.3.0
+uv==0.4.1
# via -r test-requirements.in
-zipp==3.20.0 ; python_full_version < '3.10'
+zipp==3.20.1 ; python_full_version < '3.10'
# via importlib-metadata
From 347412862d14b2f9fc3ca582b8e24b89d3818283 Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Thu, 5 Sep 2024 11:28:31 +0200
Subject: [PATCH 38/79] write docs, update newsfragments to match current
implementation
---
docs/source/reference-core.rst | 37 ++++++++++++++++++++++++++++++-
newsfragments/2512.deprecated.rst | 10 +--------
newsfragments/2512.feature.rst | 14 +-----------
src/trio/_timeouts.py | 12 ++++++++--
4 files changed, 48 insertions(+), 25 deletions(-)
diff --git a/docs/source/reference-core.rst b/docs/source/reference-core.rst
index 392eac7d20..99e392ecb2 100644
--- a/docs/source/reference-core.rst
+++ b/docs/source/reference-core.rst
@@ -561,7 +561,8 @@ situation of just wanting to impose a timeout on some code:
.. autofunction:: fail_at
:with: cancel_scope
-Cheat sheet:
+Cheat sheet
++++++++++++
* If you want to impose a timeout on a function, but you don't care
whether it timed out or not:
@@ -598,6 +599,40 @@ which is sometimes useful:
.. autofunction:: current_effective_deadline
+.. _saved_relative_timeouts:
+
+Separating initialization and entry of a :class:`CancelScope`
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+Before v0.26.3 there was no separation of initialization and entry of a :class:`CancelScope`. This meant that trying to initialize :func:`move_on_after` or :func:`fail_after` and entering them at a later point meant that the timeout was relative to initialization, leading to confusing behaviour.
+
+.. code-block:: python
+
+ # THIS WILL NOW GIVE A DEPRECATION ERROR
+ my_cs = trio.move_on_after(5)
+ trio.sleep(1)
+ with my_cs: # this would move on after 4 seconds
+ ...
+
+This is now resolved with the addition of a wrapper class :class:`trio._timeouts._RelativeCancelScope` that is transparent to end users when initializing and entering at the same time. To silence the :class:`DeprecationWarning` and use the new behavior, pass ``timeout_from_enter=True`` to :func:`move_on_after` or :func:`fail_after`.
+
+.. code-block:: python
+
+ my_cs = trio.move_on_after(5, timeout_from_enter = True)
+ trio.sleep(1)
+ with my_cs: # this will now move on after 5 seconds
+ ...
+
+If you want to retain previous behaviour, you should now instead write something like
+
+.. code-block:: python
+
+ my_cs = trio.move_on_at(trio.current_time() + 5)
+ trio.sleep(1)
+ with my_cs: # this will now unambiguously move on after 4 seconds
+ ...
+
+.. autoclass:: trio._timeouts._RelativeCancelScope
+
.. _tasks:
Tasks let you do multiple things at once
diff --git a/newsfragments/2512.deprecated.rst b/newsfragments/2512.deprecated.rst
index 1315386aa2..bbb098b1c8 100644
--- a/newsfragments/2512.deprecated.rst
+++ b/newsfragments/2512.deprecated.rst
@@ -1,9 +1 @@
-:func:`trio.move_on_after` and :func:`trio.fail_after` previously set the deadline relative to initialization time, instead of more intuitively upon entering the context manager. Any code where these two points in time do not line up (according to :func:`trio.current_time`) will now issue a `DeprecationWarning`. To silence the warning and adopt the new behaviour, pass ``timeout_from_enter = True``. If you want to retain current behaviour, you should instead use :func:`trio.move_on_at`/:func:`trio.fail_at`. For example:
-
-.. code-block:: python
-
- # this is equivalent to how trio.move_on_after(5) would function previously
- cs = trio.move_on_at(trio.current_time() + 5)
- ...
- with cs:
- ...
+:func:`trio.move_on_after` and :func:`trio.fail_after` previously set the deadline relative to initialization time, instead of more intuitively upon entering the context manager. Any code where these two points in time do not line up (according to :func:`trio.current_time`) will now issue a `DeprecationWarning`. To silence the warning and adopt the new behaviour, pass ``timeout_from_enter = True``. For more information see :ref:`saved_relative_timeouts`.
diff --git a/newsfragments/2512.feature.rst b/newsfragments/2512.feature.rst
index 1c9e2222e6..d30c891961 100644
--- a/newsfragments/2512.feature.rst
+++ b/newsfragments/2512.feature.rst
@@ -1,13 +1 @@
-:func:`trio.CancelScope` now has an attribute ``relative_deadline`` that can be used together with, or instead of, ``deadline``. :func:`trio.move_on_after` and :func:`trio.fail_after` now use this functionality in order to resolve the absolute deadline upon entering the context manager.
-
-If setting both ``deadline`` and ``relative_deadline`` before entering the cm, the deadline will be set to ``min(deadline, trio.current_time() + relative_deadline)``.
-Accessing ``relative_deadline`` after entering will return remaining time until deadline (i.e. ``deadline - trio.current_time()``. Setting ``relative_deadline`` after entering will set ``deadline`` to ``trio.current_time() + relative_deadline``
-
-Example:
-
-.. code-block:: python
-
- my_cs = trio.CancelScope(relative_deadline = 5)
- ...
- with my_cs: # my_cs.deadline will now be 5 seconds into the future
- ...
+:func:`trio.move_on_after` and :func:`trio.fail_after` now allows setting the deadline to be relative to entering the context manager. This will be the default in the future, and to opt into the new behaviour you can pass ``timeout_from_enter=True``. This does not matter in the vast majority of cases where initialization and entering of the context manager is in the same place, and those cases will not raise any :class:`DeprecationWarning`. For more information see :ref:`saved_relative_timeouts`.
diff --git a/src/trio/_timeouts.py b/src/trio/_timeouts.py
index 72e098fd75..8203520f89 100644
--- a/src/trio/_timeouts.py
+++ b/src/trio/_timeouts.py
@@ -108,7 +108,15 @@ def move_on_after(
timeout_from_enter: bool = False,
) -> _RelativeCancelScope:
"""Use as a context manager to create a cancel scope whose deadline is
- set to creation time + *seconds*.
+ set to now + *seconds*.
+
+ The deadline of the cancel scope was previously calculated at creation time,
+ not upon entering the context manager. This is still the default, but deprecated.
+ If you pass ``timeout_from_enter=True`` it will instead be calculated relative
+ to entering the cm, and silence the :class:`DeprecationWarning`.
+
+ If you're entering the cancel scope at initialization time, which is the most common
+ use case, you can treat this function as returning a :class:`CancelScope`.
Args:
seconds (float): The timeout.
@@ -238,7 +246,7 @@ def fail_after(
The deadline of the cancel scope was previously calculated at creation time,
not upon entering the context manager. This is still the default, but deprecated.
If you pass ``timeout_from_enter=True`` it will instead be calculated relative
- to entering the cm, and silence the deprecationwarning.
+ to entering the cm, and silence the :class:`DeprecationWarning`.
Args:
seconds (float): The timeout.
From df5876d29acc85b3a27f47d48a841852c5282dd0 Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Thu, 5 Sep 2024 13:41:55 +0200
Subject: [PATCH 39/79] add transitional functions
---
src/trio/_tests/test_timeouts.py | 97 +++++++++++++++++++++----
src/trio/_timeouts.py | 117 ++++++++++++++++++++++++++++++-
2 files changed, 196 insertions(+), 18 deletions(-)
diff --git a/src/trio/_tests/test_timeouts.py b/src/trio/_tests/test_timeouts.py
index 83bcd6dd3e..6833726b02 100644
--- a/src/trio/_tests/test_timeouts.py
+++ b/src/trio/_tests/test_timeouts.py
@@ -189,6 +189,7 @@ async def test_timeouts_raise_value_error() -> None:
async def test_timeout_deadline_on_entry(mock_clock: _core.MockClock) -> None:
rcs = move_on_after(5, timeout_from_enter=True)
assert rcs.relative_deadline == 5
+
mock_clock.jump(3)
start = _core.current_time()
with rcs as cs:
@@ -201,23 +202,89 @@ async def test_timeout_deadline_on_entry(mock_clock: _core.MockClock) -> None:
with rcs as cs:
assert cs.deadline == start + 5
- # TODO: not implemented
- # check that code that does not "re-save" the cancelscope still functions
- # assert rcs.deadline == start + 5
- # assert rcs.shield == False
+ # check that their shield values are linked
+ assert rcs.shield is False
+
+ cs.shield = True
+ assert rcs.shield is True
+
+ rcs.shield = False
+ assert cs.shield is False
+
+ # re-entering a _RelativeCancelScope should probably error, but it doesn't *have* to
+ with rcs as cs:
+ ...
async def test_timeout_deadline_not_on_entry(mock_clock: _core.MockClock) -> None:
"""Test that not setting timeout_from_enter gives a DeprecationWarning and
retains old behaviour."""
- with pytest.warns(DeprecationWarning, match="issues/2512"):
- rcs = move_on_after(5)
- mock_clock.jump(3)
- with rcs as cs:
- assert cs.deadline - _core.current_time() == 2
-
- with pytest.warns(DeprecationWarning, match="issues/2512"):
- cs_gen = fail_after(5)
- mock_clock.jump(3)
- with cs_gen as cs:
- assert cs.deadline - _core.current_time() == 2
+ for cs_gen_fun in (move_on_after, fail_after):
+ with pytest.warns(DeprecationWarning, match="issues/2512"):
+ rcs = cs_gen_fun(5)
+ mock_clock.jump(3)
+ with rcs as cs:
+ assert rcs.deadline == cs.deadline == _core.current_time() + 2
+
+ rcs.deadline += 1
+ assert rcs.deadline == cs.deadline == _core.current_time() + 3
+
+ cs.deadline += 1
+ assert rcs.deadline == cs.deadline == _core.current_time() + 4
+
+
+async def test_transitional_functions_fail() -> None:
+ rcs = move_on_after(5, timeout_from_enter=True)
+ # these errors are only shown if timeout_from_enter=True
+
+ match_str = "^_RelativeCancelScope does not have `deadline`. You might want `relative_deadline`.$"
+ with pytest.raises(AttributeError, match=match_str):
+ assert rcs.deadline
+ with pytest.raises(AttributeError, match=match_str):
+ rcs.deadline = 7
+
+ for prop in "cancelled_caught", "cancel_called":
+ match_str = f"_RelativeCancelScope does not have `{prop}`, and cannot have been cancelled before entering."
+ with pytest.raises(AttributeError, match=match_str):
+ assert getattr(rcs, prop)
+
+ with pytest.raises(
+ AttributeError,
+ match=match_str[:36] + "cancel`, and cannot be cancelled before entering.",
+ ):
+ rcs.cancel()
+
+ with rcs:
+ match_str = "^_RelativeCancelScope does not have `{}`. You might want to access the entered `CancelScope`.$"
+ with pytest.raises(AttributeError, match=match_str.format("deadline")):
+ assert rcs.deadline
+ with pytest.raises(AttributeError, match=match_str.format("deadline")):
+ rcs.deadline = 5
+ with pytest.raises(AttributeError, match=match_str.format("cancel")):
+ rcs.cancel()
+ with pytest.raises(AttributeError, match=match_str.format("cancelled_caught")):
+ assert rcs.cancelled_caught
+ with pytest.raises(AttributeError, match=match_str.format("cancel_called")):
+ assert rcs.cancel_called
+
+
+async def test_transitional_functions_backwards_compatibility() -> None:
+ rcs = move_on_after(5)
+ assert rcs.deadline == 5
+ rcs.deadline = 7
+ assert rcs.cancelled_caught is False
+ assert rcs.cancel_called is False
+ with pytest.raises(
+ RuntimeError,
+ match="^It is no longer possible to cancel a relative cancel scope before entering it.$",
+ ):
+ rcs.cancel()
+
+ # this does not emit any deprecationwarnings because no time has passed
+ with rcs as cs:
+ assert rcs.deadline == cs.deadline
+ assert abs(rcs.deadline - trio.current_time() - 7) < 0.1
+ rcs.cancel()
+ await trio.lowlevel.checkpoint()
+ assert rcs.cancelled_caught is cs.cancelled_caught is True
+ assert rcs.cancel_called is cs.cancel_called is True
diff --git a/src/trio/_timeouts.py b/src/trio/_timeouts.py
index 8203520f89..097676ed40 100644
--- a/src/trio/_timeouts.py
+++ b/src/trio/_timeouts.py
@@ -29,8 +29,8 @@ def __init__(
shield: bool = False,
timeout_from_enter: bool = False,
):
- self.relative_deadline = relative_deadline
- self.shield = shield
+ self.relative_deadline: float = relative_deadline
+ self._shield = shield
self._timeout_from_enter = timeout_from_enter
self._fail: bool = False
@@ -38,6 +38,12 @@ def __init__(
self._scope: trio.CancelScope | None = None
def __enter__(self) -> trio.CancelScope:
+ if self._scope is not None:
+ # This does not have to be the case, but for now we're mirroring the
+ # behaviour of CancelScope
+ raise RuntimeError(
+ "Each _RelativeCancelScope may only be used for a single 'with' block",
+ )
if (
abs(self._creation_time - trio.current_time()) > 0.01
and not self._timeout_from_enter
@@ -64,7 +70,7 @@ def __enter__(self) -> trio.CancelScope:
self._scope = trio.CancelScope(
deadline=start_time + self.relative_deadline,
- shield=self.shield,
+ shield=self._shield,
)
self._scope.__enter__()
return self._scope
@@ -82,6 +88,111 @@ def __exit__(
raise TooSlowError
return res
+ @property
+ def shield(self) -> bool:
+ # if self._timeout_from_enter and self._scope is not None:
+ # we might want to raise an error to force people to use the re-saved CancelScope
+ # directly, but I'm not sure there is a very strong reason to do that.
+ if self._scope is None:
+ return self._shield
+ return self._scope.shield
+
+ @shield.setter
+ def shield(self, new_value: bool) -> None:
+ if self._scope is None:
+ self._shield = new_value
+ else:
+ self._scope.shield = new_value
+
+ @property
+ def deadline(self) -> float:
+ """Transitional function to maintain backwards compatibility."""
+ if self._timeout_from_enter:
+ if self._scope is None:
+ raise AttributeError(
+ "_RelativeCancelScope does not have `deadline`. You might want `relative_deadline`.",
+ )
+ else:
+ raise AttributeError(
+ "_RelativeCancelScope does not have `deadline`. You might want to access the entered `CancelScope`.",
+ )
+ elif self._scope is None: # has not been entered
+ return self.relative_deadline
+ else:
+ return self._scope.deadline
+
+ @deadline.setter
+ def deadline(self, new_deadline: float) -> None:
+ """Transitional function to maintain backwards compatibility."""
+ if self._timeout_from_enter:
+ if self._scope is None:
+ raise AttributeError(
+ "_RelativeCancelScope does not have `deadline`. You might want `relative_deadline`.",
+ )
+ else:
+ raise AttributeError(
+ "_RelativeCancelScope does not have `deadline`. You might want to access the entered `CancelScope`.",
+ )
+ elif self._scope is None: # has not been entered
+ # we don't raise another deprecationwarning, leaving it for __enter__
+ self.relative_deadline = new_deadline
+ else:
+ self._scope.deadline = new_deadline
+
+ @property
+ def cancelled_caught(self) -> bool:
+ """Transitional function to maintain backwards compatibility."""
+ if self._timeout_from_enter:
+ if self._scope is None:
+ raise AttributeError(
+ "_RelativeCancelScope does not have `cancelled_caught`, and cannot have been cancelled before entering.",
+ )
+ else:
+ raise AttributeError(
+ "_RelativeCancelScope does not have `cancelled_caught`. You might want to access the entered `CancelScope`.",
+ )
+ elif self._scope is None:
+ return False
+ else:
+ return self._scope.cancelled_caught
+
+ def cancel(self) -> None:
+ """Transitional function to maintain backwards compatibility."""
+ if self._timeout_from_enter:
+ if self._scope is None:
+ raise AttributeError(
+ "_RelativeCancelScope does not have `cancel`, and cannot be cancelled before entering.",
+ )
+ else:
+ raise AttributeError(
+ "_RelativeCancelScope does not have `cancel`. You might want to access the entered `CancelScope`.",
+ )
+ elif self._scope is None:
+ # It may be possible to implement this by immediately canceling the
+ # created scope in __enter__
+ raise RuntimeError(
+ "It is no longer possible to cancel a relative cancel scope before entering it.",
+ )
+ else:
+ self._scope.cancel()
+
+ @property
+ def cancel_called(self) -> bool:
+ """Transitional function to maintain backwards compatibility."""
+ if self._timeout_from_enter:
+ if self._scope is None:
+ raise AttributeError(
+ "_RelativeCancelScope does not have `cancel_called`, and cannot have been cancelled before entering.",
+ )
+ else:
+ raise AttributeError(
+ "_RelativeCancelScope does not have `cancel_called`. You might want to access the entered `CancelScope`.",
+ )
+ elif self._scope is None:
+ return False
+ else:
+ return self._scope.cancel_called
+
def move_on_at(deadline: float, *, shield: bool = False) -> trio.CancelScope:
"""Use as a context manager to create a cancel scope with the given
From a2409481db4c8e8b59e77d26fce9dd7c097219b4 Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Thu, 5 Sep 2024 16:19:40 +0200
Subject: [PATCH 40/79] fix test
---
src/trio/_tests/test_timeouts.py | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/src/trio/_tests/test_timeouts.py b/src/trio/_tests/test_timeouts.py
index 6833726b02..9a91e230f7 100644
--- a/src/trio/_tests/test_timeouts.py
+++ b/src/trio/_tests/test_timeouts.py
@@ -212,8 +212,12 @@ async def test_timeout_deadline_on_entry(mock_clock: _core.MockClock) -> None:
assert cs.shield is False
# re-entering a _RelativeCancelScope should probably error, but it doesn't *have* to
- with rcs as cs:
- ...
+ with pytest.raises(
+ RuntimeError,
+ match="^Each _RelativeCancelScope may only be used for a single 'with' block$",
+ ):
+ with rcs as cs:
+ ...
async def test_timeout_deadline_not_on_entry(mock_clock: _core.MockClock) -> None:
From 0183b4307cb8922096cbce2b1c25eb93208d5bc9 Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Thu, 5 Sep 2024 16:43:05 +0200
Subject: [PATCH 41/79] fix tests/codecov
---
src/trio/_tests/test_timeouts.py | 20 ++++++++++++--------
1 file changed, 12 insertions(+), 8 deletions(-)
diff --git a/src/trio/_tests/test_timeouts.py b/src/trio/_tests/test_timeouts.py
index 9a91e230f7..18355079da 100644
--- a/src/trio/_tests/test_timeouts.py
+++ b/src/trio/_tests/test_timeouts.py
@@ -190,6 +190,9 @@ async def test_timeout_deadline_on_entry(mock_clock: _core.MockClock) -> None:
rcs = move_on_after(5, timeout_from_enter=True)
assert rcs.relative_deadline == 5
+ rcs.shield = True
+ assert rcs.shield
+
mock_clock.jump(3)
start = _core.current_time()
with rcs as cs:
@@ -203,13 +206,13 @@ async def test_timeout_deadline_on_entry(mock_clock: _core.MockClock) -> None:
assert cs.deadline == start + 5
# check that their shield values are linked
- assert rcs.shield is False
+ assert rcs.shield is cs.shield is True
- cs.shield = True
- assert rcs.shield is True
+ cs.shield = False
+ assert rcs.shield is cs.shield is False
- rcs.shield = False
- assert cs.shield is False
+ rcs.shield = True
+ assert rcs.shield is cs.shield is True
# re-entering a _RelativeCancelScope should probably error, but it doesn't *have* to
with pytest.raises(
@@ -289,6 +292,7 @@ async def test_transitional_functions_backwards_compatibility() -> None:
assert rcs.deadline == cs.deadline
assert abs(rcs.deadline - trio.current_time() - 7) < 0.1
rcs.cancel()
- await trio.lowlevel.checkpoint()
- assert rcs.cancelled_caught is cs.cancelled_caught is True
- assert rcs.cancel_called is cs.cancel_called is True
+ await trio.lowlevel.checkpoint() # let the cs get cancelled
+
+ assert rcs.cancelled_caught is cs.cancelled_caught is True
+ assert rcs.cancel_called is cs.cancel_called is True
From ca6335482f06ac4b7eb52e69534cc9b7c744002d Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Thu, 5 Sep 2024 16:47:05 +0200
Subject: [PATCH 42/79] fix test
---
src/trio/_tests/test_timeouts.py | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/trio/_tests/test_timeouts.py b/src/trio/_tests/test_timeouts.py
index 18355079da..32dd7713af 100644
--- a/src/trio/_tests/test_timeouts.py
+++ b/src/trio/_tests/test_timeouts.py
@@ -190,9 +190,6 @@ async def test_timeout_deadline_on_entry(mock_clock: _core.MockClock) -> None:
rcs = move_on_after(5, timeout_from_enter=True)
assert rcs.relative_deadline == 5
- rcs.shield = True
- assert rcs.shield
-
mock_clock.jump(3)
start = _core.current_time()
with rcs as cs:
@@ -200,6 +197,9 @@ async def test_timeout_deadline_on_entry(mock_clock: _core.MockClock) -> None:
assert cs.deadline == start + 5
rcs = fail_after(5, timeout_from_enter=True)
+ rcs.shield = True
+ assert rcs.shield
+
mock_clock.jump(3)
start = _core.current_time()
with rcs as cs:
From 3bd67a50334d722f38f9a900973e31062272da00 Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Fri, 6 Sep 2024 12:34:52 +0200
Subject: [PATCH 43/79] initial implementation of lot breaking
---
src/trio/_core/__init__.py | 7 +++++-
src/trio/_core/_parking_lot.py | 41 ++++++++++++++++++++++++++++++++++
src/trio/_core/_run.py | 6 +++++
src/trio/_sync.py | 4 ++++
src/trio/_tests/test_sync.py | 24 ++++++++++++++++++++
src/trio/lowlevel.py | 2 ++
6 files changed, 83 insertions(+), 1 deletion(-)
diff --git a/src/trio/_core/__init__.py b/src/trio/_core/__init__.py
index 71f5f17eb2..fdef90292d 100644
--- a/src/trio/_core/__init__.py
+++ b/src/trio/_core/__init__.py
@@ -20,7 +20,12 @@
from ._ki import currently_ki_protected, disable_ki_protection, enable_ki_protection
from ._local import RunVar, RunVarToken
from ._mock_clock import MockClock
-from ._parking_lot import ParkingLot, ParkingLotStatistics
+from ._parking_lot import (
+ ParkingLot,
+ ParkingLotStatistics,
+ add_parking_lot_breaker,
+ remove_parking_lot_breaker,
+)
# Imports that always exist
from ._run import (
diff --git a/src/trio/_core/_parking_lot.py b/src/trio/_core/_parking_lot.py
index 916e6a6e96..ea5c76ba56 100644
--- a/src/trio/_core/_parking_lot.py
+++ b/src/trio/_core/_parking_lot.py
@@ -76,6 +76,7 @@
from typing import TYPE_CHECKING
import attrs
+import outcome
from .. import _core
from .._util import final
@@ -86,6 +87,24 @@
from ._run import Task
+GLOBAL_PARKING_LOT_BREAKER: dict[Task, list[ParkingLot]] = {}
+
+
+def add_parking_lot_breaker(task: Task, lot: ParkingLot) -> None:
+ if task not in GLOBAL_PARKING_LOT_BREAKER:
+ GLOBAL_PARKING_LOT_BREAKER[task] = [lot]
+ else:
+ GLOBAL_PARKING_LOT_BREAKER[task].append(lot)
+
+
+def remove_parking_lot_breaker(task: Task, lot: ParkingLot) -> None:
+ if task not in GLOBAL_PARKING_LOT_BREAKER:
+ raise RuntimeError(
+ "Attempted to remove parking lot breaker for task that is not registered as a breaker",
+ )
+ GLOBAL_PARKING_LOT_BREAKER[task].remove(lot)
+
+
@attrs.frozen
class ParkingLotStatistics:
"""An object containing debugging information for a ParkingLot.
@@ -118,6 +137,7 @@ class ParkingLot:
# {task: None}, we just want a deque where we can quickly delete random
# items
_parked: OrderedDict[Task, None] = attrs.field(factory=OrderedDict, init=False)
+ broken_by: Task | None = None
def __len__(self) -> int:
"""Returns the number of parked tasks."""
@@ -137,6 +157,10 @@ async def park(self) -> None:
:meth:`unpark_all`.
"""
+ if self.broken_by is not None:
+ raise _core.BrokenResourceError(
+ f"Attempted to park in parking lot broken by {self.broken_by}",
+ )
task = _core.current_task()
self._parked[task] = None
task.custom_sleep_data = self
@@ -234,6 +258,23 @@ def repark_all(self, new_lot: ParkingLot) -> None:
"""
return self.repark(new_lot, count=len(self))
+ def break_lot(self, task: Task) -> None:
+ """Break this lot, causing all parked tasks to raise an error, and any
+ future tasks attempting to park (and unpark? repark?) to error. The error
+ contains a reference to the task sent as a parameter."""
+ self.broken_by = task
+ # TODO: weird to phrase this one, we probably should reraise this error in Lock
+ error = outcome.Error(
+ _core.BrokenResourceError(f"Parking lot broken by {task}"),
+ )
+
+ # TODO: is there any reason to use self._pop_several?
+ for parked_task in self._parked:
+ if parked_task is task:
+ continue
+ _core.reschedule(parked_task, error)
+ self._parked.clear()
+
def statistics(self) -> ParkingLotStatistics:
"""Return an object containing debugging information.
diff --git a/src/trio/_core/_run.py b/src/trio/_core/_run.py
index 8921ed7f12..dfb2064832 100644
--- a/src/trio/_core/_run.py
+++ b/src/trio/_core/_run.py
@@ -40,6 +40,7 @@
from ._exceptions import Cancelled, RunFinishedError, TrioInternalError
from ._instrumentation import Instruments
from ._ki import LOCALS_KEY_KI_PROTECTION_ENABLED, KIManager, enable_ki_protection
+from ._parking_lot import GLOBAL_PARKING_LOT_BREAKER
from ._thread_cache import start_thread_soon
from ._traps import (
Abort,
@@ -1859,6 +1860,11 @@ def task_exited(self, task: Task, outcome: Outcome[Any]) -> None:
assert task._parent_nursery is not None, task
task._parent_nursery._child_finished(task, outcome)
+ # before or after the other stuff in this function?
+ if task in GLOBAL_PARKING_LOT_BREAKER:
+ for lot in GLOBAL_PARKING_LOT_BREAKER[task]:
+ lot.break_lot(task)
+
if "task_exited" in self.instruments:
self.instruments.call("task_exited", task)
diff --git a/src/trio/_sync.py b/src/trio/_sync.py
index 698716ea35..d08da9f302 100644
--- a/src/trio/_sync.py
+++ b/src/trio/_sync.py
@@ -6,6 +6,7 @@
import attrs
import trio
+from trio.lowlevel import add_parking_lot_breaker, remove_parking_lot_breaker
from . import _core
from ._core import Abort, ParkingLot, RaiseCancelT, enable_ki_protection
@@ -576,6 +577,7 @@ def acquire_nowait(self) -> None:
elif self._owner is None and not self._lot:
# No-one owns it
self._owner = task
+ add_parking_lot_breaker(task, self._lot)
else:
raise trio.WouldBlock
@@ -604,8 +606,10 @@ def release(self) -> None:
task = trio.lowlevel.current_task()
if task is not self._owner:
raise RuntimeError("can't release a Lock you don't own")
+ remove_parking_lot_breaker(self._owner, self._lot)
if self._lot:
(self._owner,) = self._lot.unpark(count=1)
+ add_parking_lot_breaker(self._owner, self._lot)
else:
self._owner = None
diff --git a/src/trio/_tests/test_sync.py b/src/trio/_tests/test_sync.py
index caf3f04f5b..92fd6beec4 100644
--- a/src/trio/_tests/test_sync.py
+++ b/src/trio/_tests/test_sync.py
@@ -5,6 +5,8 @@
import pytest
+from trio.testing import Matcher, RaisesGroup
+
from .. import _core
from .._sync import *
from .._timeouts import sleep_forever
@@ -586,3 +588,25 @@ async def lock_taker() -> None:
await wait_all_tasks_blocked()
assert record == ["started"]
lock_like.release()
+
+
+async def test_lock_acquire_unowned_lock() -> None:
+ """Test that trying to acquire a lock whose owner has exited raises an error.
+ Partial fix for https://github.com/python-trio/trio/issues/3035
+ """
+ lock = trio.Lock()
+ async with trio.open_nursery() as nursery:
+ nursery.start_soon(lock.acquire)
+ with pytest.raises(
+ trio.BrokenResourceError,
+ match="^Attempted to park in parking lot broken by",
+ ):
+ await lock.acquire()
+
+
+async def test_lock_multiple_acquire() -> None:
+ lock = trio.Lock()
+ with RaisesGroup(Matcher(trio.BrokenResourceError, match="Parking lot broken by")):
+ async with trio.open_nursery() as nursery:
+ nursery.start_soon(lock.acquire)
+ nursery.start_soon(lock.acquire)
diff --git a/src/trio/lowlevel.py b/src/trio/lowlevel.py
index 1df7019637..f532ae6367 100644
--- a/src/trio/lowlevel.py
+++ b/src/trio/lowlevel.py
@@ -25,6 +25,7 @@
UnboundedQueue as UnboundedQueue,
UnboundedQueueStatistics as UnboundedQueueStatistics,
add_instrument as add_instrument,
+ add_parking_lot_breaker,
cancel_shielded_checkpoint as cancel_shielded_checkpoint,
checkpoint as checkpoint,
checkpoint_if_cancelled as checkpoint_if_cancelled,
@@ -40,6 +41,7 @@
permanently_detach_coroutine_object as permanently_detach_coroutine_object,
reattach_detached_coroutine_object as reattach_detached_coroutine_object,
remove_instrument as remove_instrument,
+ remove_parking_lot_breaker,
reschedule as reschedule,
spawn_system_task as spawn_system_task,
start_guest_run as start_guest_run,
From 4dfa1ad214f68e02e2e61c27df3b994740492da8 Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Fri, 6 Sep 2024 12:42:02 +0200
Subject: [PATCH 44/79] fix import cycle
---
src/trio/_sync.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/trio/_sync.py b/src/trio/_sync.py
index d08da9f302..8e191d00fb 100644
--- a/src/trio/_sync.py
+++ b/src/trio/_sync.py
@@ -6,10 +6,10 @@
import attrs
import trio
-from trio.lowlevel import add_parking_lot_breaker, remove_parking_lot_breaker
from . import _core
from ._core import Abort, ParkingLot, RaiseCancelT, enable_ki_protection
+from ._core._parking_lot import add_parking_lot_breaker, remove_parking_lot_breaker
from ._util import final
if TYPE_CHECKING:
From 543a087fd007ce1094e9e9f0a87b30b3a6ffd387 Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Fri, 6 Sep 2024 12:47:44 +0200
Subject: [PATCH 45/79] fix re-export for verifytypes visibility
---
src/trio/lowlevel.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/trio/lowlevel.py b/src/trio/lowlevel.py
index f532ae6367..9e385a0045 100644
--- a/src/trio/lowlevel.py
+++ b/src/trio/lowlevel.py
@@ -25,7 +25,7 @@
UnboundedQueue as UnboundedQueue,
UnboundedQueueStatistics as UnboundedQueueStatistics,
add_instrument as add_instrument,
- add_parking_lot_breaker,
+ add_parking_lot_breaker as add_parking_lot_breaker,
cancel_shielded_checkpoint as cancel_shielded_checkpoint,
checkpoint as checkpoint,
checkpoint_if_cancelled as checkpoint_if_cancelled,
@@ -41,7 +41,7 @@
permanently_detach_coroutine_object as permanently_detach_coroutine_object,
reattach_detached_coroutine_object as reattach_detached_coroutine_object,
remove_instrument as remove_instrument,
- remove_parking_lot_breaker,
+ remove_parking_lot_breaker as remove_parking_lot_breaker,
reschedule as reschedule,
spawn_system_task as spawn_system_task,
start_guest_run as start_guest_run,
From c36cdad13424f6795e8b819f410487fdb3eb16f9 Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Fri, 6 Sep 2024 12:58:38 +0200
Subject: [PATCH 46/79] update docstrings
---
src/trio/_core/_parking_lot.py | 8 ++++++++
src/trio/_sync.py | 6 +++++-
2 files changed, 13 insertions(+), 1 deletion(-)
diff --git a/src/trio/_core/_parking_lot.py b/src/trio/_core/_parking_lot.py
index ea5c76ba56..c809a445ef 100644
--- a/src/trio/_core/_parking_lot.py
+++ b/src/trio/_core/_parking_lot.py
@@ -91,6 +91,9 @@
def add_parking_lot_breaker(task: Task, lot: ParkingLot) -> None:
+ """Register a task as a breaker for a lot. This means that if the task exits without
+ having unparked from the lot, then the lot will break and raise an error for all tasks
+ parked in the lot, as well as any future task that attempt to park in it."""
if task not in GLOBAL_PARKING_LOT_BREAKER:
GLOBAL_PARKING_LOT_BREAKER[task] = [lot]
else:
@@ -98,6 +101,7 @@ def add_parking_lot_breaker(task: Task, lot: ParkingLot) -> None:
def remove_parking_lot_breaker(task: Task, lot: ParkingLot) -> None:
+ """Deregister a task as a breaker for a lot. See :func:`add_parking_lot_breaker`."""
if task not in GLOBAL_PARKING_LOT_BREAKER:
raise RuntimeError(
"Attempted to remove parking lot breaker for task that is not registered as a breaker",
@@ -156,6 +160,10 @@ async def park(self) -> None:
"""Park the current task until woken by a call to :meth:`unpark` or
:meth:`unpark_all`.
+ Raises:
+ BrokenResourceError: if attempting to park in a broken lot, or the lot
+ breaks before we get to unpark.
+
"""
if self.broken_by is not None:
raise _core.BrokenResourceError(
diff --git a/src/trio/_sync.py b/src/trio/_sync.py
index 8e191d00fb..9a812f68a4 100644
--- a/src/trio/_sync.py
+++ b/src/trio/_sync.py
@@ -583,7 +583,11 @@ def acquire_nowait(self) -> None:
@enable_ki_protection
async def acquire(self) -> None:
- """Acquire the lock, blocking if necessary."""
+ """Acquire the lock, blocking if necessary.
+
+ Raises:
+ BrokenResourceError: if the owner of the lock exits without releasing.
+ """
await trio.lowlevel.checkpoint_if_cancelled()
try:
self.acquire_nowait()
From 127c5fcaef2420bd5d8c3992792b4fedf0c0e376 Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Fri, 6 Sep 2024 13:15:16 +0200
Subject: [PATCH 47/79] fixes after review by TeamSpen210
---
src/trio/_core/_parking_lot.py | 21 ++++++++++++---------
1 file changed, 12 insertions(+), 9 deletions(-)
diff --git a/src/trio/_core/_parking_lot.py b/src/trio/_core/_parking_lot.py
index c809a445ef..f515a77291 100644
--- a/src/trio/_core/_parking_lot.py
+++ b/src/trio/_core/_parking_lot.py
@@ -102,11 +102,12 @@ def add_parking_lot_breaker(task: Task, lot: ParkingLot) -> None:
def remove_parking_lot_breaker(task: Task, lot: ParkingLot) -> None:
"""Deregister a task as a breaker for a lot. See :func:`add_parking_lot_breaker`."""
- if task not in GLOBAL_PARKING_LOT_BREAKER:
+ try:
+ GLOBAL_PARKING_LOT_BREAKER[task].remove(lot)
+ except (KeyError, ValueError):
raise RuntimeError(
- "Attempted to remove parking lot breaker for task that is not registered as a breaker",
- )
- GLOBAL_PARKING_LOT_BREAKER[task].remove(lot)
+ "Attempted to remove task as breaker for a lot it is not registered for",
+ ) from None
@attrs.frozen
@@ -271,16 +272,18 @@ def break_lot(self, task: Task) -> None:
future tasks attempting to park (and unpark? repark?) to error. The error
contains a reference to the task sent as a parameter."""
self.broken_by = task
- # TODO: weird to phrase this one, we probably should reraise this error in Lock
- error = outcome.Error(
- _core.BrokenResourceError(f"Parking lot broken by {task}"),
- )
# TODO: is there any reason to use self._pop_several?
for parked_task in self._parked:
if parked_task is task:
continue
- _core.reschedule(parked_task, error)
+ # TODO: weird to phrase this one, we maybe should reraise this error in Lock?
+ _core.reschedule(
+ parked_task,
+ outcome.Error(
+ _core.BrokenResourceError(f"Parking lot broken by {task}"),
+ ),
+ )
self._parked.clear()
def statistics(self) -> ParkingLotStatistics:
From 1f75d441f4cd842409058248c65c966eca61b45a Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Fri, 6 Sep 2024 15:55:22 +0200
Subject: [PATCH 48/79] add tests
---
src/trio/_core/_parking_lot.py | 2 -
src/trio/_core/_tests/test_parking_lot.py | 70 +++++++++++++++++++++++
2 files changed, 70 insertions(+), 2 deletions(-)
diff --git a/src/trio/_core/_parking_lot.py b/src/trio/_core/_parking_lot.py
index f515a77291..cf6f02b954 100644
--- a/src/trio/_core/_parking_lot.py
+++ b/src/trio/_core/_parking_lot.py
@@ -275,8 +275,6 @@ def break_lot(self, task: Task) -> None:
# TODO: is there any reason to use self._pop_several?
for parked_task in self._parked:
- if parked_task is task:
- continue
# TODO: weird to phrase this one, we maybe should reraise this error in Lock?
_core.reschedule(
parked_task,
diff --git a/src/trio/_core/_tests/test_parking_lot.py b/src/trio/_core/_tests/test_parking_lot.py
index ed6a17012e..740b0412c1 100644
--- a/src/trio/_core/_tests/test_parking_lot.py
+++ b/src/trio/_core/_tests/test_parking_lot.py
@@ -4,6 +4,9 @@
import pytest
+import trio.lowlevel
+from trio.testing import Matcher, RaisesGroup
+
from ... import _core
from ...testing import wait_all_tasks_blocked
from .._parking_lot import ParkingLot
@@ -215,3 +218,70 @@ async def test_parking_lot_repark_with_count() -> None:
"wake 2",
]
lot1.unpark_all()
+
+
+async def test_parking_lot_breaker_basic() -> None:
+ lot = ParkingLot()
+ task = trio.lowlevel.current_task()
+
+ with pytest.raises(
+ RuntimeError,
+ match="Attempted to remove task as breaker for a lot it is not registered for",
+ ):
+ trio.lowlevel.remove_parking_lot_breaker(task, lot)
+ trio.lowlevel.add_parking_lot_breaker(task, lot)
+ trio.lowlevel.add_parking_lot_breaker(task, lot)
+ trio.lowlevel.remove_parking_lot_breaker(task, lot)
+ trio.lowlevel.remove_parking_lot_breaker(task, lot)
+
+ with pytest.raises(
+ RuntimeError,
+ match="Attempted to remove task as breaker for a lot it is not registered for",
+ ):
+ trio.lowlevel.remove_parking_lot_breaker(task, lot)
+
+
+async def test_parking_lot_breaker() -> None:
+ async def bad_parker(lot: ParkingLot, scope: _core.CancelScope) -> None:
+ trio.lowlevel.add_parking_lot_breaker(trio.lowlevel.current_task(), lot)
+ with scope:
+ await trio.sleep_forever()
+
+ lot = ParkingLot()
+ cs = _core.CancelScope()
+
+ # check that parked task errors
+ with RaisesGroup(
+ Matcher(_core.BrokenResourceError, match="^Parking lot broken by"),
+ ):
+ async with _core.open_nursery() as nursery:
+ nursery.start_soon(bad_parker, lot, cs)
+ await wait_all_tasks_blocked()
+
+ nursery.start_soon(lot.park)
+ await wait_all_tasks_blocked()
+
+ cs.cancel()
+
+ # check that trying to park in brokena lot errors
+ with pytest.raises(_core.BrokenResourceError):
+ await lot.park()
+
+
+async def test_parking_lot_weird() -> None:
+ """break a parking lot, where the breakee is parked. Doing this is weird, but should probably be supported??
+ Although the message makes less sense"""
+
+ async def return_me_and_park(
+ lot: ParkingLot,
+ *,
+ task_status: _core.TaskStatus[_core.Task] = trio.TASK_STATUS_IGNORED,
+ ) -> None:
+ task_status.started(_core.current_task())
+ await lot.park()
+
+ lot = ParkingLot()
+ with RaisesGroup(Matcher(_core.BrokenResourceError, match="Parking lot broken by")):
+ async with _core.open_nursery() as nursery:
+ task = await nursery.start(return_me_and_park, lot)
+ lot.break_lot(task)
From 6835e873870e7b48c00b09c52bcd76a6ab54bd82 Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Fri, 6 Sep 2024 16:07:40 +0200
Subject: [PATCH 49/79] add lock handover test
---
src/trio/_tests/test_sync.py | 25 +++++++++++++++++++++++++
1 file changed, 25 insertions(+)
diff --git a/src/trio/_tests/test_sync.py b/src/trio/_tests/test_sync.py
index 92fd6beec4..0dfa5f79d3 100644
--- a/src/trio/_tests/test_sync.py
+++ b/src/trio/_tests/test_sync.py
@@ -610,3 +610,28 @@ async def test_lock_multiple_acquire() -> None:
async with trio.open_nursery() as nursery:
nursery.start_soon(lock.acquire)
nursery.start_soon(lock.acquire)
+
+
+async def test_lock_handover() -> None:
+ lock = trio.Lock()
+ lock.acquire_nowait()
+ child_task: Task | None = None
+ assert _core._parking_lot.GLOBAL_PARKING_LOT_BREAKER[_core.current_task()] == [
+ lock._lot,
+ ]
+
+ async with trio.open_nursery() as nursery:
+ nursery.start_soon(lock.acquire)
+ await wait_all_tasks_blocked()
+
+ lock.release()
+
+ assert len(_core._parking_lot.GLOBAL_PARKING_LOT_BREAKER) == 2
+ for task, lots in _core._parking_lot.GLOBAL_PARKING_LOT_BREAKER.items():
+ if task == _core.current_task():
+ assert lots == []
+ else:
+ child_task = task
+ assert lots == [lock._lot]
+
+ assert lock._lot.broken_by == child_task
From 3b86e80072e7b6dcd177003c9ee663eb11d195ff Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Fri, 6 Sep 2024 16:22:06 +0200
Subject: [PATCH 50/79] clean up breaker dict
---
src/trio/_core/_run.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/trio/_core/_run.py b/src/trio/_core/_run.py
index dfb2064832..65f367fadd 100644
--- a/src/trio/_core/_run.py
+++ b/src/trio/_core/_run.py
@@ -1864,6 +1864,7 @@ def task_exited(self, task: Task, outcome: Outcome[Any]) -> None:
if task in GLOBAL_PARKING_LOT_BREAKER:
for lot in GLOBAL_PARKING_LOT_BREAKER[task]:
lot.break_lot(task)
+ GLOBAL_PARKING_LOT_BREAKER[task]
if "task_exited" in self.instruments:
self.instruments.call("task_exited", task)
From 94ff9a27ca03213426746a97565fe31c08d1226d Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Fri, 6 Sep 2024 16:35:51 +0200
Subject: [PATCH 51/79] clean up GLOBAL_PARKING_LOT_BREAKER when task releases
or exits
---
src/trio/_core/_parking_lot.py | 2 ++
src/trio/_core/_run.py | 2 +-
src/trio/_tests/test_sync.py | 25 +++++++++++++++----------
3 files changed, 18 insertions(+), 11 deletions(-)
diff --git a/src/trio/_core/_parking_lot.py b/src/trio/_core/_parking_lot.py
index cf6f02b954..ebb7f5b726 100644
--- a/src/trio/_core/_parking_lot.py
+++ b/src/trio/_core/_parking_lot.py
@@ -108,6 +108,8 @@ def remove_parking_lot_breaker(task: Task, lot: ParkingLot) -> None:
raise RuntimeError(
"Attempted to remove task as breaker for a lot it is not registered for",
) from None
+ if not GLOBAL_PARKING_LOT_BREAKER[task]:
+ del GLOBAL_PARKING_LOT_BREAKER[task]
@attrs.frozen
diff --git a/src/trio/_core/_run.py b/src/trio/_core/_run.py
index 65f367fadd..99a4551696 100644
--- a/src/trio/_core/_run.py
+++ b/src/trio/_core/_run.py
@@ -1864,7 +1864,7 @@ def task_exited(self, task: Task, outcome: Outcome[Any]) -> None:
if task in GLOBAL_PARKING_LOT_BREAKER:
for lot in GLOBAL_PARKING_LOT_BREAKER[task]:
lot.break_lot(task)
- GLOBAL_PARKING_LOT_BREAKER[task]
+ del GLOBAL_PARKING_LOT_BREAKER[task]
if "task_exited" in self.instruments:
self.instruments.call("task_exited", task)
diff --git a/src/trio/_tests/test_sync.py b/src/trio/_tests/test_sync.py
index 0dfa5f79d3..c7238fdf91 100644
--- a/src/trio/_tests/test_sync.py
+++ b/src/trio/_tests/test_sync.py
@@ -8,6 +8,7 @@
from trio.testing import Matcher, RaisesGroup
from .. import _core
+from .._core._parking_lot import GLOBAL_PARKING_LOT_BREAKER
from .._sync import *
from .._timeouts import sleep_forever
from ..testing import assert_checkpoints, wait_all_tasks_blocked
@@ -594,6 +595,7 @@ async def test_lock_acquire_unowned_lock() -> None:
"""Test that trying to acquire a lock whose owner has exited raises an error.
Partial fix for https://github.com/python-trio/trio/issues/3035
"""
+ assert not GLOBAL_PARKING_LOT_BREAKER
lock = trio.Lock()
async with trio.open_nursery() as nursery:
nursery.start_soon(lock.acquire)
@@ -602,23 +604,29 @@ async def test_lock_acquire_unowned_lock() -> None:
match="^Attempted to park in parking lot broken by",
):
await lock.acquire()
+ assert not GLOBAL_PARKING_LOT_BREAKER
async def test_lock_multiple_acquire() -> None:
+ assert not GLOBAL_PARKING_LOT_BREAKER
lock = trio.Lock()
with RaisesGroup(Matcher(trio.BrokenResourceError, match="Parking lot broken by")):
async with trio.open_nursery() as nursery:
nursery.start_soon(lock.acquire)
nursery.start_soon(lock.acquire)
+ assert not GLOBAL_PARKING_LOT_BREAKER
async def test_lock_handover() -> None:
+ assert not GLOBAL_PARKING_LOT_BREAKER
lock = trio.Lock()
lock.acquire_nowait()
child_task: Task | None = None
- assert _core._parking_lot.GLOBAL_PARKING_LOT_BREAKER[_core.current_task()] == [
- lock._lot,
- ]
+ assert GLOBAL_PARKING_LOT_BREAKER == {
+ _core.current_task(): [
+ lock._lot,
+ ],
+ }
async with trio.open_nursery() as nursery:
nursery.start_soon(lock.acquire)
@@ -626,12 +634,9 @@ async def test_lock_handover() -> None:
lock.release()
- assert len(_core._parking_lot.GLOBAL_PARKING_LOT_BREAKER) == 2
- for task, lots in _core._parking_lot.GLOBAL_PARKING_LOT_BREAKER.items():
- if task == _core.current_task():
- assert lots == []
- else:
- child_task = task
- assert lots == [lock._lot]
+ assert len(GLOBAL_PARKING_LOT_BREAKER) == 1
+ child_task = next(iter(GLOBAL_PARKING_LOT_BREAKER))
+ assert GLOBAL_PARKING_LOT_BREAKER[child_task] == [lock._lot]
assert lock._lot.broken_by == child_task
+ assert not GLOBAL_PARKING_LOT_BREAKER
From fee3ec05864207411c5c581d5a992e4b80cd2481 Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
<66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Tue, 10 Sep 2024 02:17:54 +0200
Subject: [PATCH 52/79] [pre-commit.ci] pre-commit autoupdate (#3083)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.6.3 → v0.6.4](https://github.com/astral-sh/ruff-pre-commit/compare/v0.6.3...v0.6.4)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
---
.pre-commit-config.yaml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 9300a7152e..7371a77d3b 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -24,7 +24,7 @@ repos:
hooks:
- id: black
- repo: https://github.com/astral-sh/ruff-pre-commit
- rev: v0.6.3
+ rev: v0.6.4
hooks:
- id: ruff
types: [file]
From eb7a451022a6d02cd0d81489c5e742252c294160 Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Tue, 10 Sep 2024 15:10:57 +0200
Subject: [PATCH 53/79] add newsfragments, add StalledLockError, reraise
BrokenResourceError as it in acquire, update docstrings. docs are failing to
build locally but idk wth is wrong
---
newsfragments/3035.feature.rst | 1 +
newsfragments/3081.feature.rst | 1 +
src/trio/__init__.py | 1 +
src/trio/_core/_parking_lot.py | 7 +++----
src/trio/_sync.py | 27 +++++++++++++++++++++------
src/trio/_tests/test_sync.py | 11 ++++++++---
6 files changed, 35 insertions(+), 13 deletions(-)
create mode 100644 newsfragments/3035.feature.rst
create mode 100644 newsfragments/3081.feature.rst
diff --git a/newsfragments/3035.feature.rst b/newsfragments/3035.feature.rst
new file mode 100644
index 0000000000..bc517e8b88
--- /dev/null
+++ b/newsfragments/3035.feature.rst
@@ -0,0 +1 @@
+:class:`trio.Lock` and :class:`trio.StrictFIFOLock` will now raise :exc:`trio.StalledLockError` when ``acquire()`` would previously stall due to the owner of the lock having exited without releasing the lock.
diff --git a/newsfragments/3081.feature.rst b/newsfragments/3081.feature.rst
new file mode 100644
index 0000000000..07fde10abf
--- /dev/null
+++ b/newsfragments/3081.feature.rst
@@ -0,0 +1 @@
+Added :func:`trio.lowlevel.add_parking_lot_breaker` and :func:`trio.lowlevel.remove_parking_lot_breaker` to allow creating custom lock/semaphore implementations that will break their underlying parking lot if a task exits unexpectedly. :meth:`trio.lowlevel.ParkingLot.break_lot` is also added, to allow breaking a parking lot intentionally. Breaking a parking lot raises :exc:`trio.BrokenResourceError` for all tasks currently parked in the lot, and any tasks attempting to park in an already broken parking lot will also error. The breakage status of a lot can be viewed and manually modified with the ``trio.ParkingLot.broken_by`` attribute.
diff --git a/src/trio/__init__.py b/src/trio/__init__.py
index d2151677b1..b938569a7f 100644
--- a/src/trio/__init__.py
+++ b/src/trio/__init__.py
@@ -91,6 +91,7 @@
Lock as Lock,
LockStatistics as LockStatistics,
Semaphore as Semaphore,
+ StalledLockError as StalledLockError,
StrictFIFOLock as StrictFIFOLock,
)
from ._timeouts import (
diff --git a/src/trio/_core/_parking_lot.py b/src/trio/_core/_parking_lot.py
index ebb7f5b726..9fea4e5b25 100644
--- a/src/trio/_core/_parking_lot.py
+++ b/src/trio/_core/_parking_lot.py
@@ -269,15 +269,14 @@ def repark_all(self, new_lot: ParkingLot) -> None:
"""
return self.repark(new_lot, count=len(self))
- def break_lot(self, task: Task) -> None:
+ def break_lot(self, task: Task | None) -> None:
"""Break this lot, causing all parked tasks to raise an error, and any
future tasks attempting to park (and unpark? repark?) to error. The error
- contains a reference to the task sent as a parameter."""
+ contains a reference to the task sent as a parameter.
+ """
self.broken_by = task
- # TODO: is there any reason to use self._pop_several?
for parked_task in self._parked:
- # TODO: weird to phrase this one, we maybe should reraise this error in Lock?
_core.reschedule(
parked_task,
outcome.Error(
diff --git a/src/trio/_sync.py b/src/trio/_sync.py
index 9a812f68a4..60aa3c970d 100644
--- a/src/trio/_sync.py
+++ b/src/trio/_sync.py
@@ -19,6 +19,11 @@
from ._core._parking_lot import ParkingLotStatistics
+class StalledLockError(Exception):
+ """Raised by :meth:`Lock.acquire` and :meth:`StrictFIFOLock.acquire` if the owner
+ exits, or has previously exited, without releasing the lock."""
+
+
@attrs.frozen
class EventStatistics:
"""An object containing debugging information.
@@ -586,16 +591,21 @@ async def acquire(self) -> None:
"""Acquire the lock, blocking if necessary.
Raises:
- BrokenResourceError: if the owner of the lock exits without releasing.
+ StalledLockError: if the owner of the lock exits without releasing.
"""
await trio.lowlevel.checkpoint_if_cancelled()
try:
self.acquire_nowait()
except trio.WouldBlock:
- # NOTE: it's important that the contended acquire path is just
- # "_lot.park()", because that's how Condition.wait() acquires the
- # lock as well.
- await self._lot.park()
+ try:
+ # NOTE: it's important that the contended acquire path is just
+ # "_lot.park()", because that's how Condition.wait() acquires the
+ # lock as well.
+ await self._lot.park()
+ except trio.BrokenResourceError:
+ raise StalledLockError(
+ "Owner of this lock exited without releasing: {self._owner}",
+ ) from None
else:
await trio.lowlevel.cancel_shielded_checkpoint()
@@ -775,7 +785,11 @@ def acquire_nowait(self) -> None:
return self._lock.acquire_nowait()
async def acquire(self) -> None:
- """Acquire the underlying lock, blocking if necessary."""
+ """Acquire the underlying lock, blocking if necessary.
+
+ Raises:
+ StalledLockError: if the owner of the lock exits without releasing.
+ """
await self._lock.acquire()
def release(self) -> None:
@@ -804,6 +818,7 @@ async def wait(self) -> None:
Raises:
RuntimeError: if the calling task does not hold the lock.
+ StalledLockError: if the owner of the lock exits without releasing, when attempting to re-acquire.
"""
if trio.lowlevel.current_task() is not self._lock._owner:
diff --git a/src/trio/_tests/test_sync.py b/src/trio/_tests/test_sync.py
index c7238fdf91..6b4ac97b01 100644
--- a/src/trio/_tests/test_sync.py
+++ b/src/trio/_tests/test_sync.py
@@ -600,8 +600,8 @@ async def test_lock_acquire_unowned_lock() -> None:
async with trio.open_nursery() as nursery:
nursery.start_soon(lock.acquire)
with pytest.raises(
- trio.BrokenResourceError,
- match="^Attempted to park in parking lot broken by",
+ trio.StalledLockError,
+ match="^Owner of this lock exited without releasing",
):
await lock.acquire()
assert not GLOBAL_PARKING_LOT_BREAKER
@@ -610,7 +610,12 @@ async def test_lock_acquire_unowned_lock() -> None:
async def test_lock_multiple_acquire() -> None:
assert not GLOBAL_PARKING_LOT_BREAKER
lock = trio.Lock()
- with RaisesGroup(Matcher(trio.BrokenResourceError, match="Parking lot broken by")):
+ with RaisesGroup(
+ Matcher(
+ trio.StalledLockError,
+ match="^Owner of this lock exited without releasing",
+ ),
+ ):
async with trio.open_nursery() as nursery:
nursery.start_soon(lock.acquire)
nursery.start_soon(lock.acquire)
From e7d7205216c59ccc8455358c9c26e0a548c6e803 Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Tue, 10 Sep 2024 15:14:02 +0200
Subject: [PATCH 54/79] add test for default argument of break_lot
---
src/trio/_core/_parking_lot.py | 4 +++-
src/trio/_core/_tests/test_parking_lot.py | 3 +++
2 files changed, 6 insertions(+), 1 deletion(-)
diff --git a/src/trio/_core/_parking_lot.py b/src/trio/_core/_parking_lot.py
index 9fea4e5b25..9dd3b42dd7 100644
--- a/src/trio/_core/_parking_lot.py
+++ b/src/trio/_core/_parking_lot.py
@@ -269,11 +269,13 @@ def repark_all(self, new_lot: ParkingLot) -> None:
"""
return self.repark(new_lot, count=len(self))
- def break_lot(self, task: Task | None) -> None:
+ def break_lot(self, task: Task | None = None) -> None:
"""Break this lot, causing all parked tasks to raise an error, and any
future tasks attempting to park (and unpark? repark?) to error. The error
contains a reference to the task sent as a parameter.
"""
+ if task is None:
+ task = _core.current_task()
self.broken_by = task
for parked_task in self._parked:
diff --git a/src/trio/_core/_tests/test_parking_lot.py b/src/trio/_core/_tests/test_parking_lot.py
index 740b0412c1..5bfd009126 100644
--- a/src/trio/_core/_tests/test_parking_lot.py
+++ b/src/trio/_core/_tests/test_parking_lot.py
@@ -240,6 +240,9 @@ async def test_parking_lot_breaker_basic() -> None:
):
trio.lowlevel.remove_parking_lot_breaker(task, lot)
+ lot.break_lot()
+ assert lot.broken_by == task
+
async def test_parking_lot_breaker() -> None:
async def bad_parker(lot: ParkingLot, scope: _core.CancelScope) -> None:
From fb8b2cc2e0ff93e3be2ad69e86420d8f3971f117 Mon Sep 17 00:00:00 2001
From: CoolCat467 <52022020+CoolCat467@users.noreply.github.com>
Date: Tue, 10 Sep 2024 16:07:27 -0500
Subject: [PATCH 55/79] Add sphinx-lint Mentioned in and closes
https://github.com/python-trio/trio/issues/3082, this pull request adds the
sphinx-lint pre-commit hook
---
.pre-commit-config.yaml | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 6ea9215591..31b1837ed8 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -34,6 +34,10 @@ repos:
rev: v2.3.0
hooks:
- id: codespell
+ - repo: https://github.com/sphinx-contrib/sphinx-lint
+ rev: v0.9.1
+ hooks:
+ - id: sphinx-lint
- repo: local
hooks:
- id: regenerate-files
From 75fc52b7430d072f1f88ad490d99b65e2ed17288 Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Thu, 12 Sep 2024 13:25:27 +0200
Subject: [PATCH 56/79] quick re-implementation after new spec. Docs/docstrings
have not had a thorough pass
---
src/trio/_core/_run.py | 55 +++++++-
src/trio/_tests/test_timeouts.py | 122 +++++-------------
src/trio/_timeouts.py | 213 ++-----------------------------
3 files changed, 98 insertions(+), 292 deletions(-)
diff --git a/src/trio/_core/_run.py b/src/trio/_core/_run.py
index 8921ed7f12..e2893ed8ad 100644
--- a/src/trio/_core/_run.py
+++ b/src/trio/_core/_run.py
@@ -540,11 +540,21 @@ class CancelScope:
_has_been_entered: bool = attrs.field(default=False, init=False)
_registered_deadline: float = attrs.field(default=inf, init=False)
_cancel_called: bool = attrs.field(default=False, init=False)
+ _is_relative: bool | None = attrs.field(default=False, init=False)
cancelled_caught: bool = attrs.field(default=False, init=False)
# Constructor arguments:
- _deadline: float = attrs.field(default=inf, kw_only=True, alias="deadline")
- _shield: bool = attrs.field(default=False, kw_only=True, alias="shield")
+ _relative_deadline: float = attrs.field(default=inf, kw_only=True)
+ _deadline: float = attrs.field(default=inf, kw_only=True)
+ _shield: bool = attrs.field(default=False, kw_only=True)
+
+ def __attrs_post_init__(self) -> None:
+ if self._relative_deadline != inf:
+ if self._deadline != inf:
+ raise ValueError(
+ "Cannot specify both a deadline and a relative deadline",
+ )
+ self._is_relative = True
@enable_ki_protection
def __enter__(self) -> Self:
@@ -554,6 +564,12 @@ def __enter__(self) -> Self:
"Each CancelScope may only be used for a single 'with' block",
)
self._has_been_entered = True
+ assert self._is_relative is not None
+
+ if self._is_relative:
+ self._deadline = current_time() + self._relative_deadline
+ self._is_relative = None
+
if current_time() >= self._deadline:
self.cancel()
with self._might_change_registered_deadline():
@@ -734,13 +750,48 @@ def deadline(self) -> float:
this can be overridden by the ``deadline=`` argument to
the :class:`~trio.CancelScope` constructor.
"""
+ if self._is_relative is True:
+ raise RuntimeError(
+ "unentered relative cancel scope does not have an absolute deadline",
+ )
return self._deadline
@deadline.setter
def deadline(self, new_deadline: float) -> None:
+ if self._is_relative is True:
+ raise RuntimeError(
+ "unentered relative cancel scope does not have an absolute deadline",
+ )
with self._might_change_registered_deadline():
self._deadline = float(new_deadline)
+ @property
+ def relative_deadline(self) -> float:
+ if self._is_relative is False:
+ raise RuntimeError(
+ "unentered non-relative cancel scope does not have a relative deadline",
+ )
+ elif self._is_relative is None:
+ return self._deadline - current_time()
+ return self._relative_deadline
+
+ @relative_deadline.setter
+ def relative_deadline(self, new_relative_deadline: float) -> None:
+ if self._is_relative is False:
+ raise RuntimeError(
+ "unentered non-relative cancel scope does not have a relative deadline",
+ )
+ elif self._is_relative is True:
+ self._relative_deadline = new_relative_deadline
+ else: # entered
+ with self._might_change_registered_deadline():
+ self._deadline = current_time() + float(new_relative_deadline)
+
+ @property
+ def is_relative(self) -> bool | None:
+ """Returns None after entering"""
+ return self._is_relative
+
@property
def shield(self) -> bool:
"""Read-write, :class:`bool`, default :data:`False`. So long as
diff --git a/src/trio/_tests/test_timeouts.py b/src/trio/_tests/test_timeouts.py
index 32dd7713af..b7b65000a2 100644
--- a/src/trio/_tests/test_timeouts.py
+++ b/src/trio/_tests/test_timeouts.py
@@ -187,7 +187,7 @@ async def test_timeouts_raise_value_error() -> None:
async def test_timeout_deadline_on_entry(mock_clock: _core.MockClock) -> None:
- rcs = move_on_after(5, timeout_from_enter=True)
+ rcs = move_on_after(5)
assert rcs.relative_deadline == 5
mock_clock.jump(3)
@@ -195,104 +195,52 @@ async def test_timeout_deadline_on_entry(mock_clock: _core.MockClock) -> None:
with rcs as cs:
# This would previously be start+2
assert cs.deadline == start + 5
+ assert cs.relative_deadline == 5
+
+ cs.deadline = start + 3
+ assert cs.deadline == start + 3
+ assert cs.relative_deadline == 3
- rcs = fail_after(5, timeout_from_enter=True)
+ cs.relative_deadline = 4
+ assert cs.deadline == start + 4
+ assert cs.relative_deadline == 4
+
+ rcs = move_on_after(5)
+ assert rcs.shield is False
rcs.shield = True
- assert rcs.shield
+ assert rcs.shield is True
mock_clock.jump(3)
start = _core.current_time()
with rcs as cs:
assert cs.deadline == start + 5
- # check that their shield values are linked
- assert rcs.shield is cs.shield is True
-
- cs.shield = False
- assert rcs.shield is cs.shield is False
-
- rcs.shield = True
- assert rcs.shield is cs.shield is True
-
- # re-entering a _RelativeCancelScope should probably error, but it doesn't *have* to
- with pytest.raises(
- RuntimeError,
- match="^Each _RelativeCancelScope may only be used for a single 'with' block$",
- ):
- with rcs as cs:
- ...
-
-
-async def test_timeout_deadline_not_on_entry(mock_clock: _core.MockClock) -> None:
- """Test that not setting timeout_from_enter gives a DeprecationWarning and
- retains old behaviour."""
- for cs_gen_fun in (move_on_after, fail_after):
- with pytest.warns(DeprecationWarning, match="issues/2512"):
- rcs = cs_gen_fun(5)
- mock_clock.jump(3)
- with rcs as cs:
- assert rcs.deadline == cs.deadline == _core.current_time() + 2
-
- rcs.deadline += 1
- assert rcs.deadline == cs.deadline == _core.current_time() + 3
-
- cs.deadline += 1
- assert rcs.deadline == cs.deadline == _core.current_time() + 4
+ assert rcs is cs
-async def test_transitional_functions_fail() -> None:
- rcs = move_on_after(5, timeout_from_enter=True)
- # these errors are only shown if timeout_from_enter=True
+async def test_invalid_acces_unentered() -> None:
+ cs = move_on_after(5)
- match_str = "^_RelativeCancelScope does not have `deadline`. You might want `relative_deadline`.$"
- with pytest.raises(AttributeError, match=match_str):
- assert rcs.deadline
- with pytest.raises(AttributeError, match=match_str):
- rcs.deadline = 7
+ match_str = "^unentered relative cancel scope does not have an absolute deadline$"
+ with pytest.raises(RuntimeError, match=match_str):
+ assert cs.deadline
+ with pytest.raises(RuntimeError, match=match_str):
+ cs.deadline = 7
- for prop in "cancelled_caught", "cancel_called":
- match_str = f"_RelativeCancelScope does not have `{prop}`, and cannot have been cancelled before entering."
- with pytest.raises(AttributeError, match=match_str):
- assert getattr(rcs, prop)
+ cs = move_on_at(5)
- with pytest.raises(
- AttributeError,
- match=match_str[:36] + "cancel`, and cannot be cancelled before entering.",
- ):
- rcs.cancel()
-
- with rcs:
- match_str = "^_RelativeCancelScope does not have `{}`. You might want to access the entered `CancelScope`.$"
- with pytest.raises(AttributeError, match=match_str.format("deadline")):
- assert rcs.deadline
- with pytest.raises(AttributeError, match=match_str.format("deadline")):
- rcs.deadline = 5
- with pytest.raises(AttributeError, match=match_str.format("cancel")):
- rcs.cancel()
- with pytest.raises(AttributeError, match=match_str.format("cancelled_caught")):
- assert rcs.cancelled_caught
- with pytest.raises(AttributeError, match=match_str.format("cancel_called")):
- assert rcs.cancel_called
-
-
-async def test_transitional_functions_backwards_compatibility() -> None:
- rcs = move_on_after(5)
- assert rcs.deadline == 5
- rcs.deadline = 7
- assert rcs.cancelled_caught is False
- assert rcs.cancel_called is False
- with pytest.raises(
- RuntimeError,
- match="^It is no longer possible to cancel a relative cancel scope before entering it.$",
- ):
- rcs.cancel()
+ match_str = (
+ "^unentered non-relative cancel scope does not have a relative deadline$"
+ )
+ with pytest.raises(RuntimeError, match=match_str):
+ assert cs.relative_deadline
+ with pytest.raises(RuntimeError, match=match_str):
+ cs.relative_deadline = 7
- # this does not emit any deprecationwarnings because no time has passed
- with rcs as cs:
- assert rcs.deadline == cs.deadline
- assert abs(rcs.deadline - trio.current_time() - 7) < 0.1
- rcs.cancel()
- await trio.lowlevel.checkpoint() # let the cs get cancelled
- assert rcs.cancelled_caught is cs.cancelled_caught is True
- assert rcs.cancel_called is cs.cancel_called is True
+@pytest.mark.xfail(reason="not implemented")
+async def test_fail_access_before_entering() -> None:
+ my_fail_at = fail_at(5)
+ assert my_fail_at.deadline # type: ignore[attr-defined]
+ my_fail_after = fail_after(5)
+ assert my_fail_after.relative_deadline # type: ignore[attr-defined]
diff --git a/src/trio/_timeouts.py b/src/trio/_timeouts.py
index 097676ed40..dd3a1f01c1 100644
--- a/src/trio/_timeouts.py
+++ b/src/trio/_timeouts.py
@@ -1,197 +1,13 @@
from __future__ import annotations
import math
-import warnings
from contextlib import contextmanager
from typing import TYPE_CHECKING
import trio
-from ._util import final
-
if TYPE_CHECKING:
from collections.abc import Generator
- from types import TracebackType
-
-
-@final
-class _RelativeCancelScope:
- """Makes it possible to specify relative deadlines at initialization, that does
- not start counting until the cm is entered.
- Upon entering it returns a CancelScope, so unless initialization and entering
- are separate, this class will be transparent to end users.
- """
-
- def __init__(
- self,
- relative_deadline: float,
- *,
- shield: bool = False,
- timeout_from_enter: bool = False,
- ):
- self.relative_deadline: float = relative_deadline
- self._shield = shield
- self._timeout_from_enter = timeout_from_enter
-
- self._fail: bool = False
- self._creation_time = trio.current_time()
- self._scope: trio.CancelScope | None = None
-
- def __enter__(self) -> trio.CancelScope:
- if self._scope is not None:
- # This does not have to be the case, but for now we're mirroring the
- # behaviour of CancelScope
- raise RuntimeError(
- "Each _RelativeCancelScope may only be used for a single 'with' block",
- )
- if (
- abs(self._creation_time - trio.current_time()) > 0.01
- and not self._timeout_from_enter
- ):
- # not using warn_deprecated because the message template is a weird fit
- # TODO: mention versions in the message?
- warnings.warn(
- DeprecationWarning(
- "`move_on_after` and `fail_after` will change behaviour to "
- "start the deadline relative to entering the cm, instead of "
- "at creation time. To silence this warning and opt into the "
- "new behaviour, pass `timeout_from_enter=True`. "
- "To keep old behaviour, use `move_on_at(trio.current_time() + x)` "
- "(or `fail_at`), where `x` is the previous timeout length. "
- "See https://github.com/python-trio/trio/issues/2512",
- ),
- stacklevel=2,
- )
-
- if self._timeout_from_enter:
- start_time = trio.current_time()
- else:
- start_time = self._creation_time
-
- self._scope = trio.CancelScope(
- deadline=start_time + self.relative_deadline,
- shield=self._shield,
- )
- self._scope.__enter__()
- return self._scope
-
- def __exit__(
- self,
- exc_type: type[BaseException] | None,
- exc_value: BaseException | None,
- traceback: TracebackType | None,
- ) -> bool | None:
- if self._scope is None: # pragma: no cover
- raise RuntimeError("__exit__ called before __enter__")
- res = self._scope.__exit__(exc_type, exc_value, traceback)
- if self._fail and self._scope.cancelled_caught:
- raise TooSlowError
- return res
-
- @property
- def shield(self) -> bool:
- # if self._timeout_from_enter and self._scope is not None:
- # we might want to raise an error to force people to use the re-saved CancelScope
- # directly, but I'm not sure there is a very strong reason to do that.
- if self._scope is None:
- return self._shield
- return self._scope.shield
-
- @shield.setter
- def shield(self, new_value: bool) -> None:
- if self._scope is None:
- self._shield = new_value
- else:
- self._scope.shield = new_value
-
- @property
- def deadline(self) -> float:
- """Transitional function to maintain backwards compatibility."""
- if self._timeout_from_enter:
- if self._scope is None:
- raise AttributeError(
- "_RelativeCancelScope does not have `deadline`. You might want `relative_deadline`.",
- )
- else:
- raise AttributeError(
- "_RelativeCancelScope does not have `deadline`. You might want to access the entered `CancelScope`.",
- )
- elif self._scope is None: # has not been entered
- return self.relative_deadline
- else:
- return self._scope.deadline
-
- @deadline.setter
- def deadline(self, new_deadline: float) -> None:
- """Transitional function to maintain backwards compatibility."""
- if self._timeout_from_enter:
- if self._scope is None:
- raise AttributeError(
- "_RelativeCancelScope does not have `deadline`. You might want `relative_deadline`.",
- )
- else:
- raise AttributeError(
- "_RelativeCancelScope does not have `deadline`. You might want to access the entered `CancelScope`.",
- )
- elif self._scope is None: # has not been entered
- # we don't raise another deprecationwarning, leaving it for __enter__
- self.relative_deadline = new_deadline
- else:
- self._scope.deadline = new_deadline
-
- @property
- def cancelled_caught(self) -> bool:
- """Transitional function to maintain backwards compatibility."""
- if self._timeout_from_enter:
- if self._scope is None:
- raise AttributeError(
- "_RelativeCancelScope does not have `cancelled_caught`, and cannot have been cancelled before entering.",
- )
- else:
- raise AttributeError(
- "_RelativeCancelScope does not have `cancelled_caught`. You might want to access the entered `CancelScope`.",
- )
- elif self._scope is None:
- return False
- else:
- return self._scope.cancelled_caught
-
- def cancel(self) -> None:
- """Transitional function to maintain backwards compatibility."""
- if self._timeout_from_enter:
- if self._scope is None:
- raise AttributeError(
- "_RelativeCancelScope does not have `cancel`, and cannot be cancelled before entering.",
- )
- else:
- raise AttributeError(
- "_RelativeCancelScope does not have `cancel`. You might want to access the entered `CancelScope`.",
- )
- elif self._scope is None:
- # It may be possible to implement this by immediately canceling the
- # created scope in __enter__
- raise RuntimeError(
- "It is no longer possible to cancel a relative cancel scope before entering it.",
- )
- else:
- self._scope.cancel()
-
- @property
- def cancel_called(self) -> bool:
- """Transitional function to maintain backwards compatibility."""
- if self._timeout_from_enter:
- if self._scope is None:
- raise AttributeError(
- "_RelativeCancelScope does not have `cancel_called`, and cannot have been cancelled before entering.",
- )
- else:
- raise AttributeError(
- "_RelativeCancelScope does not have `cancel_called`. You might want to access the entered `CancelScope`.",
- )
- elif self._scope is None:
- return False
- else:
- return self._scope.cancel_called
def move_on_at(deadline: float, *, shield: bool = False) -> trio.CancelScope:
@@ -217,17 +33,11 @@ def move_on_after(
*,
shield: bool = False,
timeout_from_enter: bool = False,
-) -> _RelativeCancelScope:
+) -> trio.CancelScope:
"""Use as a context manager to create a cancel scope whose deadline is
set to now + *seconds*.
- The deadline of the cancel scope was previously calculated at creation time,
- not upon entering the context manager. This is still the default, but deprecated.
- If you pass ``timeout_from_enter=True`` it will instead be calculated relative
- to entering the cm, and silence the :class:`DeprecationWarning`.
-
- If you're entering the cancel scope at initialization time, which is the most common
- use case, you can treat this function as returning a :class:`CancelScope`.
+ The deadline of the cancel scope is calculated upon entering.
Args:
seconds (float): The timeout.
@@ -242,10 +52,9 @@ def move_on_after(
raise ValueError("timeout must be non-negative")
if math.isnan(seconds):
raise ValueError("timeout must not be NaN")
- return _RelativeCancelScope(
+ return trio.CancelScope(
shield=shield,
relative_deadline=seconds,
- timeout_from_enter=timeout_from_enter,
)
@@ -338,12 +147,12 @@ def fail_at(
raise TooSlowError
+@contextmanager
def fail_after(
seconds: float,
*,
shield: bool = False,
- timeout_from_enter: bool = False,
-) -> _RelativeCancelScope:
+) -> Generator[trio.CancelScope, None, None]:
"""Creates a cancel scope with the given timeout, and raises an error if
it is actually cancelled.
@@ -354,10 +163,7 @@ def fail_after(
it's caught and discarded. When it reaches :func:`fail_after`, then it's
caught and :exc:`TooSlowError` is raised in its place.
- The deadline of the cancel scope was previously calculated at creation time,
- not upon entering the context manager. This is still the default, but deprecated.
- If you pass ``timeout_from_enter=True`` it will instead be calculated relative
- to entering the cm, and silence the :class:`DeprecationWarning`.
+ The deadline of the cancel scope is calculated upon entering.
Args:
seconds (float): The timeout.
@@ -370,6 +176,7 @@ def fail_after(
ValueError: if *seconds* is less than zero or NaN.
"""
- rcs = move_on_after(seconds, shield=shield, timeout_from_enter=timeout_from_enter)
- rcs._fail = True
- return rcs
+ with move_on_after(seconds, shield=shield) as scope:
+ yield scope
+ if scope.cancelled_caught:
+ raise TooSlowError
From 5a32eb0ca3c329a534df3c0d45a113377fdbf0ac Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Mon, 16 Sep 2024 13:01:23 +0200
Subject: [PATCH 57/79] clean up return type of fail_at/_after for sphinx
---
docs/source/reference-core.rst | 35 ----------------------------------
src/trio/_timeouts.py | 12 +++++++++++-
2 files changed, 11 insertions(+), 36 deletions(-)
diff --git a/docs/source/reference-core.rst b/docs/source/reference-core.rst
index 99e392ecb2..d696f2b63e 100644
--- a/docs/source/reference-core.rst
+++ b/docs/source/reference-core.rst
@@ -598,41 +598,6 @@ which is sometimes useful:
.. autofunction:: current_effective_deadline
-
-.. _saved_relative_timeouts:
-
-Separating initialization and entry of a :class:`CancelScope`
-+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-Before v0.26.3 there was no separation of initialization and entry of a :class:`CancelScope`. This meant that trying to initialize :func:`move_on_after` or :func:`fail_after` and entering them at a later point meant that the timeout was relative to initialization, leading to confusing behaviour.
-
-.. code-block:: python
-
- # THIS WILL NOW GIVE A DEPRECATION ERROR
- my_cs = trio.move_on_after(5)
- trio.sleep(1)
- with my_cs: # this would move on after 4 seconds
- ...
-
-This is now resolved with the addition of a wrapper class :class:`trio._timeouts._RelativeCancelScope` that is transparent to end users when initializing and entering at the same time. To silence the :class:`DeprecationWarning` and use the new behavior, pass ``timeout_from_enter=True`` to :func:`move_on_after` or :func:`fail_after`.
-
-.. code-block:: python
-
- my_cs = trio.move_on_after(5, timeout_from_enter = True)
- trio.sleep(1)
- with my_cs: # this will now move on after 5 seconds
- ...
-
-If you want to retain previous behaviour, you should now instead write something like
-
-.. code-block:: python
-
- my_cs = trio.move_on_at(trio.current_time() + 5)
- trio.sleep(1)
- with my_cs: # this will now unambiguously move on after 4 seconds
- ...
-
-.. autoclass:: trio._timeouts._RelativeCancelScope
-
.. _tasks:
Tasks let you do multiple things at once
diff --git a/src/trio/_timeouts.py b/src/trio/_timeouts.py
index dd3a1f01c1..6f470c130b 100644
--- a/src/trio/_timeouts.py
+++ b/src/trio/_timeouts.py
@@ -1,6 +1,7 @@
from __future__ import annotations
import math
+import sys
from contextlib import contextmanager
from typing import TYPE_CHECKING
@@ -32,7 +33,6 @@ def move_on_after(
seconds: float,
*,
shield: bool = False,
- timeout_from_enter: bool = False,
) -> trio.CancelScope:
"""Use as a context manager to create a cancel scope whose deadline is
set to now + *seconds*.
@@ -180,3 +180,13 @@ def fail_after(
yield scope
if scope.cancelled_caught:
raise TooSlowError
+
+
+# Users don't need to know that fail_at & fail_after wraps move_on_at and move_on_after
+# and there is no functional difference. So we replace the return value when generating
+# documentation.
+if "sphinx" in sys.modules:
+ import inspect
+
+ for c in (fail_at, fail_after):
+ c.__signature__ = inspect.Signature.from_callable(c).replace(return_annotation=trio.CancelScope) # type: ignore[union-attr]
From c2a3d8ef0de16ad2592aab0559e55ec6e2b14b35 Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Mon, 16 Sep 2024 13:32:32 +0200
Subject: [PATCH 58/79] remove _is_relative, and calculate it on the fly
instead. Add nan handling to CancelScope. Move value validation from
move_on_after/at to CancelScope
---
src/trio/_core/_run.py | 61 ++++++++++++++++++++++++++----------------
src/trio/_timeouts.py | 7 -----
2 files changed, 38 insertions(+), 30 deletions(-)
diff --git a/src/trio/_core/_run.py b/src/trio/_core/_run.py
index e2893ed8ad..5a7736355a 100644
--- a/src/trio/_core/_run.py
+++ b/src/trio/_core/_run.py
@@ -13,7 +13,7 @@
from contextlib import AbstractAsyncContextManager, contextmanager, suppress
from contextvars import copy_context
from heapq import heapify, heappop, heappush
-from math import inf
+from math import inf, isnan
from time import perf_counter
from typing import (
TYPE_CHECKING,
@@ -540,7 +540,6 @@ class CancelScope:
_has_been_entered: bool = attrs.field(default=False, init=False)
_registered_deadline: float = attrs.field(default=inf, init=False)
_cancel_called: bool = attrs.field(default=False, init=False)
- _is_relative: bool | None = attrs.field(default=False, init=False)
cancelled_caught: bool = attrs.field(default=False, init=False)
# Constructor arguments:
@@ -549,12 +548,16 @@ class CancelScope:
_shield: bool = attrs.field(default=False, kw_only=True)
def __attrs_post_init__(self) -> None:
- if self._relative_deadline != inf:
- if self._deadline != inf:
- raise ValueError(
- "Cannot specify both a deadline and a relative deadline",
- )
- self._is_relative = True
+ if isnan(self._deadline):
+ raise ValueError("deadline must not be NaN")
+ if isnan(self._relative_deadline):
+ raise ValueError("relative deadline must not be NaN")
+ if self._relative_deadline < 0:
+ raise ValueError("timeout must be non-negative")
+ if self._relative_deadline != inf and self._deadline != inf:
+ raise ValueError(
+ "Cannot specify both a deadline and a relative deadline",
+ )
@enable_ki_protection
def __enter__(self) -> Self:
@@ -564,11 +567,11 @@ def __enter__(self) -> Self:
"Each CancelScope may only be used for a single 'with' block",
)
self._has_been_entered = True
- assert self._is_relative is not None
- if self._is_relative:
+ if self._relative_deadline != inf:
+ assert self._deadline == inf
self._deadline = current_time() + self._relative_deadline
- self._is_relative = None
+ self._relative_deadline = inf
if current_time() >= self._deadline:
self.cancel()
@@ -750,7 +753,8 @@ def deadline(self) -> float:
this can be overridden by the ``deadline=`` argument to
the :class:`~trio.CancelScope` constructor.
"""
- if self._is_relative is True:
+ if self._relative_deadline != inf:
+ assert self._deadline == inf
raise RuntimeError(
"unentered relative cancel scope does not have an absolute deadline",
)
@@ -758,7 +762,10 @@ def deadline(self) -> float:
@deadline.setter
def deadline(self, new_deadline: float) -> None:
- if self._is_relative is True:
+ if isnan(new_deadline):
+ raise ValueError("deadline must not be NaN")
+ if self._relative_deadline != inf:
+ assert self._deadline == inf
raise RuntimeError(
"unentered relative cancel scope does not have an absolute deadline",
)
@@ -767,30 +774,38 @@ def deadline(self, new_deadline: float) -> None:
@property
def relative_deadline(self) -> float:
- if self._is_relative is False:
+ if self._has_been_entered:
+ return self._deadline - current_time()
+ elif self._deadline != inf:
+ assert self._relative_deadline == inf
raise RuntimeError(
"unentered non-relative cancel scope does not have a relative deadline",
)
- elif self._is_relative is None:
- return self._deadline - current_time()
return self._relative_deadline
@relative_deadline.setter
def relative_deadline(self, new_relative_deadline: float) -> None:
- if self._is_relative is False:
+ if isnan(new_relative_deadline):
+ raise ValueError("deadline must not be NaN")
+ if self._has_been_entered:
+ with self._might_change_registered_deadline():
+ self._deadline = current_time() + float(new_relative_deadline)
+ elif self._deadline != inf:
+ assert self._relative_deadline == inf
raise RuntimeError(
"unentered non-relative cancel scope does not have a relative deadline",
)
- elif self._is_relative is True:
+ else:
self._relative_deadline = new_relative_deadline
- else: # entered
- with self._might_change_registered_deadline():
- self._deadline = current_time() + float(new_relative_deadline)
@property
def is_relative(self) -> bool | None:
- """Returns None after entering"""
- return self._is_relative
+ """Returns None after entering. Returns False if both deadline and
+ relative_deadline are inf."""
+ assert not (self._deadline != inf and self._relative_deadline != inf)
+ if self._has_been_entered:
+ return None
+ return self._deadline != inf
@property
def shield(self) -> bool:
diff --git a/src/trio/_timeouts.py b/src/trio/_timeouts.py
index 6f470c130b..d1bc9fafb9 100644
--- a/src/trio/_timeouts.py
+++ b/src/trio/_timeouts.py
@@ -1,6 +1,5 @@
from __future__ import annotations
-import math
import sys
from contextlib import contextmanager
from typing import TYPE_CHECKING
@@ -24,8 +23,6 @@ def move_on_at(deadline: float, *, shield: bool = False) -> trio.CancelScope:
ValueError: if deadline is NaN.
"""
- if math.isnan(deadline):
- raise ValueError("deadline must not be NaN")
return trio.CancelScope(deadline=deadline, shield=shield)
@@ -48,10 +45,6 @@ def move_on_after(
ValueError: if timeout is less than zero or NaN.
"""
- if seconds < 0:
- raise ValueError("timeout must be non-negative")
- if math.isnan(seconds):
- raise ValueError("timeout must not be NaN")
return trio.CancelScope(
shield=shield,
relative_deadline=seconds,
From f8e94171ae0a1850cdabd8bd7f7f58d31165490e Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Mon, 16 Sep 2024 15:18:13 +0200
Subject: [PATCH 59/79] change some RuntimeError to DeprecationWarning, fix
tests/coverage
---
src/trio/_core/_run.py | 22 ++++++++++++++++------
src/trio/_core/_tests/test_run.py | 23 ++++++++++++++++++++++-
src/trio/_tests/test_timeouts.py | 24 ++++++++++++++++++------
3 files changed, 56 insertions(+), 13 deletions(-)
diff --git a/src/trio/_core/_run.py b/src/trio/_core/_run.py
index 5a7736355a..5de75e8e6c 100644
--- a/src/trio/_core/_run.py
+++ b/src/trio/_core/_run.py
@@ -755,9 +755,13 @@ def deadline(self) -> float:
"""
if self._relative_deadline != inf:
assert self._deadline == inf
- raise RuntimeError(
- "unentered relative cancel scope does not have an absolute deadline",
+ warnings.warn(
+ DeprecationWarning(
+ "unentered relative cancel scope does not have an absolute deadline. Use `.relative_deadline`",
+ ),
+ stacklevel=2,
)
+ return current_time() + self._relative_deadline
return self._deadline
@deadline.setter
@@ -766,9 +770,13 @@ def deadline(self, new_deadline: float) -> None:
raise ValueError("deadline must not be NaN")
if self._relative_deadline != inf:
assert self._deadline == inf
- raise RuntimeError(
- "unentered relative cancel scope does not have an absolute deadline",
+ warnings.warn(
+ DeprecationWarning(
+ "unentered relative cancel scope does not have an absolute deadline. Transforming into an absolute cancel scope. First set `.relative_deadline = math.inf` if you do want an absolute cancel scope.",
+ ),
+ stacklevel=2,
)
+ self._relative_deadline = inf
with self._might_change_registered_deadline():
self._deadline = float(new_deadline)
@@ -786,7 +794,9 @@ def relative_deadline(self) -> float:
@relative_deadline.setter
def relative_deadline(self, new_relative_deadline: float) -> None:
if isnan(new_relative_deadline):
- raise ValueError("deadline must not be NaN")
+ raise ValueError("relative deadline must not be NaN")
+ if new_relative_deadline < 0:
+ raise ValueError("relative deadline must be non-negative")
if self._has_been_entered:
with self._might_change_registered_deadline():
self._deadline = current_time() + float(new_relative_deadline)
@@ -805,7 +815,7 @@ def is_relative(self) -> bool | None:
assert not (self._deadline != inf and self._relative_deadline != inf)
if self._has_been_entered:
return None
- return self._deadline != inf
+ return self._relative_deadline != inf
@property
def shield(self) -> bool:
diff --git a/src/trio/_core/_tests/test_run.py b/src/trio/_core/_tests/test_run.py
index e6ee5e869f..9d2eab787d 100644
--- a/src/trio/_core/_tests/test_run.py
+++ b/src/trio/_core/_tests/test_run.py
@@ -9,7 +9,7 @@
import types
import weakref
from contextlib import ExitStack, contextmanager, suppress
-from math import inf
+from math import inf, nan
from typing import TYPE_CHECKING, Any, NoReturn, TypeVar, cast
import outcome
@@ -365,6 +365,27 @@ async def test_cancel_scope_repr(mock_clock: _core.MockClock) -> None:
assert "exited" in repr(scope)
+async def test_cancel_scope_validation() -> None:
+ with pytest.raises(
+ ValueError,
+ match="^Cannot specify both a deadline and a relative deadline$",
+ ):
+ _core.CancelScope(deadline=7, relative_deadline=3)
+ scope = _core.CancelScope()
+
+ with pytest.raises(ValueError, match="^deadline must not be NaN$"):
+ scope.deadline = nan
+ with pytest.raises(ValueError, match="^relative deadline must not be NaN$"):
+ scope.relative_deadline = nan
+
+ with pytest.raises(ValueError, match="^relative deadline must be non-negative$"):
+ scope.relative_deadline = -3
+ scope.relative_deadline = 5
+ assert scope.relative_deadline == 5
+
+ # several related tests of CancelScope are implicitly handled by test_timeouts.py
+
+
def test_cancel_points() -> None:
async def main1() -> None:
with _core.CancelScope() as scope:
diff --git a/src/trio/_tests/test_timeouts.py b/src/trio/_tests/test_timeouts.py
index b7b65000a2..5add7cbe8c 100644
--- a/src/trio/_tests/test_timeouts.py
+++ b/src/trio/_tests/test_timeouts.py
@@ -180,7 +180,7 @@ async def test_timeouts_raise_value_error() -> None:
):
with pytest.raises(
ValueError,
- match="^(duration|deadline|timeout) must (not )*be (non-negative|NaN)$",
+ match="^(duration|deadline|timeout|relative deadline) must (not )*be (non-negative|NaN)$",
):
with cm(val):
pass # pragma: no cover
@@ -193,6 +193,8 @@ async def test_timeout_deadline_on_entry(mock_clock: _core.MockClock) -> None:
mock_clock.jump(3)
start = _core.current_time()
with rcs as cs:
+ assert cs.is_relative is None
+
# This would previously be start+2
assert cs.deadline == start + 5
assert cs.relative_deadline == 5
@@ -218,14 +220,24 @@ async def test_timeout_deadline_on_entry(mock_clock: _core.MockClock) -> None:
assert rcs is cs
-async def test_invalid_acces_unentered() -> None:
+async def test_invalid_access_unentered(mock_clock: _core.MockClock) -> None:
cs = move_on_after(5)
+ mock_clock.jump(3)
+ start = _core.current_time()
- match_str = "^unentered relative cancel scope does not have an absolute deadline$"
- with pytest.raises(RuntimeError, match=match_str):
- assert cs.deadline
- with pytest.raises(RuntimeError, match=match_str):
+ match_str = "^unentered relative cancel scope does not have an absolute deadline"
+ with pytest.warns(DeprecationWarning, match=match_str):
+ assert cs.deadline == start + 5
+ mock_clock.jump(1)
+ # this is hella sketchy, but they *have* been warned
+ with pytest.warns(DeprecationWarning, match=match_str):
+ assert cs.deadline == start + 6
+
+ with pytest.warns(DeprecationWarning, match=match_str):
cs.deadline = 7
+ # now transformed into absolute
+ assert cs.deadline == 7
+ assert not cs.is_relative
cs = move_on_at(5)
From 36786a66a804ec8d9fc59a97353ef127e981f9d4 Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Mon, 16 Sep 2024 15:39:41 +0200
Subject: [PATCH 60/79] pragma: no cover
---
src/trio/_tests/test_timeouts.py | 2 +-
src/trio/_timeouts.py | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/trio/_tests/test_timeouts.py b/src/trio/_tests/test_timeouts.py
index 5add7cbe8c..27711f0422 100644
--- a/src/trio/_tests/test_timeouts.py
+++ b/src/trio/_tests/test_timeouts.py
@@ -251,7 +251,7 @@ async def test_invalid_access_unentered(mock_clock: _core.MockClock) -> None:
@pytest.mark.xfail(reason="not implemented")
-async def test_fail_access_before_entering() -> None:
+async def test_fail_access_before_entering() -> None: # pragma: no cover
my_fail_at = fail_at(5)
assert my_fail_at.deadline # type: ignore[attr-defined]
my_fail_after = fail_after(5)
diff --git a/src/trio/_timeouts.py b/src/trio/_timeouts.py
index d1bc9fafb9..3a56262520 100644
--- a/src/trio/_timeouts.py
+++ b/src/trio/_timeouts.py
@@ -178,7 +178,7 @@ def fail_after(
# Users don't need to know that fail_at & fail_after wraps move_on_at and move_on_after
# and there is no functional difference. So we replace the return value when generating
# documentation.
-if "sphinx" in sys.modules:
+if "sphinx" in sys.modules: # pragma: no cover
import inspect
for c in (fail_at, fail_after):
From a0cc5a1e790b14c7de2431aef08565b38849cc89 Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
<66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Tue, 17 Sep 2024 17:22:00 +0900
Subject: [PATCH 61/79] [pre-commit.ci] pre-commit autoupdate (#3088)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.6.4 → v0.6.5](https://github.com/astral-sh/ruff-pre-commit/compare/v0.6.4...v0.6.5)
- [github.com/sphinx-contrib/sphinx-lint: v0.9.1 → v1.0.0](https://github.com/sphinx-contrib/sphinx-lint/compare/v0.9.1...v1.0.0)
* Remove unnecessary parenthesis
---------
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: EXPLOSION
---
.pre-commit-config.yaml | 4 ++--
docs/source/history.rst | 4 ++--
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 844c1a2b69..0c2a7c54eb 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -24,7 +24,7 @@ repos:
hooks:
- id: black
- repo: https://github.com/astral-sh/ruff-pre-commit
- rev: v0.6.4
+ rev: v0.6.5
hooks:
- id: ruff
types: [file]
@@ -35,7 +35,7 @@ repos:
hooks:
- id: codespell
- repo: https://github.com/sphinx-contrib/sphinx-lint
- rev: v0.9.1
+ rev: v1.0.0
hooks:
- id: sphinx-lint
- repo: local
diff --git a/docs/source/history.rst b/docs/source/history.rst
index 151c02af6d..da8375aea4 100644
--- a/docs/source/history.rst
+++ b/docs/source/history.rst
@@ -141,7 +141,7 @@ Trio 0.23.2 (2023-12-14)
Features
~~~~~~~~
-- `TypeVarTuple `_ is now used to fully type :meth:`nursery.start_soon() `, :func:`trio.run()`, :func:`trio.to_thread.run_sync()`, and other similar functions accepting ``(func, *args)``. This means type checkers will be able to verify types are used correctly. :meth:`nursery.start() ` is not fully typed yet however. (`#2881 `__)
+- `TypeVarTuple `_ is now used to fully type :meth:`nursery.start_soon() `, :func:`trio.run`, :func:`trio.to_thread.run_sync`, and other similar functions accepting ``(func, *args)``. This means type checkers will be able to verify types are used correctly. :meth:`nursery.start() ` is not fully typed yet however. (`#2881 `__)
Bugfixes
@@ -1000,7 +1000,7 @@ Bugfixes
- Fixed a race condition on macOS, where Trio's TCP listener would crash if an
incoming TCP connection was closed before the listener had a chance to accept
it. (`#609 `__)
-- :func:`trio.open_tcp_stream()` has been refactored to clean up unsuccessful
+- :func:`trio.open_tcp_stream` has been refactored to clean up unsuccessful
connection attempts more reliably. (`#809
`__)
From 7d81c2720d0b5bd68ab62d676b207791af7df8c1 Mon Sep 17 00:00:00 2001
From: EXPLOSION
Date: Wed, 18 Sep 2024 05:53:22 -0400
Subject: [PATCH 62/79] Update open process types (#3076)
---
newsfragments/3076.bugfix.rst | 1 +
src/trio/_subprocess.py | 30 ++++++++++++++++--------------
src/trio/_tools/gen_exports.py | 2 +-
3 files changed, 18 insertions(+), 15 deletions(-)
create mode 100644 newsfragments/3076.bugfix.rst
diff --git a/newsfragments/3076.bugfix.rst b/newsfragments/3076.bugfix.rst
new file mode 100644
index 0000000000..48aa54127a
--- /dev/null
+++ b/newsfragments/3076.bugfix.rst
@@ -0,0 +1 @@
+Update ``trio.lowlevel.open_process``'s documentation to allow bytes.
diff --git a/src/trio/_subprocess.py b/src/trio/_subprocess.py
index 90ab9cc16a..263225ffca 100644
--- a/src/trio/_subprocess.py
+++ b/src/trio/_subprocess.py
@@ -304,7 +304,7 @@ def kill(self) -> None:
async def _open_process(
- command: list[str] | str,
+ command: StrOrBytesPath | Sequence[StrOrBytesPath],
*,
stdin: int | HasFileno | None = None,
stdout: int | HasFileno | None = None,
@@ -329,13 +329,14 @@ async def _open_process(
want.
Args:
- command (list or str): The command to run. Typically this is a
- sequence of strings such as ``['ls', '-l', 'directory with spaces']``,
- where the first element names the executable to invoke and the other
- elements specify its arguments. With ``shell=True`` in the
- ``**options``, or on Windows, ``command`` may alternatively
- be a string, which will be parsed following platform-dependent
- :ref:`quoting rules `.
+ command: The command to run. Typically this is a sequence of strings or
+ bytes such as ``['ls', '-l', 'directory with spaces']``, where the
+ first element names the executable to invoke and the other elements
+ specify its arguments. With ``shell=True`` in the ``**options``, or on
+ Windows, ``command`` can be a string or bytes, which will be parsed
+ following platform-dependent :ref:`quoting rules
+ `. In all cases ``command`` can be a path or a
+ sequence of paths.
stdin: Specifies what the child process's standard input
stream should connect to: output written by the parent
(``subprocess.PIPE``), nothing (``subprocess.DEVNULL``),
@@ -369,15 +370,16 @@ async def _open_process(
)
if os.name == "posix":
- if isinstance(command, str) and not options.get("shell"):
+ # TODO: how do paths and sequences thereof play with `shell=True`?
+ if isinstance(command, (str, bytes)) and not options.get("shell"):
raise TypeError(
- "command must be a sequence (not a string) if shell=False "
- "on UNIX systems",
+ "command must be a sequence (not a string or bytes) if "
+ "shell=False on UNIX systems",
)
- if not isinstance(command, str) and options.get("shell"):
+ if not isinstance(command, (str, bytes)) and options.get("shell"):
raise TypeError(
- "command must be a string (not a sequence) if shell=True "
- "on UNIX systems",
+ "command must be a string or bytes (not a sequence) if "
+ "shell=True on UNIX systems",
)
trio_stdin: ClosableSendStream | None = None
diff --git a/src/trio/_tools/gen_exports.py b/src/trio/_tools/gen_exports.py
index 524a65f90d..91969d6bfe 100755
--- a/src/trio/_tools/gen_exports.py
+++ b/src/trio/_tools/gen_exports.py
@@ -303,7 +303,7 @@ def process(files: Iterable[File], *, do_test: bool) -> None:
print("Generated sources are up to date.")
else:
for new_path, new_source in new_files.items():
- with open(new_path, "w", encoding="utf-8") as f:
+ with open(new_path, "w", encoding="utf-8", newline="\n") as f:
f.write(new_source)
print("Regenerated sources successfully.")
if not matches_disk:
From b834f73cbdd6b0c087ac2d19c5c47f7101a279bd Mon Sep 17 00:00:00 2001
From: Spencer Brown
Date: Wed, 18 Sep 2024 20:13:17 +1000
Subject: [PATCH 63/79] During docs builds, leave newsfragments unchanged
(#3086)
* Undo towncrier changes when building docs
* Restore git index after towncrier run
* Add type annotation for history_new
---
docs/source/conf.py | 64 ++++++++++++++++++++++++++++++++++++++-------
1 file changed, 55 insertions(+), 9 deletions(-)
diff --git a/docs/source/conf.py b/docs/source/conf.py
index 3a2b3d5f14..ecdbdd4701 100755
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -19,9 +19,11 @@
from __future__ import annotations
import collections.abc
+import glob
import os
import sys
import types
+from pathlib import Path
from typing import TYPE_CHECKING, cast
if TYPE_CHECKING:
@@ -36,19 +38,62 @@
# Enable reloading with `typing.TYPE_CHECKING` being True
os.environ["SPHINX_AUTODOC_RELOAD_MODULES"] = "1"
-# https://docs.readthedocs.io/en/stable/builds.html#build-environment
-if "READTHEDOCS" in os.environ:
- import glob
-
- if glob.glob("../../newsfragments/*.*.rst"):
- print("-- Found newsfragments; running towncrier --", flush=True)
- import subprocess
-
+# Handle writing newsfragments into the history file.
+# We want to keep files unchanged when testing locally.
+# So immediately revert the contents after running towncrier,
+# then substitute when Sphinx wants to read it in.
+history_file = Path("history.rst")
+
+history_new: str | None
+if glob.glob("../../newsfragments/*.*.rst"):
+ print("-- Found newsfragments; running towncrier --", flush=True)
+ history_orig = history_file.read_bytes()
+ import subprocess
+
+ # In case changes were staged, preserve indexed version.
+ # This grabs the hash of the current staged version.
+ history_staged = subprocess.run(
+ ["git", "rev-parse", "--verify", ":docs/source/history.rst"],
+ check=True,
+ cwd="../..",
+ stdout=subprocess.PIPE,
+ encoding="ascii",
+ ).stdout.strip()
+ try:
subprocess.run(
- ["towncrier", "--yes", "--date", "not released yet"],
+ ["towncrier", "--keep", "--date", "not released yet"],
cwd="../..",
check=True,
)
+ history_new = history_file.read_text("utf8")
+ finally:
+ # Make sure this reverts even if a failure occurred.
+ # Restore whatever was staged.
+ print(f"Restoring history.rst = {history_staged}")
+ subprocess.run(
+ [
+ "git",
+ "update-index",
+ "--cacheinfo",
+ f"100644,{history_staged},docs/source/history.rst",
+ ],
+ cwd="../..",
+ check=False,
+ )
+ # And restore the working copy.
+ history_file.write_bytes(history_orig)
+ del history_orig # We don't need this any more.
+else:
+ # Leave it as is.
+ history_new = None
+
+
+def on_read_source(app: Sphinx, docname: str, content: list[str]) -> None:
+ """Substitute the modified history file."""
+ if docname == "history" and history_new is not None:
+ # This is a 1-item list with the file contents.
+ content[0] = history_new
+
# Sphinx is very finicky, and somewhat buggy, so we have several different
# methods to help it resolve links.
@@ -153,6 +198,7 @@ def setup(app: Sphinx) -> None:
app.connect("autodoc-process-signature", autodoc_process_signature)
# After Intersphinx runs, add additional mappings.
app.connect("builder-inited", add_intersphinx, priority=1000)
+ app.connect("source-read", on_read_source)
# -- General configuration ------------------------------------------------
From 277c7da13cd9d18cb1b0b9ab1cc4aa8bbc5a2106 Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Wed, 18 Sep 2024 12:14:36 +0200
Subject: [PATCH 64/79] various fixes after review
---
docs/source/reference-lowlevel.rst | 4 ++++
newsfragments/3035.feature.rst | 2 +-
newsfragments/3081.feature.rst | 2 +-
src/trio/__init__.py | 1 -
src/trio/_core/_parking_lot.py | 23 +++++++++++++++----
src/trio/_core/_tests/test_parking_lot.py | 28 ++++++++++++++++++++++-
src/trio/_sync.py | 13 ++++-------
src/trio/_tests/test_sync.py | 4 ++--
8 files changed, 57 insertions(+), 20 deletions(-)
diff --git a/docs/source/reference-lowlevel.rst b/docs/source/reference-lowlevel.rst
index 70133b9839..10c1ddfdc0 100644
--- a/docs/source/reference-lowlevel.rst
+++ b/docs/source/reference-lowlevel.rst
@@ -393,6 +393,10 @@ Wait queue abstraction
.. autoclass:: ParkingLotStatistics
:members:
+.. autofunction:: add_parking_lot_breaker
+
+.. autofunction:: remove_parking_lot_breaker
+
Low-level checkpoint functions
------------------------------
diff --git a/newsfragments/3035.feature.rst b/newsfragments/3035.feature.rst
index bc517e8b88..c25841c47c 100644
--- a/newsfragments/3035.feature.rst
+++ b/newsfragments/3035.feature.rst
@@ -1 +1 @@
-:class:`trio.Lock` and :class:`trio.StrictFIFOLock` will now raise :exc:`trio.StalledLockError` when ``acquire()`` would previously stall due to the owner of the lock having exited without releasing the lock.
+:class:`trio.Lock` and :class:`trio.StrictFIFOLock` will now raise :exc:`trio.BrokenResourceError` when :meth:`trio.Lock.acquire` would previously stall due to the owner of the lock having exited without releasing the lock.
diff --git a/newsfragments/3081.feature.rst b/newsfragments/3081.feature.rst
index 07fde10abf..34a073b265 100644
--- a/newsfragments/3081.feature.rst
+++ b/newsfragments/3081.feature.rst
@@ -1 +1 @@
-Added :func:`trio.lowlevel.add_parking_lot_breaker` and :func:`trio.lowlevel.remove_parking_lot_breaker` to allow creating custom lock/semaphore implementations that will break their underlying parking lot if a task exits unexpectedly. :meth:`trio.lowlevel.ParkingLot.break_lot` is also added, to allow breaking a parking lot intentionally. Breaking a parking lot raises :exc:`trio.BrokenResourceError` for all tasks currently parked in the lot, and any tasks attempting to park in an already broken parking lot will also error. The breakage status of a lot can be viewed and manually modified with the ``trio.ParkingLot.broken_by`` attribute.
+Added :func:`trio.lowlevel.add_parking_lot_breaker` and :func:`trio.lowlevel.remove_parking_lot_breaker` to allow creating custom lock/semaphore implementations that will break their underlying parking lot if a task exits unexpectedly. :meth:`trio.lowlevel.ParkingLot.break_lot` is also added, to allow breaking a parking lot intentionally.
diff --git a/src/trio/__init__.py b/src/trio/__init__.py
index b938569a7f..d2151677b1 100644
--- a/src/trio/__init__.py
+++ b/src/trio/__init__.py
@@ -91,7 +91,6 @@
Lock as Lock,
LockStatistics as LockStatistics,
Semaphore as Semaphore,
- StalledLockError as StalledLockError,
StrictFIFOLock as StrictFIFOLock,
)
from ._timeouts import (
diff --git a/src/trio/_core/_parking_lot.py b/src/trio/_core/_parking_lot.py
index 9dd3b42dd7..e7be7913e7 100644
--- a/src/trio/_core/_parking_lot.py
+++ b/src/trio/_core/_parking_lot.py
@@ -72,6 +72,7 @@
from __future__ import annotations
import math
+import warnings
from collections import OrderedDict
from typing import TYPE_CHECKING
@@ -91,9 +92,11 @@
def add_parking_lot_breaker(task: Task, lot: ParkingLot) -> None:
- """Register a task as a breaker for a lot. This means that if the task exits without
- having unparked from the lot, then the lot will break and raise an error for all tasks
- parked in the lot, as well as any future task that attempt to park in it."""
+ """Register a task as a breaker for a lot. If this task exits without being removed
+ as a breaker, the lot will break. This will cause an error to be raised for all
+ tasks currently parked in the lot, as well as any future tasks that attempt to
+ park in it.
+ """
if task not in GLOBAL_PARKING_LOT_BREAKER:
GLOBAL_PARKING_LOT_BREAKER[task] = [lot]
else:
@@ -271,11 +274,21 @@ def repark_all(self, new_lot: ParkingLot) -> None:
def break_lot(self, task: Task | None = None) -> None:
"""Break this lot, causing all parked tasks to raise an error, and any
- future tasks attempting to park (and unpark? repark?) to error. The error
- contains a reference to the task sent as a parameter.
+ future tasks attempting to park to error. Unpark & repark become no-ops as the
+ parking lot is empty.
+ The error raised contains a reference to the task sent as a parameter.
"""
if task is None:
task = _core.current_task()
+ if self.broken_by is not None:
+ if self.broken_by != task:
+ warnings.warn(
+ RuntimeWarning(
+ f"{task} attempted to break parking lot {self} already broken by {self.broken_by}",
+ ),
+ stacklevel=2,
+ )
+ return
self.broken_by = task
for parked_task in self._parked:
diff --git a/src/trio/_core/_tests/test_parking_lot.py b/src/trio/_core/_tests/test_parking_lot.py
index 5bfd009126..59c4a1a4c2 100644
--- a/src/trio/_core/_tests/test_parking_lot.py
+++ b/src/trio/_core/_tests/test_parking_lot.py
@@ -229,6 +229,8 @@ async def test_parking_lot_breaker_basic() -> None:
match="Attempted to remove task as breaker for a lot it is not registered for",
):
trio.lowlevel.remove_parking_lot_breaker(task, lot)
+
+ # check that a task can be registered as breaker for the same lot multiple times
trio.lowlevel.add_parking_lot_breaker(task, lot)
trio.lowlevel.add_parking_lot_breaker(task, lot)
trio.lowlevel.remove_parking_lot_breaker(task, lot)
@@ -240,9 +242,33 @@ async def test_parking_lot_breaker_basic() -> None:
):
trio.lowlevel.remove_parking_lot_breaker(task, lot)
+ # defaults to current task
lot.break_lot()
assert lot.broken_by == task
+ # breaking the lot again with the same task is a no-op
+ lot.break_lot()
+
+ # but with a different task it gives a warning
+ async def dummy_task(
+ task_status: _core.TaskStatus[_core.Task] = trio.TASK_STATUS_IGNORED,
+ ) -> None:
+ task_status.started(_core.current_task())
+
+ # The nursery is only to create a task we can pass to lot.break_lot
+ # and has no effect on the test otherwise.
+ async with trio.open_nursery() as nursery:
+ child_task = await nursery.start(dummy_task)
+ with pytest.warns(
+ RuntimeWarning,
+ match="attempted to break parking .* already broken by .*",
+ ):
+ lot.break_lot(child_task)
+ nursery.cancel_scope.cancel()
+
+ # and doesn't change broken_by
+ assert lot.broken_by == task
+
async def test_parking_lot_breaker() -> None:
async def bad_parker(lot: ParkingLot, scope: _core.CancelScope) -> None:
@@ -266,7 +292,7 @@ async def bad_parker(lot: ParkingLot, scope: _core.CancelScope) -> None:
cs.cancel()
- # check that trying to park in brokena lot errors
+ # check that trying to park in broken lot errors
with pytest.raises(_core.BrokenResourceError):
await lot.park()
diff --git a/src/trio/_sync.py b/src/trio/_sync.py
index 60aa3c970d..3d7d28914b 100644
--- a/src/trio/_sync.py
+++ b/src/trio/_sync.py
@@ -19,11 +19,6 @@
from ._core._parking_lot import ParkingLotStatistics
-class StalledLockError(Exception):
- """Raised by :meth:`Lock.acquire` and :meth:`StrictFIFOLock.acquire` if the owner
- exits, or has previously exited, without releasing the lock."""
-
-
@attrs.frozen
class EventStatistics:
"""An object containing debugging information.
@@ -591,7 +586,7 @@ async def acquire(self) -> None:
"""Acquire the lock, blocking if necessary.
Raises:
- StalledLockError: if the owner of the lock exits without releasing.
+ BrokenResourceError: if the owner of the lock exits without releasing.
"""
await trio.lowlevel.checkpoint_if_cancelled()
try:
@@ -603,7 +598,7 @@ async def acquire(self) -> None:
# lock as well.
await self._lot.park()
except trio.BrokenResourceError:
- raise StalledLockError(
+ raise trio.BrokenResourceError(
"Owner of this lock exited without releasing: {self._owner}",
) from None
else:
@@ -788,7 +783,7 @@ async def acquire(self) -> None:
"""Acquire the underlying lock, blocking if necessary.
Raises:
- StalledLockError: if the owner of the lock exits without releasing.
+ BrokenResourceError: if the owner of the lock exits without releasing.
"""
await self._lock.acquire()
@@ -818,7 +813,7 @@ async def wait(self) -> None:
Raises:
RuntimeError: if the calling task does not hold the lock.
- StalledLockError: if the owner of the lock exits without releasing, when attempting to re-acquire.
+ BrokenResourceError: if the owner of the lock exits without releasing, when attempting to re-acquire.
"""
if trio.lowlevel.current_task() is not self._lock._owner:
diff --git a/src/trio/_tests/test_sync.py b/src/trio/_tests/test_sync.py
index 6b4ac97b01..401ab0f55f 100644
--- a/src/trio/_tests/test_sync.py
+++ b/src/trio/_tests/test_sync.py
@@ -600,7 +600,7 @@ async def test_lock_acquire_unowned_lock() -> None:
async with trio.open_nursery() as nursery:
nursery.start_soon(lock.acquire)
with pytest.raises(
- trio.StalledLockError,
+ trio.BrokenResourceError,
match="^Owner of this lock exited without releasing",
):
await lock.acquire()
@@ -612,7 +612,7 @@ async def test_lock_multiple_acquire() -> None:
lock = trio.Lock()
with RaisesGroup(
Matcher(
- trio.StalledLockError,
+ trio.BrokenResourceError,
match="^Owner of this lock exited without releasing",
),
):
From 6a7650df7cdc4dc46608c2bbdcf71d9379dc2313 Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Wed, 18 Sep 2024 12:28:29 +0200
Subject: [PATCH 65/79] duplicate validation logic, bump pyright
---
src/trio/_tests/test_timeouts.py | 4 ++--
src/trio/_timeouts.py | 11 +++++++++--
test-requirements.txt | 2 +-
3 files changed, 12 insertions(+), 5 deletions(-)
diff --git a/src/trio/_tests/test_timeouts.py b/src/trio/_tests/test_timeouts.py
index 27711f0422..515f536c1f 100644
--- a/src/trio/_tests/test_timeouts.py
+++ b/src/trio/_tests/test_timeouts.py
@@ -166,7 +166,7 @@ async def test_timeouts_raise_value_error() -> None:
):
with pytest.raises(
ValueError,
- match="^(duration|deadline|timeout) must (not )*be (non-negative|NaN)$",
+ match="^(deadline|`seconds`) must (not )*be (non-negative|NaN)$",
):
await fun(val)
@@ -180,7 +180,7 @@ async def test_timeouts_raise_value_error() -> None:
):
with pytest.raises(
ValueError,
- match="^(duration|deadline|timeout|relative deadline) must (not )*be (non-negative|NaN)$",
+ match="^(deadline|`seconds`) must (not )*be (non-negative|NaN)$",
):
with cm(val):
pass # pragma: no cover
diff --git a/src/trio/_timeouts.py b/src/trio/_timeouts.py
index 3a56262520..6673389558 100644
--- a/src/trio/_timeouts.py
+++ b/src/trio/_timeouts.py
@@ -1,5 +1,6 @@
from __future__ import annotations
+import math
import sys
from contextlib import contextmanager
from typing import TYPE_CHECKING
@@ -23,6 +24,7 @@ def move_on_at(deadline: float, *, shield: bool = False) -> trio.CancelScope:
ValueError: if deadline is NaN.
"""
+ # CancelScope validates that deadline isn't math.nan
return trio.CancelScope(deadline=deadline, shield=shield)
@@ -42,9 +44,14 @@ def move_on_after(
of the newly created cancel scope.
Raises:
- ValueError: if timeout is less than zero or NaN.
+ ValueError: if `seconds` is less than zero or NaN.
"""
+ # duplicate validation logic to have the correct parameter name
+ if seconds < 0:
+ raise ValueError("`seconds` must be non-negative")
+ if math.isnan(seconds):
+ raise ValueError("`seconds` must not be NaN")
return trio.CancelScope(
shield=shield,
relative_deadline=seconds,
@@ -92,7 +99,7 @@ async def sleep(seconds: float) -> None:
"""
if seconds < 0:
- raise ValueError("duration must be non-negative")
+ raise ValueError("`seconds` must be non-negative")
if seconds == 0:
await trio.lowlevel.checkpoint()
else:
diff --git a/test-requirements.txt b/test-requirements.txt
index 47aea97d8f..1173aff405 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -105,7 +105,7 @@ pylint==3.2.7
# via -r test-requirements.in
pyopenssl==24.2.1
# via -r test-requirements.in
-pyright==1.1.378
+pyright==1.1.381
# via -r test-requirements.in
pytest==8.3.2
# via -r test-requirements.in
From bf12a8011ce4dbb558d2a643cd09f5ccf2a35951 Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Wed, 18 Sep 2024 13:24:40 +0200
Subject: [PATCH 66/79] update docs/newsfragments/docstrings
---
docs/source/conf.py | 1 +
docs/source/reference-core.rst | 4 ++++
newsfragments/2512.breaking.rst | 2 ++
newsfragments/2512.deprecated.rst | 1 -
newsfragments/2512.feature.rst | 2 +-
src/trio/_timeouts.py | 2 +-
6 files changed, 9 insertions(+), 3 deletions(-)
create mode 100644 newsfragments/2512.breaking.rst
delete mode 100644 newsfragments/2512.deprecated.rst
diff --git a/docs/source/conf.py b/docs/source/conf.py
index ecdbdd4701..2e84d91532 100755
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -229,6 +229,7 @@ def setup(app: Sphinx) -> None:
"pyopenssl": ("https://www.pyopenssl.org/en/stable/", None),
"sniffio": ("https://sniffio.readthedocs.io/en/latest/", None),
"trio-util": ("https://trio-util.readthedocs.io/en/latest/", None),
+ "flake8-async": ("https://flake8-async.readthedocs.io/en/latest/", None),
}
# See https://sphinx-hoverxref.readthedocs.io/en/latest/configuration.html
diff --git a/docs/source/reference-core.rst b/docs/source/reference-core.rst
index d696f2b63e..4b023292e9 100644
--- a/docs/source/reference-core.rst
+++ b/docs/source/reference-core.rst
@@ -527,8 +527,12 @@ objects.
.. autoattribute:: deadline
+ .. autoattribute:: relative_deadline
+
.. autoattribute:: shield
+ .. automethod:: is_relative()
+
.. automethod:: cancel()
.. attribute:: cancelled_caught
diff --git a/newsfragments/2512.breaking.rst b/newsfragments/2512.breaking.rst
new file mode 100644
index 0000000000..ef91f684bb
--- /dev/null
+++ b/newsfragments/2512.breaking.rst
@@ -0,0 +1,2 @@
+:func:`trio.move_on_after` and :func:`trio.fail_after` previously set the deadline relative to initialization time, instead of more intuitively upon entering the context manager. This might change timeouts if a program relied on this behavior. If you want to restore previous behavior you should instead use ``trio.move_on_at(trio.current_time() + ...)``.
+flake8-async has a new rule to catch this, in case you're supporting older trio versions. See :ref:`ASYNC121` (TODO: except it will be ASYNC122, I'm just making sure intersphinx work)
diff --git a/newsfragments/2512.deprecated.rst b/newsfragments/2512.deprecated.rst
deleted file mode 100644
index bbb098b1c8..0000000000
--- a/newsfragments/2512.deprecated.rst
+++ /dev/null
@@ -1 +0,0 @@
-:func:`trio.move_on_after` and :func:`trio.fail_after` previously set the deadline relative to initialization time, instead of more intuitively upon entering the context manager. Any code where these two points in time do not line up (according to :func:`trio.current_time`) will now issue a `DeprecationWarning`. To silence the warning and adopt the new behaviour, pass ``timeout_from_enter = True``. For more information see :ref:`saved_relative_timeouts`.
diff --git a/newsfragments/2512.feature.rst b/newsfragments/2512.feature.rst
index d30c891961..fe9197cde5 100644
--- a/newsfragments/2512.feature.rst
+++ b/newsfragments/2512.feature.rst
@@ -1 +1 @@
-:func:`trio.move_on_after` and :func:`trio.fail_after` now allows setting the deadline to be relative to entering the context manager. This will be the default in the future, and to opt into the new behaviour you can pass ``timeout_from_enter=True``. This does not matter in the vast majority of cases where initialization and entering of the context manager is in the same place, and those cases will not raise any :class:`DeprecationWarning`. For more information see :ref:`saved_relative_timeouts`.
+:meth:`CancelScope.relative_deadline` and :meth:`CancelScope.is_relative` added, as well as a ``relative_deadline`` parameter to ``__init__``. This allows initializing scopes ahead of time, but where the specified relative deadline doesn't count down until the scope is entered.
diff --git a/src/trio/_timeouts.py b/src/trio/_timeouts.py
index 6673389558..1a5e2e1db3 100644
--- a/src/trio/_timeouts.py
+++ b/src/trio/_timeouts.py
@@ -44,7 +44,7 @@ def move_on_after(
of the newly created cancel scope.
Raises:
- ValueError: if `seconds` is less than zero or NaN.
+ ValueError: if ``seconds`` is less than zero or NaN.
"""
# duplicate validation logic to have the correct parameter name
From 45f78f46e636e6672179292fe69f5f39fb87e2d9 Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Thu, 19 Sep 2024 12:30:41 +0200
Subject: [PATCH 67/79] break lots before other checks, minor phrasing
improvement in docstring
---
src/trio/_core/_run.py | 11 +++++------
src/trio/_sync.py | 2 +-
2 files changed, 6 insertions(+), 7 deletions(-)
diff --git a/src/trio/_core/_run.py b/src/trio/_core/_run.py
index 99a4551696..3ea7c269b3 100644
--- a/src/trio/_core/_run.py
+++ b/src/trio/_core/_run.py
@@ -1821,6 +1821,11 @@ async def python_wrapper(orig_coro: Awaitable[RetT]) -> RetT:
return task
def task_exited(self, task: Task, outcome: Outcome[Any]) -> None:
+ if task in GLOBAL_PARKING_LOT_BREAKER:
+ for lot in GLOBAL_PARKING_LOT_BREAKER[task]:
+ lot.break_lot(task)
+ del GLOBAL_PARKING_LOT_BREAKER[task]
+
if (
task._cancel_status is not None
and task._cancel_status.abandoned_by_misnesting
@@ -1860,12 +1865,6 @@ def task_exited(self, task: Task, outcome: Outcome[Any]) -> None:
assert task._parent_nursery is not None, task
task._parent_nursery._child_finished(task, outcome)
- # before or after the other stuff in this function?
- if task in GLOBAL_PARKING_LOT_BREAKER:
- for lot in GLOBAL_PARKING_LOT_BREAKER[task]:
- lot.break_lot(task)
- del GLOBAL_PARKING_LOT_BREAKER[task]
-
if "task_exited" in self.instruments:
self.instruments.call("task_exited", task)
diff --git a/src/trio/_sync.py b/src/trio/_sync.py
index 3d7d28914b..1b72d7ec76 100644
--- a/src/trio/_sync.py
+++ b/src/trio/_sync.py
@@ -783,7 +783,7 @@ async def acquire(self) -> None:
"""Acquire the underlying lock, blocking if necessary.
Raises:
- BrokenResourceError: if the owner of the lock exits without releasing.
+ BrokenResourceError: if the owner of the underlying lock exits without releasing.
"""
await self._lock.acquire()
From 9961abcef5a65dd3ed4f849f880337a5d827de0f Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Thu, 19 Sep 2024 13:03:27 +0200
Subject: [PATCH 68/79] properly link to ASYNC122
---
newsfragments/2512.breaking.rst | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/newsfragments/2512.breaking.rst b/newsfragments/2512.breaking.rst
index ef91f684bb..f9a6b42631 100644
--- a/newsfragments/2512.breaking.rst
+++ b/newsfragments/2512.breaking.rst
@@ -1,2 +1,2 @@
:func:`trio.move_on_after` and :func:`trio.fail_after` previously set the deadline relative to initialization time, instead of more intuitively upon entering the context manager. This might change timeouts if a program relied on this behavior. If you want to restore previous behavior you should instead use ``trio.move_on_at(trio.current_time() + ...)``.
-flake8-async has a new rule to catch this, in case you're supporting older trio versions. See :ref:`ASYNC121` (TODO: except it will be ASYNC122, I'm just making sure intersphinx work)
+flake8-async has a new rule to catch this, in case you're supporting older trio versions. See :ref:`ASYNC122`.
From 4e979bfef4aac8c756fd4c6c521977cfcfdde1df Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
<66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Mon, 23 Sep 2024 19:56:30 -0400
Subject: [PATCH 69/79] [pre-commit.ci] pre-commit autoupdate (#3093)
---
.pre-commit-config.yaml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 0c2a7c54eb..ef1e6136ec 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -24,7 +24,7 @@ repos:
hooks:
- id: black
- repo: https://github.com/astral-sh/ruff-pre-commit
- rev: v0.6.5
+ rev: v0.6.7
hooks:
- id: ruff
types: [file]
From ec488633c597f84c4b0c952a82bbb80aa0222097 Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Fri, 27 Sep 2024 12:57:26 +0200
Subject: [PATCH 70/79] docstring updates after A5rocks review
---
src/trio/_core/_parking_lot.py | 16 ++++++++--------
src/trio/_core/_run.py | 1 +
src/trio/_core/_tests/test_parking_lot.py | 4 +++-
src/trio/_sync.py | 12 +++++++++---
src/trio/_tests/test_sync.py | 4 +++-
5 files changed, 24 insertions(+), 13 deletions(-)
diff --git a/src/trio/_core/_parking_lot.py b/src/trio/_core/_parking_lot.py
index e7be7913e7..eaf9225c2c 100644
--- a/src/trio/_core/_parking_lot.py
+++ b/src/trio/_core/_parking_lot.py
@@ -92,11 +92,7 @@
def add_parking_lot_breaker(task: Task, lot: ParkingLot) -> None:
- """Register a task as a breaker for a lot. If this task exits without being removed
- as a breaker, the lot will break. This will cause an error to be raised for all
- tasks currently parked in the lot, as well as any future tasks that attempt to
- park in it.
- """
+ """Register a task as a breaker for a lot. See :meth:`ParkingLot.break_lot`"""
if task not in GLOBAL_PARKING_LOT_BREAKER:
GLOBAL_PARKING_LOT_BREAKER[task] = [lot]
else:
@@ -104,7 +100,7 @@ def add_parking_lot_breaker(task: Task, lot: ParkingLot) -> None:
def remove_parking_lot_breaker(task: Task, lot: ParkingLot) -> None:
- """Deregister a task as a breaker for a lot. See :func:`add_parking_lot_breaker`."""
+ """Deregister a task as a breaker for a lot. See :meth:`ParkingLot.break_lot`"""
try:
GLOBAL_PARKING_LOT_BREAKER[task].remove(lot)
except (KeyError, ValueError):
@@ -273,10 +269,14 @@ def repark_all(self, new_lot: ParkingLot) -> None:
return self.repark(new_lot, count=len(self))
def break_lot(self, task: Task | None = None) -> None:
- """Break this lot, causing all parked tasks to raise an error, and any
+ """Break this lot, with ``task`` noted as the task that broke it.
+
+ This causes all parked tasks to raise an error, and any
future tasks attempting to park to error. Unpark & repark become no-ops as the
parking lot is empty.
- The error raised contains a reference to the task sent as a parameter.
+
+ The error raised contains a reference to the task sent as a parameter. It is also
+ saved in the ``broken_by`` attribute.
"""
if task is None:
task = _core.current_task()
diff --git a/src/trio/_core/_run.py b/src/trio/_core/_run.py
index 3ea7c269b3..4fa651313e 100644
--- a/src/trio/_core/_run.py
+++ b/src/trio/_core/_run.py
@@ -1821,6 +1821,7 @@ async def python_wrapper(orig_coro: Awaitable[RetT]) -> RetT:
return task
def task_exited(self, task: Task, outcome: Outcome[Any]) -> None:
+ # break parking lots associated with the task exiting
if task in GLOBAL_PARKING_LOT_BREAKER:
for lot in GLOBAL_PARKING_LOT_BREAKER[task]:
lot.break_lot(task)
diff --git a/src/trio/_core/_tests/test_parking_lot.py b/src/trio/_core/_tests/test_parking_lot.py
index 59c4a1a4c2..8cfaf84b24 100644
--- a/src/trio/_core/_tests/test_parking_lot.py
+++ b/src/trio/_core/_tests/test_parking_lot.py
@@ -310,7 +310,9 @@ async def return_me_and_park(
await lot.park()
lot = ParkingLot()
- with RaisesGroup(Matcher(_core.BrokenResourceError, match="Parking lot broken by")):
+ with RaisesGroup(
+ Matcher(_core.BrokenResourceError, match="^Parking lot broken by"),
+ ):
async with _core.open_nursery() as nursery:
task = await nursery.start(return_me_and_park, lot)
lot.break_lot(task)
diff --git a/src/trio/_sync.py b/src/trio/_sync.py
index 1b72d7ec76..03d518aab9 100644
--- a/src/trio/_sync.py
+++ b/src/trio/_sync.py
@@ -8,8 +8,14 @@
import trio
from . import _core
-from ._core import Abort, ParkingLot, RaiseCancelT, enable_ki_protection
-from ._core._parking_lot import add_parking_lot_breaker, remove_parking_lot_breaker
+from ._core import (
+ Abort,
+ ParkingLot,
+ RaiseCancelT,
+ add_parking_lot_breaker,
+ enable_ki_protection,
+ remove_parking_lot_breaker,
+)
from ._util import final
if TYPE_CHECKING:
@@ -599,7 +605,7 @@ async def acquire(self) -> None:
await self._lot.park()
except trio.BrokenResourceError:
raise trio.BrokenResourceError(
- "Owner of this lock exited without releasing: {self._owner}",
+ f"Owner of this lock exited without releasing: {self._owner}",
) from None
else:
await trio.lowlevel.cancel_shielded_checkpoint()
diff --git a/src/trio/_tests/test_sync.py b/src/trio/_tests/test_sync.py
index 401ab0f55f..1a0b230e3e 100644
--- a/src/trio/_tests/test_sync.py
+++ b/src/trio/_tests/test_sync.py
@@ -593,7 +593,7 @@ async def lock_taker() -> None:
async def test_lock_acquire_unowned_lock() -> None:
"""Test that trying to acquire a lock whose owner has exited raises an error.
- Partial fix for https://github.com/python-trio/trio/issues/3035
+ see https://github.com/python-trio/trio/issues/3035
"""
assert not GLOBAL_PARKING_LOT_BREAKER
lock = trio.Lock()
@@ -608,6 +608,8 @@ async def test_lock_acquire_unowned_lock() -> None:
async def test_lock_multiple_acquire() -> None:
+ """Test for error if awaiting on a lock whose owner exits without releasing.
+ see https://github.com/python-trio/trio/issues/3035"""
assert not GLOBAL_PARKING_LOT_BREAKER
lock = trio.Lock()
with RaisesGroup(
From f508bb4e4f060eb178b85e468db9c97f37497996 Mon Sep 17 00:00:00 2001
From: Redoubts
Date: Mon, 30 Sep 2024 18:09:49 -0400
Subject: [PATCH 71/79] trio.sleep_forever should be NoReturn (#3096)
* NoReturn
* rst
* fix "implicit return"
* TeamSpen210 rc
* Add a test for the error
---------
Co-authored-by: Spencer Brown
---
newsfragments/3095.bugfix.rst | 1 +
src/trio/_tests/test_timeouts.py | 14 ++++++++++++++
src/trio/_timeouts.py | 5 +++--
3 files changed, 18 insertions(+), 2 deletions(-)
create mode 100644 newsfragments/3095.bugfix.rst
diff --git a/newsfragments/3095.bugfix.rst b/newsfragments/3095.bugfix.rst
new file mode 100644
index 0000000000..7a7d756e5b
--- /dev/null
+++ b/newsfragments/3095.bugfix.rst
@@ -0,0 +1 @@
+Update :func:`trio.sleep_forever` to be `NoReturn`.
diff --git a/src/trio/_tests/test_timeouts.py b/src/trio/_tests/test_timeouts.py
index 515f536c1f..574e1db1a4 100644
--- a/src/trio/_tests/test_timeouts.py
+++ b/src/trio/_tests/test_timeouts.py
@@ -86,6 +86,20 @@ async def sleep_3() -> None:
await check_takes_about(sleep_3, TARGET)
+async def test_cannot_wake_sleep_forever() -> None:
+ # Test an error occurs if you manually wake sleep_forever().
+ task = trio.lowlevel.current_task()
+
+ async def wake_task() -> None:
+ await trio.lowlevel.checkpoint()
+ trio.lowlevel.reschedule(task, outcome.Value(None))
+
+ async with trio.open_nursery() as nursery:
+ nursery.start_soon(wake_task)
+ with pytest.raises(RuntimeError):
+ await trio.sleep_forever()
+
+
class TimeoutScope(Protocol):
def __call__(self, seconds: float, *, shield: bool) -> trio.CancelScope: ...
diff --git a/src/trio/_timeouts.py b/src/trio/_timeouts.py
index 1a5e2e1db3..417f1818fa 100644
--- a/src/trio/_timeouts.py
+++ b/src/trio/_timeouts.py
@@ -3,7 +3,7 @@
import math
import sys
from contextlib import contextmanager
-from typing import TYPE_CHECKING
+from typing import TYPE_CHECKING, NoReturn
import trio
@@ -58,13 +58,14 @@ def move_on_after(
)
-async def sleep_forever() -> None:
+async def sleep_forever() -> NoReturn:
"""Pause execution of the current task forever (or until cancelled).
Equivalent to calling ``await sleep(math.inf)``.
"""
await trio.lowlevel.wait_task_rescheduled(lambda _: trio.lowlevel.Abort.SUCCEEDED)
+ raise RuntimeError("Should never have been rescheduled!")
async def sleep_until(deadline: float) -> None:
From 498325d4d744661814c94899d37c39767c59430a Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Tue, 1 Oct 2024 00:21:51 +0000
Subject: [PATCH 72/79] Dependency updates (#3100)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
---
.pre-commit-config.yaml | 2 +-
docs-requirements.txt | 10 +++++-----
test-requirements.txt | 31 ++++++++++++++++---------------
3 files changed, 22 insertions(+), 21 deletions(-)
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index ef1e6136ec..321386c997 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -24,7 +24,7 @@ repos:
hooks:
- id: black
- repo: https://github.com/astral-sh/ruff-pre-commit
- rev: v0.6.7
+ rev: v0.6.8
hooks:
- id: ruff
types: [file]
diff --git a/docs-requirements.txt b/docs-requirements.txt
index aaf3fad2c7..fa9163713a 100644
--- a/docs-requirements.txt
+++ b/docs-requirements.txt
@@ -12,7 +12,7 @@ beautifulsoup4==4.12.3
# via sphinx-codeautolink
certifi==2024.8.30
# via requests
-cffi==1.17.0 ; platform_python_implementation != 'PyPy' or os_name == 'nt'
+cffi==1.17.1 ; platform_python_implementation != 'PyPy' or os_name == 'nt'
# via
# -r docs-requirements.in
# cryptography
@@ -24,7 +24,7 @@ colorama==0.4.6 ; sys_platform == 'win32' or platform_system == 'Windows'
# via
# click
# sphinx
-cryptography==43.0.0
+cryptography==43.0.1
# via pyopenssl
docutils==0.20.1
# via
@@ -32,7 +32,7 @@ docutils==0.20.1
# sphinx-rtd-theme
exceptiongroup==1.2.2
# via -r docs-requirements.in
-idna==3.8
+idna==3.10
# via
# -r docs-requirements.in
# requests
@@ -77,7 +77,7 @@ sphinx==7.4.7
# sphinxcontrib-trio
sphinx-codeautolink==0.15.2
# via -r docs-requirements.in
-sphinx-hoverxref==1.4.0
+sphinx-hoverxref==1.4.1
# via -r docs-requirements.in
sphinx-rtd-theme==2.0.0
# via -r docs-requirements.in
@@ -102,5 +102,5 @@ sphinxcontrib-trio==1.1.2
# via -r docs-requirements.in
towncrier==24.8.0
# via -r docs-requirements.in
-urllib3==2.2.2
+urllib3==2.2.3
# via requests
diff --git a/test-requirements.txt b/test-requirements.txt
index 1173aff405..a544456455 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -18,7 +18,7 @@ black==24.8.0 ; implementation_name == 'cpython'
# via -r test-requirements.in
certifi==2024.8.30
# via requests
-cffi==1.17.0 ; platform_python_implementation != 'PyPy' or os_name == 'nt'
+cffi==1.17.1 ; platform_python_implementation != 'PyPy' or os_name == 'nt'
# via
# -r test-requirements.in
# cryptography
@@ -36,13 +36,13 @@ colorama==0.4.6 ; (implementation_name != 'cpython' and sys_platform == 'win32')
# sphinx
coverage==7.6.1
# via -r test-requirements.in
-cryptography==43.0.0
+cryptography==43.0.1
# via
# -r test-requirements.in
# pyopenssl
# trustme
# types-pyopenssl
-dill==0.3.8
+dill==0.3.9
# via pylint
docutils==0.20.1
# via sphinx
@@ -50,14 +50,14 @@ exceptiongroup==1.2.2 ; python_full_version < '3.11'
# via
# -r test-requirements.in
# pytest
-idna==3.8
+idna==3.10
# via
# -r test-requirements.in
# requests
# trustme
imagesize==1.4.1
# via sphinx
-importlib-metadata==8.4.0 ; python_full_version < '3.10'
+importlib-metadata==8.5.0 ; python_full_version < '3.10'
# via sphinx
iniconfig==2.0.0
# via pytest
@@ -91,7 +91,7 @@ parso==0.8.4 ; implementation_name == 'cpython'
# via jedi
pathspec==0.12.1 ; implementation_name == 'cpython'
# via black
-platformdirs==4.2.2
+platformdirs==4.3.6
# via
# black
# pylint
@@ -105,15 +105,15 @@ pylint==3.2.7
# via -r test-requirements.in
pyopenssl==24.2.1
# via -r test-requirements.in
-pyright==1.1.381
+pyright==1.1.382.post1
# via -r test-requirements.in
-pytest==8.3.2
+pytest==8.3.3
# via -r test-requirements.in
-pytz==2024.1 ; python_full_version < '3.9'
+pytz==2024.2 ; python_full_version < '3.9'
# via babel
requests==2.32.3
# via sphinx
-ruff==0.6.3
+ruff==0.6.8
# via -r test-requirements.in
sniffio==1.3.1
# via -r test-requirements.in
@@ -149,11 +149,11 @@ types-cffi==1.16.0.20240331
# via
# -r test-requirements.in
# types-pyopenssl
-types-docutils==0.21.0.20240724
+types-docutils==0.21.0.20240907
# via -r test-requirements.in
types-pyopenssl==24.1.0.20240722
# via -r test-requirements.in
-types-setuptools==74.0.0.20240831
+types-setuptools==75.1.0.20240917
# via types-cffi
typing-extensions==4.12.2
# via
@@ -162,9 +162,10 @@ typing-extensions==4.12.2
# black
# mypy
# pylint
-urllib3==2.2.2
+ # pyright
+urllib3==2.2.3
# via requests
-uv==0.4.1
+uv==0.4.17
# via -r test-requirements.in
-zipp==3.20.1 ; python_full_version < '3.10'
+zipp==3.20.2 ; python_full_version < '3.10'
# via importlib-metadata
From 7a1ce5b755b5775153a3525bea793ab7da05fb44 Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Wed, 2 Oct 2024 12:41:47 +0200
Subject: [PATCH 73/79] raise brokenresourceerror if registering an already
exited task. fix docstring. fix runtimewarning transforming into
triointernalerror. add a bunch of tests
---
src/trio/_core/_parking_lot.py | 15 +++-
src/trio/_core/_run.py | 2 +
src/trio/_core/_tests/test_parking_lot.py | 100 ++++++++++++++++++----
3 files changed, 96 insertions(+), 21 deletions(-)
diff --git a/src/trio/_core/_parking_lot.py b/src/trio/_core/_parking_lot.py
index eaf9225c2c..3f2edc5bac 100644
--- a/src/trio/_core/_parking_lot.py
+++ b/src/trio/_core/_parking_lot.py
@@ -71,6 +71,7 @@
# See: https://github.com/python-trio/trio/issues/53
from __future__ import annotations
+import inspect
import math
import warnings
from collections import OrderedDict
@@ -92,7 +93,15 @@
def add_parking_lot_breaker(task: Task, lot: ParkingLot) -> None:
- """Register a task as a breaker for a lot. See :meth:`ParkingLot.break_lot`"""
+ """Register a task as a breaker for a lot. See :meth:`ParkingLot.break_lot`.
+
+ raises:
+ trio.BrokenResourceError: if the task has already exited.
+ """
+ if inspect.getcoroutinestate(task.coro) == inspect.CORO_CLOSED:
+ raise _core._exceptions.BrokenResourceError(
+ "Attempted to add already exited task as lot breaker.",
+ )
if task not in GLOBAL_PARKING_LOT_BREAKER:
GLOBAL_PARKING_LOT_BREAKER[task] = [lot]
else:
@@ -275,8 +284,8 @@ def break_lot(self, task: Task | None = None) -> None:
future tasks attempting to park to error. Unpark & repark become no-ops as the
parking lot is empty.
- The error raised contains a reference to the task sent as a parameter. It is also
- saved in the ``broken_by`` attribute.
+ The error raised contains a reference to the task sent as a parameter. The task
+ is also saved in the parking lot in the ``broken_by`` attribute.
"""
if task is None:
task = _core.current_task()
diff --git a/src/trio/_core/_run.py b/src/trio/_core/_run.py
index defbaa6a80..962a649cd3 100644
--- a/src/trio/_core/_run.py
+++ b/src/trio/_core/_run.py
@@ -2800,6 +2800,8 @@ def unrolled_run(
),
stacklevel=1,
)
+ except RuntimeWarning:
+ raise
except TrioInternalError:
raise
except BaseException as exc:
diff --git a/src/trio/_core/_tests/test_parking_lot.py b/src/trio/_core/_tests/test_parking_lot.py
index 8cfaf84b24..9ad4ac693b 100644
--- a/src/trio/_core/_tests/test_parking_lot.py
+++ b/src/trio/_core/_tests/test_parking_lot.py
@@ -4,7 +4,12 @@
import pytest
-import trio.lowlevel
+import trio
+from trio.lowlevel import (
+ add_parking_lot_breaker,
+ current_task,
+ remove_parking_lot_breaker,
+)
from trio.testing import Matcher, RaisesGroup
from ... import _core
@@ -220,27 +225,34 @@ async def test_parking_lot_repark_with_count() -> None:
lot1.unpark_all()
+async def dummy_task(
+ task_status: _core.TaskStatus[_core.Task] = trio.TASK_STATUS_IGNORED,
+) -> None:
+ task_status.started(_core.current_task())
+ await trio.sleep_forever()
+
+
async def test_parking_lot_breaker_basic() -> None:
lot = ParkingLot()
- task = trio.lowlevel.current_task()
+ task = current_task()
with pytest.raises(
RuntimeError,
match="Attempted to remove task as breaker for a lot it is not registered for",
):
- trio.lowlevel.remove_parking_lot_breaker(task, lot)
+ remove_parking_lot_breaker(task, lot)
# check that a task can be registered as breaker for the same lot multiple times
- trio.lowlevel.add_parking_lot_breaker(task, lot)
- trio.lowlevel.add_parking_lot_breaker(task, lot)
- trio.lowlevel.remove_parking_lot_breaker(task, lot)
- trio.lowlevel.remove_parking_lot_breaker(task, lot)
+ add_parking_lot_breaker(task, lot)
+ add_parking_lot_breaker(task, lot)
+ remove_parking_lot_breaker(task, lot)
+ remove_parking_lot_breaker(task, lot)
with pytest.raises(
RuntimeError,
match="Attempted to remove task as breaker for a lot it is not registered for",
):
- trio.lowlevel.remove_parking_lot_breaker(task, lot)
+ remove_parking_lot_breaker(task, lot)
# defaults to current task
lot.break_lot()
@@ -249,30 +261,81 @@ async def test_parking_lot_breaker_basic() -> None:
# breaking the lot again with the same task is a no-op
lot.break_lot()
- # but with a different task it gives a warning
- async def dummy_task(
- task_status: _core.TaskStatus[_core.Task] = trio.TASK_STATUS_IGNORED,
- ) -> None:
- task_status.started(_core.current_task())
+ child_task = None
+ with pytest.warns(RuntimeWarning):
+ async with trio.open_nursery() as nursery:
+ child_task = await nursery.start(dummy_task)
+ # registering a task as breaker on an already broken lot is fine... though it
+ # maybe shouldn't be as it will always cause a RuntimeWarning???
+ # Or is this a sign that we shouldn't raise a warning?
+ add_parking_lot_breaker(child_task, lot)
+ nursery.cancel_scope.cancel()
+
+ # manually breaking a lot with an already exited task is fine
+ lot = ParkingLot()
+ lot.break_lot(child_task)
+
+
+async def test_parking_lot_breaker_warnings() -> None:
+ lot = ParkingLot()
+ task = current_task()
+ lot.break_lot()
+ warn_str = "attempted to break parking .* already broken by .*"
+ # breaking an already broken lot with a different task gives a warning
# The nursery is only to create a task we can pass to lot.break_lot
- # and has no effect on the test otherwise.
async with trio.open_nursery() as nursery:
child_task = await nursery.start(dummy_task)
with pytest.warns(
RuntimeWarning,
- match="attempted to break parking .* already broken by .*",
+ match=warn_str,
):
lot.break_lot(child_task)
nursery.cancel_scope.cancel()
+ # note that this get put into an exceptiongroup if inside a nursery, making any
+ # stacklevel arguments irrelevant
+ with RaisesGroup(Matcher(RuntimeWarning, match=warn_str)):
+ async with trio.open_nursery() as nursery:
+ child_task = await nursery.start(dummy_task)
+ lot.break_lot(child_task)
+ nursery.cancel_scope.cancel()
+
# and doesn't change broken_by
assert lot.broken_by == task
+ # register multiple tasks as lot breakers, then have them all exit
+ lot = ParkingLot()
+ child_task = None
+ # This does not give an exception group, as the warning is raised by the nursery
+ # exiting, and not any of the tasks inside the nursery.
+ # And we only get a single warning because... of the default warning filter? and the
+ # location being the same? (because I haven't figured out why stacklevel makes no
+ # difference)
+ with pytest.warns(
+ RuntimeWarning,
+ match=warn_str,
+ ):
+ async with trio.open_nursery() as nursery:
+ child_task = await nursery.start(dummy_task)
+ child_task2 = await nursery.start(dummy_task)
+ child_task3 = await nursery.start(dummy_task)
+ add_parking_lot_breaker(child_task, lot)
+ add_parking_lot_breaker(child_task2, lot)
+ add_parking_lot_breaker(child_task3, lot)
+ nursery.cancel_scope.cancel()
+
+ # trying to register an exited task as lot breaker errors
+ with pytest.raises(
+ trio.BrokenResourceError,
+ match="^Attempted to add already exited task as lot breaker.$",
+ ):
+ add_parking_lot_breaker(child_task, lot)
+
async def test_parking_lot_breaker() -> None:
async def bad_parker(lot: ParkingLot, scope: _core.CancelScope) -> None:
- trio.lowlevel.add_parking_lot_breaker(trio.lowlevel.current_task(), lot)
+ add_parking_lot_breaker(current_task(), lot)
with scope:
await trio.sleep_forever()
@@ -298,8 +361,9 @@ async def bad_parker(lot: ParkingLot, scope: _core.CancelScope) -> None:
async def test_parking_lot_weird() -> None:
- """break a parking lot, where the breakee is parked. Doing this is weird, but should probably be supported??
- Although the message makes less sense"""
+ """Break a parking lot, where the breakee is parked.
+ Doing this is weird, but should probably be supported.
+ """
async def return_me_and_park(
lot: ParkingLot,
From 5d9067b775d5c19ccba866ac9806a0976cd895c1 Mon Sep 17 00:00:00 2001
From: EXPLOSION
Date: Mon, 7 Oct 2024 03:44:16 -0400
Subject: [PATCH 74/79] Add documentation for `statistics()` and `aclose()` on
memory channels (#3101)
* Add documentation to document members
* Clean up copy-paste
* Mark new pyright checktypes error
---
docs/source/reference-core.rst | 2 ++
newsfragments/3101.doc.rst | 1 +
src/trio/__init__.py | 1 +
src/trio/_channel.py | 20 ++++++++++++++-----
src/trio/_tests/_check_type_completeness.json | 7 ++-----
5 files changed, 21 insertions(+), 10 deletions(-)
create mode 100644 newsfragments/3101.doc.rst
diff --git a/docs/source/reference-core.rst b/docs/source/reference-core.rst
index 4b023292e9..a9bb3909d9 100644
--- a/docs/source/reference-core.rst
+++ b/docs/source/reference-core.rst
@@ -1241,6 +1241,8 @@ more features beyond the core channel interface:
.. autoclass:: MemoryReceiveChannel
:members:
+.. autoclass:: MemoryChannelStatistics
+ :members:
A simple channel example
++++++++++++++++++++++++
diff --git a/newsfragments/3101.doc.rst b/newsfragments/3101.doc.rst
new file mode 100644
index 0000000000..cd3035844e
--- /dev/null
+++ b/newsfragments/3101.doc.rst
@@ -0,0 +1 @@
+Add docstrings for memory channels' ``statistics()`` and ``aclose`` methods.
diff --git a/src/trio/__init__.py b/src/trio/__init__.py
index d2151677b1..34fda84525 100644
--- a/src/trio/__init__.py
+++ b/src/trio/__init__.py
@@ -25,6 +25,7 @@
# Submodules imported by default
from . import abc, from_thread, lowlevel, socket, to_thread
from ._channel import (
+ MemoryChannelStatistics as MemoryChannelStatistics,
MemoryReceiveChannel as MemoryReceiveChannel,
MemorySendChannel as MemorySendChannel,
open_memory_channel as open_memory_channel,
diff --git a/src/trio/_channel.py b/src/trio/_channel.py
index 8a7dba140c..41a7fc6824 100644
--- a/src/trio/_channel.py
+++ b/src/trio/_channel.py
@@ -111,7 +111,7 @@ def __init__(self, max_buffer_size: int | float): # noqa: PYI041
@attrs.frozen
-class MemoryChannelStats:
+class MemoryChannelStatistics:
current_buffer_used: int
max_buffer_size: int | float
open_send_channels: int
@@ -132,8 +132,8 @@ class MemoryChannelState(Generic[T]):
# {task: None}
receive_tasks: OrderedDict[Task, None] = attrs.Factory(OrderedDict)
- def statistics(self) -> MemoryChannelStats:
- return MemoryChannelStats(
+ def statistics(self) -> MemoryChannelStatistics:
+ return MemoryChannelStatistics(
current_buffer_used=len(self.data),
max_buffer_size=self.max_buffer_size,
open_send_channels=self.open_send_channels,
@@ -159,7 +159,9 @@ def __attrs_post_init__(self) -> None:
def __repr__(self) -> str:
return f""
- def statistics(self) -> MemoryChannelStats:
+ def statistics(self) -> MemoryChannelStatistics:
+ """Returns a `MemoryChannelStatistics` for the memory channel this is
+ associated with."""
# XX should we also report statistics specific to this object?
return self._state.statistics()
@@ -282,6 +284,9 @@ def close(self) -> None:
@enable_ki_protection
async def aclose(self) -> None:
+ """Close this send channel object asynchronously.
+
+ See `MemorySendChannel.close`."""
self.close()
await trio.lowlevel.checkpoint()
@@ -296,7 +301,9 @@ class MemoryReceiveChannel(ReceiveChannel[ReceiveType], metaclass=NoPublicConstr
def __attrs_post_init__(self) -> None:
self._state.open_receive_channels += 1
- def statistics(self) -> MemoryChannelStats:
+ def statistics(self) -> MemoryChannelStatistics:
+ """Returns a `MemoryChannelStatistics` for the memory channel this is
+ associated with."""
return self._state.statistics()
def __repr__(self) -> str:
@@ -430,5 +437,8 @@ def close(self) -> None:
@enable_ki_protection
async def aclose(self) -> None:
+ """Close this receive channel object asynchronously.
+
+ See `MemoryReceiveChannel.close`."""
self.close()
await trio.lowlevel.checkpoint()
diff --git a/src/trio/_tests/_check_type_completeness.json b/src/trio/_tests/_check_type_completeness.json
index 0bbd47fada..badb7cba17 100644
--- a/src/trio/_tests/_check_type_completeness.json
+++ b/src/trio/_tests/_check_type_completeness.json
@@ -20,13 +20,10 @@
"all": [
"No docstring found for class \"trio.MemoryReceiveChannel\"",
"No docstring found for class \"trio._channel.MemoryReceiveChannel\"",
- "No docstring found for function \"trio._channel.MemoryReceiveChannel.statistics\"",
- "No docstring found for class \"trio._channel.MemoryChannelStats\"",
- "No docstring found for function \"trio._channel.MemoryReceiveChannel.aclose\"",
+ "No docstring found for class \"trio.MemoryChannelStatistics\"",
+ "No docstring found for class \"trio._channel.MemoryChannelStatistics\"",
"No docstring found for class \"trio.MemorySendChannel\"",
"No docstring found for class \"trio._channel.MemorySendChannel\"",
- "No docstring found for function \"trio._channel.MemorySendChannel.statistics\"",
- "No docstring found for function \"trio._channel.MemorySendChannel.aclose\"",
"No docstring found for class \"trio._core._run.Task\"",
"No docstring found for class \"trio._socket.SocketType\"",
"No docstring found for function \"trio._highlevel_socket.SocketStream.send_all\"",
From cc97cca24fc52f73d18581cbf6bd77fceeb3dd6f Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Mon, 7 Oct 2024 14:52:06 +0200
Subject: [PATCH 75/79] remove warning on task exit
---
src/trio/_core/_run.py | 5 +--
src/trio/_core/_tests/test_parking_lot.py | 49 +++++++----------------
2 files changed, 17 insertions(+), 37 deletions(-)
diff --git a/src/trio/_core/_run.py b/src/trio/_core/_run.py
index 962a649cd3..871016963c 100644
--- a/src/trio/_core/_run.py
+++ b/src/trio/_core/_run.py
@@ -1900,7 +1900,8 @@ def task_exited(self, task: Task, outcome: Outcome[Any]) -> None:
# break parking lots associated with the task exiting
if task in GLOBAL_PARKING_LOT_BREAKER:
for lot in GLOBAL_PARKING_LOT_BREAKER[task]:
- lot.break_lot(task)
+ if lot.broken_by is None:
+ lot.break_lot(task)
del GLOBAL_PARKING_LOT_BREAKER[task]
if (
@@ -2800,8 +2801,6 @@ def unrolled_run(
),
stacklevel=1,
)
- except RuntimeWarning:
- raise
except TrioInternalError:
raise
except BaseException as exc:
diff --git a/src/trio/_core/_tests/test_parking_lot.py b/src/trio/_core/_tests/test_parking_lot.py
index 9ad4ac693b..b8cb97712f 100644
--- a/src/trio/_core/_tests/test_parking_lot.py
+++ b/src/trio/_core/_tests/test_parking_lot.py
@@ -261,15 +261,12 @@ async def test_parking_lot_breaker_basic() -> None:
# breaking the lot again with the same task is a no-op
lot.break_lot()
+ # registering a task as a breaker on an already broken lot is a no-op.
child_task = None
- with pytest.warns(RuntimeWarning):
- async with trio.open_nursery() as nursery:
- child_task = await nursery.start(dummy_task)
- # registering a task as breaker on an already broken lot is fine... though it
- # maybe shouldn't be as it will always cause a RuntimeWarning???
- # Or is this a sign that we shouldn't raise a warning?
- add_parking_lot_breaker(child_task, lot)
- nursery.cancel_scope.cancel()
+ async with trio.open_nursery() as nursery:
+ child_task = await nursery.start(dummy_task)
+ add_parking_lot_breaker(child_task, lot)
+ nursery.cancel_scope.cancel()
# manually breaking a lot with an already exited task is fine
lot = ParkingLot()
@@ -293,37 +290,21 @@ async def test_parking_lot_breaker_warnings() -> None:
lot.break_lot(child_task)
nursery.cancel_scope.cancel()
- # note that this get put into an exceptiongroup if inside a nursery, making any
- # stacklevel arguments irrelevant
- with RaisesGroup(Matcher(RuntimeWarning, match=warn_str)):
- async with trio.open_nursery() as nursery:
- child_task = await nursery.start(dummy_task)
- lot.break_lot(child_task)
- nursery.cancel_scope.cancel()
-
# and doesn't change broken_by
assert lot.broken_by == task
# register multiple tasks as lot breakers, then have them all exit
+ # No warning is given on task exit, even if the lot is already broken.
lot = ParkingLot()
child_task = None
- # This does not give an exception group, as the warning is raised by the nursery
- # exiting, and not any of the tasks inside the nursery.
- # And we only get a single warning because... of the default warning filter? and the
- # location being the same? (because I haven't figured out why stacklevel makes no
- # difference)
- with pytest.warns(
- RuntimeWarning,
- match=warn_str,
- ):
- async with trio.open_nursery() as nursery:
- child_task = await nursery.start(dummy_task)
- child_task2 = await nursery.start(dummy_task)
- child_task3 = await nursery.start(dummy_task)
- add_parking_lot_breaker(child_task, lot)
- add_parking_lot_breaker(child_task2, lot)
- add_parking_lot_breaker(child_task3, lot)
- nursery.cancel_scope.cancel()
+ async with trio.open_nursery() as nursery:
+ child_task = await nursery.start(dummy_task)
+ child_task2 = await nursery.start(dummy_task)
+ child_task3 = await nursery.start(dummy_task)
+ add_parking_lot_breaker(child_task, lot)
+ add_parking_lot_breaker(child_task2, lot)
+ add_parking_lot_breaker(child_task3, lot)
+ nursery.cancel_scope.cancel()
# trying to register an exited task as lot breaker errors
with pytest.raises(
@@ -333,7 +314,7 @@ async def test_parking_lot_breaker_warnings() -> None:
add_parking_lot_breaker(child_task, lot)
-async def test_parking_lot_breaker() -> None:
+async def test_parking_lot_breaker_bad_parker() -> None:
async def bad_parker(lot: ParkingLot, scope: _core.CancelScope) -> None:
add_parking_lot_breaker(current_task(), lot)
with scope:
From d0158fa100bf8e065425ee8fb24cb57e6d0ca3d9 Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
<66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Tue, 8 Oct 2024 10:29:40 +0900
Subject: [PATCH 76/79] [pre-commit.ci] pre-commit autoupdate (#3102)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
updates:
- [github.com/pre-commit/pre-commit-hooks: v4.6.0 → v5.0.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.6.0...v5.0.0)
- [github.com/psf/black-pre-commit-mirror: 24.8.0 → 24.10.0](https://github.com/psf/black-pre-commit-mirror/compare/24.8.0...24.10.0)
- [github.com/astral-sh/ruff-pre-commit: v0.6.8 → v0.6.9](https://github.com/astral-sh/ruff-pre-commit/compare/v0.6.8...v0.6.9)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
---
.pre-commit-config.yaml | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 321386c997..209d5e26a2 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -8,7 +8,7 @@ ci:
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
- rev: v4.6.0
+ rev: v5.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
@@ -20,11 +20,11 @@ repos:
- id: sort-simple-yaml
files: .pre-commit-config.yaml
- repo: https://github.com/psf/black-pre-commit-mirror
- rev: 24.8.0
+ rev: 24.10.0
hooks:
- id: black
- repo: https://github.com/astral-sh/ruff-pre-commit
- rev: v0.6.8
+ rev: v0.6.9
hooks:
- id: ruff
types: [file]
From 1d7ece30766c7076bdbac84286870d958769a8ab Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Tue, 8 Oct 2024 12:37:15 +0200
Subject: [PATCH 77/79] make broken_by attribute a list, clean up tests
---
src/trio/_core/_parking_lot.py | 20 ++--
src/trio/_core/_run.py | 3 +-
src/trio/_core/_tests/test_parking_lot.py | 123 +++++++++++++---------
3 files changed, 82 insertions(+), 64 deletions(-)
diff --git a/src/trio/_core/_parking_lot.py b/src/trio/_core/_parking_lot.py
index 3f2edc5bac..af6ff610ee 100644
--- a/src/trio/_core/_parking_lot.py
+++ b/src/trio/_core/_parking_lot.py
@@ -73,7 +73,6 @@
import inspect
import math
-import warnings
from collections import OrderedDict
from typing import TYPE_CHECKING
@@ -152,7 +151,7 @@ class ParkingLot:
# {task: None}, we just want a deque where we can quickly delete random
# items
_parked: OrderedDict[Task, None] = attrs.field(factory=OrderedDict, init=False)
- broken_by: Task | None = None
+ broken_by: list[Task] = attrs.field(factory=list, init=False)
def __len__(self) -> int:
"""Returns the number of parked tasks."""
@@ -176,7 +175,7 @@ async def park(self) -> None:
breaks before we get to unpark.
"""
- if self.broken_by is not None:
+ if self.broken_by:
raise _core.BrokenResourceError(
f"Attempted to park in parking lot broken by {self.broken_by}",
)
@@ -289,16 +288,13 @@ def break_lot(self, task: Task | None = None) -> None:
"""
if task is None:
task = _core.current_task()
- if self.broken_by is not None:
- if self.broken_by != task:
- warnings.warn(
- RuntimeWarning(
- f"{task} attempted to break parking lot {self} already broken by {self.broken_by}",
- ),
- stacklevel=2,
- )
+
+ # if lot is already broken, just mark this as another breaker and return
+ if self.broken_by:
+ self.broken_by.append(task)
return
- self.broken_by = task
+
+ self.broken_by.append(task)
for parked_task in self._parked:
_core.reschedule(
diff --git a/src/trio/_core/_run.py b/src/trio/_core/_run.py
index 871016963c..defbaa6a80 100644
--- a/src/trio/_core/_run.py
+++ b/src/trio/_core/_run.py
@@ -1900,8 +1900,7 @@ def task_exited(self, task: Task, outcome: Outcome[Any]) -> None:
# break parking lots associated with the task exiting
if task in GLOBAL_PARKING_LOT_BREAKER:
for lot in GLOBAL_PARKING_LOT_BREAKER[task]:
- if lot.broken_by is None:
- lot.break_lot(task)
+ lot.break_lot(task)
del GLOBAL_PARKING_LOT_BREAKER[task]
if (
diff --git a/src/trio/_core/_tests/test_parking_lot.py b/src/trio/_core/_tests/test_parking_lot.py
index b8cb97712f..cad3e7b431 100644
--- a/src/trio/_core/_tests/test_parking_lot.py
+++ b/src/trio/_core/_tests/test_parking_lot.py
@@ -1,5 +1,6 @@
from __future__ import annotations
+import re
from typing import TypeVar
import pytest
@@ -233,6 +234,53 @@ async def dummy_task(
async def test_parking_lot_breaker_basic() -> None:
+ """Test basic functionality for breaking lots."""
+ lot = ParkingLot()
+ task = current_task()
+
+ # defaults to current task
+ lot.break_lot()
+ assert lot.broken_by == [task]
+
+ # breaking the lot again with the same task appends another copy in `broken_by`
+ lot.break_lot()
+ assert lot.broken_by == [task, task]
+
+ # trying to park in broken lot errors
+ broken_by_str = re.escape(str([task, task]))
+ with pytest.raises(
+ _core.BrokenResourceError,
+ match=f"^Attempted to park in parking lot broken by {broken_by_str}$",
+ ):
+ await lot.park()
+
+
+async def test_parking_lot_break_parking_tasks() -> None:
+ """Checks that tasks currently waiting to park raise an error when the breaker exits."""
+
+ async def bad_parker(lot: ParkingLot, scope: _core.CancelScope) -> None:
+ add_parking_lot_breaker(current_task(), lot)
+ with scope:
+ await trio.sleep_forever()
+
+ lot = ParkingLot()
+ cs = _core.CancelScope()
+
+ # check that parked task errors
+ with RaisesGroup(
+ Matcher(_core.BrokenResourceError, match="^Parking lot broken by"),
+ ):
+ async with _core.open_nursery() as nursery:
+ nursery.start_soon(bad_parker, lot, cs)
+ await wait_all_tasks_blocked()
+
+ nursery.start_soon(lot.park)
+ await wait_all_tasks_blocked()
+
+ cs.cancel()
+
+
+async def test_parking_lot_breaker_registration() -> None:
lot = ParkingLot()
task = current_task()
@@ -254,58 +302,60 @@ async def test_parking_lot_breaker_basic() -> None:
):
remove_parking_lot_breaker(task, lot)
- # defaults to current task
- lot.break_lot()
- assert lot.broken_by == task
-
- # breaking the lot again with the same task is a no-op
+ # registering a task as breaker on an already broken lot is fine
lot.break_lot()
-
- # registering a task as a breaker on an already broken lot is a no-op.
child_task = None
async with trio.open_nursery() as nursery:
child_task = await nursery.start(dummy_task)
add_parking_lot_breaker(child_task, lot)
nursery.cancel_scope.cancel()
+ assert lot.broken_by == [task, child_task]
# manually breaking a lot with an already exited task is fine
lot = ParkingLot()
lot.break_lot(child_task)
+ assert lot.broken_by == [child_task]
-async def test_parking_lot_breaker_warnings() -> None:
+async def test_parking_lot_breaker_rebreak() -> None:
lot = ParkingLot()
task = current_task()
lot.break_lot()
- warn_str = "attempted to break parking .* already broken by .*"
- # breaking an already broken lot with a different task gives a warning
+ # breaking an already broken lot with a different task is allowed
# The nursery is only to create a task we can pass to lot.break_lot
async with trio.open_nursery() as nursery:
child_task = await nursery.start(dummy_task)
- with pytest.warns(
- RuntimeWarning,
- match=warn_str,
- ):
- lot.break_lot(child_task)
+ lot.break_lot(child_task)
nursery.cancel_scope.cancel()
- # and doesn't change broken_by
- assert lot.broken_by == task
+ # and appends the task
+ assert lot.broken_by == [task, child_task]
+
+async def test_parking_lot_multiple_breakers_exit() -> None:
# register multiple tasks as lot breakers, then have them all exit
# No warning is given on task exit, even if the lot is already broken.
lot = ParkingLot()
- child_task = None
async with trio.open_nursery() as nursery:
- child_task = await nursery.start(dummy_task)
+ child_task1 = await nursery.start(dummy_task)
child_task2 = await nursery.start(dummy_task)
child_task3 = await nursery.start(dummy_task)
- add_parking_lot_breaker(child_task, lot)
+ add_parking_lot_breaker(child_task1, lot)
add_parking_lot_breaker(child_task2, lot)
add_parking_lot_breaker(child_task3, lot)
nursery.cancel_scope.cancel()
+ # I think the order is guaranteed currently, but doesn't hurt to be safe.
+ assert set(lot.broken_by) == {child_task1, child_task2, child_task3}
+
+
+async def test_parking_lot_breaker_register_exited_task() -> None:
+ lot = ParkingLot()
+ child_task = None
+ async with trio.open_nursery() as nursery:
+ child_task = await nursery.start(dummy_task)
+ nursery.cancel_scope.cancel()
# trying to register an exited task as lot breaker errors
with pytest.raises(
trio.BrokenResourceError,
@@ -314,34 +364,7 @@ async def test_parking_lot_breaker_warnings() -> None:
add_parking_lot_breaker(child_task, lot)
-async def test_parking_lot_breaker_bad_parker() -> None:
- async def bad_parker(lot: ParkingLot, scope: _core.CancelScope) -> None:
- add_parking_lot_breaker(current_task(), lot)
- with scope:
- await trio.sleep_forever()
-
- lot = ParkingLot()
- cs = _core.CancelScope()
-
- # check that parked task errors
- with RaisesGroup(
- Matcher(_core.BrokenResourceError, match="^Parking lot broken by"),
- ):
- async with _core.open_nursery() as nursery:
- nursery.start_soon(bad_parker, lot, cs)
- await wait_all_tasks_blocked()
-
- nursery.start_soon(lot.park)
- await wait_all_tasks_blocked()
-
- cs.cancel()
-
- # check that trying to park in broken lot errors
- with pytest.raises(_core.BrokenResourceError):
- await lot.park()
-
-
-async def test_parking_lot_weird() -> None:
+async def test_parking_lot_break_itself() -> None:
"""Break a parking lot, where the breakee is parked.
Doing this is weird, but should probably be supported.
"""
@@ -359,5 +382,5 @@ async def return_me_and_park(
Matcher(_core.BrokenResourceError, match="^Parking lot broken by"),
):
async with _core.open_nursery() as nursery:
- task = await nursery.start(return_me_and_park, lot)
- lot.break_lot(task)
+ child_task = await nursery.start(return_me_and_park, lot)
+ lot.break_lot(child_task)
From 92f9799d80d25bbf00f5021f41384bf320bb1c5e Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Tue, 8 Oct 2024 13:03:47 +0200
Subject: [PATCH 78/79] fix test. polish comments and tests
---
newsfragments/3035.feature.rst | 2 +-
src/trio/_core/_run.py | 2 +-
src/trio/_core/_tests/test_parking_lot.py | 2 --
src/trio/_tests/test_sync.py | 14 ++++++++++----
4 files changed, 12 insertions(+), 8 deletions(-)
diff --git a/newsfragments/3035.feature.rst b/newsfragments/3035.feature.rst
index c25841c47c..a1761fa282 100644
--- a/newsfragments/3035.feature.rst
+++ b/newsfragments/3035.feature.rst
@@ -1 +1 @@
-:class:`trio.Lock` and :class:`trio.StrictFIFOLock` will now raise :exc:`trio.BrokenResourceError` when :meth:`trio.Lock.acquire` would previously stall due to the owner of the lock having exited without releasing the lock.
+:class:`trio.Lock` and :class:`trio.StrictFIFOLock` will now raise :exc:`trio.BrokenResourceError` when :meth:`trio.Lock.acquire` would previously stall due to the owner of the lock exiting without releasing the lock.
diff --git a/src/trio/_core/_run.py b/src/trio/_core/_run.py
index defbaa6a80..cba7a8dec0 100644
--- a/src/trio/_core/_run.py
+++ b/src/trio/_core/_run.py
@@ -1897,7 +1897,7 @@ async def python_wrapper(orig_coro: Awaitable[RetT]) -> RetT:
return task
def task_exited(self, task: Task, outcome: Outcome[Any]) -> None:
- # break parking lots associated with the task exiting
+ # break parking lots associated with the exiting task
if task in GLOBAL_PARKING_LOT_BREAKER:
for lot in GLOBAL_PARKING_LOT_BREAKER[task]:
lot.break_lot(task)
diff --git a/src/trio/_core/_tests/test_parking_lot.py b/src/trio/_core/_tests/test_parking_lot.py
index cad3e7b431..d9afee83d4 100644
--- a/src/trio/_core/_tests/test_parking_lot.py
+++ b/src/trio/_core/_tests/test_parking_lot.py
@@ -329,13 +329,11 @@ async def test_parking_lot_breaker_rebreak() -> None:
lot.break_lot(child_task)
nursery.cancel_scope.cancel()
- # and appends the task
assert lot.broken_by == [task, child_task]
async def test_parking_lot_multiple_breakers_exit() -> None:
# register multiple tasks as lot breakers, then have them all exit
- # No warning is given on task exit, even if the lot is already broken.
lot = ParkingLot()
async with trio.open_nursery() as nursery:
child_task1 = await nursery.start(dummy_task)
diff --git a/src/trio/_tests/test_sync.py b/src/trio/_tests/test_sync.py
index 1a0b230e3e..f506e84ffc 100644
--- a/src/trio/_tests/test_sync.py
+++ b/src/trio/_tests/test_sync.py
@@ -1,5 +1,6 @@
from __future__ import annotations
+import re
import weakref
from typing import TYPE_CHECKING, Callable, Union
@@ -599,9 +600,10 @@ async def test_lock_acquire_unowned_lock() -> None:
lock = trio.Lock()
async with trio.open_nursery() as nursery:
nursery.start_soon(lock.acquire)
+ owner_str = re.escape(str(lock._lot.broken_by[0]))
with pytest.raises(
trio.BrokenResourceError,
- match="^Owner of this lock exited without releasing",
+ match=f"^Owner of this lock exited without releasing: {owner_str}$",
):
await lock.acquire()
assert not GLOBAL_PARKING_LOT_BREAKER
@@ -615,7 +617,7 @@ async def test_lock_multiple_acquire() -> None:
with RaisesGroup(
Matcher(
trio.BrokenResourceError,
- match="^Owner of this lock exited without releasing",
+ match="^Owner of this lock exited without releasing: ",
),
):
async with trio.open_nursery() as nursery:
@@ -626,9 +628,11 @@ async def test_lock_multiple_acquire() -> None:
async def test_lock_handover() -> None:
assert not GLOBAL_PARKING_LOT_BREAKER
+ child_task: Task | None = None
lock = trio.Lock()
+
+ # this task acquires the lock
lock.acquire_nowait()
- child_task: Task | None = None
assert GLOBAL_PARKING_LOT_BREAKER == {
_core.current_task(): [
lock._lot,
@@ -639,11 +643,13 @@ async def test_lock_handover() -> None:
nursery.start_soon(lock.acquire)
await wait_all_tasks_blocked()
+ # hand over the lock to the child task
lock.release()
+ # check values, and get the identifier out of the dict for later check
assert len(GLOBAL_PARKING_LOT_BREAKER) == 1
child_task = next(iter(GLOBAL_PARKING_LOT_BREAKER))
assert GLOBAL_PARKING_LOT_BREAKER[child_task] == [lock._lot]
- assert lock._lot.broken_by == child_task
+ assert lock._lot.broken_by == [child_task]
assert not GLOBAL_PARKING_LOT_BREAKER
From 11a7fc6c3b483bac1c39a170d0c1f0284cdf9505 Mon Sep 17 00:00:00 2001
From: A5rocks
Date: Thu, 17 Oct 2024 08:15:17 +0900
Subject: [PATCH 79/79] Bump version to 0.27.0
---
docs/source/history.rst | 33 +++++++++++++++++++++
newsfragments/2512.breaking.rst | 2 --
newsfragments/2512.feature.rst | 1 -
newsfragments/3035.feature.rst | 1 -
newsfragments/3041.bugfix.rst | 1 -
newsfragments/3052.feature.rst | 1 -
newsfragments/3076.bugfix.rst | 1 -
newsfragments/3081.feature.rst | 1 -
newsfragments/3095.bugfix.rst | 1 -
newsfragments/3101.doc.rst | 1 -
src/trio/_tests/test_testing_raisesgroup.py | 9 ------
src/trio/_version.py | 2 +-
src/trio/testing/_raises_group.py | 11 -------
13 files changed, 34 insertions(+), 31 deletions(-)
delete mode 100644 newsfragments/2512.breaking.rst
delete mode 100644 newsfragments/2512.feature.rst
delete mode 100644 newsfragments/3035.feature.rst
delete mode 100644 newsfragments/3041.bugfix.rst
delete mode 100644 newsfragments/3052.feature.rst
delete mode 100644 newsfragments/3076.bugfix.rst
delete mode 100644 newsfragments/3081.feature.rst
delete mode 100644 newsfragments/3095.bugfix.rst
delete mode 100644 newsfragments/3101.doc.rst
diff --git a/docs/source/history.rst b/docs/source/history.rst
index da8375aea4..34e2fe9772 100644
--- a/docs/source/history.rst
+++ b/docs/source/history.rst
@@ -5,6 +5,39 @@ Release history
.. towncrier release notes start
+Trio 0.27.0 (2024-10-17)
+------------------------
+
+Breaking changes
+~~~~~~~~~~~~~~~~
+
+- :func:`trio.move_on_after` and :func:`trio.fail_after` previously set the deadline relative to initialization time, instead of more intuitively upon entering the context manager. This might change timeouts if a program relied on this behavior. If you want to restore previous behavior you should instead use ``trio.move_on_at(trio.current_time() + ...)``.
+ flake8-async has a new rule to catch this, in case you're supporting older trio versions. See :ref:`ASYNC122`. (`#2512 `__)
+
+
+Features
+~~~~~~~~
+
+- :meth:`CancelScope.relative_deadline` and :meth:`CancelScope.is_relative` added, as well as a ``relative_deadline`` parameter to ``__init__``. This allows initializing scopes ahead of time, but where the specified relative deadline doesn't count down until the scope is entered. (`#2512 `__)
+- :class:`trio.Lock` and :class:`trio.StrictFIFOLock` will now raise :exc:`trio.BrokenResourceError` when :meth:`trio.Lock.acquire` would previously stall due to the owner of the lock exiting without releasing the lock. (`#3035 `__)
+- `trio.move_on_at`, `trio.move_on_after`, `trio.fail_at` and `trio.fail_after` now accept *shield* as a keyword argument. If specified, it provides an initial value for the `~trio.CancelScope.shield` attribute of the `trio.CancelScope` object created by the context manager. (`#3052 `__)
+- Added :func:`trio.lowlevel.add_parking_lot_breaker` and :func:`trio.lowlevel.remove_parking_lot_breaker` to allow creating custom lock/semaphore implementations that will break their underlying parking lot if a task exits unexpectedly. :meth:`trio.lowlevel.ParkingLot.break_lot` is also added, to allow breaking a parking lot intentionally. (`#3081 `__)
+
+
+Bugfixes
+~~~~~~~~
+
+- Allow sockets to bind any ``os.PathLike`` object. (`#3041 `__)
+- Update ``trio.lowlevel.open_process``'s documentation to allow bytes. (`#3076 `__)
+- Update :func:`trio.sleep_forever` to be `NoReturn`. (`#3095 `__)
+
+
+Improved documentation
+~~~~~~~~~~~~~~~~~~~~~~
+
+- Add docstrings for memory channels' ``statistics()`` and ``aclose`` methods. (`#3101 `__)
+
+
Trio 0.26.2 (2024-08-08)
------------------------
diff --git a/newsfragments/2512.breaking.rst b/newsfragments/2512.breaking.rst
deleted file mode 100644
index f9a6b42631..0000000000
--- a/newsfragments/2512.breaking.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-:func:`trio.move_on_after` and :func:`trio.fail_after` previously set the deadline relative to initialization time, instead of more intuitively upon entering the context manager. This might change timeouts if a program relied on this behavior. If you want to restore previous behavior you should instead use ``trio.move_on_at(trio.current_time() + ...)``.
-flake8-async has a new rule to catch this, in case you're supporting older trio versions. See :ref:`ASYNC122`.
diff --git a/newsfragments/2512.feature.rst b/newsfragments/2512.feature.rst
deleted file mode 100644
index fe9197cde5..0000000000
--- a/newsfragments/2512.feature.rst
+++ /dev/null
@@ -1 +0,0 @@
-:meth:`CancelScope.relative_deadline` and :meth:`CancelScope.is_relative` added, as well as a ``relative_deadline`` parameter to ``__init__``. This allows initializing scopes ahead of time, but where the specified relative deadline doesn't count down until the scope is entered.
diff --git a/newsfragments/3035.feature.rst b/newsfragments/3035.feature.rst
deleted file mode 100644
index a1761fa282..0000000000
--- a/newsfragments/3035.feature.rst
+++ /dev/null
@@ -1 +0,0 @@
-:class:`trio.Lock` and :class:`trio.StrictFIFOLock` will now raise :exc:`trio.BrokenResourceError` when :meth:`trio.Lock.acquire` would previously stall due to the owner of the lock exiting without releasing the lock.
diff --git a/newsfragments/3041.bugfix.rst b/newsfragments/3041.bugfix.rst
deleted file mode 100644
index b21851e5b3..0000000000
--- a/newsfragments/3041.bugfix.rst
+++ /dev/null
@@ -1 +0,0 @@
-Allow sockets to bind any ``os.PathLike`` object.
diff --git a/newsfragments/3052.feature.rst b/newsfragments/3052.feature.rst
deleted file mode 100644
index 3d843b4feb..0000000000
--- a/newsfragments/3052.feature.rst
+++ /dev/null
@@ -1 +0,0 @@
-`trio.move_on_at`, `trio.move_on_after`, `trio.fail_at` and `trio.fail_after` now accept *shield* as a keyword argument. If specified, it provides an initial value for the `~trio.CancelScope.shield` attribute of the `trio.CancelScope` object created by the context manager.
diff --git a/newsfragments/3076.bugfix.rst b/newsfragments/3076.bugfix.rst
deleted file mode 100644
index 48aa54127a..0000000000
--- a/newsfragments/3076.bugfix.rst
+++ /dev/null
@@ -1 +0,0 @@
-Update ``trio.lowlevel.open_process``'s documentation to allow bytes.
diff --git a/newsfragments/3081.feature.rst b/newsfragments/3081.feature.rst
deleted file mode 100644
index 34a073b265..0000000000
--- a/newsfragments/3081.feature.rst
+++ /dev/null
@@ -1 +0,0 @@
-Added :func:`trio.lowlevel.add_parking_lot_breaker` and :func:`trio.lowlevel.remove_parking_lot_breaker` to allow creating custom lock/semaphore implementations that will break their underlying parking lot if a task exits unexpectedly. :meth:`trio.lowlevel.ParkingLot.break_lot` is also added, to allow breaking a parking lot intentionally.
diff --git a/newsfragments/3095.bugfix.rst b/newsfragments/3095.bugfix.rst
deleted file mode 100644
index 7a7d756e5b..0000000000
--- a/newsfragments/3095.bugfix.rst
+++ /dev/null
@@ -1 +0,0 @@
-Update :func:`trio.sleep_forever` to be `NoReturn`.
diff --git a/newsfragments/3101.doc.rst b/newsfragments/3101.doc.rst
deleted file mode 100644
index cd3035844e..0000000000
--- a/newsfragments/3101.doc.rst
+++ /dev/null
@@ -1 +0,0 @@
-Add docstrings for memory channels' ``statistics()`` and ``aclose`` methods.
diff --git a/src/trio/_tests/test_testing_raisesgroup.py b/src/trio/_tests/test_testing_raisesgroup.py
index f877b5bd0c..17eb6afcc7 100644
--- a/src/trio/_tests/test_testing_raisesgroup.py
+++ b/src/trio/_tests/test_testing_raisesgroup.py
@@ -372,12 +372,3 @@ def test__ExceptionInfo(monkeypatch: pytest.MonkeyPatch) -> None:
assert excinfo.type is ExceptionGroup
assert excinfo.value.exceptions[0].args == ("hello",)
assert isinstance(excinfo.tb, TracebackType)
-
-
-def test_deprecated_strict() -> None:
- """`strict` has been replaced with `flatten_subgroups`"""
- # parameter is not included in overloaded signatures at all
- with pytest.deprecated_call():
- RaisesGroup(ValueError, strict=False) # type: ignore[call-overload]
- with pytest.deprecated_call():
- RaisesGroup(ValueError, strict=True) # type: ignore[call-overload]
diff --git a/src/trio/_version.py b/src/trio/_version.py
index f8aaf8b6f5..f9218d068c 100644
--- a/src/trio/_version.py
+++ b/src/trio/_version.py
@@ -1,3 +1,3 @@
# This file is imported from __init__.py and parsed by setuptools
-__version__ = "0.26.2+dev"
+__version__ = "0.27.0"
diff --git a/src/trio/testing/_raises_group.py b/src/trio/testing/_raises_group.py
index 627663ffb5..551398a302 100644
--- a/src/trio/testing/_raises_group.py
+++ b/src/trio/testing/_raises_group.py
@@ -14,7 +14,6 @@
overload,
)
-from trio._deprecate import warn_deprecated
from trio._util import final
if TYPE_CHECKING:
@@ -384,7 +383,6 @@ def __init__(
flatten_subgroups: bool = False,
match: str | Pattern[str] | None = None,
check: Callable[[BaseExceptionGroup[E]], bool] | None = None,
- strict: None = None,
):
self.expected_exceptions: tuple[type[E] | Matcher[E] | E, ...] = (
exception,
@@ -396,15 +394,6 @@ def __init__(
self.check = check
self.is_baseexceptiongroup = False
- if strict is not None:
- warn_deprecated(
- "The `strict` parameter",
- "0.25.1",
- issue=2989,
- instead="flatten_subgroups=True (for strict=False}",
- )
- self.flatten_subgroups = not strict
-
if allow_unwrapped and other_exceptions:
raise ValueError(
"You cannot specify multiple exceptions with `allow_unwrapped=True.`"