diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a4489e609d..15cfcbaa64 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -155,7 +155,7 @@ jobs: strategy: fail-fast: false matrix: - python: ['3.9', '3.10', '3.11', '3.12', '3.13'] + python: ['3.9', '3.10', '3.11', '3.12', '3.13', '3.14'] arch: ['x86', 'x64'] lsp: [''] lsp_extract_file: [''] @@ -237,7 +237,7 @@ jobs: strategy: fail-fast: false matrix: - python: ['pypy-3.10', '3.9', '3.10', '3.11', '3.12', '3.13'] + python: ['pypy-3.10', '3.9', '3.10', '3.11', '3.12', '3.13', '3.14'] check_formatting: ['0'] no_test_requirements: ['0'] extra_name: [''] @@ -276,10 +276,15 @@ jobs: 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: Check Formatting + if: matrix.check_formatting == '1' + run: + python -m pip install tox && + tox -m check - name: Run tests + if: matrix.check_formatting == '0' run: ./ci.sh env: - CHECK_FORMATTING: '${{ matrix.check_formatting }}' NO_TEST_REQUIREMENTS: '${{ matrix.no_test_requirements }}' - if: >- always() @@ -301,7 +306,7 @@ jobs: strategy: fail-fast: false matrix: - python: ['pypy-3.10', '3.9', '3.10', '3.11', '3.12', '3.13'] + python: ['pypy-3.10', '3.9', '3.10', '3.11', '3.12', '3.13', '3.14'] continue-on-error: >- ${{ ( @@ -422,6 +427,11 @@ jobs: run: >- echo "version=$(python -V | cut -d' ' -f2 | cut -d'.' -f1,2)" >> "${GITHUB_OUTPUT}" + + - run: | + coverage combine + coverage report + - if: always() uses: codecov/codecov-action@v5 with: diff --git a/.gitignore b/.gitignore index cad76cb460..e63a1a5556 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# generated by cythonize +tests/cython/test_cython.c + # In case somebody wants to restore the directory for local testing notes-to-self/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index fba114f1f6..b0d1a3781a 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.9.6 + rev: v0.11.4 hooks: - id: ruff types: [file] @@ -38,7 +38,7 @@ repos: # tomli needed on 3.10. tomllib is available in stdlib on 3.11+ - tomli - repo: https://github.com/crate-ci/typos - rev: typos-dict-v0.12.4 + rev: v1.31.1 hooks: - id: typos - repo: https://github.com/sphinx-contrib/sphinx-lint @@ -46,7 +46,7 @@ repos: hooks: - id: sphinx-lint - repo: https://github.com/woodruffw/zizmor-pre-commit - rev: v1.3.1 + rev: v1.5.2 hooks: - id: zizmor - repo: local @@ -59,7 +59,7 @@ repos: additional_dependencies: ["astor", "attrs", "black", "ruff"] files: ^src\/trio\/_core\/(_run|(_i(o_(common|epoll|kqueue|windows)|nstrumentation)))\.py$ - repo: https://github.com/astral-sh/uv-pre-commit - rev: 0.5.30 + rev: 0.6.13 hooks: # Compile requirements - id: pip-compile diff --git a/README.rst b/README.rst index e3620546a0..f62c2a37af 100644 --- a/README.rst +++ b/README.rst @@ -56,13 +56,13 @@ I/O-oriented programs easier, less error-prone, and just plain more fun. `Perhaps you'll find the same `__. -This project is young and still somewhat experimental: the overall -design is solid, and the existing features are fully tested and -documented, but you may encounter missing functionality or rough -edges. We *do* encourage you to use it, but you should `read and -subscribe to issue #1 -`__ to get a warning and a -chance to give feedback about any compatibility-breaking changes. +Trio is a mature and well-tested project: the overall design is solid, +and the existing features are fully documented and widely used in +production. While we occasionally make minor interface adjustments, +breaking changes are rare. We encourage you to use Trio with confidence, +but if you rely on long-term API stability, consider `subscribing to +issue #1 `__ for advance +notice of any compatibility updates. Where to next? diff --git a/check.sh b/check.sh deleted file mode 100755 index 701db8ce61..0000000000 --- a/check.sh +++ /dev/null @@ -1,81 +0,0 @@ -#!/bin/bash - -set -ex - -ON_GITHUB_CI=true -EXIT_STATUS=0 - -# If not running on Github's CI, discard the summaries -if [ -z "${GITHUB_STEP_SUMMARY+x}" ]; then - GITHUB_STEP_SUMMARY=/dev/null - ON_GITHUB_CI=false -fi - -# Test if the generated code is still up to date -echo "::group::Generate Exports" -python ./src/trio/_tools/gen_exports.py --test \ - || EXIT_STATUS=$? -echo "::endgroup::" - -# Run mypy on all supported platforms -# MYPY is set if any of them fail. -MYPY=0 -echo "::group::Mypy" -# Cleanup previous runs. -rm -f mypy_annotate.dat -# Pipefail makes these pipelines fail if mypy does, even if mypy_annotate.py succeeds. -set -o pipefail -mypy --show-error-end --platform linux | python ./src/trio/_tools/mypy_annotate.py --dumpfile mypy_annotate.dat --platform Linux \ - || { echo "* Mypy (Linux) found type errors." >> "$GITHUB_STEP_SUMMARY"; MYPY=1; } -# Darwin tests FreeBSD too -mypy --show-error-end --platform darwin | python ./src/trio/_tools/mypy_annotate.py --dumpfile mypy_annotate.dat --platform Mac \ - || { echo "* Mypy (Mac) found type errors." >> "$GITHUB_STEP_SUMMARY"; MYPY=1; } -mypy --show-error-end --platform win32 | python ./src/trio/_tools/mypy_annotate.py --dumpfile mypy_annotate.dat --platform Windows \ - || { echo "* Mypy (Windows) found type errors." >> "$GITHUB_STEP_SUMMARY"; MYPY=1; } -set +o pipefail -# Re-display errors using Github's syntax, read out of mypy_annotate.dat -python ./src/trio/_tools/mypy_annotate.py --dumpfile mypy_annotate.dat -# Then discard. -rm -f mypy_annotate.dat -echo "::endgroup::" -# Display a big error if we failed, outside the group so it can't be collapsed. -if [ $MYPY -ne 0 ]; then - echo "::error:: Mypy found type errors." - EXIT_STATUS=1 -fi - -# Check pip compile is consistent -echo "::group::Pip Compile - Tests & Docs" -pre-commit run pip-compile --all-files \ - || EXIT_STATUS=$? -echo "::endgroup::" - -echo "::group::Pyright interface tests" -python src/trio/_tests/check_type_completeness.py || EXIT_STATUS=$? - -pyright src/trio/_tests/type_tests || EXIT_STATUS=$? -pyright src/trio/_core/_tests/type_tests || EXIT_STATUS=$? -echo "::endgroup::" - -# Finally, leave a really clear warning of any issues and exit -if [ $EXIT_STATUS -ne 0 ]; then - cat <> "$GITHUB_STEP_SUMMARY" -exit 0 diff --git a/ci.sh b/ci.sh index 83ec65748b..f414d94a2c 100755 --- a/ci.sh +++ b/ci.sh @@ -47,115 +47,109 @@ python -m build wheel_package=$(ls dist/*.whl) python -m uv pip install "trio @ $wheel_package" -c test-requirements.txt -if [ "$CHECK_FORMATTING" = "1" ]; then - python -m uv pip install -r test-requirements.txt exceptiongroup - echo "::endgroup::" - source check.sh +# 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 uv pip install pytest coverage -c test-requirements.txt + flags="--skip-optional-imports" 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 uv pip install pytest coverage -c test-requirements.txt - flags="--skip-optional-imports" - else - python -m uv pip install -r test-requirements.txt - flags="" - fi + python -m uv pip install -r test-requirements.txt + flags="" +fi - # So we can run the test for our apport/excepthook interaction working - if [ -e /etc/lsb-release ] && grep -q Ubuntu /etc/lsb-release; then - sudo apt install -q python3-apport - fi +# So we can run the test for our apport/excepthook interaction working +if [ -e /etc/lsb-release ] && grep -q Ubuntu /etc/lsb-release; then + sudo apt install -q python3-apport +fi - # If we're testing with a LSP installed, then it might break network - # stuff, so wait until after we've finished setting everything else - # up. - if [ "$LSP" != "" ]; then - echo "Installing LSP from ${LSP}" - # We use --insecure because one of the LSP's has been observed to give - # cert verification errors: - # - # https://github.com/python-trio/trio/issues/1478 - # - # *Normally*, you should never ever use --insecure, especially when - # fetching an executable! But *in this case*, we're intentionally - # installing some untrustworthy quasi-malware onto into a sandboxed - # machine for testing. So MITM attacks are really the least of our - # worries. - if [ "$LSP_EXTRACT_FILE" != "" ]; then - # We host the Astrill VPN installer ourselves, and encrypt it - # so as to decrease the chances of becoming an inadvertent - # public redistributor. - curl-harder -o lsp-installer.zip "$LSP" - unzip -P "not very secret trio ci key" lsp-installer.zip "$LSP_EXTRACT_FILE" - mv "$LSP_EXTRACT_FILE" lsp-installer.exe - else - curl-harder --insecure -o lsp-installer.exe "$LSP" - fi - # This is only needed for the Astrill LSP, but there's no harm in - # doing it all the time. The cert was manually extracted by installing - # the package in a VM, clicking "Always trust from this publisher" - # when installing, and then running 'certmgr.msc' and exporting the - # certificate. See: - # http://www.migee.com/2010/09/24/solution-for-unattendedsilent-installs-and-would-you-like-to-install-this-device-software/ - certutil -addstore "TrustedPublisher" src/trio/_tests/astrill-codesigning-cert.cer - # Double-slashes are how you tell windows-bash that you want a single - # slash, and don't treat this as a unix-style filename that needs to - # be replaced by a windows-style filename. - # http://www.mingw.org/wiki/Posix_path_conversion - ./lsp-installer.exe //silent //norestart - echo "Waiting for LSP to appear in Winsock catalog" - while ! netsh winsock show catalog | grep "Layered Chain Entry"; do - sleep 1 - done - netsh winsock show catalog +# If we're testing with a LSP installed, then it might break network +# stuff, so wait until after we've finished setting everything else +# up. +if [ "$LSP" != "" ]; then + echo "Installing LSP from ${LSP}" + # We use --insecure because one of the LSP's has been observed to give + # cert verification errors: + # + # https://github.com/python-trio/trio/issues/1478 + # + # *Normally*, you should never ever use --insecure, especially when + # fetching an executable! But *in this case*, we're intentionally + # installing some untrustworthy quasi-malware onto into a sandboxed + # machine for testing. So MITM attacks are really the least of our + # worries. + if [ "$LSP_EXTRACT_FILE" != "" ]; then + # We host the Astrill VPN installer ourselves, and encrypt it + # so as to decrease the chances of becoming an inadvertent + # public redistributor. + curl-harder -o lsp-installer.zip "$LSP" + unzip -P "not very secret trio ci key" lsp-installer.zip "$LSP_EXTRACT_FILE" + mv "$LSP_EXTRACT_FILE" lsp-installer.exe + else + curl-harder --insecure -o lsp-installer.exe "$LSP" fi - echo "::endgroup::" - - echo "::group::Setup for tests" + # This is only needed for the Astrill LSP, but there's no harm in + # doing it all the time. The cert was manually extracted by installing + # the package in a VM, clicking "Always trust from this publisher" + # when installing, and then running 'certmgr.msc' and exporting the + # certificate. See: + # http://www.migee.com/2010/09/24/solution-for-unattendedsilent-installs-and-would-you-like-to-install-this-device-software/ + certutil -addstore "TrustedPublisher" src/trio/_tests/astrill-codesigning-cert.cer + # Double-slashes are how you tell windows-bash that you want a single + # slash, and don't treat this as a unix-style filename that needs to + # be replaced by a windows-style filename. + # http://www.mingw.org/wiki/Posix_path_conversion + ./lsp-installer.exe //silent //norestart + echo "Waiting for LSP to appear in Winsock catalog" + while ! netsh winsock show catalog | grep "Layered Chain Entry"; do + sleep 1 + done + netsh winsock show catalog +fi +echo "::endgroup::" - # 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 already - # been created by a previous run. - mkdir empty || true - cd empty +echo "::group::Setup for tests" - INSTALLDIR=$(python -c "import os, trio; print(os.path.dirname(trio.__file__))") - cp ../pyproject.toml "$INSTALLDIR" # TODO: remove this +# 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 already +# been created by a previous run. +mkdir empty || true +cd empty - # get mypy tests a nice cache - MYPYPATH=".." mypy --config-file= --cache-dir=./.mypy_cache -c "import trio" >/dev/null 2>/dev/null || true +INSTALLDIR=$(python -c "import os, trio; print(os.path.dirname(trio.__file__))") +cp ../pyproject.toml "$INSTALLDIR" # TODO: remove this - # support subprocess spawning with coverage.py - echo "import coverage; coverage.process_startup()" | tee -a "$INSTALLDIR/../sitecustomize.py" +# get mypy tests a nice cache +MYPYPATH=".." mypy --config-file= --cache-dir=./.mypy_cache -c "import trio" >/dev/null 2>/dev/null || true - perl -i -pe 's/-p trio\._tests\.pytest_plugin//' "$INSTALLDIR/pyproject.toml" +# support subprocess spawning with coverage.py +echo "import coverage; coverage.process_startup()" | tee -a "$INSTALLDIR/../sitecustomize.py" - echo "::endgroup::" - echo "::group:: Run Tests" - 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 - fi - echo "::endgroup::" - echo "::group::Coverage" +perl -i -pe 's/-p trio\._tests\.pytest_plugin//' "$INSTALLDIR/pyproject.toml" - coverage combine --rcfile ../pyproject.toml - coverage report -m --rcfile ../pyproject.toml - coverage xml --rcfile ../pyproject.toml +echo "::endgroup::" +echo "::group:: Run Tests" +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 +fi +echo "::endgroup::" +echo "::group::Coverage" - # Remove the LSP again; again we want to do this ASAP to avoid - # accidentally breaking other stuff. - if [ "$LSP" != "" ]; then - netsh winsock reset - fi +coverage combine --rcfile ../pyproject.toml +coverage report -m --rcfile ../pyproject.toml +coverage xml --rcfile ../pyproject.toml - echo "::endgroup::" - $PASSED +# Remove the LSP again; again we want to do this ASAP to avoid +# accidentally breaking other stuff. +if [ "$LSP" != "" ]; then + netsh winsock reset fi + +echo "::endgroup::" +$PASSED diff --git a/docs-requirements.in b/docs-requirements.in index 7094b9e471..d942c084dc 100644 --- a/docs-requirements.in +++ b/docs-requirements.in @@ -7,7 +7,6 @@ sphinx_rtd_theme >= 3 sphinxcontrib-jquery sphinxcontrib-trio towncrier -sphinx-hoverxref sphinx-codeautolink # Trio's own dependencies @@ -23,5 +22,4 @@ exceptiongroup >= 1.0.0rc9 immutables >= 0.6 # types used in annotations -# TODO: fix support for importing typing-extensions -pyOpenSSL < 25.0.0 +pyOpenSSL diff --git a/docs-requirements.txt b/docs-requirements.txt index 7b5d481c8a..1897984332 100644 --- a/docs-requirements.txt +++ b/docs-requirements.txt @@ -2,13 +2,13 @@ # uv pip compile --universal --python-version=3.11 docs-requirements.in -o docs-requirements.txt alabaster==1.0.0 # via sphinx -attrs==25.1.0 +attrs==25.3.0 # via # -r docs-requirements.in # outcome babel==2.17.0 # via sphinx -beautifulsoup4==4.12.3 +beautifulsoup4==4.13.3 # via sphinx-codeautolink certifi==2025.1.31 # via requests @@ -24,7 +24,7 @@ colorama==0.4.6 ; sys_platform == 'win32' # via # click # sphinx -cryptography==44.0.1 +cryptography==44.0.2 # via pyopenssl docutils==0.21.2 # via @@ -40,7 +40,7 @@ imagesize==1.4.1 # via sphinx immutables==0.21 # via -r docs-requirements.in -jinja2==3.1.5 +jinja2==3.1.6 # via # -r docs-requirements.in # sphinx @@ -55,10 +55,12 @@ pycparser==2.22 ; os_name == 'nt' or platform_python_implementation != 'PyPy' # via cffi pygments==2.19.1 # via sphinx -pyopenssl==24.3.0 +pyopenssl==25.0.0 # via -r docs-requirements.in requests==2.32.3 # via sphinx +roman-numerals-py==3.1.0 + # via sphinx sniffio==1.3.1 # via -r docs-requirements.in snowballstemmer==2.2.0 @@ -67,17 +69,14 @@ sortedcontainers==2.4.0 # via -r docs-requirements.in soupsieve==2.6 # via beautifulsoup4 -sphinx==8.1.3 +sphinx==8.2.3 # via # -r docs-requirements.in # sphinx-codeautolink - # sphinx-hoverxref # sphinx-rtd-theme # sphinxcontrib-jquery # sphinxcontrib-trio -sphinx-codeautolink==0.16.2 - # via -r docs-requirements.in -sphinx-hoverxref==1.4.2 +sphinx-codeautolink==0.17.4 # via -r docs-requirements.in sphinx-rtd-theme==3.0.2 # via -r docs-requirements.in @@ -90,7 +89,6 @@ sphinxcontrib-htmlhelp==2.1.0 sphinxcontrib-jquery==4.1 # via # -r docs-requirements.in - # sphinx-hoverxref # sphinx-rtd-theme sphinxcontrib-jsmath==1.0.1 # via sphinx @@ -102,5 +100,9 @@ sphinxcontrib-trio==1.1.2 # via -r docs-requirements.in towncrier==24.8.0 # via -r docs-requirements.in +typing-extensions==4.13.0 + # via + # beautifulsoup4 + # pyopenssl urllib3==2.3.0 # via requests diff --git a/docs/source/_static/hackrtd.css b/docs/source/_static/styles.css similarity index 76% rename from docs/source/_static/hackrtd.css rename to docs/source/_static/styles.css index 48401f2389..ab5888f450 100644 --- a/docs/source/_static/hackrtd.css +++ b/docs/source/_static/styles.css @@ -1,10 +1,3 @@ -/* Temporary hack to work around bug in rtd theme 2.0 through 2.4 - See https://github.com/rtfd/sphinx_rtd_theme/pull/382 -*/ -pre { - line-height: normal !important; -} - /* Make .. deprecation:: blocks visible * (by default they're entirely unstyled) */ @@ -23,6 +16,10 @@ pre { * thingummy also has an
in it, and putting the ornament on that looks * *really weird*. (In particular, the background color is wrong.) */ +.rst-content hr { + overflow: visible; +} + .rst-content hr:after { /* This .svg gets displayed on top of the middle of the hrule. It has a box * behind the logo that's colored to match the RTD theme body background @@ -41,39 +38,21 @@ pre { /* Hacks to make the upper-left logo area look nicer */ -.wy-side-nav-search { - /* Lighter background color to match logo */ - background-color: #d2e7fa !important; -} - .wy-side-nav-search > a { color: #306998 !important; } -.wy-side-nav-search > a.logo { - display: block !important; - padding-bottom: 0.809em !important; +/* vertically center version text */ +.wy-side-nav-search > a { + display: flex; + align-items: center; + margin: auto; + width: max-content; } .wy-side-nav-search > a img.logo { - display: inline !important; - padding: 0 !important; -} - -.trio-version { - display: inline; - /* I *cannot* figure out how to get the version text vertically centered - on the logo. Oh well... - height: 32px; - line-height: 32px; - */ -} - -.wy-side-nav-search > a { - /* Mostly this is just to simplify things, so we don't have margin/padding - * on both the and the inside it */ - margin: 0 !important; - padding: 0 !important; + margin-left: 0; + margin-right: 5px; } /* Get rid of the weird super dark "Contents" label that wastes vertical space diff --git a/docs/source/_templates/layout.html b/docs/source/_templates/layout.html index dbebf5c2ae..6ca0353cfb 100644 --- a/docs/source/_templates/layout.html +++ b/docs/source/_templates/layout.html @@ -12,7 +12,7 @@