Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into enable-flake8-return
Browse files Browse the repository at this point in the history
  • Loading branch information
CoolCat467 committed Dec 15, 2024
2 parents 2fdf4af + c4c8ce4 commit e47c52e
Show file tree
Hide file tree
Showing 80 changed files with 1,574 additions and 830 deletions.
22 changes: 13 additions & 9 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@ name: CI
on:
push:
branches-ignore:
- "dependabot/**"
# these branches always have another event associated
- gh-readonly-queue/** # GitHub's merge queue uses `merge_group`
- autodeps/** # autodeps always makes a PR
- pre-commit-ci-update-config # pre-commit.ci's updates always have a PR
pull_request:
merge_group:

concurrency:
group: ${{ github.ref }}-${{ github.workflow }}-${{ github.event_name }}${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && format('-{0}', github.sha) || '' }}
Expand All @@ -18,7 +22,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python: ['pypy-3.10', '3.9', '3.10', '3.11', '3.12', '3.13']
python: ['3.9', '3.10', '3.11', '3.12', '3.13']
arch: ['x86', 'x64']
lsp: ['']
lsp_extract_file: ['']
Expand All @@ -34,6 +38,11 @@ jobs:
lsp: 'https://www.proxifier.com/download/legacy/ProxifierSetup342.exe'
lsp_extract_file: ''
extra_name: ', with IFS LSP'
- python: 'pypy-3.10'
arch: 'x64'
lsp: ''
lsp_extract_file: ''
extra_name: ''
#- python: '3.9'
# arch: 'x64'
# lsp: 'http://download.pctools.com/mirror/updates/9.0.0.2308-SDavfree-lite_en.exe'
Expand Down Expand Up @@ -113,16 +122,10 @@ jobs:
uses: actions/checkout@v4
- name: Setup python
uses: actions/setup-python@v5
if: "!endsWith(matrix.python, '-dev')"
with:
python-version: ${{ fromJSON(format('["{0}", "{1}"]', format('{0}.0-alpha - {0}.X', matrix.python), matrix.python))[startsWith(matrix.python, 'pypy')] }}
cache: pip
cache-dependency-path: test-requirements.txt
- name: Setup python (dev)
uses: deadsnakes/[email protected]
if: endsWith(matrix.python, '-dev')
with:
python-version: '${{ matrix.python }}'
- name: Run tests
run: ./ci.sh
env:
Expand Down Expand Up @@ -184,7 +187,8 @@ jobs:
# can't use setup-python because that python doesn't seem to work;
# `python3-dev` (rather than `python:alpine`) for some ctypes reason,
# `nodejs` for pyright (`node-env` pulls in nodejs but that takes a while and can time out the test).
run: apk update && apk add python3-dev bash nodejs
# `perl` for a platform independent `sed -i` alternative
run: apk update && apk add python3-dev bash nodejs perl
- name: Enter virtual environment
run: python -m venv .venv
- name: Run tests
Expand Down
6 changes: 2 additions & 4 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
ci:
autofix_commit_msg: "[pre-commit.ci] auto fixes from pre-commit.com hooks"
autofix_prs: false
autoupdate_commit_msg: "[pre-commit.ci] pre-commit autoupdate"
autofix_prs: true
autoupdate_schedule: weekly
submodules: false
skip: [regenerate-files]
Expand All @@ -24,7 +22,7 @@ repos:
hooks:
- id: black
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.6.9
rev: v0.8.2
hooks:
- id: ruff
types: [file]
Expand Down
14 changes: 10 additions & 4 deletions ci.sh
Original file line number Diff line number Diff line change
Expand Up @@ -116,23 +116,29 @@ else
echo "::group::Setup for tests"

# We run the tests from inside an empty directory, to make sure Python
# doesn't pick up any .py files from our working dir. Might have been
# pre-created by some of the code above.
# doesn't pick up any .py files from our working dir. Might have already
# been created by a previous run.
mkdir empty || true
cd empty

INSTALLDIR=$(python -c "import os, trio; print(os.path.dirname(trio.__file__))")
cp ../pyproject.toml "$INSTALLDIR"
cp ../pyproject.toml "$INSTALLDIR" # TODO: remove this

# get mypy tests a nice cache
MYPYPATH=".." mypy --config-file= --cache-dir=./.mypy_cache -c "import trio" >/dev/null 2>/dev/null || true

# support subprocess spawning with coverage.py
echo "import coverage; coverage.process_startup()" | tee -a "$INSTALLDIR/../sitecustomize.py"

perl -i -pe 's/-p trio\._tests\.pytest_plugin//' "$INSTALLDIR/pyproject.toml"

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 PYTHONPATH=../tests COVERAGE_PROCESS_START=$(pwd)/../pyproject.toml \
coverage run --rcfile=../pyproject.toml -m \
pytest -ra --junitxml=../test-results.xml \
-p _trio_check_attrs_aliases --verbose --durations=10 \
-p trio._tests.pytest_plugin --run-slow $flags "${INSTALLDIR}"; then
PASSED=true
else
PASSED=false
Expand Down
22 changes: 11 additions & 11 deletions docs-requirements.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This file was autogenerated by uv via the following command:
# uv pip compile --universal --python-version=3.11 docs-requirements.in -o docs-requirements.txt
alabaster==0.7.16
alabaster==1.0.0
# via sphinx
attrs==24.2.0
# via
Expand All @@ -16,17 +16,17 @@ cffi==1.17.1 ; platform_python_implementation != 'PyPy' or os_name == 'nt'
# via
# -r docs-requirements.in
# cryptography
charset-normalizer==3.3.2
charset-normalizer==3.4.0
# via requests
click==8.1.7
# via towncrier
colorama==0.4.6 ; sys_platform == 'win32' or platform_system == 'Windows'
# via
# click
# sphinx
cryptography==43.0.1
cryptography==44.0.0
# via pyopenssl
docutils==0.20.1
docutils==0.21.2
# via
# sphinx
# sphinx-rtd-theme
Expand All @@ -38,24 +38,24 @@ idna==3.10
# requests
imagesize==1.4.1
# via sphinx
immutables==0.20
immutables==0.21
# via -r docs-requirements.in
jinja2==3.1.4
# via
# -r docs-requirements.in
# sphinx
# towncrier
markupsafe==2.1.5
markupsafe==3.0.2
# via jinja2
outcome==1.3.0.post0
# via -r docs-requirements.in
packaging==24.1
packaging==24.2
# via sphinx
pycparser==2.22 ; platform_python_implementation != 'PyPy' or os_name == 'nt'
# via cffi
pygments==2.18.0
# via sphinx
pyopenssl==24.2.1
pyopenssl==24.3.0
# via -r docs-requirements.in
requests==2.32.3
# via sphinx
Expand All @@ -67,7 +67,7 @@ sortedcontainers==2.4.0
# via -r docs-requirements.in
soupsieve==2.6
# via beautifulsoup4
sphinx==7.4.7
sphinx==8.1.3
# via
# -r docs-requirements.in
# sphinx-codeautolink
Expand All @@ -77,9 +77,9 @@ sphinx==7.4.7
# sphinxcontrib-trio
sphinx-codeautolink==0.15.2
# via -r docs-requirements.in
sphinx-hoverxref==1.4.1
sphinx-hoverxref==1.4.2
# via -r docs-requirements.in
sphinx-rtd-theme==3.0.0
sphinx-rtd-theme==3.0.2
# via -r docs-requirements.in
sphinxcontrib-applehelp==2.0.0
# via sphinx
Expand Down
21 changes: 0 additions & 21 deletions docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
import glob
import os
import sys
import types
from pathlib import Path
from typing import TYPE_CHECKING, cast

Expand Down Expand Up @@ -152,16 +151,6 @@ def autodoc_process_signature(
return_annotation: str,
) -> tuple[str, str]:
"""Modify found signatures to fix various issues."""
if name == "trio.testing._raises_group._ExceptionInfo.type":
# This has the type "type[E]", which gets resolved into the property itself.
# That means Sphinx can't resolve it. Fix the issue by overwriting with a fully-qualified
# name.
assert isinstance(obj, property), obj
assert isinstance(obj.fget, types.FunctionType), obj.fget
assert (
obj.fget.__annotations__["return"] == "type[MatchE]"
), obj.fget.__annotations__
obj.fget.__annotations__["return"] = "type[~trio.testing._raises_group.MatchE]"
if signature is not None:
signature = signature.replace("~_contextvars.Context", "~contextvars.Context")
if name == "trio.lowlevel.RunVar": # Typevar is not useful here.
Expand All @@ -170,16 +159,6 @@ def autodoc_process_signature(
# Strip the type from the union, make it look like = ...
signature = signature.replace(" | type[trio._core._local._NoValue]", "")
signature = signature.replace("<class 'trio._core._local._NoValue'>", "...")
if name in ("trio.testing.RaisesGroup", "trio.testing.Matcher") and (
"+E" in signature or "+MatchE" in signature
):
# This typevar being covariant isn't handled correctly in some cases, strip the +
# and insert the fully-qualified name.
signature = signature.replace("+E", "~trio.testing._raises_group.E")
signature = signature.replace(
"+MatchE",
"~trio.testing._raises_group.MatchE",
)
if "DTLS" in name:
signature = signature.replace("SSL.Context", "OpenSSL.SSL.Context")
# Don't specify PathLike[str] | PathLike[bytes], this is just for humans.
Expand Down
40 changes: 40 additions & 0 deletions docs/source/reference-lowlevel.rst
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,46 @@ These transitions are accomplished using two function decorators:
poorly-timed :exc:`KeyboardInterrupt` could leave the lock in an
inconsistent state and cause a deadlock.

Since KeyboardInterrupt protection is tracked per code object, any attempt to
conditionally protect the same block of code in different ways is unlikely to behave
how you expect. If you try to conditionally protect a closure, it will be
unconditionally protected instead::

def example(protect: bool) -> bool:
def inner() -> bool:
return trio.lowlevel.currently_ki_protected()
if protect:
inner = trio.lowlevel.enable_ki_protection(inner)
return inner()

async def amain():
assert example(False) == False
assert example(True) == True # once protected ...
assert example(False) == True # ... always protected

trio.run(amain)

If you really need conditional protection, you can achieve it by giving each
KI-protected instance of the closure its own code object::

def example(protect: bool) -> bool:
def inner() -> bool:
return trio.lowlevel.currently_ki_protected()
if protect:
inner.__code__ = inner.__code__.replace()
inner = trio.lowlevel.enable_ki_protection(inner)
return inner()

async def amain():
assert example(False) == False
assert example(True) == True
assert example(False) == False

trio.run(amain)

(This isn't done by default because it carries some memory overhead and reduces
the potential for specializing optimizations in recent versions of CPython.)

.. autofunction:: currently_ki_protected


Expand Down
2 changes: 2 additions & 0 deletions newsfragments/2670.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
:func:`inspect.iscoroutinefunction` and the like now give correct answers when
called on KI-protected functions.
1 change: 1 addition & 0 deletions newsfragments/3087.doc.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Improve error message when run after gevent's monkey patching.
1 change: 1 addition & 0 deletions newsfragments/3097.removal.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Remove workaround for OpenSSL 1.1.1 DTLS ClientHello bug.
26 changes: 26 additions & 0 deletions newsfragments/3108.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
Rework KeyboardInterrupt protection to track code objects, rather than frames,
as protected or not. The new implementation no longer needs to access
``frame.f_locals`` dictionaries, so it won't artificially extend the lifetime of
local variables. Since KeyboardInterrupt protection is now imposed statically
(when a protected function is defined) rather than each time the function runs,
its previously-noticeable performance overhead should now be near zero.
The lack of a call-time wrapper has some other benefits as well:

* :func:`inspect.iscoroutinefunction` and the like now give correct answers when
called on KI-protected functions.

* Calling a synchronous KI-protected function no longer pushes an additional stack
frame, so tracebacks are clearer.

* A synchronous KI-protected function invoked from C code (such as a weakref
finalizer) is now guaranteed to start executing; previously there would be a brief
window in which KeyboardInterrupt could be raised before the protection was
established.

One minor drawback of the new approach is that multiple instances of the same
closure share a single KeyboardInterrupt protection state (because they share a
single code object). That means that if you apply
`@enable_ki_protection <trio.lowlevel.enable_ki_protection>` to some of them
and not others, you won't get the protection semantics you asked for. See the
documentation of `@enable_ki_protection <trio.lowlevel.enable_ki_protection>`
for more details and a workaround.
5 changes: 5 additions & 0 deletions newsfragments/3112.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Rework foreign async generator finalization to track async generator
ids rather than mutating ``ag_frame.f_locals``. This fixes an issue
with the previous implementation: locals' lifetimes will no longer be
extended by materialization in the ``ag_frame.f_locals`` dictionary that
the previous finalization dispatcher logic needed to access to do its work.
1 change: 1 addition & 0 deletions newsfragments/3114.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Ensure that Pyright recognizes our underscore prefixed attributes for attrs classes.
1 change: 1 addition & 0 deletions newsfragments/3121.misc.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Improve type annotations in several places by removing `Any` usage.
1 change: 1 addition & 0 deletions newsfragments/3141.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix `trio.testing.RaisesGroup`'s typing.
4 changes: 2 additions & 2 deletions notes-to-self/aio-guest-test.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ async def trio_main():
to_trio, from_aio = trio.open_memory_channel(float("inf"))
from_trio = asyncio.Queue()

_task_ref = asyncio.create_task(aio_pingpong(from_trio, to_trio))
task_ref = asyncio.create_task(aio_pingpong(from_trio, to_trio))

from_trio.put_nowait(0)

Expand All @@ -37,7 +37,7 @@ async def trio_main():
from_trio.put_nowait(n + 1)
if n >= 10:
return
del _task_ref
del task_ref


async def aio_pingpong(from_trio, to_trio):
Expand Down
9 changes: 7 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ allowed-confusables = ["–"]

select = [
"A", # flake8-builtins
"ANN", # flake8-annotations
"ASYNC", # flake8-async
"B", # flake8-bugbear
"C4", # flake8-comprehensions
Expand All @@ -125,13 +126,14 @@ select = [
"RET", # flake8-return
"RUF", # Ruff-specific rules
"SIM", # flake8-simplify
"TCH", # flake8-type-checking
"TC", # flake8-type-checking
"UP", # pyupgrade
"W", # Warning
"YTT", # flake8-2020
]
extend-ignore = [
'A002', # builtin-argument-shadowing
'ANN401', # any-type (mypy's `disallow_any_explicit` is better)
'E402', # module-import-not-at-top-of-file (usually OS-specific)
'E501', # line-too-long
'F403', # undefined-local-with-import-star
Expand Down Expand Up @@ -161,6 +163,8 @@ extend-ignore = [
'src/trio/_abc.py' = ['A005']
'src/trio/_socket.py' = ['A005']
'src/trio/_ssl.py' = ['A005']
# Don't check annotations in notes-to-self
'notes-to-self/*.py' = ['ANN001', 'ANN002', 'ANN003', 'ANN201', 'ANN202', 'ANN204']

[tool.ruff.lint.isort]
combine-as-imports = true
Expand All @@ -184,6 +188,7 @@ warn_return_any = true

# Avoid subtle backsliding
disallow_any_decorated = true
disallow_any_explicit = true
disallow_any_generics = true
disallow_any_unimported = true
disallow_incomplete_defs = true
Expand All @@ -199,7 +204,7 @@ reportUnnecessaryTypeIgnoreComment = true
typeCheckingMode = "strict"

[tool.pytest.ini_options]
addopts = ["--strict-markers", "--strict-config", "-p trio._tests.pytest_plugin"]
addopts = ["--strict-markers", "--strict-config", "-p trio._tests.pytest_plugin", "--import-mode=importlib"]
faulthandler_timeout = 60
filterwarnings = [
"error",
Expand Down
Loading

0 comments on commit e47c52e

Please sign in to comment.