Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: pytest-dev/pytest-asyncio
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v0.25.0
Choose a base ref
...
head repository: pytest-dev/pytest-asyncio
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: main
Choose a head ref
Loading
Showing with 804 additions and 1,813 deletions.
  1. +103 −30 .github/workflows/main.yml
  2. +10 −6 .pre-commit-config.yaml
  3. 0 tests/loop_fixture_scope/__init__.py → changelog.d/.gitkeep
  4. +9 −9 dependencies/default/constraints.txt
  5. +9 −9 dependencies/docs/constraints.txt
  6. +5 −1 docs/concepts.rst
  7. +24 −0 docs/how-to-guides/change_default_test_loop.rst
  8. +1 −1 docs/how-to-guides/index.rst
  9. +6 −1 docs/how-to-guides/multiple_loops_example.py
  10. +0 −10 docs/how-to-guides/run_session_tests_in_same_loop.rst
  11. +0 −10 docs/how-to-guides/session_scoped_loop_example.py
  12. +0 −63 docs/how-to-guides/test_session_scoped_loop_example.py
  13. +1 −1 docs/how-to-guides/uvloop.rst
  14. +92 −1 docs/reference/changelog.rst
  15. +6 −0 docs/reference/configuration.rst
  16. +0 −5 docs/reference/fixtures/event_loop_example.py
  17. +7 −1 docs/reference/fixtures/event_loop_policy_example.py
  18. +7 −2 docs/reference/fixtures/event_loop_policy_parametrized_example.py
  19. +0 −18 docs/reference/fixtures/index.rst
  20. +7 −3 docs/reference/markers/class_scoped_loop_custom_policies_strict_mode_example.py
  21. +3 −5 docs/reference/markers/index.rst
  22. +58 −11 pyproject.toml
  23. +204 −570 pytest_asyncio/plugin.py
  24. +0 −32 tests/async_fixtures/test_async_fixtures_contextvars.py
  25. +0 −30 tests/async_fixtures/test_async_fixtures_scope.py
  26. +0 −63 tests/async_fixtures/test_async_fixtures_with_finalizer.py
  27. +0 −53 tests/async_fixtures/test_async_gen_fixtures.py
  28. +0 −48 tests/async_fixtures/test_parametrized_loop.py
  29. +0 −31 tests/conftest.py
  30. +0 −37 tests/hypothesis/test_base.py
  31. +0 −17 tests/loop_fixture_scope/conftest.py
  32. +0 −19 tests/loop_fixture_scope/test_loop_fixture_scope.py
  33. +0 −23 tests/markers/test_class_scope.py
  34. +1 −24 tests/markers/test_function_scope.py
  35. +36 −0 tests/markers/test_mixed_scope.py
  36. +0 −75 tests/markers/test_module_scope.py
  37. +0 −42 tests/markers/test_package_scope.py
  38. +0 −22 tests/markers/test_session_scope.py
  39. +18 −5 tests/modes/test_strict_mode.py
  40. +77 −0 tests/test_asyncio_mark.py
  41. +0 −16 tests/test_dependent_fixtures.py
  42. +88 −0 tests/test_event_loop_fixture.py
  43. +0 −148 tests/test_event_loop_fixture_finalizer.py
  44. +0 −113 tests/test_event_loop_fixture_override_deprecation.py
  45. +0 −168 tests/test_explicit_event_loop_fixture_request.py
  46. +0 −72 tests/test_multiloop.py
  47. +0 −7 tests/test_simple.py
  48. +0 −10 tests/test_subprocess.py
  49. +30 −0 tests/test_task_cleanup.py
  50. +2 −1 tox.ini
133 changes: 103 additions & 30 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -10,6 +10,8 @@ on:
merge_group:
workflow_dispatch:

permissions: {}

env:
PYTHON_LATEST: 3.13

@@ -24,6 +26,7 @@ jobs:
- uses: actions/checkout@v4
with:
fetch-depth: 0
persist-credentials: false
- uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_LATEST }}
@@ -58,20 +61,26 @@ jobs:
test:
name: ${{ matrix.os }} - Python ${{ matrix.python-version }}
runs-on: ${{ matrix.os }}-latest

continue-on-error: ${{ !matrix.required }}
strategy:
matrix:
os: [ubuntu, windows]
python-version: ['3.9', '3.10', '3.11', '3.12', '3.13']
required: [true]
include:
- os: ubuntu
python-version: 3.14-dev
required: false
- os: windows
python-version: 3.14-dev
required: false


steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
if: "!endsWith(matrix.python-version, '-dev')"
with:
python-version: ${{ matrix.python-version }}
- uses: deadsnakes/action@v3.2.0
if: endsWith(matrix.python-version, '-dev')
persist-credentials: false
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
@@ -91,17 +100,25 @@ jobs:
path: coverage/coverage.*
if-no-files-found: error

lint-github-actions:
name: Lint GitHub Actions
permissions:
security-events: write
uses: zizmorcore/workflow/.github/workflows/reusable-zizmor.yml@3bb5e95068d0f44b6d2f3f7e91379bed1d2f96a8

check:
name: Check
if: always()
needs: [lint, test]
runs-on: ubuntu-latest
steps:
- name: Decide whether the needed jobs succeeded or failed
uses: re-actors/alls-green@release/v1
uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # v1.2.2
with:
jobs: ${{ toJSON(needs) }}
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_LATEST }}
@@ -120,47 +137,103 @@ jobs:
coverage combine
coverage xml
- name: Upload coverage report
uses: codecov/codecov-action@v5
uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3
with:
files: coverage.xml
fail_ci_if_error: true
token: ${{ secrets.CODECOV_TOKEN }}

deploy:
name: Deploy
environment: release
# Run only on pushing a tag
if: github.event_name == 'push' && contains(github.ref, 'refs/tags/')
create-github-release:
name: Create GitHub release
needs: [lint, check]
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
persist-credentials: false
- name: Install Python
uses: actions/setup-python@v5
- name: Install towncrier
run: pip install towncrier==24.8.0
- name: Install pandoc
run: |
sudo apt-get install -y pandoc
- name: Checkout
uses: actions/checkout@v4
- name: Install pytest-asyncio
run: pip install .
- name: Compile Release Notes Draft
if: ${{ !contains(github.ref, 'refs/tags/') }}
run: towncrier build --draft --version "${version}" > release-notes.rst
env:
version: ${{ needs.lint.outputs.version }}
- name: Extract release notes from Git tag
if: github.event_name == 'push' && contains(github.ref, 'refs/tags/')
run: |
set -e
git fetch --tags --force # see https://github.com/actions/checkout/issues/290
git for-each-ref "${GITHUB_REF}" --format='%(contents)' > release-notes.rst
# Strip PGP signature from signed tags
sed -i "/-----BEGIN PGP SIGNATURE-----/,/-----END PGP SIGNATURE-----\n/d" release-notes.rst
- name: Convert Release Notes to Markdown
run: |
pandoc --wrap=preserve -o release-notes.md release-notes.rst
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: release-notes.md
path: release-notes.md
- name: Download distributions
uses: actions/download-artifact@v4
with:
name: dist
path: dist
- name: Collected dists
run: |
tree dist
- name: Convert README.rst to Markdown
run: |
pandoc -s -o README.md README.rst
- name: PyPI upload
uses: pypa/gh-action-pypi-publish@v1.12.3
with:
attestations: true
packages-dir: dist
password: ${{ secrets.PYPI_API_TOKEN }}
- name: GitHub Release
uses: ncipollo/release-action@v1
- name: Create GitHub Release
if: github.event_name == 'push' && contains(github.ref, 'refs/tags/')
uses: ncipollo/release-action@bcfe5470707e8832e12347755757cec0eb3c22af # v1.18.0
with:
name: pytest-asyncio ${{ needs.lint.outputs.version }}
artifacts: dist/*
bodyFile: README.md
bodyFile: release-notes.md
prerelease: ${{ needs.lint.outputs.prerelease }}
token: ${{ secrets.GITHUB_TOKEN }}

publish-test-pypi:
name: Publish packages to test.pypi.org
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
needs: [lint, check]
runs-on: ubuntu-latest
permissions:
id-token: write
steps:
- name: Download distributions
uses: actions/download-artifact@v4
with:
name: dist
path: dist
- name: Upload to test.pypi.org
uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc # v1.12.4
with:
repository-url: https://test.pypi.org/legacy/

publish-pypi:
name: Publish packages to pypi.org
environment: release
if: github.event_name == 'push' && contains(github.ref, 'refs/tags/')
needs: [lint, check]
runs-on: ubuntu-latest
permissions:
id-token: write
steps:
- name: Download distributions
uses: actions/download-artifact@v4
with:
name: dist
path: dist
- name: Collected dists
run: |
tree dist
- name: PyPI upload
uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc # v1.12.4
16 changes: 10 additions & 6 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -6,7 +6,7 @@ repos:
- id: check-merge-conflict
exclude: rst$
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.8.2
rev: v0.12.4
hooks:
- id: ruff
args: [--fix]
@@ -15,7 +15,7 @@ repos:
hooks:
- id: yesqa
- repo: https://github.com/Zac-HD/shed
rev: 2024.10.1
rev: 2025.6.1
hooks:
- id: shed
args:
@@ -42,7 +42,7 @@ repos:
- id: check-yaml
- id: debug-statements
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.13.0
rev: v1.17.0
hooks:
- id: mypy
exclude: ^(docs|tests)/.*
@@ -53,7 +53,7 @@ repos:
hooks:
- id: python-use-type-annotations
- repo: https://github.com/rhysd/actionlint
rev: v1.7.4
rev: v1.7.7
hooks:
- id: actionlint-docker
args:
@@ -65,15 +65,19 @@ repos:
- 'SC1004:'
stages: [manual]
- repo: https://github.com/sirosen/check-jsonschema
rev: 0.30.0
rev: 0.33.2
hooks:
- id: check-github-actions
- repo: https://github.com/tox-dev/pyproject-fmt
rev: v2.5.0
rev: v2.6.0
hooks:
- id: pyproject-fmt
# https://pyproject-fmt.readthedocs.io/en/latest/#calculating-max-supported-python-version
additional_dependencies: [tox>=4.9]
- repo: https://github.com/zizmorcore/zizmor-pre-commit
rev: v1.11.0
hooks:
- id: zizmor
ci:
skip:
- actionlint-docker
File renamed without changes.
18 changes: 9 additions & 9 deletions dependencies/default/constraints.txt
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
attrs==24.2.0
coverage==7.6.9
exceptiongroup==1.2.2
hypothesis==6.122.3
iniconfig==2.0.0
packaging==24.2
pluggy==1.5.0
pytest==8.3.4
attrs==25.3.0
coverage==7.9.2
exceptiongroup==1.3.0
hypothesis==6.136.1
iniconfig==2.1.0
packaging==25.0
pluggy==1.6.0
pytest==8.4.1
sortedcontainers==2.4.0
tomli==2.2.1
typing_extensions==4.12.2
typing_extensions==4.14.1
18 changes: 9 additions & 9 deletions dependencies/docs/constraints.txt
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
alabaster==0.7.16
Babel==2.16.0
certifi==2024.8.30
charset-normalizer==3.4.0
Babel==2.17.0
certifi==2025.7.14
charset-normalizer==3.4.2
docutils==0.21.2
idna==3.10
imagesize==1.4.1
Jinja2==3.1.4
Jinja2==3.1.6
MarkupSafe==3.0.2
packaging==24.2
Pygments==2.18.0
requests==2.32.3
snowballstemmer==2.2.0
packaging==25.0
Pygments==2.19.2
requests==2.32.4
snowballstemmer==3.0.1
Sphinx==8.0.2
sphinx-rtd-theme==3.0.2
sphinxcontrib-applehelp==2.0.0
@@ -20,4 +20,4 @@ sphinxcontrib-jquery==4.1
sphinxcontrib-jsmath==1.0.1
sphinxcontrib-qthelp==2.0.0
sphinxcontrib-serializinghtml==2.0.0
urllib3==2.2.3
urllib3==2.5.0
6 changes: 5 additions & 1 deletion docs/concepts.rst
Original file line number Diff line number Diff line change
@@ -47,8 +47,12 @@ Assigning neighboring tests to different event loop scopes is discouraged as it
Test discovery modes
====================

Pytest-asyncio provides two modes for test discovery, *strict* and *auto*.
Pytest-asyncio provides two modes for test discovery, *strict* and *auto*. This can be set through Pytest's ``--asyncio-mode`` command line flag, or through the configuration file:

.. code-block:: toml
[tool.pytest.ini_options]
asyncio_mode = "auto" # or "strict"
Strict mode
-----------
24 changes: 24 additions & 0 deletions docs/how-to-guides/change_default_test_loop.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
=======================================================
How to change the default event loop scope of all tests
=======================================================
The :ref:`configuration/asyncio_default_test_loop_scope` configuration option sets the default event loop scope for asynchronous tests. The following code snippets configure all tests to run in a session-scoped loop by default:

.. code-block:: ini
:caption: pytest.ini
[pytest]
asyncio_default_test_loop_scope = session
.. code-block:: toml
:caption: pyproject.toml
[tool.pytest.ini_options]
asyncio_default_test_loop_scope = "session"
.. code-block:: ini
:caption: setup.cfg
[tool:pytest]
asyncio_default_test_loop_scope = session
Please refer to :ref:`configuration/asyncio_default_test_loop_scope` for other valid scopes.
2 changes: 1 addition & 1 deletion docs/how-to-guides/index.rst
Original file line number Diff line number Diff line change
@@ -9,10 +9,10 @@ How-To Guides
migrate_from_0_23
change_fixture_loop
change_default_fixture_loop
change_default_test_loop
run_class_tests_in_same_loop
run_module_tests_in_same_loop
run_package_tests_in_same_loop
run_session_tests_in_same_loop
multiple_loops
uvloop
test_item_is_async
7 changes: 6 additions & 1 deletion docs/how-to-guides/multiple_loops_example.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import asyncio
from asyncio import DefaultEventLoopPolicy
import warnings

with warnings.catch_warnings():
warnings.simplefilter("ignore", DeprecationWarning)
from asyncio import DefaultEventLoopPolicy

import pytest

@@ -20,5 +24,6 @@ def event_loop_policy(request):


@pytest.mark.asyncio
@pytest.mark.filterwarnings("ignore::DeprecationWarning")
async def test_uses_custom_event_loop_policy():
assert isinstance(asyncio.get_event_loop_policy(), CustomEventLoopPolicy)
10 changes: 0 additions & 10 deletions docs/how-to-guides/run_session_tests_in_same_loop.rst

This file was deleted.

Loading