pax_global_header 0000666 0000000 0000000 00000000064 15205265572 0014523 g ustar 00root root 0000000 0000000 52 comment=6e14cd2af9292dca1fa2b027a06bbc40b0e0e425
pytest-dev-pytest-asyncio-dbe96ef/ 0000775 0000000 0000000 00000000000 15205265572 0017406 5 ustar 00root root 0000000 0000000 pytest-dev-pytest-asyncio-dbe96ef/.github/ 0000775 0000000 0000000 00000000000 15205265572 0020746 5 ustar 00root root 0000000 0000000 pytest-dev-pytest-asyncio-dbe96ef/.github/CODEOWNERS 0000664 0000000 0000000 00000000036 15205265572 0022340 0 ustar 00root root 0000000 0000000 * @asvetlov @seifertm @Tinche
pytest-dev-pytest-asyncio-dbe96ef/.github/actionlint-matcher.json 0000664 0000000 0000000 00000000663 15205265572 0025433 0 ustar 00root root 0000000 0000000 {
"problemMatcher": [
{
"owner": "actionlint",
"pattern": [
{
"code": 5,
"column": 3,
"file": 1,
"line": 2,
"message": 4,
"regexp": "^(?:\\x1b\\[\\d+m)?(.+?)(?:\\x1b\\[\\d+m)*:(?:\\x1b\\[\\d+m)*(\\d+)(?:\\x1b\\[\\d+m)*:(?:\\x1b\\[\\d+m)*(\\d+)(?:\\x1b\\[\\d+m)*: (?:\\x1b\\[\\d+m)*(.+?)(?:\\x1b\\[\\d+m)* \\[(.+?)\\]$"
}
]
}
]
}
pytest-dev-pytest-asyncio-dbe96ef/.github/chronographer.yml 0000664 0000000 0000000 00000001001 15205265572 0024322 0 ustar 00root root 0000000 0000000 ---
branch-protection-check-name: Timeline protection
action-hints:
inline-markdown: |
This PR is missing news fragments.
Add a file under `changelog.d/`, like:
`123.changed.rst`. You can use the Towncrier CLI to create it. Make sure to match the writing style with the existing change log content.
If this change isn't user-facing, ask the maintainers to apply the `skip-changelog` label. Please, include a justification.
enforce-name:
suffix: .rst
labels:
skip-changelog: skip-changelog
pytest-dev-pytest-asyncio-dbe96ef/.github/dependabot.yml 0000664 0000000 0000000 00000000510 15205265572 0023572 0 ustar 00root root 0000000 0000000 ---
version: 2
updates:
- package-ecosystem: pip
directory: /
schedule:
interval: weekly
open-pull-requests-limit: 10
target-branch: main
cooldown:
default-days: 7
- package-ecosystem: github-actions
directory: /
schedule:
interval: daily
open-pull-requests-limit: 10
cooldown:
default-days: 7
pytest-dev-pytest-asyncio-dbe96ef/.github/workflows/ 0000775 0000000 0000000 00000000000 15205265572 0023003 5 ustar 00root root 0000000 0000000 pytest-dev-pytest-asyncio-dbe96ef/.github/workflows/main.yml 0000664 0000000 0000000 00000021456 15205265572 0024462 0 ustar 00root root 0000000 0000000 ---
name: CI
on:
push:
branches: [main]
tags: [v*]
pull_request:
branches: [main]
merge_group:
workflow_dispatch:
permissions: {}
env:
PYTHON_LATEST: 3.13
jobs:
build:
name: Build package
runs-on: ubuntu-latest
outputs:
version: ${{ steps.version.outputs.version }}
prerelease: ${{ steps.version.outputs.prerelease }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
with:
fetch-depth: 0
persist-credentials: false
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405
with:
python-version: ${{ env.PYTHON_LATEST }}
- name: Install tox
run: python -m pip install tox
- name: Build package and check distributions
run: tox run -e build
- name: List result
run: ls -l dist
- name: Install pytest-asyncio
run: pip install .
- name: Get version info
id: version
run: python ./tools/get-version.py >> $GITHUB_OUTPUT
- name: Upload artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
with:
name: dist
path: dist
lint:
name: Run linters
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
with:
fetch-depth: 0
persist-credentials: false
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405
with:
python-version: ${{ env.PYTHON_LATEST }}
- name: Install GitHub matcher for ActionLint checker
run: |
echo "::add-matcher::.github/actionlint-matcher.json"
- name: Install pre-commit
run: python -m pip install pre-commit
- name: Run pre-commit checks
run: pre-commit run --all-files --show-diff-on-failure
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.10', '3.11', '3.12', '3.13', '3.14', 3.14t]
required: [true]
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
with:
persist-credentials: false
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -VV
python -m site
python -m pip install --upgrade pip
python -m pip install --upgrade coverage[toml] virtualenv tox tox-gh-actions
- name: Run tox targets for ${{ matrix.python-version }}
run: python -m tox
- name: Store coverage data
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
if: "!endsWith(matrix.os, 'windows')"
with:
name: coverage-python-${{ matrix.python-version }}
path: coverage/coverage.*
if-no-files-found: error
test-pytest-dev:
name: ubuntu - Python latest - pytest main
runs-on: ubuntu-latest
continue-on-error: true
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
with:
persist-credentials: false
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405
with:
python-version: ${{ env.PYTHON_LATEST }}
- name: Install dependencies
run: |
python -VV
python -m site
python -m pip install --upgrade pip
python -m pip install --upgrade tox
- name: Run tests against pytest main
run: python -m tox run -e pytest-dev
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: [build, lint, test]
runs-on: ubuntu-latest
steps:
- name: Decide whether the needed jobs succeeded or failed
uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # v1.2.2
with:
jobs: ${{ toJSON(needs) }}
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
with:
persist-credentials: false
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405
with:
python-version: ${{ env.PYTHON_LATEST }}
- name: Install Coverage.py
run: |
set -xe
python -m pip install --upgrade coverage[toml]
- name: Download coverage data for all test runs
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
with:
pattern: coverage-*
path: coverage
merge-multiple: true
- name: Combine coverage data and create report
run: |
coverage combine
coverage xml
- name: Upload coverage report
uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1
with:
files: coverage.xml
fail_ci_if_error: true
token: ${{ secrets.CODECOV_TOKEN }}
create-github-release:
name: Create GitHub release
needs: [check]
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
with:
fetch-depth: 0
persist-credentials: false
- name: Install Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405
- name: Install towncrier
run: pip install towncrier==24.8.0
- name: Install pandoc
run: |
sudo apt-get install -y pandoc
- 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.build.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 signature from signed tags
sed -i -e "/-----BEGIN PGP SIGNATURE-----/,/-----END PGP SIGNATURE-----\n/d" \
-e "/-----BEGIN SSH SIGNATURE-----/,/-----END SSH 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@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
with:
name: release-notes.md
path: release-notes.md
- name: Download distributions
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
with:
name: dist
path: dist
- name: Create GitHub Release
if: github.event_name == 'push' && contains(github.ref, 'refs/tags/')
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PRERELEASE: ${{ needs.build.outputs.prerelease }}
run: |
set +e
TAG_NAME="${GITHUB_REF#refs/tags/}"
gh release view "${TAG_NAME}" >/dev/null && echo "Release already exists. Exiting." && exit 0
gh release create "${TAG_NAME}" \
--title "pytest-asyncio ${TAG_NAME}" \
--notes-file release-notes.md \
--draft \
$( [ "${PRERELEASE}" = "true" ] && echo "--prerelease" ) \
dist/*
publish-test-pypi:
name: Publish packages to test.pypi.org
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
needs: [create-github-release]
runs-on: ubuntu-latest
permissions:
id-token: write
steps:
- name: Download distributions
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
with:
name: dist
path: dist
- name: Upload to test.pypi.org
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
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: [create-github-release]
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write
steps:
- name: Download distributions
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
with:
name: dist
path: dist
- name: PyPI upload
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
- name: Publish GitHub Release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set +e
TAG_NAME="${GITHUB_REF#refs/tags/}"
gh release edit "${TAG_NAME}" --draft=false
pytest-dev-pytest-asyncio-dbe96ef/.gitignore 0000664 0000000 0000000 00000001365 15205265572 0021403 0 ustar 00root root 0000000 0000000 # Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
# C extensions
*.so
# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg
.hypothesis/
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
coverage/
.pytest_cache
nosetests.xml
coverage.xml
*,cover
# Translations
*.mo
*.pot
# Django stuff:
*.log
# Sphinx documentation
docs/_build/
# PyBuilder
target/
.venv*
.idea
.vscode
# pyenv
.python-version
pytest-dev-pytest-asyncio-dbe96ef/.pre-commit-config.yaml 0000664 0000000 0000000 00000003515 15205265572 0023673 0 ustar 00root root 0000000 0000000 ---
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-merge-conflict
exclude: rst$
- id: check-case-conflict
- id: check-json
- id: check-xml
- id: check-yaml
- id: debug-statements
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.14.13
hooks:
- id: ruff
args: [--fix]
- repo: https://github.com/asottile/pyupgrade
rev: v3.21.2
hooks:
- id: pyupgrade
- repo: https://github.com/asottile/yesqa
rev: v1.5.0
hooks:
- id: yesqa
- repo: https://github.com/Zac-HD/shed
rev: 2025.6.1
hooks:
- id: shed
args:
- --refactor
types_or:
- python
- markdown
- rst
- repo: https://github.com/jumanjihouse/pre-commit-hook-yamlfmt
rev: 0.2.3
hooks:
- id: yamlfmt
args: [--mapping, '2', --sequence, '2', --offset, '0']
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.19.1
hooks:
- id: mypy
exclude: ^(docs|tests)/.*
additional_dependencies:
- pytest
- repo: https://github.com/pre-commit/pygrep-hooks
rev: v1.10.0
hooks:
- id: python-use-type-annotations
- repo: https://github.com/rhysd/actionlint
rev: v1.7.10
hooks:
- id: actionlint-docker
args:
- -ignore
- 'SC2155:'
- -ignore
- 'SC2086:'
- -ignore
- 'SC1004:'
stages: [manual]
- repo: https://github.com/sirosen/check-jsonschema
rev: 0.36.0
hooks:
- id: check-github-actions
- repo: https://github.com/tox-dev/pyproject-fmt
rev: v2.11.1
hooks:
- id: pyproject-fmt
# https://pyproject-fmt.readthedocs.io/en/latest/#calculating-max-supported-python-version
additional_dependencies: [tox>=4.28]
- repo: https://github.com/zizmorcore/zizmor-pre-commit
rev: v1.22.0
hooks:
- id: zizmor
ci:
skip:
- actionlint-docker
- check-github-actions
pytest-dev-pytest-asyncio-dbe96ef/.readthedocs.yaml 0000664 0000000 0000000 00000001115 15205265572 0022633 0 ustar 00root root 0000000 0000000 ---
version: 2
build:
os: ubuntu-24.04
tools:
python: >-
3.12
commands:
- >-
PYTHONWARNINGS=error
python3 -Im venv "${READTHEDOCS_VIRTUALENV_PATH}"
- >-
PYTHONWARNINGS=error
"${READTHEDOCS_VIRTUALENV_PATH}"/bin/python -Im
pip install tox
- >-
PYTHONWARNINGS=error
"${READTHEDOCS_VIRTUALENV_PATH}"/bin/python -Im
tox -e docs --notest -vvvvv
- >-
PYTHONWARNINGS=error
"${READTHEDOCS_VIRTUALENV_PATH}"/bin/python -Im
tox -e docs --skip-pkg-install -q
--
"${READTHEDOCS_OUTPUT}"/html
-b html
-D language=en
pytest-dev-pytest-asyncio-dbe96ef/LICENSE 0000664 0000000 0000000 00000026074 15205265572 0020424 0 ustar 00root root 0000000 0000000 Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
pytest-dev-pytest-asyncio-dbe96ef/MANIFEST.in 0000664 0000000 0000000 00000000117 15205265572 0021143 0 ustar 00root root 0000000 0000000 recursive-exclude .github *
exclude .gitignore
exclude .pre-commit-config.yaml
pytest-dev-pytest-asyncio-dbe96ef/Makefile 0000664 0000000 0000000 00000001313 15205265572 0021044 0 ustar 00root root 0000000 0000000 .PHONY: clean clean-build clean-pyc clean-test lint test
clean: clean-build clean-pyc clean-test ## remove all build, test, coverage and Python artifacts
clean-build: ## remove build artifacts
rm -fr build/
rm -fr dist/
rm -fr .eggs/
find . -name '*.egg-info' -exec rm -fr {} +
find . -name '*.egg' -exec rm -f {} +
clean-pyc: ## remove Python file artifacts
find . -name '*.pyc' -exec rm -f {} +
find . -name '*.pyo' -exec rm -f {} +
find . -name '*~' -exec rm -f {} +
find . -name '__pycache__' -exec rm -fr {} +
clean-test: ## remove test and coverage artifacts
rm -fr .tox/
rm -fr coverage/
rm -fr htmlcov/
test:
coverage run -m pytest
install:
pip install -U pre-commit
pre-commit install
pytest-dev-pytest-asyncio-dbe96ef/README.rst 0000664 0000000 0000000 00000004525 15205265572 0021103 0 ustar 00root root 0000000 0000000 pytest-asyncio
==============
.. image:: https://img.shields.io/pypi/v/pytest-asyncio.svg
:target: https://pypi.python.org/pypi/pytest-asyncio
.. image:: https://github.com/pytest-dev/pytest-asyncio/workflows/CI/badge.svg
:target: https://github.com/pytest-dev/pytest-asyncio/actions?workflow=CI
.. image:: https://codecov.io/gh/pytest-dev/pytest-asyncio/branch/main/graph/badge.svg
:target: https://codecov.io/gh/pytest-dev/pytest-asyncio
.. image:: https://img.shields.io/pypi/pyversions/pytest-asyncio.svg
:target: https://github.com/pytest-dev/pytest-asyncio
:alt: Supported Python versions
.. image:: https://img.shields.io/badge/Matrix-%23pytest--asyncio-brightgreen
:alt: Matrix chat room: #pytest-asyncio
:target: https://matrix.to/#/#pytest-asyncio:matrix.org
`pytest-asyncio `_ is a `pytest `_ plugin. It facilitates testing of code that uses the `asyncio `_ library.
Specifically, pytest-asyncio provides support for coroutines as test functions. This allows users to *await* code inside their tests. For example, the following code is executed as a test item by pytest:
.. code-block:: python
@pytest.mark.asyncio
async def test_some_asyncio_code():
res = await library.do_something()
assert b"expected result" == res
More details can be found in the `documentation `_.
Note that test classes subclassing the standard `unittest `__ library are not supported. Users
are advised to use `unittest.IsolatedAsyncioTestCase `__
or an async framework such as `asynctest `__.
pytest-asyncio is available under the `Apache License 2.0 `_.
Installation
------------
To install pytest-asyncio, simply:
.. code-block:: bash
$ pip install pytest-asyncio
This is enough for pytest to pick up pytest-asyncio.
Contributing
------------
Contributions are very welcome. Tests can be run with ``tox``, please ensure
the coverage at least stays the same before you submit a pull request.
pytest-dev-pytest-asyncio-dbe96ef/SECURITY.rst 0000664 0000000 0000000 00000000336 15205265572 0021411 0 ustar 00root root 0000000 0000000 Security contact information
============================
To report a security vulnerability, please use the `Tidelift security contact. `__ Tidelift will coordinate the fix and disclosure.
pytest-dev-pytest-asyncio-dbe96ef/changelog.d/ 0000775 0000000 0000000 00000000000 15205265572 0021557 5 ustar 00root root 0000000 0000000 pytest-dev-pytest-asyncio-dbe96ef/changelog.d/.gitkeep 0000664 0000000 0000000 00000000000 15205265572 0023176 0 ustar 00root root 0000000 0000000 pytest-dev-pytest-asyncio-dbe96ef/constraints.txt 0000664 0000000 0000000 00000002456 15205265572 0022525 0 ustar 00root root 0000000 0000000 alabaster==1.0.0
annotated-types-0.7.0
attrs==26.1.0
babel==2.18.0
backports.asyncio.runner==1.2.0
backports.tarfile==1.2.0
certifi==2026.4.22
charset-normalizer==3.4.7
check-wheel-contents==0.6.3
cffi==2.0.0
click==8.4.0
coverage==7.14.0
cryptography==48.0.0
docutils==0.21.2
exceptiongroup==1.3.1
hypothesis==6.152.8
iniconfig==2.3.0
id==1.6.1
idna==3.15
imagesize==2.0.0
importlib_metadata==9.0.0
iniconfig==2.3.0
jaraco.classes==3.4.0
jaraco.context==6.1.2
jaraco.functools==4.5.0
jeepney==0.9.0
Jinja2==3.1.6
keyring==25.7.0
markdown-it-py==4.2.0
MarkupSafe==3.0.3
mdurl==0.1.2
more-itertools==11.0.2
nh3==0.3.5
packaging==26.2
pluggy==1.6.0
Pygments==2.20.0
pycparser==3.0
pydantic==2.13.4
pydantic-core==2.46.4
pytest==9.0.3
readme-renderer==44.0
requests==2.34.2
requests-toolbelt==1.0.0
rfc3986==2.0.0
rich==15.0.0
SecretStorage==3.5.0
setuptools==82.0.1
setuptools-scm==10.0.5
snowballstemmer==3.0.1
sortedcontainers==2.4.0
Sphinx==8.1.3
sphinx-tabs==3.5.0
sphinx-rtd-theme==3.1.0
sphinxcontrib-applehelp==2.0.0
sphinxcontrib-devhelp==2.0.0
sphinxcontrib-htmlhelp==2.1.0
sphinxcontrib-jquery==4.1
sphinxcontrib-jsmath==1.0.1
sphinxcontrib-qthelp==2.0.0
sphinxcontrib-serializinghtml==2.0.0
tomli==2.4.1
twine==6.2.0
typing_extensions==4.15.0
typing-inspection==0.4.2
urllib3==2.7.0
wheel-filename==1.4.2
zipp==4.1.0
pytest-dev-pytest-asyncio-dbe96ef/dependencies/ 0000775 0000000 0000000 00000000000 15205265572 0022034 5 ustar 00root root 0000000 0000000 pytest-dev-pytest-asyncio-dbe96ef/dependencies/docs/ 0000775 0000000 0000000 00000000000 15205265572 0022764 5 ustar 00root root 0000000 0000000 pytest-dev-pytest-asyncio-dbe96ef/dependencies/docs/constraints.txt 0000664 0000000 0000000 00000000750 15205265572 0026076 0 ustar 00root root 0000000 0000000 alabaster==0.7.16
Babel==2.17.0
certifi==2025.8.3
charset-normalizer==3.4.3
docutils==0.21.2
idna==3.10
imagesize==1.4.1
Jinja2==3.1.6
MarkupSafe==3.0.2
packaging==25.0
Pygments==2.19.2
requests==2.32.5
snowballstemmer==3.0.1
Sphinx==8.0.2
sphinx-rtd-theme==3.0.2
sphinxcontrib-applehelp==2.0.0
sphinxcontrib-devhelp==2.0.0
sphinxcontrib-htmlhelp==2.1.0
sphinxcontrib-jquery==4.1
sphinxcontrib-jsmath==1.0.1
sphinxcontrib-qthelp==2.0.0
sphinxcontrib-serializinghtml==2.0.0
urllib3==2.5.0
pytest-dev-pytest-asyncio-dbe96ef/dependencies/pytest-min/ 0000775 0000000 0000000 00000000000 15205265572 0024145 5 ustar 00root root 0000000 0000000 pytest-dev-pytest-asyncio-dbe96ef/dependencies/pytest-min/constraints.txt 0000664 0000000 0000000 00000000553 15205265572 0027260 0 ustar 00root root 0000000 0000000 argcomplete==3.1.2
attrs==23.1.0
certifi==2023.7.22
charset-normalizer==3.3.1
coverage==7.3.2
elementpath==4.1.5
exceptiongroup==1.1.3
hypothesis==6.88.3
idna==3.4
iniconfig==2.0.0
mock==5.1.0
nose==1.3.7
packaging==23.2
pluggy==1.5.0
py==1.11.0
Pygments==2.16.1
pytest==8.4.0
requests==2.31.0
sortedcontainers==2.4.0
tomli==2.0.1
urllib3==2.0.7
xmlschema==2.5.0
pytest-dev-pytest-asyncio-dbe96ef/dependencies/pytest-min/requirements.txt 0000664 0000000 0000000 00000000211 15205265572 0027423 0 ustar 00root root 0000000 0000000 # Always adjust install_requires in setup.cfg and requirements.txt
# when changing minimum version dependencies
pytest[testing] == 8.4.0
pytest-dev-pytest-asyncio-dbe96ef/docs/ 0000775 0000000 0000000 00000000000 15205265572 0020336 5 ustar 00root root 0000000 0000000 pytest-dev-pytest-asyncio-dbe96ef/docs/concepts.rst 0000664 0000000 0000000 00000011710 15205265572 0022706 0 ustar 00root root 0000000 0000000 ========
Concepts
========
.. _concepts/event_loops:
asyncio event loops
===================
In order to understand how pytest-asyncio works, it helps to understand how pytest collectors work.
If you already know about pytest collectors, please :ref:`skip ahead `.
Otherwise, continue reading.
Let's assume we have a test suite with a file named *test_all_the_things.py* holding a single test, async or not:
.. include:: concepts_function_scope_example.py
:code: python
The file *test_all_the_things.py* is a Python module with a Python test function.
When we run pytest, the test runner descends into Python packages, modules, and classes, in order to find all tests, regardless whether the tests will run or not.
This process is referred to as *test collection* by pytest.
In our particular example, pytest will find our test module and the test function.
We can visualize the collection result by running ``pytest --collect-only``::
The example illustrates that the code of our test suite is hierarchical.
Pytest uses so called *collectors* for each level of the hierarchy.
Our contrived example test suite uses the *Module* and *Function* collectors, but real world test code may contain additional hierarchy levels via the *Package* or *Class* collectors.
There's also a special *Session* collector at the root of the hierarchy.
You may notice that the individual levels resemble the possible `scopes of a pytest fixture. `__
.. _pytest-asyncio-event-loops:
Pytest-asyncio provides one asyncio event loop for each pytest collector.
By default, each test runs in the event loop provided by the *Function* collector, i.e. tests use the loop with the narrowest scope.
This gives the highest level of isolation between tests.
If two or more tests share a common ancestor collector, the tests can be configured to run in their ancestor's loop by passing the appropriate *loop_scope* keyword argument to the *asyncio* mark.
For example, the following two tests use the asyncio event loop provided by the *Module* collector:
.. include:: concepts_module_scope_example.py
:code: python
It's highly recommended for neighboring tests to use the same event loop scope.
For example, all tests in a class or module should use the same scope.
Assigning neighboring tests to different event loop scopes is discouraged as it can make test code hard to follow.
Test discovery modes
====================
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.
.. tabs::
.. group-tab:: Strict mode
.. code-block:: toml
[tool.pytest.ini_options]
asyncio_mode = "strict"
In strict mode pytest-asyncio will only run tests that have the *asyncio* marker
and will only evaluate async fixtures decorated with ``@pytest_asyncio.fixture``.
Test functions and fixtures without these markers and decorators will not be
handled by pytest-asyncio.
This mode is intended for projects that want to support multiple asynchronous
programming libraries as it allows pytest-asyncio to coexist with other async
testing plugins in the same codebase.
Pytest automatically enables installed plugins. As a result pytest plugins
need to coexist peacefully in their default configuration. This is why strict
mode is the default mode.
.. group-tab:: Auto mode
.. code-block:: toml
[tool.pytest.ini_options]
asyncio_mode = "auto"
In *auto* mode pytest-asyncio automatically adds the *asyncio* marker to all
asynchronous test functions. It will also take ownership of all async fixtures,
regardless of whether they are decorated with ``@pytest.fixture`` or
``@pytest_asyncio.fixture``.
This mode is intended for projects that use *asyncio* as their only asynchronous
programming library. Auto mode makes for the simplest test and fixture
configuration and is the recommended default.
If you intend to support multiple asynchronous programming libraries,
e.g. *asyncio* and *trio*, strict mode will be the preferred option.
.. _concepts/concurrent_execution:
Test execution and concurrency
==============================
pytest-asyncio runs async tests sequentially, just like how pytest runs synchronous tests. Each asynchronous test runs within its assigned event loop. For example, consider the following two tests:
.. include:: concepts_concurrent_execution_example.py
:code: python
This sequential execution is intentional and important for maintaining test isolation. Running tests concurrently could introduce race conditions and side effects where one test could interfere with another, making test results unreliable and difficult to debug.
pytest-dev-pytest-asyncio-dbe96ef/docs/concepts_concurrent_execution_example.py 0000664 0000000 0000000 00000000416 15205265572 0030567 0 ustar 00root root 0000000 0000000 import asyncio
import pytest
@pytest.mark.asyncio
async def test_first():
await asyncio.sleep(2) # Takes 2 seconds
@pytest.mark.asyncio
async def test_second():
await asyncio.sleep(2) # Takes 2 seconds
# Total execution time: ~4 seconds, not ~2 seconds
pytest-dev-pytest-asyncio-dbe96ef/docs/concepts_function_scope_example.py 0000664 0000000 0000000 00000000174 15205265572 0027341 0 ustar 00root root 0000000 0000000 import asyncio
import pytest
@pytest.mark.asyncio
async def test_runs_in_a_loop():
assert asyncio.get_running_loop()
pytest-dev-pytest-asyncio-dbe96ef/docs/concepts_module_scope_example.py 0000664 0000000 0000000 00000000514 15205265572 0026777 0 ustar 00root root 0000000 0000000 import asyncio
import pytest
loop: asyncio.AbstractEventLoop
@pytest.mark.asyncio(loop_scope="module")
async def test_remember_loop():
global loop
loop = asyncio.get_running_loop()
@pytest.mark.asyncio(loop_scope="module")
async def test_runs_in_a_loop():
global loop
assert asyncio.get_running_loop() is loop
pytest-dev-pytest-asyncio-dbe96ef/docs/conf.py 0000664 0000000 0000000 00000001775 15205265572 0021647 0 ustar 00root root 0000000 0000000 # Configuration file for the Sphinx documentation builder.
#
# For the full list of built-in configuration values, see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html
# -- Project information -----------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
import importlib.metadata
project = "pytest-asyncio"
copyright = "2023, pytest-asyncio contributors"
author = "Tin Tvrtković"
release = importlib.metadata.version(project)
# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
extensions = ["sphinx_tabs.tabs"]
templates_path = ["_templates"]
exclude_patterns = []
# -- Options for HTML output -------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
html_theme = "sphinx_rtd_theme"
html_static_path = []
pytest-dev-pytest-asyncio-dbe96ef/docs/how-to-guides/ 0000775 0000000 0000000 00000000000 15205265572 0023031 5 ustar 00root root 0000000 0000000 pytest-dev-pytest-asyncio-dbe96ef/docs/how-to-guides/change_default_fixture_loop.rst 0000664 0000000 0000000 00000001632 15205265572 0031315 0 ustar 00root root 0000000 0000000 ==========================================================
How to change the default event loop scope of all fixtures
==========================================================
The :ref:`configuration/asyncio_default_fixture_loop_scope` configuration option sets the default event loop scope for asynchronous fixtures. The following code snippets configure all fixtures to run in a session-scoped loop by default:
.. tabs::
.. tab:: pytest.ini
.. code-block:: ini
[pytest]
asyncio_default_fixture_loop_scope = session
.. tab:: pyproject.toml
.. code-block:: toml
[tool.pytest.ini_options]
asyncio_default_fixture_loop_scope = "session"
.. tab:: setup.cfg
.. code-block:: ini
[tool:pytest]
asyncio_default_fixture_loop_scope = session
Please refer to :ref:`configuration/asyncio_default_fixture_loop_scope` for other valid scopes.
pytest-dev-pytest-asyncio-dbe96ef/docs/how-to-guides/change_default_test_loop.rst 0000664 0000000 0000000 00000001574 15205265572 0030613 0 ustar 00root root 0000000 0000000 =======================================================
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:
.. tabs::
.. tab:: pytest.ini
.. code-block:: ini
[pytest]
asyncio_default_test_loop_scope = session
.. tab:: pyproject.toml
.. code-block:: toml
[tool.pytest.ini_options]
asyncio_default_test_loop_scope = "session"
.. tab:: setup.cfg
.. code-block:: ini
[tool:pytest]
asyncio_default_test_loop_scope = session
Please refer to :ref:`configuration/asyncio_default_test_loop_scope`
for other valid scopes.
pytest-dev-pytest-asyncio-dbe96ef/docs/how-to-guides/change_fixture_loop.rst 0000664 0000000 0000000 00000000664 15205265572 0027615 0 ustar 00root root 0000000 0000000 ===============================================
How to change the event loop scope of a fixture
===============================================
The event loop scope of an asynchronous fixture is specified via the *loop_scope* keyword argument to :ref:`pytest_asyncio.fixture `. The following fixture runs in the module-scoped event loop:
.. include:: change_fixture_loop_example.py
:code: python
pytest-dev-pytest-asyncio-dbe96ef/docs/how-to-guides/change_fixture_loop_example.py 0000664 0000000 0000000 00000000470 15205265572 0031143 0 ustar 00root root 0000000 0000000 import asyncio
import pytest
import pytest_asyncio
@pytest_asyncio.fixture(loop_scope="module")
async def current_loop():
return asyncio.get_running_loop()
@pytest.mark.asyncio(loop_scope="module")
async def test_runs_in_module_loop(current_loop):
assert current_loop is asyncio.get_running_loop()
pytest-dev-pytest-asyncio-dbe96ef/docs/how-to-guides/class_scoped_loop_example.py 0000664 0000000 0000000 00000000566 15205265572 0030620 0 ustar 00root root 0000000 0000000 import asyncio
import pytest
@pytest.mark.asyncio(loop_scope="class")
class TestInOneEventLoopPerClass:
loop: asyncio.AbstractEventLoop
async def test_remember_loop(self):
TestInOneEventLoopPerClass.loop = asyncio.get_running_loop()
async def test_assert_same_loop(self):
assert asyncio.get_running_loop() is TestInOneEventLoopPerClass.loop
pytest-dev-pytest-asyncio-dbe96ef/docs/how-to-guides/configure_loop_factories_per_test.rst 0000664 0000000 0000000 00000003074 15205265572 0032545 0 ustar 00root root 0000000 0000000 ========================================================
How to configure event loop factories from the test item
========================================================
``pytest_asyncio_loop_factories`` is called with the current pytest ``item``.
Use that item to decide which named event loop factories are available for the test being collected.
For example, a hook can inspect the test's fixtures and return a different factory mapping for tests that request a particular fixture.
In ``conftest.py``, check the current item's fixture names and build the factory mapping for that item:
.. include:: configure_loop_factories_per_test/conftest.py
:code: python
Then request the fixture from tests that should use the custom factory:
.. include:: configure_loop_factories_per_test/test_extra_loop_factories.py
:code: python
In this example, ``test_runs_with_default_factory_only`` is parametrized only over ``default``, while ``test_runs_with_custom_factory_only`` is parametrized only over ``custom``.
The same pattern works with any information available from the current pytest item, such as fixture names, markers, node IDs, or file paths.
Because this is a standard pytest hook, its placement also matters.
An implementation in a nested ``conftest.py`` applies to tests collected under that directory.
Use this when a whole package or directory should share the same factory set.
For declaring factories without item-specific logic, see :doc:`custom_loop_factory`.
For selecting a subset of available factories from a test, see :doc:`run_test_with_specific_loop_factories`.
pytest-dev-pytest-asyncio-dbe96ef/docs/how-to-guides/configure_loop_factories_per_test/ 0000775 0000000 0000000 00000000000 15205265572 0032007 5 ustar 00root root 0000000 0000000 pytest-dev-pytest-asyncio-dbe96ef/docs/how-to-guides/configure_loop_factories_per_test/conftest.py 0000664 0000000 0000000 00000000523 15205265572 0034206 0 ustar 00root root 0000000 0000000 import asyncio
import pytest
class CustomEventLoop(asyncio.SelectorEventLoop):
pass
@pytest.fixture
def requires_custom_loop():
pass
def pytest_asyncio_loop_factories(config, item):
if "requires_custom_loop" in item.fixturenames:
return {"custom": CustomEventLoop}
return {"default": asyncio.new_event_loop}
test_extra_loop_factories.py 0000664 0000000 0000000 00000000303 15205265572 0037550 0 ustar 00root root 0000000 0000000 pytest-dev-pytest-asyncio-dbe96ef/docs/how-to-guides/configure_loop_factories_per_test import pytest
@pytest.mark.asyncio
async def test_runs_with_default_factory_only():
pass
@pytest.mark.asyncio
async def test_runs_with_custom_factory_only(requires_custom_loop):
pass
pytest-dev-pytest-asyncio-dbe96ef/docs/how-to-guides/custom_loop_factory.rst 0000664 0000000 0000000 00000002012 15205265572 0027650 0 ustar 00root root 0000000 0000000 ================================================
How to use custom event loop factories for tests
================================================
pytest-asyncio can run asynchronous tests with custom event loop factories by implementing ``pytest_asyncio_loop_factories`` in ``conftest.py``. The hook provides the named event loop factories that are available for a test item by returning a mapping from factory names to loop factory callables:
.. code-block:: python
import asyncio
import pytest
class CustomEventLoop(asyncio.SelectorEventLoop):
pass
def pytest_asyncio_loop_factories(config, item):
return {
"stdlib": asyncio.new_event_loop,
"custom": CustomEventLoop,
}
The hook receives the current pytest ``item``, so it can return different factory mappings for different tests. See :doc:`configure_loop_factories_per_test` for item-based factory configuration.
To run a test with only some configured factories, see :doc:`run_test_with_specific_loop_factories`.
pytest-dev-pytest-asyncio-dbe96ef/docs/how-to-guides/index.rst 0000664 0000000 0000000 00000001077 15205265572 0024677 0 ustar 00root root 0000000 0000000 =============
How-To Guides
=============
.. toctree::
:hidden:
migrate_from_0_21
migrate_from_0_23
change_fixture_loop
change_default_fixture_loop
change_default_test_loop
custom_loop_factory
configure_loop_factories_per_test
run_test_with_specific_loop_factories
run_class_tests_in_same_loop
run_module_tests_in_same_loop
run_package_tests_in_same_loop
multiple_loops
parametrize_with_asyncio
uvloop
test_item_is_async
This section of the documentation provides code snippets and recipes to accomplish specific tasks with pytest-asyncio.
pytest-dev-pytest-asyncio-dbe96ef/docs/how-to-guides/migrate_from_0_21.rst 0000664 0000000 0000000 00000003744 15205265572 0026767 0 ustar 00root root 0000000 0000000 .. _how_to_guides/migrate_from_0_21:
========================================
How to migrate from pytest-asyncio v0.21
========================================
1. If your test suite re-implements the *event_loop* fixture, make sure the fixture implementations don't do anything besides creating a new asyncio event loop, yielding it, and closing it.
2. Convert all synchronous test cases requesting the *event_loop* fixture to asynchronous test cases.
3. Convert all synchronous fixtures requesting the *event_loop* fixture to asynchronous fixtures.
4. Remove the *event_loop* argument from all asynchronous test cases in favor of ``event_loop = asyncio.get_running_loop()``.
5. Remove the *event_loop* argument from all asynchronous fixtures in favor of ``event_loop = asyncio.get_running_loop()``.
Go through all re-implemented *event_loop* fixtures in your test suite one by one, starting with the the fixture with the deepest nesting level and take note of the fixture scope:
1. For all tests and fixtures affected by the re-implemented *event_loop* fixture, configure the *loop_scope* for async tests and fixtures to match the *event_loop* fixture scope. This can be done for each test and fixture individually using either the ``pytest.mark.asyncio(loop_scope="…")`` marker for async tests or ``@pytest_asyncio.fixture(loop_scope="…")`` for async fixtures. Alternatively, you can set the default loop scope for fixtures using the :ref:`asyncio_default_fixture_loop_scope ` configuration option. Snippets to mark all tests with the same *asyncio* marker, thus sharing the same loop scope, are present in the how-to section of the documentation. Depending on the homogeneity of your test suite, you may want a mixture of explicit decorators and default settings.
2. Remove the re-implemented *event_loop* fixture.
If you haven't set the *asyncio_default_fixture_loop_scope* configuration option, yet, set it to *function* to silence the deprecation warning.
pytest-dev-pytest-asyncio-dbe96ef/docs/how-to-guides/migrate_from_0_23.rst 0000664 0000000 0000000 00000002436 15205265572 0026766 0 ustar 00root root 0000000 0000000 .. _how_to_guides/migrate_from_0_23:
========================================
How to migrate from pytest-asyncio v0.23
========================================
The following steps assume that your test suite has no re-implementations of the *event_loop* fixture, nor explicit fixtures requests for it. If this isn't the case, please follow the :ref:`migration guide for pytest-asyncio v0.21. `
1. Explicitly set the *loop_scope* of async fixtures by replacing occurrences of ``@pytest.fixture(scope="…")`` and ``@pytest_asyncio.fixture(scope="…")`` with ``@pytest_asyncio.fixture(loop_scope="…", scope="…")`` such that *loop_scope* and *scope* are the same. If you use auto mode, resolve all import errors from missing imports of *pytest_asyncio*. If your async fixtures all use the same *loop_scope*, you may choose to set the *asyncio_default_fixture_loop_scope* configuration option to that loop scope, instead.
2. If you haven't set *asyncio_default_fixture_loop_scope*, set it to *function* to address the deprecation warning about the unset configuration option.
3. Change all occurrences of ``pytest.mark.asyncio(scope="…")`` to ``pytest.mark.asyncio(loop_scope="…")`` to address the deprecation warning about the *scope* argument to the *asyncio* marker.
pytest-dev-pytest-asyncio-dbe96ef/docs/how-to-guides/module_scoped_loop_example.py 0000664 0000000 0000000 00000000461 15205265572 0030772 0 ustar 00root root 0000000 0000000 import asyncio
import pytest
pytestmark = pytest.mark.asyncio(loop_scope="module")
loop: asyncio.AbstractEventLoop
async def test_remember_loop():
global loop
loop = asyncio.get_running_loop()
async def test_assert_same_loop():
global loop
assert asyncio.get_running_loop() is loop
pytest-dev-pytest-asyncio-dbe96ef/docs/how-to-guides/multiple_loops.rst 0000664 0000000 0000000 00000001377 15205265572 0026642 0 ustar 00root root 0000000 0000000 ======================================
How to test with different event loops
======================================
.. warning::
Overriding the *event_loop_policy* fixture is deprecated and will be removed in a future version of pytest-asyncio. Use the ``pytest_asyncio_loop_factories`` hook instead. See :doc:`custom_loop_factory` for details.
Parametrizing the *event_loop_policy* fixture parametrizes all async tests. The following example causes all async tests to run multiple times, once for each event loop in the fixture parameters:
.. include:: multiple_loops_example.py
:code: python
You may choose to limit the scope of the fixture to *package,* *module,* or *class,* if you only want a subset of your tests to run with different event loops.
pytest-dev-pytest-asyncio-dbe96ef/docs/how-to-guides/multiple_loops_example.py 0000664 0000000 0000000 00000001176 15205265572 0030172 0 ustar 00root root 0000000 0000000 import asyncio
import warnings
with warnings.catch_warnings():
warnings.simplefilter("ignore", DeprecationWarning)
from asyncio import DefaultEventLoopPolicy
import pytest
class CustomEventLoopPolicy(DefaultEventLoopPolicy):
pass
@pytest.fixture(
scope="session",
params=(
CustomEventLoopPolicy(),
CustomEventLoopPolicy(),
),
)
def event_loop_policy(request):
return request.param
@pytest.mark.asyncio
@pytest.mark.filterwarnings("ignore::DeprecationWarning")
async def test_uses_custom_event_loop_policy():
assert isinstance(asyncio.get_event_loop_policy(), CustomEventLoopPolicy)
pytest-dev-pytest-asyncio-dbe96ef/docs/how-to-guides/package_scoped_loop_example.py 0000664 0000000 0000000 00000000106 15205265572 0031074 0 ustar 00root root 0000000 0000000 import pytest
pytestmark = pytest.mark.asyncio(loop_scope="package")
pytest-dev-pytest-asyncio-dbe96ef/docs/how-to-guides/parametrize_with_asyncio.rst 0000664 0000000 0000000 00000001165 15205265572 0030671 0 ustar 00root root 0000000 0000000 =====================================
How to parametrize asynchronous tests
=====================================
The ``pytest.mark.parametrize`` marker works with asynchronous tests the same as with synchronous tests. You can apply both ``pytest.mark.asyncio`` and ``pytest.mark.parametrize`` to asynchronous test functions:
.. include:: parametrize_with_asyncio_example.py
:code: python
.. note::
Whilst asynchronous tests can be parametrized, each individual test case still runs sequentially, not concurrently. For more information about how pytest-asyncio executes tests, see :ref:`concepts/concurrent_execution`.
pytest-dev-pytest-asyncio-dbe96ef/docs/how-to-guides/parametrize_with_asyncio_example.py 0000664 0000000 0000000 00000000305 15205265572 0032217 0 ustar 00root root 0000000 0000000 import asyncio
import pytest
@pytest.mark.asyncio
@pytest.mark.parametrize("value", [1, 2, 3])
async def test_parametrized_async_function(value):
await asyncio.sleep(1)
assert value > 0
pytest-dev-pytest-asyncio-dbe96ef/docs/how-to-guides/run_class_tests_in_same_loop.rst 0000664 0000000 0000000 00000000640 15205265572 0031522 0 ustar 00root root 0000000 0000000 ======================================================
How to run all tests in a class in the same event loop
======================================================
All tests can be run inside the same event loop by marking them with ``pytest.mark.asyncio(loop_scope="class")``.
This is easily achieved by using the *asyncio* marker as a class decorator.
.. include:: class_scoped_loop_example.py
:code: python
pytest-dev-pytest-asyncio-dbe96ef/docs/how-to-guides/run_module_tests_in_same_loop.rst 0000664 0000000 0000000 00000000644 15205265572 0031706 0 ustar 00root root 0000000 0000000 =======================================================
How to run all tests in a module in the same event loop
=======================================================
All tests can be run inside the same event loop by marking them with ``pytest.mark.asyncio(loop_scope="module")``.
This is easily achieved by adding a `pytestmark` statement to your module.
.. include:: module_scoped_loop_example.py
:code: python
pytest-dev-pytest-asyncio-dbe96ef/docs/how-to-guides/run_package_tests_in_same_loop.rst 0000664 0000000 0000000 00000001030 15205265572 0032002 0 ustar 00root root 0000000 0000000 ========================================================
How to run all tests in a package in the same event loop
========================================================
All tests can be run inside the same event loop by marking them with ``pytest.mark.asyncio(loop_scope="package")``.
Add the following code to the ``__init__.py`` of the test package:
.. include:: package_scoped_loop_example.py
:code: python
Note that this marker is not passed down to tests in subpackages.
Subpackages constitute their own, separate package.
pytest-dev-pytest-asyncio-dbe96ef/docs/how-to-guides/run_test_with_specific_loop_factories.rst 0000664 0000000 0000000 00000002063 15205265572 0033417 0 ustar 00root root 0000000 0000000 =========================================================
How to run a test with specific event loop factories only
=========================================================
``pytest_asyncio_loop_factories`` determines which named event loop factories are available for each test item.
By default, pytest-asyncio parametrizes a test with every factory returned for that item.
Use ``loop_factories`` to select a subset of the factory names returned by the hook.
Assume ``conftest.py`` provides two named factories:
.. include:: run_test_with_specific_loop_factories/conftest.py
:code: python
Then use ``loop_factories`` to select which available factory names a test should run with:
.. include:: run_test_with_specific_loop_factories/test_loop_factories_subset.py
:code: python
If a requested factory name is not available from the hook, the test variant for that factory is skipped.
For declaring the factories themselves, see :doc:`custom_loop_factory`.
For choosing the available factories from the pytest item, see :doc:`configure_loop_factories_per_test`.
pytest-dev-pytest-asyncio-dbe96ef/docs/how-to-guides/run_test_with_specific_loop_factories/ 0000775 0000000 0000000 00000000000 15205265572 0032664 5 ustar 00root root 0000000 0000000 conftest.py 0000664 0000000 0000000 00000000340 15205265572 0035001 0 ustar 00root root 0000000 0000000 pytest-dev-pytest-asyncio-dbe96ef/docs/how-to-guides/run_test_with_specific_loop_factories import asyncio
class CustomEventLoop(asyncio.SelectorEventLoop):
pass
def pytest_asyncio_loop_factories(config, item):
return {
"default": asyncio.new_event_loop,
"custom": CustomEventLoop,
}
test_loop_factories_subset.py 0000664 0000000 0000000 00000000316 15205265572 0040613 0 ustar 00root root 0000000 0000000 pytest-dev-pytest-asyncio-dbe96ef/docs/how-to-guides/run_test_with_specific_loop_factories import pytest
@pytest.mark.asyncio
async def test_runs_with_every_configured_factory():
pass
@pytest.mark.asyncio(loop_factories=["custom"])
async def test_runs_with_only_custom_factory():
pass
pytest-dev-pytest-asyncio-dbe96ef/docs/how-to-guides/test_item_is_async.rst 0000664 0000000 0000000 00000000446 15205265572 0027454 0 ustar 00root root 0000000 0000000 =======================================
How to tell if a test function is async
=======================================
Use ``pytest_asyncio.is_async_item`` to determine if a test item is asynchronous and managed by pytest-asyncio.
.. include:: test_item_is_async_example.py
:code: python
pytest-dev-pytest-asyncio-dbe96ef/docs/how-to-guides/test_item_is_async_example.py 0000664 0000000 0000000 00000000235 15205265572 0031003 0 ustar 00root root 0000000 0000000 from pytest_asyncio import is_async_test
def pytest_collection_modifyitems(items):
for item in items:
if is_async_test(item):
pass
pytest-dev-pytest-asyncio-dbe96ef/docs/how-to-guides/uvloop.rst 0000664 0000000 0000000 00000002461 15205265572 0025112 0 ustar 00root root 0000000 0000000 =======================
How to test with uvloop
=======================
Define a ``pytest_asyncio_loop_factories`` hook in your *conftest.py* that maps factory names to loop factories:
.. code-block:: python
import uvloop
def pytest_asyncio_loop_factories(config, item):
return {
"uvloop": uvloop.new_event_loop,
}
.. seealso::
:doc:`custom_loop_factory`
More details on the ``pytest_asyncio_loop_factories`` hook, including per-test factory selection and multiple factory parametrization.
Using the event_loop_policy fixture
-----------------------------------
.. note::
``asyncio.AbstractEventLoopPolicy`` is deprecated as of Python 3.14 (removal planned for 3.16), and ``uvloop.EventLoopPolicy`` will be removed alongside it. Overriding the *event_loop_policy* fixture is also deprecated in pytest-asyncio. Prefer the hook approach above.
For older versions of Python and uvloop, you can override the *event_loop_policy* fixture in your *conftest.py:*
.. code-block:: python
import pytest
import uvloop
@pytest.fixture(scope="session")
def event_loop_policy():
return uvloop.EventLoopPolicy()
You may choose to limit the scope of the fixture to *package,* *module,* or *class,* if you only want a subset of your tests to run with uvloop.
pytest-dev-pytest-asyncio-dbe96ef/docs/index.rst 0000664 0000000 0000000 00000002364 15205265572 0022204 0 ustar 00root root 0000000 0000000 ==========================
Welcome to pytest-asyncio!
==========================
.. toctree::
:maxdepth: 1
:hidden:
concepts
how-to-guides/index
reference/index
support
pytest-asyncio is a `pytest `_ plugin. It facilitates testing of code that uses the `asyncio `_ library.
Specifically, pytest-asyncio provides support for coroutines as test functions. This allows users to *await* code inside their tests. For example, the following code is executed as a test item by pytest:
.. code-block:: python
@pytest.mark.asyncio
async def test_some_asyncio_code():
res = await library.do_something()
assert b"expected result" == res
Note that test classes subclassing the standard `unittest `__ library are not supported. Users
are advised to use `unittest.IsolatedAsyncioTestCase `__
or an async framework such as `asynctest `__.
pytest-asyncio is available under the `Apache License 2.0 `_.
pytest-dev-pytest-asyncio-dbe96ef/docs/reference/ 0000775 0000000 0000000 00000000000 15205265572 0022274 5 ustar 00root root 0000000 0000000 pytest-dev-pytest-asyncio-dbe96ef/docs/reference/changelog.rst 0000664 0000000 0000000 00000073632 15205265572 0024770 0 ustar 00root root 0000000 0000000 =========
Changelog
=========
All notable changes to this project will be documented in this file.
The format is based on `Keep a Changelog `__, and this project adheres to `Semantic Versioning `__.
This project uses `towncrier `__ for changelog management and the changes for the upcoming release can be found in https://github.com/pytest-dev/pytest-asyncio/tree/main/changelog.d/.
.. towncrier release notes start
`1.4.0 `_ - 2026-05-26
===============================================================================
Deprecated
----------
- Overriding the *event_loop_policy* fixture is deprecated. Use the ``pytest_asyncio_loop_factories`` hook instead. (`#1419 `_)
Added
-----
- Added the ``pytest_asyncio_loop_factories`` hook to parametrize asyncio tests with custom event loop factories.
The hook returns a mapping of factory names to loop factories, and ``pytest.mark.asyncio(loop_factories=[...])`` selects a subset of configured factories per test. When a single factory is configured, test names are unchanged.
Synchronous ``@pytest_asyncio.fixture`` functions now see the correct event loop when custom loop factories are configured, even when test code disrupts the current event loop (e.g., via ``asyncio.run()`` or ``asyncio.set_event_loop(None)``). (`#1164 `_)
Changed
-------
- Improved the readability of the warning message that is displayed when ``asyncio_default_fixture_loop_scope`` is unset (`#1298 `_)
- Only import ``asyncio.AbstractEventLoopPolicy`` for type checking to avoid raising
a DeprecationWarning. (`#1394 `_)
- Updated minimum supported pytest version to v8.4.0. (`#1397 `_)
Fixed
-----
- Fixed a ``ResourceWarning: unclosed event loop`` warning that could occur when a synchronous test called ``asyncio.run()`` or otherwise unset the current event loop after pytest-asyncio had run an async test or fixture. (`#724 `_)
Notes for Downstream Packagers
------------------------------
- Added dependency on ``sphinx-tabs >= 3.5`` to organize documentation examples into tabs. (`#1395 `_)
`1.3.0 `_ - 2025-11-10
===============================================================================
Removed
-------
- Support for Python 3.9 (`#1278 `_)
Added
-----
- Support for pytest 9 (`#1279 `_)
Notes for Downstream Packagers
------------------------------
- Tested Python versions include free threaded Python 3.14t (`#1274 `_)
- Tests are run in the same pytest process, instead of spawning a subprocess with `pytest.Pytester.runpytest_subprocess`. This prevents the test suite from accidentally using a system installation of pytest-asyncio, which could result in test errors. (`#1275 `_)
`1.2.0 `_ - 2025-09-12
===============================================================================
Added
-----
- ``--asyncio-debug`` CLI option and ``asyncio_debug`` configuration option to enable asyncio debug mode for the default event loop. (`#980 `_)
- A ``pytest.UsageError`` for invalid configuration values of ``asyncio_default_fixture_loop_scope`` and ``asyncio_default_test_loop_scope``. (`#1189 `_)
- Compatibility with the `Pyright` type checker (`#731 `_)
Fixed
-----
- ``RuntimeError: There is no current event loop in thread 'MainThread'`` when any test unsets the event loop (such as when using ``asyncio.run`` and ``asyncio.Runner``). (`#1177 `_)
- Deprecation warning when decorating an asynchronous fixture with ``@pytest.fixture`` in `strict` mode. The warning message now refers to the correct package. (`#1198 `_)
Notes for Downstream Packagers
------------------------------
- Bump the minimum required version of tox to v4.28. This change is only relevant if you use the ``tox.ini`` file provided by pytest-asyncio to run tests.
- Extend dependency on typing-extensions>=4.12 from Python<3.10 to Python<3.13.
`1.1.0 `_ - 2025-07-16
===============================================================================
Added
-----
- Propagation of ContextVars from async fixtures to other fixtures and tests on Python 3.10 and older (`#127 `_)
- Cancellation of tasks when the `loop_scope` ends (`#200 `_)
- Warning when the current event loop is closed by a test
Fixed
-----
- Error about missing loop when calling functions requiring a loop in the `finally` clause of a task (`#878 `_)
- An error that could cause duplicate warnings to be issued
Notes for Downstream Packagers
------------------------------
- Added runtime dependency on `backports.asyncio.runner `__ for use with Python 3.10 and older
`1.0.0 `_ - 2025-05-26
===============================================================================
.. seealso::
:ref:`How to migrate from pytest-asyncio v0.23 `
Removed
-------
- The deprecated *event_loop* fixture. (`#1106 `_)
Added
-----
- Prelimiary support for Python 3.14 (`#1025 `_)
Changed
-------
- Scoped event loops (e.g. module-scoped loops) are created once rather than per scope (e.g. per module). This reduces the number of fixtures and speeds up collection time, especially for large test suites. (`#1107 `_)
- The *loop_scope* argument to ``pytest.mark.asyncio`` no longer forces that a pytest Collector exists at the level of the specified scope. For example, a test function marked with ``pytest.mark.asyncio(loop_scope="class")`` no longer requires a class surrounding the test. This is consistent with the behavior of the *scope* argument to ``pytest_asyncio.fixture``. (`#1112 `_)
Fixed
-----
- An error caused when using pytest's `--setup-plan` option. (`#630 `_)
- Unsuppressed import errors with pytest option ``--doctest-ignore-import-errors`` (`#797 `_)
- A "fixture not found" error in connection with package-scoped loops (`#1052 `_)
Notes for Downstream Packagers
------------------------------
- Removed a test that had an ordering dependency on other tests. (`#1114 `_)
0.26.0 (2025-03-25)
===================
- Adds configuration option that sets default event loop scope for all tests `#793 `_
- Improved type annotations for ``pytest_asyncio.fixture`` `#1045 `_
- Added ``typing-extensions`` as additional dependency for Python ``<3.10`` `#1045 `_
0.25.3 (2025-01-28)
===================
- Avoid errors in cleanup of async generators when event loop is already closed `#1040 `_
0.25.2 (2025-01-08)
===================
- Call ``loop.shutdown_asyncgens()`` before closing the event loop to ensure async generators are closed in the same manner as ``asyncio.run`` does `#1034 `_
0.25.1 (2025-01-02)
===================
- Fixes an issue that caused a broken event loop when a function-scoped test was executed in between two tests with wider loop scope `#950 `_
- Improves test collection speed in auto mode `#1020 `_
- Corrects the warning that is emitted upon redefining the event_loop fixture
0.25.0 (2024-12-13)
===================
- Deprecated: Added warning when asyncio test requests async ``@pytest.fixture`` in strict mode. This will become an error in a future version of pytest-asyncio. `#979 `_
- Updates the error message about `pytest.mark.asyncio`'s `scope` keyword argument to say `loop_scope` instead. `#1004 `_
- Verbose log displays correct parameter name: asyncio_default_fixture_loop_scope `#990 `_
- Propagates `contextvars` set in async fixtures to other fixtures and tests on Python 3.11 and above. `#1008 `_
0.24.0 (2024-08-22)
===================
- BREAKING: Updated minimum supported pytest version to v8.2.0
- Adds an optional `loop_scope` keyword argument to `pytest.mark.asyncio`. This argument controls which event loop is used to run the marked async test. `#706`_, `#871 `_
- Deprecates the optional `scope` keyword argument to `pytest.mark.asyncio` for API consistency with ``pytest_asyncio.fixture``. Users are encouraged to use the `loop_scope` keyword argument, which does exactly the same.
- Raises an error when passing `scope` or `loop_scope` as a positional argument to ``@pytest.mark.asyncio``. `#812 `_
- Fixes a bug that caused module-scoped async fixtures to fail when reused in other modules `#862 `_ `#668 `_
- Added the ``asyncio_default_fixture_loop_scope`` configuration option `c74d1c3 `_
0.23.8 (2024-07-17)
===================
- Fixes a bug that caused duplicate markers in async tests `#813 `_
- Declare support for Python 3.13
Known issues
------------
As of v0.23, pytest-asyncio attaches an asyncio event loop to each item of the test suite (i.e. session, packages, modules, classes, functions) and allows tests to be run in those loops when marked accordingly. Pytest-asyncio currently assumes that async fixture scope is correlated with the new event loop scope. This prevents fixtures from being evaluated independently from the event loop scope and breaks some existing test suites (see `#706`_). For example, a test suite may require all fixtures and tests to run in the same event loop, but have async fixtures that are set up and torn down for each module. If you're affected by this issue, please continue using the v0.21 release, until it is resolved.
0.23.7 (2024-05-19)
===================
- Silence deprecation warnings about unclosed event loops that occurred with certain CPython patch releases `#817 `_
Known issues
------------
As of v0.23, pytest-asyncio attaches an asyncio event loop to each item of the test suite (i.e. session, packages, modules, classes, functions) and allows tests to be run in those loops when marked accordingly. Pytest-asyncio currently assumes that async fixture scope is correlated with the new event loop scope. This prevents fixtures from being evaluated independently from the event loop scope and breaks some existing test suites (see `#706`_). For example, a test suite may require all fixtures and tests to run in the same event loop, but have async fixtures that are set up and torn down for each module. If you're affected by this issue, please continue using the v0.21 release, until it is resolved.
0.23.6 (2024-03-19)
===================
- Fix compatibility with pytest 8.2 `#800 `_
Known issues
------------
As of v0.23, pytest-asyncio attaches an asyncio event loop to each item of the test suite (i.e. session, packages, modules, classes, functions) and allows tests to be run in those loops when marked accordingly. Pytest-asyncio currently assumes that async fixture scope is correlated with the new event loop scope. This prevents fixtures from being evaluated independently from the event loop scope and breaks some existing test suites (see `#706`_). For example, a test suite may require all fixtures and tests to run in the same event loop, but have async fixtures that are set up and torn down for each module. If you're affected by this issue, please continue using the v0.21 release, until it is resolved.
0.23.5 (2024-02-09)
===================
- Declare compatibility with pytest 8 `#737 `_
- Fix typing errors with recent versions of mypy `#769 `_
- Prevent DeprecationWarning about internal use of `asyncio.get_event_loop()` from affecting test cases `#757 `_
Known issues
------------
As of v0.23, pytest-asyncio attaches an asyncio event loop to each item of the test suite (i.e. session, packages, modules, classes, functions) and allows tests to be run in those loops when marked accordingly. Pytest-asyncio currently assumes that async fixture scope is correlated with the new event loop scope. This prevents fixtures from being evaluated independently from the event loop scope and breaks some existing test suites (see `#706`_). For example, a test suite may require all fixtures and tests to run in the same event loop, but have async fixtures that are set up and torn down for each module. If you're affected by this issue, please continue using the v0.21 release, until it is resolved.
0.23.4 (2024-01-28)
===================
- pytest-asyncio no longer imports additional, unrelated packages during test collection `#729 `_
- Addresses further issues that caused an internal pytest error during test collection
- Declares incompatibility with pytest 8 `#737 `_
Known issues
------------
As of v0.23, pytest-asyncio attaches an asyncio event loop to each item of the test suite (i.e. session, packages, modules, classes, functions) and allows tests to be run in those loops when marked accordingly. Pytest-asyncio currently assumes that async fixture scope is correlated with the new event loop scope. This prevents fixtures from being evaluated independently from the event loop scope and breaks some existing test suites (see `#706`_). For example, a test suite may require all fixtures and tests to run in the same event loop, but have async fixtures that are set up and torn down for each module. If you're affected by this issue, please continue using the v0.21 release, until it is resolved.
0.23.3 (2024-01-01)
===================
- Fixes a bug that caused event loops to be closed prematurely when using async generator fixtures with class scope or wider in a function-scoped test `#706 `_
- Fixes various bugs that caused an internal pytest error during test collection `#711 `_ `#713 `_ `#719 `_
Known issues
------------
As of v0.23, pytest-asyncio attaches an asyncio event loop to each item of the test suite (i.e. session, packages, modules, classes, functions) and allows tests to be run in those loops when marked accordingly. Pytest-asyncio currently assumes that async fixture scope is correlated with the new event loop scope. This prevents fixtures from being evaluated independently from the event loop scope and breaks some existing test suites (see `#706`_). For example, a test suite may require all fixtures and tests to run in the same event loop, but have async fixtures that are set up and torn down for each module. If you're affected by this issue, please continue using the v0.21 release, until it is resolved.
0.23.2 (2023-12-04)
===================
- Fixes a bug that caused an internal pytest error when collecting .txt files `#703 `_
0.23.1 (2023-12-03)
===================
- Fixes a bug that caused an internal pytest error when using module-level skips `#701 `_
0.23.0 (2023-12-03)
===================
This release is backwards-compatible with v0.21.
Changes are non-breaking, unless you upgrade from v0.22.
.. seealso::
:ref:`How to migrate from pytest-asyncio v0.21 `
- BREAKING: The *asyncio_event_loop* mark has been removed. Event loops with class, module, package, and session scopes can be requested via the *scope* keyword argument to the _asyncio_ mark.
- Introduces the *event_loop_policy* fixture which allows testing with non-default or multiple event loops `#662 `_
- Introduces ``pytest_asyncio.is_async_test`` which returns whether a test item is managed by pytest-asyncio `#376 `_
- Removes and *pytest-trio,* *mypy,* and *flaky* from the test dependencies `#620 `_, `#674 `_, `#678 `_,
0.22.0 (2023-10-31)
===================
This release has been yanked from PyPI due to fundamental issues with the _asyncio_event_loop_ mark.
- Class-scoped and module-scoped event loops can be requested
via the _asyncio_event_loop_ mark. `#620 `_
- Deprecate redefinition of the `event_loop` fixture. `#587 `_
Users requiring a class-scoped or module-scoped asyncio event loop for their tests
should mark the corresponding class or module with `asyncio_event_loop`.
- Test items based on asynchronous generators always exit with *xfail* status and emit a warning during the collection phase. This behavior is consistent with synchronous yield tests. `#642 `__
- Remove support for Python 3.7
- Declare support for Python 3.12
0.21.2 (2024-04-29)
===================
- Fix compatibility with pytest 8.2. Backport of `#800 `_ to pytest-asyncio v0.21 for users who are unable to upgrade to a more recent version (see `#706`_)
0.21.1 (2023-07-12)
===================
- Output a proper error message when an invalid ``asyncio_mode`` is selected.
- Extend warning message about unclosed event loops with additional possible cause.
`#531 `_
- Previously, some tests reported "skipped" or "xfailed" as a result. Now all tests report a "success" result.
0.21.0 (2023-03-19)
===================
- Drop compatibility with pytest 6.1. Pytest-asyncio now depends on pytest 7.0 or newer.
- pytest-asyncio cleans up any stale event loops when setting up and tearing down the
event_loop fixture. This behavior has been deprecated and pytest-asyncio emits a
DeprecationWarning when tearing down the event_loop fixture and current event loop
has not been closed.
0.20.3 (2022-12-08)
===================
- Prevent DeprecationWarning to bubble up on CPython 3.10.9 and 3.11.1.
`#460 `_
0.20.2 (2022-11-11)
===================
- Fixes an issue with async fixtures that are defined as methods on a test class not being rebound to the actual test instance. `#197 `_
- Replaced usage of deprecated ``@pytest.mark.tryfirst`` with ``@pytest.hookimpl(tryfirst=True)`` `#438 `_
0.20.1 (2022-10-21)
===================
- Fixes an issue that warned about using an old version of pytest, even though the most recent version was installed. `#430 `_
0.20.0 (2022-10-21)
===================
- BREAKING: Removed *legacy* mode. If you're upgrading from v0.19 and you haven't configured ``asyncio_mode = legacy``, you can upgrade without taking any additional action. If you're upgrading from an earlier version or you have explicitly enabled *legacy* mode, you need to switch to *auto* or *strict* mode before upgrading to this version.
- Deprecate use of pytest v6.
- Fixed an issue which prevented fixture setup from being cached. `#404 `_
0.19.0 (2022-07-13)
===================
- BREAKING: The default ``asyncio_mode`` is now *strict*. `#293 `_
- Removes `setup.py` since all relevant configuration is present `setup.cfg`. Users requiring an editable installation of pytest-asyncio need to use pip v21.1 or newer. `#283 `_
- Declare support for Python 3.11.
0.18.3 (2022-03-25)
===================
- Adds `pytest-trio `_ to the test dependencies
- Fixes a bug that caused pytest-asyncio to try to set up async pytest_trio fixtures in strict mode. `#298 `_
0.18.2 (2022-03-03)
===================
- Fix asyncio auto mode not marking static methods. `#295 `_
- Fix a compatibility issue with Hypothesis 6.39.0. `#302 `_
0.18.1 (2022-02-10)
===================
- Fixes a regression that prevented async fixtures from working in synchronous tests. `#286 `_
0.18.0 (2022-02-07)
===================
- Raise a warning if @pytest.mark.asyncio is applied to non-async function. `#275 `_
- Support parametrized ``event_loop`` fixture. `#278 `_
0.17.2 (2022-01-17)
===================
- Require ``typing-extensions`` on Python<3.8 only. `#269 `_
- Fix a regression in tests collection introduced by 0.17.1, the plugin works fine with non-python tests again. `#267 `_
0.17.1 (2022-01-16)
===================
- Fixes a bug that prevents async Hypothesis tests from working without explicit ``asyncio`` marker when ``--asyncio-mode=auto`` is set. `#258 `_
- Fixed a bug that closes the default event loop if the loop doesn't exist `#257 `_
- Added type annotations. `#198 `_
- Show asyncio mode in pytest report headers. `#266 `_
- Relax ``asyncio_mode`` type definition; it allows to support pytest 6.1+. `#262 `_
0.17.0 (2022-01-13)
===================
- `pytest-asyncio` no longer alters existing event loop policies. `#168 `_, `#188 `_
- Drop support for Python 3.6
- Fixed an issue when pytest-asyncio was used in combination with `flaky` or inherited asynchronous Hypothesis tests. `#178 `_ `#231 `_
- Added `flaky `_ to test dependencies
- Added ``unused_udp_port`` and ``unused_udp_port_factory`` fixtures (similar to ``unused_tcp_port`` and ``unused_tcp_port_factory`` counterparts. `#99 `_
- Added the plugin modes: *strict*, *auto*, and *legacy*. See `documentation `_ for details. `#125 `_
- Correctly process ``KeyboardInterrupt`` during async fixture setup phase `#219 `_
0.16.0 (2021-10-16)
===================
- Add support for Python 3.10
0.15.1 (2021-04-22)
===================
- Hotfix for errors while closing event loops while replacing them.
`#209 `_
`#210 `_
0.15.0 (2021-04-19)
===================
- Add support for Python 3.9
- Abandon support for Python 3.5. If you still require support for Python 3.5, please use pytest-asyncio v0.14 or earlier.
- Set ``unused_tcp_port_factory`` fixture scope to 'session'.
`#163 `_
- Properly close event loops when replacing them.
`#208 `_
0.14.0 (2020-06-24)
===================
- Fix `#162 `_, and ``event_loop`` fixture behavior now is coherent on all scopes.
`#164 `_
0.12.0 (2020-05-04)
===================
- Run the event loop fixture as soon as possible. This helps with fixtures that have an implicit dependency on the event loop.
`#156 `_
0.11.0 (2020-04-20)
===================
- Test on 3.8, drop 3.3 and 3.4. Stick to 0.10 for these versions.
`#152 `_
- Use the new Pytest 5.4.0 Function API. We therefore depend on pytest >= 5.4.0.
`#142 `_
- Better ``pytest.skip`` support.
`#126 `_
0.10.0 (2019-01-08)
====================
- ``pytest-asyncio`` integrates with `Hypothesis `_
to support ``@given`` on async test functions using ``asyncio``.
`#102 `_
- Pytest 4.1 support.
`#105 `_
0.9.0 (2018-07-28)
==================
- Python 3.7 support.
- Remove ``event_loop_process_pool`` fixture and
``pytest.mark.asyncio_process_pool`` marker (see
https://bugs.python.org/issue34075 for deprecation and removal details)
0.8.0 (2017-09-23)
==================
- Improve integration with other packages (like aiohttp) with more careful event loop handling.
`#64 `_
0.7.0 (2017-09-08)
==================
- Python versions pre-3.6 can use the async_generator library for async fixtures.
`#62 `
0.6.0 (2017-05-28)
==================
- Support for Python versions pre-3.5 has been dropped.
- ``pytestmark`` now works on both module and class level.
- The ``forbid_global_loop`` parameter has been removed.
- Support for async and async gen fixtures has been added.
`#45 `_
- The deprecation warning regarding ``asyncio.async()`` has been fixed.
`#51 `_
0.5.0 (2016-09-07)
==================
- Introduced a changelog.
`#31 `_
- The ``event_loop`` fixture is again responsible for closing itself.
This makes the fixture slightly harder to correctly override, but enables
other fixtures to depend on it correctly.
`#30 `_
- Deal with the event loop policy by wrapping a special pytest hook,
``pytest_fixture_setup``. This allows setting the policy before fixtures
dependent on the ``event_loop`` fixture run, thus allowing them to take
advantage of the ``forbid_global_loop`` parameter. As a consequence of this,
we now depend on pytest 3.0.
`#29 `_
0.4.1 (2016-06-01)
==================
- Fix a bug preventing the propagation of exceptions from the plugin.
`#25 `_
0.4.0 (2016-05-30)
==================
- Make ``event_loop`` fixtures simpler to override by closing them in the
plugin, instead of directly in the fixture.
`#21 `_
- Introduce the ``forbid_global_loop`` parameter.
`#21 `_
0.3.0 (2015-12-19)
==================
- Support for Python 3.5 ``async``/``await`` syntax.
`#17 `_
0.2.0 (2015-08-01)
==================
- ``unused_tcp_port_factory`` fixture.
`#10 `_
0.1.1 (2015-04-23)
==================
Initial release.
pytest-dev-pytest-asyncio-dbe96ef/docs/reference/configuration.rst 0000664 0000000 0000000 00000004023 15205265572 0025674 0 ustar 00root root 0000000 0000000 =============
Configuration
=============
.. _configuration/asyncio_default_fixture_loop_scope:
asyncio_default_fixture_loop_scope
==================================
Determines the default event loop scope of asynchronous fixtures. When this configuration option is unset, it defaults to the fixture scope. In future versions of pytest-asyncio, the value will default to ``function`` when unset. Possible values are: ``function``, ``class``, ``module``, ``package``, ``session``
.. _configuration/asyncio_default_test_loop_scope:
asyncio_default_test_loop_scope
===============================
Determines the default event loop scope of asynchronous tests. When this configuration option is unset, it default to function scope. Possible values are: ``function``, ``class``, ``module``, ``package``, ``session``
.. _configuration/asyncio_debug:
asyncio_debug
=============
Enables `asyncio debug mode `_ for the default event loop used by asynchronous tests and fixtures.
The debug mode can be set by the ``asyncio_debug`` configuration option in the `configuration file
`_:
.. code-block:: ini
# pytest.ini
[pytest]
asyncio_debug = true
The value can also be set via the ``--asyncio-debug`` command-line option:
.. code-block:: bash
$ pytest tests --asyncio-debug
By default, asyncio debug mode is disabled.
asyncio_mode
============
The pytest-asyncio mode can be set by the ``asyncio_mode`` configuration option in the `configuration file
`_:
.. code-block:: ini
# pytest.ini
[pytest]
asyncio_mode = auto
The value can also be set via the ``--asyncio-mode`` command-line option:
.. code-block:: bash
$ pytest tests --asyncio-mode=strict
If the asyncio mode is set in both the pytest configuration file and the command-line option, the command-line option takes precedence. If no asyncio mode is specified, the mode defaults to `strict`.
pytest-dev-pytest-asyncio-dbe96ef/docs/reference/decorators/ 0000775 0000000 0000000 00000000000 15205265572 0024441 5 ustar 00root root 0000000 0000000 pytest-dev-pytest-asyncio-dbe96ef/docs/reference/decorators/index.rst 0000664 0000000 0000000 00000002220 15205265572 0026276 0 ustar 00root root 0000000 0000000 .. _decorators/pytest_asyncio_fixture:
==========
Decorators
==========
The ``@pytest_asyncio.fixture`` decorator allows coroutines and async generator functions to be used as pytest fixtures.
The decorator takes all arguments supported by `@pytest.fixture`.
Additionally, ``@pytest_asyncio.fixture`` supports the *loop_scope* keyword argument, which selects the event loop in which the fixture is run (see :ref:`concepts/event_loops`).
The default event loop scope is *function* scope.
Possible loop scopes are *session,* *package,* *module,* *class,* and *function*.
The *loop_scope* of a fixture can be chosen independently from its caching *scope*.
However, the event loop scope must be larger or the same as the fixture's caching scope.
In other words, it's possible to reevaluate an async fixture multiple times within the same event loop, but it's not possible to switch out the running event loop in an async fixture.
Examples:
.. include:: pytest_asyncio_fixture_example.py
:code: python
*auto* mode automatically converts coroutines and async generator functions declared with the standard ``@pytest.fixture`` decorator to pytest-asyncio fixtures.
pytest-dev-pytest-asyncio-dbe96ef/docs/reference/decorators/pytest_asyncio_fixture_example.py 0000664 0000000 0000000 00000000727 15205265572 0033357 0 ustar 00root root 0000000 0000000 import pytest_asyncio
@pytest_asyncio.fixture
async def fixture_runs_in_fresh_loop_for_every_function(): ...
@pytest_asyncio.fixture(loop_scope="session", scope="module")
async def fixture_runs_in_session_loop_once_per_module(): ...
@pytest_asyncio.fixture(loop_scope="module", scope="module")
async def fixture_runs_in_module_loop_once_per_module(): ...
@pytest_asyncio.fixture(loop_scope="module")
async def fixture_runs_in_module_loop_once_per_function(): ...
pytest-dev-pytest-asyncio-dbe96ef/docs/reference/fixtures/ 0000775 0000000 0000000 00000000000 15205265572 0024145 5 ustar 00root root 0000000 0000000 pytest-dev-pytest-asyncio-dbe96ef/docs/reference/fixtures/event_loop_policy_example.py 0000664 0000000 0000000 00000001077 15205265572 0031770 0 ustar 00root root 0000000 0000000 import asyncio
import warnings
with warnings.catch_warnings():
warnings.simplefilter("ignore", DeprecationWarning)
from asyncio import DefaultEventLoopPolicy
import pytest
class CustomEventLoopPolicy(DefaultEventLoopPolicy):
pass
@pytest.fixture(scope="module")
def event_loop_policy(request):
return CustomEventLoopPolicy()
@pytest.mark.asyncio(loop_scope="module")
@pytest.mark.filterwarnings("ignore::DeprecationWarning")
async def test_uses_custom_event_loop_policy():
assert isinstance(asyncio.get_event_loop_policy(), CustomEventLoopPolicy)
pytest-dev-pytest-asyncio-dbe96ef/docs/reference/fixtures/event_loop_policy_parametrized_example.py 0000664 0000000 0000000 00000001153 15205265572 0034532 0 ustar 00root root 0000000 0000000 import asyncio
import warnings
with warnings.catch_warnings():
warnings.simplefilter("ignore", DeprecationWarning)
from asyncio import DefaultEventLoopPolicy
import pytest
class CustomEventLoopPolicy(DefaultEventLoopPolicy):
pass
@pytest.fixture(
params=(
DefaultEventLoopPolicy(),
CustomEventLoopPolicy(),
),
)
def event_loop_policy(request):
return request.param
@pytest.mark.asyncio
@pytest.mark.filterwarnings("ignore::DeprecationWarning")
async def test_uses_custom_event_loop_policy():
assert isinstance(asyncio.get_event_loop_policy(), DefaultEventLoopPolicy)
pytest-dev-pytest-asyncio-dbe96ef/docs/reference/fixtures/index.rst 0000664 0000000 0000000 00000003014 15205265572 0026004 0 ustar 00root root 0000000 0000000 ========
Fixtures
========
event_loop_policy
=================
.. warning::
Overriding the *event_loop_policy* fixture is deprecated and will be removed in a future version of pytest-asyncio. Use the ``pytest_asyncio_loop_factories`` hook instead. See :doc:`../hooks` for details.
Returns the event loop policy used to create asyncio event loops.
The default return value is *asyncio.get_event_loop_policy().*
This fixture can be overridden when a different event loop policy should be used.
.. include:: event_loop_policy_example.py
:code: python
Multiple policies can be provided via fixture parameters.
The fixture is automatically applied to all pytest-asyncio tests.
Therefore, all tests managed by pytest-asyncio are run once for each fixture parameter.
The following example runs the test with different event loop policies.
.. include:: event_loop_policy_parametrized_example.py
:code: python
unused_tcp_port
===============
Finds and yields a single unused TCP port on the localhost interface. Useful for
binding temporary test servers.
unused_tcp_port_factory
=======================
A callable which returns a different unused TCP port each invocation. Useful
when several unused TCP ports are required in a test.
.. code-block:: python
def a_test(unused_tcp_port_factory):
_port1, _port2 = unused_tcp_port_factory(), unused_tcp_port_factory()
unused_udp_port and unused_udp_port_factory
===========================================
Works just like their TCP counterparts but returns unused UDP ports.
pytest-dev-pytest-asyncio-dbe96ef/docs/reference/functions.rst 0000664 0000000 0000000 00000000370 15205265572 0025036 0 ustar 00root root 0000000 0000000 =========
Functions
=========
is_async_test
=============
Returns whether a specific pytest Item is an asynchronous test managed by pytest-asyncio.
This function is intended to be used in pytest hooks or by plugins that depend on pytest-asyncio.
pytest-dev-pytest-asyncio-dbe96ef/docs/reference/hooks.rst 0000664 0000000 0000000 00000002110 15205265572 0024143 0 ustar 00root root 0000000 0000000 =====
Hooks
=====
``pytest_asyncio_loop_factories``
=================================
This hook returns a mapping from factory name strings to event loop factory callables for the current test item.
By default, each pytest-asyncio test is run once per configured factory. Tests managed by other async plugins are unaffected. Synchronous tests are not parametrized. The configured loop scope still determines how long each event loop instance is kept alive.
Factories should be callables without required parameters and should return an ``asyncio.AbstractEventLoop`` instance. The effective hook result must be a non-empty mapping of non-empty string names to callables.
When multiple ``pytest_asyncio_loop_factories`` implementations are present, pytest-asyncio uses the first non-``None`` result in pytest's hook dispatch order.
When the hook is defined, async tests are parametrized via ``pytest.metafunc.parametrize``, and mapping keys are used as test IDs. For example, a test ``test_example`` with an event loop factory key ``foo`` will appear as ``test_example[foo]`` in test output.
pytest-dev-pytest-asyncio-dbe96ef/docs/reference/index.rst 0000664 0000000 0000000 00000000537 15205265572 0024142 0 ustar 00root root 0000000 0000000 =========
Reference
=========
.. toctree::
:hidden:
configuration
fixtures/index
functions
hooks
markers/index
decorators/index
changelog
This section of the documentation provides descriptions of the individual parts provided by pytest-asyncio.
The reference section also provides a chronological list of changes for each release.
pytest-dev-pytest-asyncio-dbe96ef/docs/reference/markers/ 0000775 0000000 0000000 00000000000 15205265572 0023740 5 ustar 00root root 0000000 0000000 pytest-dev-pytest-asyncio-dbe96ef/docs/reference/markers/class_scoped_loop_strict_mode_example.py 0000664 0000000 0000000 00000000547 15205265572 0034122 0 ustar 00root root 0000000 0000000 import asyncio
import pytest
@pytest.mark.asyncio(loop_scope="class")
class TestClassScopedLoop:
loop: asyncio.AbstractEventLoop
async def test_remember_loop(self):
TestClassScopedLoop.loop = asyncio.get_running_loop()
async def test_this_runs_in_same_loop(self):
assert asyncio.get_running_loop() is TestClassScopedLoop.loop
class_scoped_loop_with_fixture_strict_mode_example.py 0000664 0000000 0000000 00000000670 15205265572 0036641 0 ustar 00root root 0000000 0000000 pytest-dev-pytest-asyncio-dbe96ef/docs/reference/markers import asyncio
import pytest
import pytest_asyncio
@pytest.mark.asyncio(loop_scope="class")
class TestClassScopedLoop:
loop: asyncio.AbstractEventLoop
@pytest_asyncio.fixture(loop_scope="class")
async def my_fixture(self):
TestClassScopedLoop.loop = asyncio.get_running_loop()
async def test_runs_is_same_loop_as_fixture(self, my_fixture):
assert asyncio.get_running_loop() is TestClassScopedLoop.loop
function_scoped_loop_pytestmark_strict_mode_example.py 0000664 0000000 0000000 00000000300 15205265572 0037031 0 ustar 00root root 0000000 0000000 pytest-dev-pytest-asyncio-dbe96ef/docs/reference/markers import asyncio
import pytest
# Marks all test coroutines in this module
pytestmark = pytest.mark.asyncio
async def test_runs_in_asyncio_event_loop():
assert asyncio.get_running_loop()
pytest-dev-pytest-asyncio-dbe96ef/docs/reference/markers/function_scoped_loop_strict_mode_example.py0000664 0000000 0000000 00000000210 15205265572 0034625 0 ustar 00root root 0000000 0000000 import asyncio
import pytest
@pytest.mark.asyncio
async def test_runs_in_asyncio_event_loop():
assert asyncio.get_running_loop()
pytest-dev-pytest-asyncio-dbe96ef/docs/reference/markers/index.rst 0000664 0000000 0000000 00000003755 15205265572 0025613 0 ustar 00root root 0000000 0000000 =======
Markers
=======
.. _reference/markers/asyncio:
``pytest.mark.asyncio``
=======================
A coroutine or async generator with this marker is treated as a test function by pytest.
The marked function is executed as an asyncio task in the event loop provided by pytest-asyncio.
Multiple async tests in a single class or module can be marked in different ways:
.. tabs::
.. tab:: decorator
.. include:: function_scoped_loop_strict_mode_example.py
:code: python
.. tab:: pytestmark
.. include:: function_scoped_loop_pytestmark_strict_mode_example.py
:code: python
The ``pytest.mark.asyncio`` marker can be omitted entirely in |auto mode|_ where the *asyncio* marker is added automatically to *async* test functions.
By default, each test runs in its own asyncio event loop.
Multiple tests can share the same event loop by providing a *loop_scope* keyword argument to the *asyncio* mark.
The supported scopes are *function*, *class*, *module*, *package*, and *session*.
The following code example provides a shared event loop for all tests in ``TestClassScopedLoop``:
.. include:: class_scoped_loop_strict_mode_example.py
:code: python
Similar to class-scoped event loops, a module-scoped loop is provided when setting mark's scope to *module*:
.. include:: module_scoped_loop_strict_mode_example.py
:code: python
Subpackages do not share the loop with their parent package.
Tests marked with *session* scope share the same event loop, even if the tests exist in different packages.
The ``pytest.mark.asyncio`` marker also accepts a ``loop_factories`` keyword argument to select a subset of configured event loop factories for a test. If ``loop_factories`` contains names not available from the hook, those test variants are skipped.
.. |auto mode| replace:: *auto mode*
.. _auto mode: ../../concepts.html#auto-mode
.. |pytestmark| replace:: ``pytestmark``
.. _pytestmark: http://doc.pytest.org/en/latest/example/markers.html#marking-whole-classes-or-modules
pytest-dev-pytest-asyncio-dbe96ef/docs/reference/markers/module_scoped_loop_strict_mode_example.py 0000664 0000000 0000000 00000000702 15205265572 0034273 0 ustar 00root root 0000000 0000000 import asyncio
import pytest
pytestmark = pytest.mark.asyncio(loop_scope="module")
loop: asyncio.AbstractEventLoop
async def test_remember_loop():
global loop
loop = asyncio.get_running_loop()
async def test_this_runs_in_same_loop():
global loop
assert asyncio.get_running_loop() is loop
class TestClassA:
async def test_this_runs_in_same_loop(self):
global loop
assert asyncio.get_running_loop() is loop
pytest-dev-pytest-asyncio-dbe96ef/docs/support.rst 0000664 0000000 0000000 00000002454 15205265572 0022611 0 ustar 00root root 0000000 0000000 ===============
Getting support
===============
Enterprise support
==================
`Tidelift `_ works with maintainers of numerous open source projects to ensure enterprise-grade support for your software supply chain.
The Tidelift subscription includes security updates, verified license compliance, continuous software maintenance, and more. As a result, you get the guarantees provided by commercial software for the open source packages you use.
Consider `signing up for the Tidelift subscription `__.
Direct maintainer support
=========================
If you require commercial support outside of the Tidelift subscription, reach out to `Michael Seifert, `__ one of the project's maintainers.
Community support
=================
The GitHub page of pytest-asyncio offers free community support on a best-effort basis. Please use the `issue tracker `__ to report bugs and the Matrix chat room `#pytest-asyncio:matrix.org `__ or `GitHub discussions `__ to ask questions.
pytest-dev-pytest-asyncio-dbe96ef/pyproject.toml 0000664 0000000 0000000 00000011520 15205265572 0022321 0 ustar 00root root 0000000 0000000 [build-system]
build-backend = "setuptools.build_meta"
requires = [
"setuptools>=77",
"setuptools-scm[toml]>=6.2",
]
[project]
name = "pytest-asyncio"
description = "Pytest support for asyncio"
readme.content-type = "text/x-rst"
readme.file = "README.rst"
license = "Apache-2.0"
license-files = [
"LICENSE",
]
maintainers = [
{ name = "Michael Seifert", email = "m.seifert@digitalernachschub.de" },
]
authors = [
{ name = "Tin Tvrtković", email = "tinchester@gmail.com" },
]
requires-python = ">=3.10"
classifiers = [
"Development Status :: 5 - Production/Stable",
"Framework :: AsyncIO",
"Framework :: Pytest",
"Intended Audience :: Developers",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Topic :: Software Development :: Testing",
"Typing :: Typed",
]
dynamic = [
"version",
]
dependencies = [
"backports-asyncio-runner>=1.1,<2; python_version<'3.11'",
"pytest>=8.4,<10",
"typing-extensions>=4.12; python_version<'3.13'",
]
optional-dependencies.docs = [
"sphinx>=5.3",
"sphinx-rtd-theme>=1",
"sphinx-tabs>=3.5",
]
optional-dependencies.testing = [
"coverage>=6.2",
"hypothesis>=5.7.1",
]
urls."Bug Tracker" = "https://github.com/pytest-dev/pytest-asyncio/issues"
urls.Changelog = "https://pytest-asyncio.readthedocs.io/en/stable/reference/changelog.html"
urls.Documentation = "https://pytest-asyncio.readthedocs.io"
urls.Homepage = "https://github.com/pytest-dev/pytest-asyncio"
urls."Source Code" = "https://github.com/pytest-dev/pytest-asyncio"
entry-points.pytest11.asyncio = "pytest_asyncio.plugin"
[tool.setuptools]
packages = [
"pytest_asyncio",
]
include-package-data = true
[tool.setuptools_scm]
local_scheme = "no-local-version"
[tool.ruff]
line-length = 88
format.docstring-code-format = true
lint.select = [
"B", # bugbear
"D", # pydocstyle
"E", # pycodestyle
"F", # pyflakes
"FA100", # add future annotations
"PGH004", # pygrep-hooks - Use specific rule codes when using noqa
"PIE", # flake8-pie
"PLE", # pylint error
"PYI", # flake8-pyi
"RUF", # ruff
"T100", # flake8-debugger
"UP", # pyupgrade
"W", # pycodestyle
]
lint.ignore = [
# bugbear ignore
"B028", # No explicit `stacklevel` keyword argument found
# pydocstyle ignore
"D100", # Missing docstring in public module
"D101", # Missing docstring in public class
"D102", # Missing docstring in public method
"D103", # Missing docstring in public function
"D104", # Missing docstring in public package
"D105", # Missing docstring in magic method
"D106", # Missing docstring in public nested class
"D107", # Missing docstring in `__init__`
"D203", # `one-blank-line-before-class` (D203) and `no-blank-line-before-class` (D211) are incompatible
"D205", # 1 blank line required between summary line and description
"D209", # [*] Multi-line docstring closing quotes should be on a separate line
"D212", # `multi-line-summary-first-line` (D212) and `multi-line-summary-second-line` (D213) are incompatible.
"D400", # First line should end with a period
"D401", # First line of docstring should be in imperative mood
"D402", # First line should not be the function's signature
"D404", # First word of the docstring should not be "This"
"D415", # First line should end with a period, question mark, or exclamation point
]
[tool.pyproject-fmt]
max_supported_python = "3.14"
[tool.pytest.ini_options]
python_files = [
"test_*.py",
"*_example.py",
]
addopts = "-rsx --tb=short"
testpaths = [
"docs",
"tests",
]
asyncio_mode = "auto"
asyncio_default_fixture_loop_scope = "function"
junit_family = "xunit2"
filterwarnings = [
"error",
]
[tool.coverage.run]
source = [
"pytest_asyncio",
]
branch = true
data_file = "coverage/coverage"
parallel = true
[tool.coverage.report]
show_missing = true
[tool.towncrier]
directory = "changelog.d"
filename = "docs/reference/changelog.rst"
title_format = "`{version} `_ - {project_date}"
issue_format = "`#{issue} `_"
[[tool.towncrier.type]]
directory = "security"
name = "Security"
showcontent = true
[[tool.towncrier.type]]
directory = "removed"
name = "Removed"
showcontent = true
[[tool.towncrier.type]]
directory = "deprecated"
name = "Deprecated"
showcontent = true
[[tool.towncrier.type]]
directory = "added"
name = "Added"
showcontent = true
[[tool.towncrier.type]]
directory = "changed"
name = "Changed"
showcontent = true
[[tool.towncrier.type]]
directory = "fixed"
name = "Fixed"
showcontent = true
[[tool.towncrier.type]]
directory = "downstream"
name = "Notes for Downstream Packagers"
showcontent = true
pytest-dev-pytest-asyncio-dbe96ef/pytest_asyncio/ 0000775 0000000 0000000 00000000000 15205265572 0022463 5 ustar 00root root 0000000 0000000 pytest-dev-pytest-asyncio-dbe96ef/pytest_asyncio/__init__.py 0000664 0000000 0000000 00000000372 15205265572 0024576 0 ustar 00root root 0000000 0000000 """The main point for importing pytest-asyncio items."""
from __future__ import annotations
from importlib.metadata import version
from .plugin import fixture, is_async_test
__version__ = version(__name__)
__all__ = ("fixture", "is_async_test")
pytest-dev-pytest-asyncio-dbe96ef/pytest_asyncio/plugin.py 0000664 0000000 0000000 00000112470 15205265572 0024340 0 ustar 00root root 0000000 0000000 """pytest-asyncio implementation."""
from __future__ import annotations
import asyncio
import contextlib
import contextvars
import enum
import functools
import inspect
import socket
import sys
import traceback
import warnings
from asyncio import AbstractEventLoop
from collections.abc import (
AsyncIterator,
Awaitable,
Callable,
Collection,
Generator,
Iterable,
Iterator,
Mapping,
Sequence,
)
from types import AsyncGeneratorType, CoroutineType
from typing import (
TYPE_CHECKING,
Any,
Literal,
ParamSpec,
TypeAlias,
TypeVar,
overload,
)
import pluggy
import pytest
from _pytest.fixtures import resolve_fixture_function
from _pytest.scope import Scope
from pytest import (
Config,
FixtureDef,
FixtureRequest,
Function,
Item,
Mark,
MonkeyPatch,
Parser,
PytestCollectionWarning,
PytestDeprecationWarning,
PytestPluginManager,
)
if sys.version_info >= (3, 11):
from asyncio import Runner
else:
from backports.asyncio.runner import Runner
if sys.version_info >= (3, 13):
from typing import TypeIs
else:
from typing_extensions import TypeIs
if TYPE_CHECKING:
# AbstractEventLoopPolicy is deprecated and scheduled for removal in Python 3.16
# Import it for type checking only to avoid raising a DeprecationWarning.
from asyncio import AbstractEventLoopPolicy
_ScopeName = Literal["session", "package", "module", "class", "function"]
_R = TypeVar("_R", bound=Awaitable[Any] | AsyncIterator[Any])
_P = ParamSpec("_P")
FixtureFunction = Callable[_P, _R]
LoopFactory: TypeAlias = Callable[[], AbstractEventLoop]
class PytestAsyncioError(Exception):
"""Base class for exceptions raised by pytest-asyncio"""
class Mode(str, enum.Enum):
AUTO = "auto"
STRICT = "strict"
hookspec = pluggy.HookspecMarker("pytest")
class PytestAsyncioSpecs:
@hookspec(firstresult=True)
def pytest_asyncio_loop_factories(
self,
config: Config,
item: Item,
) -> Mapping[str, LoopFactory] | None:
raise NotImplementedError # pragma: no cover
ASYNCIO_MODE_HELP = """\
'auto' - for automatically handling all async functions by the plugin
'strict' - for autoprocessing disabling (useful if different async frameworks \
should be tested together, e.g. \
both pytest-asyncio and pytest-trio are used in the same project)
"""
def pytest_addoption(parser: Parser, pluginmanager: PytestPluginManager) -> None:
pluginmanager.add_hookspecs(PytestAsyncioSpecs)
group = parser.getgroup("asyncio")
group.addoption(
"--asyncio-mode",
dest="asyncio_mode",
default=None,
metavar="MODE",
help=ASYNCIO_MODE_HELP,
)
group.addoption(
"--asyncio-debug",
dest="asyncio_debug",
action="store_true",
default=None,
help="enable asyncio debug mode for the default event loop",
)
parser.addini(
"asyncio_mode",
help="default value for --asyncio-mode",
default="strict",
)
parser.addini(
"asyncio_debug",
help="enable asyncio debug mode for the default event loop",
type="bool",
default="false",
)
parser.addini(
"asyncio_default_fixture_loop_scope",
type="string",
help="default scope of the asyncio event loop used to execute async fixtures",
default=None,
)
parser.addini(
"asyncio_default_test_loop_scope",
type="string",
help="default scope of the asyncio event loop used to execute tests",
default="function",
)
@overload
def fixture(
fixture_function: FixtureFunction[_P, _R],
*,
scope: _ScopeName | Callable[[str, Config], _ScopeName] = ...,
loop_scope: _ScopeName | None = ...,
params: Iterable[object] | None = ...,
autouse: bool = ...,
ids: (
Iterable[str | float | int | bool | None]
| Callable[[Any], object | None]
| None
) = ...,
name: str | None = ...,
) -> FixtureFunction[_P, _R]: ...
@overload
def fixture(
fixture_function: None = ...,
*,
scope: _ScopeName | Callable[[str, Config], _ScopeName] = ...,
loop_scope: _ScopeName | None = ...,
params: Iterable[object] | None = ...,
autouse: bool = ...,
ids: (
Iterable[str | float | int | bool | None]
| Callable[[Any], object | None]
| None
) = ...,
name: str | None = None,
) -> Callable[[FixtureFunction[_P, _R]], FixtureFunction[_P, _R]]: ...
def fixture(
fixture_function: FixtureFunction[_P, _R] | None = None,
loop_scope: _ScopeName | None = None,
**kwargs: Any,
) -> (
FixtureFunction[_P, _R]
| Callable[[FixtureFunction[_P, _R]], FixtureFunction[_P, _R]]
):
if fixture_function is not None:
_make_asyncio_fixture_function(fixture_function, loop_scope)
return pytest.fixture(fixture_function, **kwargs)
else:
@functools.wraps(fixture)
def inner(fixture_function: FixtureFunction[_P, _R]) -> FixtureFunction[_P, _R]:
return fixture(fixture_function, loop_scope=loop_scope, **kwargs)
return inner
def _is_asyncio_fixture_function(obj: Any) -> bool:
obj = getattr(obj, "__func__", obj) # instance method maybe?
return getattr(obj, "_force_asyncio_fixture", False)
def _make_asyncio_fixture_function(obj: Any, loop_scope: _ScopeName | None) -> None:
if hasattr(obj, "__func__"):
# instance method, check the function object
obj = obj.__func__
obj._force_asyncio_fixture = True
obj._loop_scope = loop_scope
def _is_coroutine_or_asyncgen(obj: Any) -> bool:
return inspect.iscoroutinefunction(obj) or inspect.isasyncgenfunction(obj)
def _get_asyncio_mode(config: Config) -> Mode:
val = config.getoption("asyncio_mode")
if val is None:
val = config.getini("asyncio_mode")
try:
return Mode(val)
except ValueError as e:
modes = ", ".join(m.value for m in Mode)
raise pytest.UsageError(
f"{val!r} is not a valid asyncio_mode. Valid modes: {modes}."
) from e
def _get_asyncio_debug(config: Config) -> bool:
val = config.getoption("asyncio_debug")
if val is None:
val = config.getini("asyncio_debug")
if isinstance(val, bool):
return val
else:
return val == "true"
_INVALID_LOOP_FACTORIES = """\
pytest_asyncio_loop_factories must return a non-empty mapping of \
factory names to callables.
"""
def _collect_hook_loop_factories(
config: Config,
item: Item,
) -> dict[str, LoopFactory] | None:
hook_caller = item.ihook.pytest_asyncio_loop_factories
if not hook_caller.get_hookimpls():
return None
result = hook_caller(config=config, item=item)
if result is None or not isinstance(result, Mapping):
raise pytest.UsageError(_INVALID_LOOP_FACTORIES)
# Copy into an isolated snapshot so later mutations of the hook's
# original container do not affect parametrization.
factories = dict(result)
if not factories or any(
not isinstance(name, str) or not name or not callable(factory)
for name, factory in factories.items()
):
raise pytest.UsageError(_INVALID_LOOP_FACTORIES)
return factories
_DEFAULT_FIXTURE_LOOP_SCOPE_UNSET = """\
The configuration option "asyncio_default_fixture_loop_scope" is unset.
The event loop scope for asynchronous fixtures will default to the "fixture" caching \
scope. Future versions of pytest-asyncio will default the loop scope for asynchronous \
fixtures to "function" scope. Set the default fixture loop scope explicitly in order \
to avoid unexpected behavior in the future. Valid fixture loop scopes are: \
"function", "class", "module", "package", "session"
"""
def _validate_scope(scope: str | None, option_name: str) -> None:
if scope is None:
return
valid_scopes = [s.value for s in Scope]
if scope not in valid_scopes:
raise pytest.UsageError(
f"{scope!r} is not a valid {option_name}. "
f"Valid scopes are: {', '.join(valid_scopes)}."
)
def pytest_configure(config: Config) -> None:
default_fixture_loop_scope = config.getini("asyncio_default_fixture_loop_scope")
_validate_scope(default_fixture_loop_scope, "asyncio_default_fixture_loop_scope")
if not default_fixture_loop_scope:
warnings.warn(PytestDeprecationWarning(_DEFAULT_FIXTURE_LOOP_SCOPE_UNSET))
default_test_loop_scope = config.getini("asyncio_default_test_loop_scope")
_validate_scope(default_test_loop_scope, "asyncio_default_test_loop_scope")
config.addinivalue_line(
"markers",
"asyncio: "
"mark the test as a coroutine, it will be "
"run using an asyncio event loop",
)
@pytest.hookimpl(tryfirst=True)
def pytest_report_header(config: Config) -> list[str]:
"""Add asyncio config to pytest header."""
mode = _get_asyncio_mode(config)
debug = _get_asyncio_debug(config)
default_fixture_loop_scope = config.getini("asyncio_default_fixture_loop_scope")
default_test_loop_scope = _get_default_test_loop_scope(config)
header = [
f"mode={mode}",
f"debug={debug}",
f"asyncio_default_fixture_loop_scope={default_fixture_loop_scope}",
f"asyncio_default_test_loop_scope={default_test_loop_scope}",
]
return [
"asyncio: " + ", ".join(header),
]
def _fixture_synchronizer(
fixturedef: FixtureDef, runner: Runner, request: FixtureRequest
) -> Callable:
"""Returns a synchronous function evaluating the specified fixture."""
fixture_function = resolve_fixture_function(fixturedef, request)
if inspect.isasyncgenfunction(fixturedef.func):
return _wrap_asyncgen_fixture(fixture_function, runner, request) # type: ignore[arg-type]
elif inspect.iscoroutinefunction(fixturedef.func):
return _wrap_async_fixture(fixture_function, runner, request) # type: ignore[arg-type]
elif inspect.isgeneratorfunction(fixturedef.func):
return _wrap_syncgen_fixture(fixture_function, runner) # type: ignore[arg-type]
else:
return _wrap_sync_fixture(fixture_function, runner) # type: ignore[arg-type]
SyncGenFixtureParams = ParamSpec("SyncGenFixtureParams")
SyncGenFixtureYieldType = TypeVar("SyncGenFixtureYieldType")
def _wrap_syncgen_fixture(
fixture_function: Callable[
SyncGenFixtureParams, Generator[SyncGenFixtureYieldType]
],
runner: Runner,
) -> Callable[SyncGenFixtureParams, Generator[SyncGenFixtureYieldType]]:
@functools.wraps(fixture_function)
def _syncgen_fixture_wrapper(
*args: SyncGenFixtureParams.args,
**kwargs: SyncGenFixtureParams.kwargs,
) -> Generator[SyncGenFixtureYieldType]:
with _temporary_event_loop(runner.get_loop()):
yield from fixture_function(*args, **kwargs)
return _syncgen_fixture_wrapper
SyncFixtureParams = ParamSpec("SyncFixtureParams")
SyncFixtureReturnType = TypeVar("SyncFixtureReturnType")
def _wrap_sync_fixture(
fixture_function: Callable[SyncFixtureParams, SyncFixtureReturnType],
runner: Runner,
) -> Callable[SyncFixtureParams, SyncFixtureReturnType]:
@functools.wraps(fixture_function)
def _sync_fixture_wrapper(
*args: SyncFixtureParams.args,
**kwargs: SyncFixtureParams.kwargs,
) -> SyncFixtureReturnType:
with _temporary_event_loop(runner.get_loop()):
return fixture_function(*args, **kwargs)
return _sync_fixture_wrapper
AsyncGenFixtureParams = ParamSpec("AsyncGenFixtureParams")
AsyncGenFixtureYieldType = TypeVar("AsyncGenFixtureYieldType")
def _wrap_asyncgen_fixture(
fixture_function: Callable[
AsyncGenFixtureParams, AsyncGeneratorType[AsyncGenFixtureYieldType, Any]
],
runner: Runner,
request: FixtureRequest,
) -> Callable[AsyncGenFixtureParams, AsyncGenFixtureYieldType]:
@functools.wraps(fixture_function)
def _asyncgen_fixture_wrapper(
*args: AsyncGenFixtureParams.args,
**kwargs: AsyncGenFixtureParams.kwargs,
):
gen_obj = fixture_function(*args, **kwargs)
async def setup():
res = await gen_obj.__anext__()
return res
context = contextvars.copy_context()
result = runner.run(setup(), context=context)
reset_contextvars = _apply_contextvar_changes(context)
def finalizer() -> None:
"""Yield again, to finalize."""
async def async_finalizer() -> None:
try:
await gen_obj.__anext__()
except StopAsyncIteration:
pass
else:
msg = "Async generator fixture didn't stop."
msg += "Yield only once."
raise ValueError(msg)
runner.run(async_finalizer(), context=context)
if reset_contextvars is not None:
reset_contextvars()
request.addfinalizer(finalizer)
return result
return _asyncgen_fixture_wrapper
AsyncFixtureParams = ParamSpec("AsyncFixtureParams")
AsyncFixtureReturnType = TypeVar("AsyncFixtureReturnType")
def _wrap_async_fixture(
fixture_function: Callable[
AsyncFixtureParams, CoroutineType[Any, Any, AsyncFixtureReturnType]
],
runner: Runner,
request: FixtureRequest,
) -> Callable[AsyncFixtureParams, AsyncFixtureReturnType]:
@functools.wraps(fixture_function)
def _async_fixture_wrapper(
*args: AsyncFixtureParams.args,
**kwargs: AsyncFixtureParams.kwargs,
):
async def setup():
res = await fixture_function(*args, **kwargs)
return res
context = contextvars.copy_context()
result = runner.run(setup(), context=context)
# Copy the context vars modified by the setup task into the current
# context, and (if needed) add a finalizer to reset them.
#
# Note that this is slightly different from the behavior of a non-async
# fixture, which would rely on the fixture author to add a finalizer
# to reset the variables. In this case, the author of the fixture can't
# write such a finalizer because they have no way to capture the Context
# in which the setup function was run, so we need to do it for them.
reset_contextvars = _apply_contextvar_changes(context)
if reset_contextvars is not None:
request.addfinalizer(reset_contextvars)
return result
return _async_fixture_wrapper
def _apply_contextvar_changes(
context: contextvars.Context,
) -> Callable[[], None] | None:
"""
Copy contextvar changes from the given context to the current context.
If any contextvars were modified by the fixture, return a finalizer that
will restore them.
"""
context_tokens = []
for var in context:
try:
if var.get() is context.get(var):
# This variable is not modified, so leave it as-is.
continue
except LookupError:
# This variable isn't yet set in the current context at all.
pass
token = var.set(context.get(var))
context_tokens.append((var, token))
if not context_tokens:
return None
def restore_contextvars():
while context_tokens:
var, token = context_tokens.pop()
var.reset(token)
return restore_contextvars
class PytestAsyncioFunction(Function):
"""Base class for all test functions managed by pytest-asyncio."""
@classmethod
def item_subclass_for(cls, item: Function, /) -> type[PytestAsyncioFunction] | None:
"""
Returns a subclass of PytestAsyncioFunction if there is a specialized subclass
for the specified function item.
Return None if no specialized subclass exists for the specified item.
"""
for subclass in cls.__subclasses__():
if subclass._can_substitute(item):
return subclass
return None
@classmethod
def _from_function(cls, function: Function, /) -> Function:
"""
Instantiates this specific PytestAsyncioFunction type from the specified
Function item.
"""
assert function.get_closest_marker("asyncio")
assert function.parent is not None
subclass_instance = cls.from_parent(
function.parent,
name=function.name,
callspec=getattr(function, "callspec", None),
callobj=function.obj,
fixtureinfo=function._fixtureinfo,
keywords=function.keywords,
originalname=function.originalname,
)
subclass_instance.own_markers = function.own_markers
assert subclass_instance.own_markers == function.own_markers
return subclass_instance
@staticmethod
def _can_substitute(item: Function) -> bool:
"""Returns whether the specified function can be replaced by this class"""
raise NotImplementedError()
def setup(self) -> None:
runner_fixture_id = f"_{self._loop_scope}_scoped_runner"
if runner_fixture_id not in self.fixturenames:
self.fixturenames.append(runner_fixture_id)
# When loop factories are configured, resolve the loop factory
# fixture early so that a factory variant change cascades cache
# invalidation before any async fixture checks its cache.
hook_caller = self.config.hook.pytest_asyncio_loop_factories
if hook_caller.get_hookimpls():
_ = self._request.getfixturevalue(_asyncio_loop_factory.__name__)
return super().setup()
def runtest(self) -> None:
runner_fixture_id = f"_{self._loop_scope}_scoped_runner"
runner = self._request.getfixturevalue(runner_fixture_id)
context = contextvars.copy_context()
synchronized_obj = _synchronize_coroutine(
getattr(*self._synchronization_target_attr), runner, context
)
with MonkeyPatch.context() as c:
c.setattr(*self._synchronization_target_attr, synchronized_obj)
super().runtest()
@functools.cached_property
def _loop_scope(self) -> _ScopeName:
"""
Return the scope of the asyncio event loop this item is run in.
The effective scope is determined lazily. It is identical to to the
`loop_scope` value of the closest `asyncio` pytest marker. If no such
marker is present, the the loop scope is determined by the configuration
value of `asyncio_default_test_loop_scope`, instead.
"""
marker = self.get_closest_marker("asyncio")
assert marker is not None
default_loop_scope = _get_default_test_loop_scope(self.config)
loop_scope = marker.kwargs.get("loop_scope") or marker.kwargs.get("scope")
if loop_scope is None:
return default_loop_scope
else:
return loop_scope
@property
def _synchronization_target_attr(self) -> tuple[object, str]:
"""
Return the coroutine that needs to be synchronized during the test run.
This method is intended to be overwritten by subclasses when they need to apply
the coroutine synchronizer to a value that's different from self.obj
e.g. the AsyncHypothesisTest subclass.
"""
return self, "obj"
class Coroutine(PytestAsyncioFunction):
"""Pytest item created by a coroutine"""
@staticmethod
def _can_substitute(item: Function) -> bool:
func = item.obj
return inspect.iscoroutinefunction(func)
class AsyncGenerator(PytestAsyncioFunction):
"""Pytest item created by an asynchronous generator"""
@staticmethod
def _can_substitute(item: Function) -> bool:
func = item.obj
return inspect.isasyncgenfunction(func)
@classmethod
def _from_function(cls, function: Function, /) -> Function:
async_gen_item = super()._from_function(function)
unsupported_item_type_message = (
f"Tests based on asynchronous generators are not supported. "
f"{function.name} will be ignored."
)
async_gen_item.warn(PytestCollectionWarning(unsupported_item_type_message))
async_gen_item.add_marker(
pytest.mark.xfail(run=False, reason=unsupported_item_type_message)
)
return async_gen_item
class AsyncStaticMethod(PytestAsyncioFunction):
"""
Pytest item that is a coroutine or an asynchronous generator
decorated with staticmethod
"""
@staticmethod
def _can_substitute(item: Function) -> bool:
func = item.obj
return isinstance(func, staticmethod) and _is_coroutine_or_asyncgen(
func.__func__
)
class AsyncHypothesisTest(PytestAsyncioFunction):
"""
Pytest item that is coroutine or an asynchronous generator decorated by
@hypothesis.given.
"""
def setup(self) -> None:
if not getattr(self.obj, "hypothesis", False) and getattr(
self.obj, "is_hypothesis_test", False
):
pytest.fail(
f"test function `{self!r}` is using Hypothesis, but pytest-asyncio "
"only works with Hypothesis 3.64.0 or later."
)
return super().setup()
@staticmethod
def _can_substitute(item: Function) -> bool:
func = item.obj
return (
getattr(func, "is_hypothesis_test", False) # type: ignore[return-value]
and getattr(func, "hypothesis", None)
and inspect.iscoroutinefunction(func.hypothesis.inner_test)
)
@property
def _synchronization_target_attr(self) -> tuple[object, str]:
return self.obj.hypothesis, "inner_test"
def _resolve_asyncio_marker(item: Function) -> Mark | None:
marker = item.get_closest_marker("asyncio")
if marker is not None:
return marker
if _get_asyncio_mode(item.config) == Mode.AUTO:
item.add_marker("asyncio")
return item.get_closest_marker("asyncio")
return None
# The function name needs to start with "pytest_"
# see https://github.com/pytest-dev/pytest/issues/11307
@pytest.hookimpl(specname="pytest_pycollect_makeitem", hookwrapper=True)
def pytest_pycollect_makeitem_convert_async_functions_to_subclass(
collector: pytest.Module | pytest.Class, name: str, obj: object
) -> Generator[None, pluggy.Result, None]:
"""
Converts coroutines and async generators collected as pytest.Functions
to AsyncFunction items.
"""
hook_result = yield
try:
node_or_list_of_nodes: (
pytest.Item | pytest.Collector | list[pytest.Item | pytest.Collector] | None
) = hook_result.get_result()
except BaseException as e:
hook_result.force_exception(e)
return
if not node_or_list_of_nodes:
return
if isinstance(node_or_list_of_nodes, Sequence):
node_iterator = iter(node_or_list_of_nodes)
else:
# Treat single node as a single-element iterable
node_iterator = iter((node_or_list_of_nodes,))
updated_node_collection = []
for node in node_iterator:
updated_item = node
if isinstance(node, Function):
specialized_item_class = PytestAsyncioFunction.item_subclass_for(node)
if (
specialized_item_class is not None
and _resolve_asyncio_marker(node) is not None
):
updated_item = specialized_item_class._from_function(node)
updated_node_collection.append(updated_item)
hook_result.force_result(updated_node_collection)
@pytest.hookimpl(tryfirst=True)
def pytest_generate_tests(metafunc: pytest.Metafunc) -> None:
specialized_item_class = PytestAsyncioFunction.item_subclass_for(
metafunc.definition
)
if specialized_item_class is None:
return
asyncio_marker = _resolve_asyncio_marker(metafunc.definition)
if asyncio_marker is None:
return
marker_loop_scope, marker_selected_factory_names = _parse_asyncio_marker(
asyncio_marker
)
hook_factories = _collect_hook_loop_factories(metafunc.config, metafunc.definition)
if hook_factories is None:
if marker_selected_factory_names is not None:
raise pytest.UsageError(
"mark.asyncio 'loop_factories' requires at least one "
"pytest_asyncio_loop_factories hook implementation."
)
return
factory_params: Collection[object]
factory_ids: Collection[str]
if marker_selected_factory_names is None:
factory_params = hook_factories.values()
factory_ids = hook_factories.keys()
else:
# Iterate in marker order to preserve explicit user selection
# order.
factory_ids = marker_selected_factory_names
factory_params = [
(
hook_factories[name]
if name in hook_factories
else pytest.param(
None,
marks=pytest.mark.skip(
reason=(
f"Loop factory {name!r} is not available."
f" Available factories:"
f" {', '.join(hook_factories)}."
),
),
)
)
for name in marker_selected_factory_names
]
metafunc.fixturenames.append(_asyncio_loop_factory.__name__)
default_loop_scope = _get_default_test_loop_scope(metafunc.config)
loop_scope = marker_loop_scope or default_loop_scope
# pytest.HIDDEN_PARAM was added in pytest 8.4
hide_id = len(factory_ids) == 1 and hasattr(pytest, "HIDDEN_PARAM")
metafunc.parametrize(
_asyncio_loop_factory.__name__,
factory_params,
ids=(pytest.HIDDEN_PARAM,) if hide_id else factory_ids,
indirect=True,
scope=loop_scope,
)
@contextlib.contextmanager
def _temporary_event_loop(loop: AbstractEventLoop) -> Iterator[None]:
try:
old_loop = _get_event_loop_no_warn()
except RuntimeError:
old_loop = None
if old_loop is loop:
yield
return
_set_event_loop(loop)
try:
yield
finally:
_set_event_loop(old_loop)
@contextlib.contextmanager
def _temporary_event_loop_policy(
policy: AbstractEventLoopPolicy,
) -> Iterator[None]:
old_loop_policy = _get_event_loop_policy()
_set_event_loop_policy(policy)
try:
yield
finally:
_set_event_loop_policy(old_loop_policy)
def _get_event_loop_policy() -> AbstractEventLoopPolicy:
with warnings.catch_warnings():
warnings.simplefilter("ignore", DeprecationWarning)
return asyncio.get_event_loop_policy()
def _set_event_loop_policy(policy: AbstractEventLoopPolicy) -> None:
with warnings.catch_warnings():
warnings.simplefilter("ignore", DeprecationWarning)
asyncio.set_event_loop_policy(policy)
def _get_event_loop_no_warn(
policy: AbstractEventLoopPolicy | None = None,
) -> asyncio.AbstractEventLoop:
with warnings.catch_warnings():
warnings.simplefilter("ignore", DeprecationWarning)
if policy is not None:
return policy.get_event_loop()
else:
return asyncio.get_event_loop()
def _set_event_loop(loop: AbstractEventLoop | None) -> None:
with warnings.catch_warnings():
warnings.simplefilter("ignore", DeprecationWarning)
asyncio.set_event_loop(loop)
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_pyfunc_call(pyfuncitem: Function) -> object | None:
"""Pytest hook called before a test case is run."""
if pyfuncitem.get_closest_marker("asyncio") is not None:
if is_async_test(pyfuncitem):
asyncio_mode = _get_asyncio_mode(pyfuncitem.config)
for fixname, fixtures in pyfuncitem._fixtureinfo.name2fixturedefs.items():
# name2fixturedefs is a dict between fixture name and a list of matching
# fixturedefs. The last entry in the list is closest and the one used.
func = fixtures[-1].func
if (
asyncio_mode == Mode.STRICT
and _is_coroutine_or_asyncgen(func)
and not _is_asyncio_fixture_function(func)
):
warnings.warn(
PytestDeprecationWarning(
f"asyncio test {pyfuncitem.name!r} requested async "
"@pytest.fixture "
f"{fixname!r} in strict mode. "
"You might want to use @pytest_asyncio.fixture or switch "
"to auto mode. "
"This will become an error in future versions of "
"pytest-asyncio."
),
stacklevel=1,
)
# no stacklevel points at the users code, so we set stacklevel=1
# so it at least indicates that it's the plugin complaining.
# Pytest gives the test file & name in the warnings summary at least
else:
pyfuncitem.warn(
pytest.PytestWarning(
f"The test {pyfuncitem} is marked with '@pytest.mark.asyncio' "
"but it is not an async function. "
"Please remove the asyncio mark. "
"If the test is not marked explicitly, "
"check for global marks applied via 'pytestmark'."
)
)
yield
return None
def _synchronize_coroutine(
func: Callable[..., CoroutineType],
runner: asyncio.Runner,
context: contextvars.Context,
):
"""
Return a sync wrapper around a coroutine executing it in the
specified runner and context.
"""
@functools.wraps(func)
def inner(*args, **kwargs):
coro = func(*args, **kwargs)
runner.run(coro, context=context)
return inner
@pytest.hookimpl(wrapper=True)
def pytest_fixture_setup(fixturedef: FixtureDef, request) -> object | None:
if (
fixturedef.argname == "event_loop_policy"
and fixturedef.func.__module__ != __name__
):
warnings.warn(
PytestDeprecationWarning(_EVENT_LOOP_POLICY_FIXTURE_DEPRECATION_WARNING),
)
asyncio_mode = _get_asyncio_mode(request.config)
if not _is_asyncio_fixture_function(fixturedef.func):
if asyncio_mode == Mode.STRICT:
# Ignore async fixtures without explicit asyncio mark in strict mode
# This applies to pytest_trio fixtures, for example
return (yield)
if not _is_coroutine_or_asyncgen(fixturedef.func):
return (yield)
default_loop_scope = request.config.getini("asyncio_default_fixture_loop_scope")
loop_scope = (
getattr(fixturedef.func, "_loop_scope", None)
or default_loop_scope
or fixturedef.scope
)
runner_fixture_id = f"_{loop_scope}_scoped_runner"
runner = request.getfixturevalue(runner_fixture_id)
# Prevent the runner closing before the fixture's async teardown.
runner_fixturedef = request._get_active_fixturedef(runner_fixture_id)
runner_fixturedef.addfinalizer(
functools.partial(fixturedef.finish, request=request)
)
synchronizer = _fixture_synchronizer(fixturedef, runner, request)
_make_asyncio_fixture_function(synchronizer, loop_scope)
with MonkeyPatch.context() as c:
c.setattr(fixturedef, "func", synchronizer)
hook_result = yield
return hook_result
_DUPLICATE_LOOP_SCOPE_DEFINITION_ERROR = """\
An asyncio pytest marker defines both "scope" and "loop_scope", \
but it should only use "loop_scope".
"""
_MARKER_SCOPE_KWARG_DEPRECATION_WARNING = """\
The "scope" keyword argument to the asyncio marker has been deprecated. \
Please use the "loop_scope" argument instead.
"""
_INVALID_LOOP_FACTORIES_KWARG = """\
mark.asyncio 'loop_factories' must be a non-empty sequence of strings.
"""
_EVENT_LOOP_POLICY_FIXTURE_DEPRECATION_WARNING = """\
Overriding the "event_loop_policy" fixture is deprecated \
and will be removed in a future version of pytest-asyncio. \
Use the "pytest_asyncio_loop_factories" hook to customize event loop creation.\
"""
def _parse_asyncio_marker(
asyncio_marker: Mark,
) -> tuple[_ScopeName | None, Sequence[str] | None]:
assert asyncio_marker.name == "asyncio"
_validate_asyncio_marker(asyncio_marker)
if "scope" in asyncio_marker.kwargs:
if "loop_scope" in asyncio_marker.kwargs:
raise pytest.UsageError(_DUPLICATE_LOOP_SCOPE_DEFINITION_ERROR)
warnings.warn(PytestDeprecationWarning(_MARKER_SCOPE_KWARG_DEPRECATION_WARNING))
scope = asyncio_marker.kwargs.get("loop_scope") or asyncio_marker.kwargs.get(
"scope"
)
if scope is not None:
assert scope in {"function", "class", "module", "package", "session"}
marker_value = asyncio_marker.kwargs.get("loop_factories")
if marker_value is None:
return scope, None
if isinstance(marker_value, str) or not isinstance(marker_value, Sequence):
raise ValueError(_INVALID_LOOP_FACTORIES_KWARG)
if not marker_value or any(
not isinstance(factory_name, str) or not factory_name
for factory_name in marker_value
):
raise ValueError(_INVALID_LOOP_FACTORIES_KWARG)
return scope, marker_value
def _validate_asyncio_marker(asyncio_marker: Mark) -> None:
if asyncio_marker.args or (
asyncio_marker.kwargs
and set(asyncio_marker.kwargs) - {"loop_scope", "scope", "loop_factories"}
):
msg = (
"mark.asyncio accepts only keyword arguments 'loop_scope' and"
" 'loop_factories'."
)
raise ValueError(msg)
def _get_default_test_loop_scope(config: Config) -> Any:
return config.getini("asyncio_default_test_loop_scope")
_RUNNER_TEARDOWN_WARNING = """\
An exception occurred during teardown of an asyncio.Runner. \
The reason is likely that you closed the underlying event loop in a test, \
which prevents the cleanup of asynchronous generators by the runner.
This warning will become an error in future versions of pytest-asyncio. \
Please ensure that your tests don't close the event loop. \
Here is the traceback of the exception triggered during teardown:
%s
"""
def _create_scoped_runner_fixture(scope: _ScopeName) -> Callable:
@pytest.fixture(
scope=scope,
name=f"_{scope}_scoped_runner",
)
def _scoped_runner(
event_loop_policy,
_asyncio_loop_factory,
request: FixtureRequest,
) -> Iterator[Runner]:
new_loop_policy = event_loop_policy
debug_mode = _get_asyncio_debug(request.config)
with _temporary_event_loop_policy(new_loop_policy):
runner = Runner(
debug=debug_mode,
loop_factory=_asyncio_loop_factory,
).__enter__()
if _asyncio_loop_factory is not None:
_set_event_loop(runner.get_loop())
try:
yield runner
except Exception as e:
runner.__exit__(type(e), e, e.__traceback__)
else:
with warnings.catch_warnings():
warnings.filterwarnings(
"ignore", ".*BaseEventLoop.shutdown_asyncgens.*", RuntimeWarning
)
try:
runner.__exit__(None, None, None)
except RuntimeError:
warnings.warn(
_RUNNER_TEARDOWN_WARNING % traceback.format_exc(),
RuntimeWarning,
)
finally:
if _asyncio_loop_factory is not None:
_set_event_loop(None)
return _scoped_runner
for scope in Scope:
globals()[f"_{scope.value}_scoped_runner"] = _create_scoped_runner_fixture(
scope.value
)
@pytest.fixture(scope="session")
def _asyncio_loop_factory(request: FixtureRequest) -> LoopFactory | None:
return getattr(request, "param", None)
@pytest.fixture(scope="session", autouse=True)
def event_loop_policy() -> AbstractEventLoopPolicy:
"""Return an instance of the policy used to create asyncio event loops."""
return _get_event_loop_policy()
def is_async_test(item: Item) -> TypeIs[PytestAsyncioFunction]:
"""Returns whether a test item is a pytest-asyncio test"""
return isinstance(item, PytestAsyncioFunction)
def _unused_port(socket_type: int) -> int:
"""Find an unused localhost port from 1024-65535 and return it."""
with contextlib.closing(socket.socket(type=socket_type)) as sock:
sock.bind(("127.0.0.1", 0))
return sock.getsockname()[1]
@pytest.fixture
def unused_tcp_port() -> int:
return _unused_port(socket.SOCK_STREAM)
@pytest.fixture
def unused_udp_port() -> int:
return _unused_port(socket.SOCK_DGRAM)
@pytest.fixture(scope="session")
def unused_tcp_port_factory() -> Callable[[], int]:
"""A factory function, producing different unused TCP ports."""
produced = set()
def factory():
"""Return an unused port."""
port = _unused_port(socket.SOCK_STREAM)
while port in produced:
port = _unused_port(socket.SOCK_STREAM)
produced.add(port)
return port
return factory
@pytest.fixture(scope="session")
def unused_udp_port_factory() -> Callable[[], int]:
"""A factory function, producing different unused UDP ports."""
produced = set()
def factory():
"""Return an unused port."""
port = _unused_port(socket.SOCK_DGRAM)
while port in produced:
port = _unused_port(socket.SOCK_DGRAM)
produced.add(port)
return port
return factory
pytest-dev-pytest-asyncio-dbe96ef/pytest_asyncio/py.typed 0000664 0000000 0000000 00000000000 15205265572 0024150 0 ustar 00root root 0000000 0000000 pytest-dev-pytest-asyncio-dbe96ef/tests/ 0000775 0000000 0000000 00000000000 15205265572 0020550 5 ustar 00root root 0000000 0000000 pytest-dev-pytest-asyncio-dbe96ef/tests/__init__.py 0000664 0000000 0000000 00000000000 15205265572 0022647 0 ustar 00root root 0000000 0000000 pytest-dev-pytest-asyncio-dbe96ef/tests/async_fixtures/ 0000775 0000000 0000000 00000000000 15205265572 0023616 5 ustar 00root root 0000000 0000000 pytest-dev-pytest-asyncio-dbe96ef/tests/async_fixtures/__init__.py 0000664 0000000 0000000 00000000000 15205265572 0025715 0 ustar 00root root 0000000 0000000 pytest-dev-pytest-asyncio-dbe96ef/tests/async_fixtures/test_async_fixtures.py 0000664 0000000 0000000 00000001456 15205265572 0030303 0 ustar 00root root 0000000 0000000 from __future__ import annotations
import asyncio
import unittest.mock
import pytest
START = object()
END = object()
RETVAL = object()
@pytest.fixture
def mock():
return unittest.mock.Mock(return_value=RETVAL)
@pytest.fixture
async def async_fixture(mock):
return await asyncio.sleep(0.1, result=mock(START))
@pytest.mark.asyncio
async def test_async_fixture(async_fixture, mock):
assert mock.call_count == 1
assert mock.call_args_list[-1] == unittest.mock.call(START)
assert async_fixture is RETVAL
class TestAsyncFixtureMethod:
is_same_instance = False
@pytest.fixture(autouse=True)
async def async_fixture_method(self):
self.is_same_instance = True
@pytest.mark.asyncio
async def test_async_fixture_method(self):
assert self.is_same_instance
pytest-dev-pytest-asyncio-dbe96ef/tests/async_fixtures/test_async_fixtures_contextvars.py 0000664 0000000 0000000 00000016600 15205265572 0032740 0 ustar 00root root 0000000 0000000 """
Regression test for https://github.com/pytest-dev/pytest-asyncio/issues/127:
contextvars were not properly maintained among fixtures and tests.
"""
from __future__ import annotations
from textwrap import dedent
from typing import Literal
import pytest
from pytest import Pytester
_prelude = dedent("""
import pytest
import pytest_asyncio
from contextlib import contextmanager
from contextvars import ContextVar
_context_var = ContextVar("context_var")
@contextmanager
def context_var_manager(value):
token = _context_var.set(value)
try:
yield
finally:
_context_var.reset(token)
""")
def test_var_from_sync_generator_propagates_to_async(pytester: Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(_prelude + dedent("""
@pytest.fixture
def var_fixture():
with context_var_manager("value"):
yield
@pytest_asyncio.fixture
async def check_var_fixture(var_fixture):
assert _context_var.get() == "value"
@pytest.mark.asyncio
async def test(check_var_fixture):
assert _context_var.get() == "value"
"""))
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=1)
def test_var_from_async_generator_propagates_to_sync(pytester: Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(_prelude + dedent("""
@pytest_asyncio.fixture
async def var_fixture():
with context_var_manager("value"):
yield
@pytest.fixture
def check_var_fixture(var_fixture):
assert _context_var.get() == "value"
@pytest.mark.asyncio
async def test(check_var_fixture):
assert _context_var.get() == "value"
"""))
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=1)
def test_var_from_async_fixture_propagates_to_sync(pytester: Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(_prelude + dedent("""
@pytest_asyncio.fixture
async def var_fixture():
_context_var.set("value")
# Rely on async fixture teardown to reset the context var.
@pytest.fixture
def check_var_fixture(var_fixture):
assert _context_var.get() == "value"
def test(check_var_fixture):
assert _context_var.get() == "value"
"""))
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=1)
def test_var_from_generator_reset_before_previous_fixture_cleanup(pytester: Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(_prelude + dedent("""
@pytest_asyncio.fixture
async def no_var_fixture():
with pytest.raises(LookupError):
_context_var.get()
yield
with pytest.raises(LookupError):
_context_var.get()
@pytest_asyncio.fixture
async def var_fixture(no_var_fixture):
with context_var_manager("value"):
yield
@pytest.mark.asyncio
async def test(var_fixture):
assert _context_var.get() == "value"
"""))
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=1)
def test_var_from_fixture_reset_before_previous_fixture_cleanup(pytester: Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(_prelude + dedent("""
@pytest_asyncio.fixture
async def no_var_fixture():
with pytest.raises(LookupError):
_context_var.get()
yield
with pytest.raises(LookupError):
_context_var.get()
@pytest_asyncio.fixture
async def var_fixture(no_var_fixture):
_context_var.set("value")
# Rely on async fixture teardown to reset the context var.
@pytest.mark.asyncio
async def test(var_fixture):
assert _context_var.get() == "value"
"""))
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=1)
def test_var_previous_value_restored_after_fixture(pytester: Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(_prelude + dedent("""
@pytest_asyncio.fixture
async def var_fixture_1():
with context_var_manager("value1"):
yield
assert _context_var.get() == "value1"
@pytest_asyncio.fixture
async def var_fixture_2(var_fixture_1):
with context_var_manager("value2"):
yield
assert _context_var.get() == "value2"
@pytest.mark.asyncio
async def test(var_fixture_2):
assert _context_var.get() == "value2"
"""))
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=1)
def test_var_set_to_existing_value_ok(pytester: Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(_prelude + dedent("""
@pytest_asyncio.fixture
async def var_fixture():
with context_var_manager("value"):
yield
@pytest_asyncio.fixture
async def same_var_fixture(var_fixture):
with context_var_manager(_context_var.get()):
yield
@pytest.mark.asyncio
async def test(same_var_fixture):
assert _context_var.get() == "value"
"""))
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=1)
def test_no_isolation_against_context_changes_in_sync_tests(pytester: Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""
import pytest
import pytest_asyncio
from contextvars import ContextVar
_context_var = ContextVar("my_var")
def test_sync():
_context_var.set("new_value")
@pytest.mark.asyncio
async def test_async():
assert _context_var.get() == "new_value"
"""))
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=2)
@pytest.mark.parametrize("loop_scope", ("function", "module"))
def test_isolation_against_context_changes_in_async_tests(
pytester: Pytester, loop_scope: Literal["function", "module"]
):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent(f"""
import pytest
import pytest_asyncio
from contextvars import ContextVar
_context_var = ContextVar("my_var")
@pytest.mark.asyncio(loop_scope="{loop_scope}")
async def test_async_first():
_context_var.set("new_value")
@pytest.mark.asyncio(loop_scope="{loop_scope}")
async def test_async_second():
with pytest.raises(LookupError):
_context_var.get()
"""))
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=2)
pytest-dev-pytest-asyncio-dbe96ef/tests/async_fixtures/test_nested.py 0000664 0000000 0000000 00000001063 15205265572 0026511 0 ustar 00root root 0000000 0000000 from __future__ import annotations
import asyncio
import pytest
@pytest.fixture()
async def async_inner_fixture():
await asyncio.sleep(0.01)
print("inner start")
yield True
print("inner stop")
@pytest.fixture()
async def async_fixture_outer(async_inner_fixture):
await asyncio.sleep(0.01)
print("outer start")
assert async_inner_fixture is True
yield True
print("outer stop")
@pytest.mark.asyncio
async def test_async_fixture(async_fixture_outer):
assert async_fixture_outer is True
print("test_async_fixture")
pytest-dev-pytest-asyncio-dbe96ef/tests/async_fixtures/test_shared_module_fixture.py 0000664 0000000 0000000 00000002202 15205265572 0031604 0 ustar 00root root 0000000 0000000 from __future__ import annotations
from textwrap import dedent
from pytest import Pytester
def test_asyncio_mark_provides_package_scoped_loop_strict_mode(pytester: Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(
__init__="",
conftest=dedent("""\
import pytest_asyncio
@pytest_asyncio.fixture(loop_scope="module", scope="module")
async def async_shared_module_fixture():
return True
"""),
test_module_one=dedent("""\
import pytest
@pytest.mark.asyncio
async def test_shared_module_fixture_use_a(async_shared_module_fixture):
assert async_shared_module_fixture is True
"""),
test_module_two=dedent("""\
import pytest
@pytest.mark.asyncio
async def test_shared_module_fixture_use_b(async_shared_module_fixture):
assert async_shared_module_fixture is True
"""),
)
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=2)
pytest-dev-pytest-asyncio-dbe96ef/tests/conftest.py 0000664 0000000 0000000 00000000100 15205265572 0022736 0 ustar 00root root 0000000 0000000 from __future__ import annotations
pytest_plugins = "pytester"
pytest-dev-pytest-asyncio-dbe96ef/tests/hypothesis/ 0000775 0000000 0000000 00000000000 15205265572 0022747 5 ustar 00root root 0000000 0000000 pytest-dev-pytest-asyncio-dbe96ef/tests/hypothesis/__init__.py 0000664 0000000 0000000 00000000000 15205265572 0025046 0 ustar 00root root 0000000 0000000 pytest-dev-pytest-asyncio-dbe96ef/tests/hypothesis/test_base.py 0000664 0000000 0000000 00000004547 15205265572 0025304 0 ustar 00root root 0000000 0000000 """
Tests for the Hypothesis integration, which wraps async functions in a
sync shim for Hypothesis.
"""
from __future__ import annotations
from textwrap import dedent
import pytest
from hypothesis import given, strategies as st
from pytest import Pytester
def test_hypothesis_given_decorator_before_asyncio_mark(pytester: Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
import pytest
from hypothesis import given, strategies as st
@given(st.integers())
@pytest.mark.asyncio
async def test_mark_inner(n):
assert isinstance(n, int)
"""))
result = pytester.runpytest("--asyncio-mode=strict", "-W default")
result.assert_outcomes(passed=1)
@pytest.mark.asyncio
@given(st.integers())
async def test_mark_outer(n):
assert isinstance(n, int)
@pytest.mark.parametrize("y", [1, 2])
@given(x=st.none())
@pytest.mark.asyncio
async def test_mark_and_parametrize(x, y):
assert x is None
assert y in (1, 2)
def test_async_auto_marked(pytester: Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
import asyncio
import pytest
from hypothesis import given
import hypothesis.strategies as st
pytest_plugins = 'pytest_asyncio'
@given(n=st.integers())
async def test_hypothesis(n: int):
assert isinstance(n, int)
"""))
result = pytester.runpytest("--asyncio-mode=auto")
result.assert_outcomes(passed=1)
def test_sync_not_auto_marked(pytester: Pytester):
"""Assert that synchronous Hypothesis functions are not marked with asyncio"""
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
import asyncio
import pytest
from hypothesis import given
import hypothesis.strategies as st
pytest_plugins = 'pytest_asyncio'
@given(n=st.integers())
def test_hypothesis(request, n: int):
markers = [marker.name for marker in request.node.own_markers]
assert "asyncio" not in markers
assert isinstance(n, int)
"""))
result = pytester.runpytest("--asyncio-mode=auto")
result.assert_outcomes(passed=1)
pytest-dev-pytest-asyncio-dbe96ef/tests/markers/ 0000775 0000000 0000000 00000000000 15205265572 0022214 5 ustar 00root root 0000000 0000000 pytest-dev-pytest-asyncio-dbe96ef/tests/markers/__init__.py 0000664 0000000 0000000 00000000000 15205265572 0024313 0 ustar 00root root 0000000 0000000 pytest-dev-pytest-asyncio-dbe96ef/tests/markers/test_class_scope.py 0000664 0000000 0000000 00000022552 15205265572 0026131 0 ustar 00root root 0000000 0000000 """Test if pytestmark works when defined on a class."""
from __future__ import annotations
import asyncio
import sys
from textwrap import dedent
import pytest
class TestPyTestMark:
pytestmark = pytest.mark.asyncio
async def test_is_asyncio(self, sample_fixture):
assert asyncio.get_event_loop()
counter = 1
async def inc():
nonlocal counter
counter += 1
await asyncio.sleep(0)
await asyncio.ensure_future(inc())
assert counter == 2
@pytest.fixture
def sample_fixture():
return None
def test_asyncio_mark_provides_class_scoped_loop_when_applied_to_functions(
pytester: pytest.Pytester,
):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
import asyncio
import pytest
class TestClassScopedLoop:
loop: asyncio.AbstractEventLoop
@pytest.mark.asyncio(loop_scope="class")
async def test_remember_loop(self):
TestClassScopedLoop.loop = asyncio.get_running_loop()
@pytest.mark.asyncio(loop_scope="class")
async def test_this_runs_in_same_loop(self):
assert asyncio.get_running_loop() is TestClassScopedLoop.loop
"""))
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=2)
def test_asyncio_mark_provides_class_scoped_loop_when_applied_to_class(
pytester: pytest.Pytester,
):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
import asyncio
import pytest
@pytest.mark.asyncio(loop_scope="class")
class TestClassScopedLoop:
loop: asyncio.AbstractEventLoop
async def test_remember_loop(self):
TestClassScopedLoop.loop = asyncio.get_running_loop()
async def test_this_runs_in_same_loop(self):
assert asyncio.get_running_loop() is TestClassScopedLoop.loop
"""))
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=2)
def test_asyncio_mark_is_inherited_to_subclasses(pytester: pytest.Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
import asyncio
import pytest
@pytest.mark.asyncio(loop_scope="class")
class TestSuperClassWithMark:
pass
class TestWithoutMark(TestSuperClassWithMark):
loop: asyncio.AbstractEventLoop
async def test_remember_loop(self):
TestWithoutMark.loop = asyncio.get_running_loop()
async def test_this_runs_in_same_loop(self):
assert asyncio.get_running_loop() is TestWithoutMark.loop
"""))
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=2)
def test_asyncio_mark_respects_the_loop_policy(
pytester: pytest.Pytester,
):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
import asyncio
import pytest
class CustomEventLoopPolicy(asyncio.DefaultEventLoopPolicy):
pass
class TestUsesCustomEventLoop:
@pytest.fixture(scope="class")
def event_loop_policy(self):
return CustomEventLoopPolicy()
@pytest.mark.asyncio
async def test_uses_custom_event_loop_policy(self):
assert isinstance(
asyncio.get_event_loop_policy(),
CustomEventLoopPolicy,
)
@pytest.mark.asyncio
async def test_does_not_use_custom_event_loop_policy():
assert not isinstance(
asyncio.get_event_loop_policy(),
CustomEventLoopPolicy,
)
"""))
pytest_args = ["--asyncio-mode=strict"]
if sys.version_info >= (3, 14):
pytest_args.extend(["-W", "default"])
result = pytester.runpytest(*pytest_args)
if sys.version_info >= (3, 14):
result.assert_outcomes(passed=2, warnings=4)
result.stdout.fnmatch_lines("*DefaultEventLoopPolicy*")
else:
result.assert_outcomes(passed=2)
def test_asyncio_mark_respects_parametrized_loop_policies(
pytester: pytest.Pytester,
):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
import asyncio
import pytest
@pytest.fixture(
scope="class",
params=[
asyncio.DefaultEventLoopPolicy(),
asyncio.DefaultEventLoopPolicy(),
]
)
def event_loop_policy(request):
return request.param
@pytest.mark.asyncio(loop_scope="class")
class TestWithDifferentLoopPolicies:
async def test_parametrized_loop(self, request):
pass
"""))
pytest_args = ["--asyncio-mode=strict"]
if sys.version_info >= (3, 14):
pytest_args.extend(["-W", "default"])
result = pytester.runpytest(*pytest_args)
if sys.version_info >= (3, 14):
result.assert_outcomes(passed=2, warnings=4)
result.stdout.fnmatch_lines("*DefaultEventLoopPolicy*")
else:
result.assert_outcomes(passed=2)
def test_asyncio_mark_provides_class_scoped_loop_to_fixtures(
pytester: pytest.Pytester,
):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
import asyncio
import pytest
import pytest_asyncio
@pytest.mark.asyncio(loop_scope="class")
class TestClassScopedLoop:
loop: asyncio.AbstractEventLoop
@pytest_asyncio.fixture
async def my_fixture(self):
TestClassScopedLoop.loop = asyncio.get_running_loop()
@pytest.mark.asyncio
async def test_runs_is_same_loop_as_fixture(self, my_fixture):
assert asyncio.get_running_loop() is TestClassScopedLoop.loop
"""))
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=1)
def test_asyncio_mark_allows_combining_class_scoped_fixture_with_function_scoped_test(
pytester: pytest.Pytester,
):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(
dedent("""\
import asyncio
import pytest
import pytest_asyncio
loop: asyncio.AbstractEventLoop
class TestMixedScopes:
@pytest_asyncio.fixture(loop_scope="class", scope="class")
async def async_fixture(self):
global loop
loop = asyncio.get_running_loop()
@pytest.mark.asyncio(loop_scope="function")
async def test_runs_in_different_loop_as_fixture(self, async_fixture):
global loop
assert asyncio.get_running_loop() is not loop
"""),
)
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=1)
def test_asyncio_mark_handles_missing_event_loop_triggered_by_fixture(
pytester: pytest.Pytester,
):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
import pytest
import asyncio
class TestClass:
@pytest.fixture(scope="class")
def sets_event_loop_to_none(self):
# asyncio.run() creates a new event loop without closing the
# existing one. For any test, but the first one, this leads to
# a ResourceWarning when the discarded loop is destroyed by the
# garbage collector. We close the current loop to avoid this.
try:
asyncio.get_event_loop().close()
except RuntimeError:
pass
return asyncio.run(asyncio.sleep(0))
# asyncio.run() sets the current event loop to None when finished
@pytest.mark.asyncio(loop_scope="class")
# parametrization may impact fixture ordering
@pytest.mark.parametrize("n", (0, 1))
async def test_does_not_fail(self, sets_event_loop_to_none, n):
pass
"""))
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=2)
def test_standalone_test_does_not_trigger_warning_about_no_current_event_loop_being_set(
pytester: pytest.Pytester,
):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
import pytest
@pytest.mark.asyncio(loop_scope="class")
class TestClass:
async def test_anything(self):
pass
"""))
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(warnings=0, passed=1)
pytest-dev-pytest-asyncio-dbe96ef/tests/markers/test_function_scope.py 0000664 0000000 0000000 00000023061 15205265572 0026645 0 ustar 00root root 0000000 0000000 from __future__ import annotations
import sys
from textwrap import dedent
from pytest import Pytester
def test_asyncio_mark_provides_function_scoped_loop_strict_mode(pytester: Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
import asyncio
import pytest
pytestmark = pytest.mark.asyncio
loop: asyncio.AbstractEventLoop
async def test_remember_loop():
global loop
loop = asyncio.get_running_loop()
async def test_does_not_run_in_same_loop():
global loop
assert asyncio.get_running_loop() is not loop
"""))
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=2)
def test_loop_scope_function_provides_function_scoped_event_loop(pytester: Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
import asyncio
import pytest
pytestmark = pytest.mark.asyncio(loop_scope="function")
loop: asyncio.AbstractEventLoop
async def test_remember_loop():
global loop
loop = asyncio.get_running_loop()
async def test_does_not_run_in_same_loop():
global loop
assert asyncio.get_running_loop() is not loop
"""))
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=2)
def test_raises_when_scope_and_loop_scope_arguments_are_present(pytester: Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
import pytest
@pytest.mark.asyncio(scope="function", loop_scope="function")
async def test_raises():
...
"""))
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(errors=1)
def test_warns_when_scope_argument_is_present(pytester: Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
import pytest
@pytest.mark.asyncio(scope="function")
async def test_warns():
...
"""))
result = pytester.runpytest("--asyncio-mode=strict", "-W", "default")
result.assert_outcomes(passed=1, warnings=1)
result.stdout.fnmatch_lines("*DeprecationWarning*")
def test_asyncio_mark_respects_the_loop_policy(
pytester: Pytester,
):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(
dedent("""\
import asyncio
import pytest
pytestmark = pytest.mark.asyncio
class CustomEventLoopPolicy(asyncio.DefaultEventLoopPolicy):
pass
@pytest.fixture(scope="function")
def event_loop_policy():
return CustomEventLoopPolicy()
async def test_uses_custom_event_loop_policy():
assert isinstance(
asyncio.get_event_loop_policy(),
CustomEventLoopPolicy,
)
"""),
)
pytest_args = ["--asyncio-mode=strict"]
if sys.version_info >= (3, 14):
pytest_args.extend(["-W", "default"])
result = pytester.runpytest(*pytest_args)
if sys.version_info >= (3, 14):
result.assert_outcomes(passed=1, warnings=3)
result.stdout.fnmatch_lines("*DefaultEventLoopPolicy*")
else:
result.assert_outcomes(passed=1)
def test_asyncio_mark_respects_parametrized_loop_policies(
pytester: Pytester,
):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
import asyncio
import pytest
pytestmark = pytest.mark.asyncio
class CustomEventLoopPolicy(asyncio.DefaultEventLoopPolicy):
pass
@pytest.fixture(
scope="module",
params=[
CustomEventLoopPolicy(),
CustomEventLoopPolicy(),
],
)
def event_loop_policy(request):
return request.param
async def test_parametrized_loop():
assert isinstance(
asyncio.get_event_loop_policy(),
CustomEventLoopPolicy,
)
"""))
pytest_args = ["--asyncio-mode=strict"]
if sys.version_info >= (3, 14):
pytest_args.extend(["-W", "default"])
result = pytester.runpytest(*pytest_args)
if sys.version_info >= (3, 14):
result.assert_outcomes(passed=2, warnings=5)
result.stdout.fnmatch_lines("*DefaultEventLoopPolicy*")
else:
result.assert_outcomes(passed=2)
def test_event_loop_policy_fixture_override_emits_deprecation_warning(
pytester: Pytester,
):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(
dedent("""\
import asyncio
import pytest
pytestmark = pytest.mark.asyncio
@pytest.fixture
def event_loop_policy():
return asyncio.DefaultEventLoopPolicy()
async def test_anything():
pass
"""),
)
result = pytester.runpytest("--asyncio-mode=strict", "-W", "default")
result.assert_outcomes(passed=1)
result.stdout.fnmatch_lines(
"*PytestDeprecationWarning*event_loop_policy*deprecated*"
)
def test_default_event_loop_policy_fixture_does_not_warn(
pytester: Pytester,
):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(
dedent("""\
import pytest
pytestmark = pytest.mark.asyncio
async def test_anything():
pass
"""),
)
result = pytester.runpytest("--asyncio-mode=strict", "-W", "default")
result.assert_outcomes(passed=1)
result.stdout.no_fnmatch_line("*PytestDeprecationWarning*event_loop_policy*")
def test_asyncio_mark_provides_function_scoped_loop_to_fixtures(
pytester: Pytester,
):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
import asyncio
import pytest
import pytest_asyncio
pytestmark = pytest.mark.asyncio
loop: asyncio.AbstractEventLoop
@pytest_asyncio.fixture
async def my_fixture():
global loop
loop = asyncio.get_running_loop()
async def test_runs_is_same_loop_as_fixture(my_fixture):
global loop
assert asyncio.get_running_loop() is loop
"""))
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=1)
def test_asyncio_mark_handles_missing_event_loop_triggered_by_fixture(
pytester: Pytester,
):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
import pytest
import asyncio
@pytest.fixture
def sets_event_loop_to_none():
# asyncio.run() creates a new event loop without closing the existing
# one. For any test, but the first one, this leads to a ResourceWarning
# when the discarded loop is destroyed by the garbage collector.
# We close the current loop to avoid this
try:
asyncio.get_event_loop().close()
except RuntimeError:
pass
return asyncio.run(asyncio.sleep(0))
# asyncio.run() sets the current event loop to None when finished
@pytest.mark.asyncio
# parametrization may impact fixture ordering
@pytest.mark.parametrize("n", (0, 1))
async def test_does_not_fail(sets_event_loop_to_none, n):
pass
"""))
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=2)
def test_standalone_test_does_not_trigger_warning_about_no_current_event_loop_being_set(
pytester: Pytester,
):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
import pytest
@pytest.mark.asyncio
async def test_anything():
pass
"""))
result = pytester.runpytest("--asyncio-mode=strict", "--assert=plain")
result.assert_outcomes(warnings=0, passed=1)
def test_asyncio_mark_does_not_duplicate_other_marks_in_auto_mode(
pytester: Pytester,
):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makeconftest(dedent("""\
def pytest_configure(config):
config.addinivalue_line(
"markers", "dummy_marker: mark used for testing purposes"
)
"""))
pytester.makepyfile(dedent("""\
import pytest
@pytest.mark.dummy_marker
async def test_markers_not_duplicated(request):
markers = []
for node, marker in request.node.iter_markers_with_node():
markers.append(marker)
assert len(markers) == 2
"""))
result = pytester.runpytest("--asyncio-mode=auto", "--assert=plain")
result.assert_outcomes(warnings=0, passed=1)
pytest-dev-pytest-asyncio-dbe96ef/tests/markers/test_invalid_arguments.py 0000664 0000000 0000000 00000006404 15205265572 0027344 0 ustar 00root root 0000000 0000000 from __future__ import annotations
from textwrap import dedent
import pytest
def test_no_error_when_scope_passed_as_sole_keyword_argument(
pytester: pytest.Pytester,
):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
import pytest
@pytest.mark.asyncio(loop_scope="session")
async def test_anything():
pass
"""))
result = pytester.runpytest("--assert=plain")
result.assert_outcomes(passed=1)
result.stdout.no_fnmatch_line("*ValueError*")
def test_error_when_scope_passed_as_positional_argument(
pytester: pytest.Pytester,
):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
import pytest
@pytest.mark.asyncio("session")
async def test_anything():
pass
"""))
result = pytester.runpytest("--assert=plain")
result.assert_outcomes(errors=1)
result.stdout.fnmatch_lines(
["*ValueError: mark.asyncio accepts only keyword arguments*"]
)
def test_error_when_wrong_keyword_argument_is_passed(
pytester: pytest.Pytester,
):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
import pytest
@pytest.mark.asyncio(cope="session")
async def test_anything():
pass
"""))
result = pytester.runpytest("--assert=plain")
result.assert_outcomes(errors=1)
result.stdout.fnmatch_lines(
["*ValueError: mark.asyncio accepts only keyword arguments*"]
)
def test_error_when_additional_keyword_arguments_are_passed(
pytester: pytest.Pytester,
):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
import pytest
@pytest.mark.asyncio(loop_scope="session", more="stuff")
async def test_anything():
pass
"""))
result = pytester.runpytest("--assert=plain")
result.assert_outcomes(errors=1)
result.stdout.fnmatch_lines(
["*ValueError: mark.asyncio accepts only keyword arguments*"]
)
@pytest.mark.parametrize(
"loop_factories_value",
('"custom"', "[]", '[""]', "[1]"),
)
def test_error_when_loop_factories_marker_value_is_invalid(
pytester: pytest.Pytester, loop_factories_value: str
):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makeconftest(dedent("""\
import asyncio
class CustomEventLoop(asyncio.SelectorEventLoop):
pass
def pytest_asyncio_loop_factories(config, item):
return {"custom": CustomEventLoop}
"""))
pytester.makepyfile(dedent(f"""\
import pytest
pytest_plugins = "pytest_asyncio"
@pytest.mark.asyncio(loop_factories={loop_factories_value})
async def test_anything():
pass
"""))
result = pytester.runpytest("--assert=plain")
result.assert_outcomes(errors=1)
result.stdout.fnmatch_lines(
["*ValueError: mark.asyncio 'loop_factories' must be a non-empty sequence*"]
)
pytest-dev-pytest-asyncio-dbe96ef/tests/markers/test_mixed_scope.py 0000664 0000000 0000000 00000002015 15205265572 0026122 0 ustar 00root root 0000000 0000000 from __future__ import annotations
from textwrap import dedent
from pytest import Pytester
def test_function_scoped_loop_restores_previous_loop_scope(pytester: Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
import asyncio
import pytest
module_loop: asyncio.AbstractEventLoop
@pytest.mark.asyncio(loop_scope="module")
async def test_remember_loop():
global module_loop
module_loop = asyncio.get_running_loop()
@pytest.mark.asyncio(loop_scope="function")
async def test_with_function_scoped_loop():
pass
@pytest.mark.asyncio(loop_scope="module")
async def test_runs_in_same_loop():
global module_loop
assert asyncio.get_running_loop() is module_loop
"""))
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=3)
pytest-dev-pytest-asyncio-dbe96ef/tests/markers/test_module_scope.py 0000664 0000000 0000000 00000022344 15205265572 0026310 0 ustar 00root root 0000000 0000000 from __future__ import annotations
import sys
from textwrap import dedent
from pytest import Pytester
def test_asyncio_mark_provides_module_scoped_loop_strict_mode(pytester: Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
import asyncio
import pytest
pytestmark = pytest.mark.asyncio(loop_scope="module")
loop: asyncio.AbstractEventLoop
async def test_remember_loop():
global loop
loop = asyncio.get_running_loop()
async def test_this_runs_in_same_loop():
global loop
assert asyncio.get_running_loop() is loop
class TestClassA:
async def test_this_runs_in_same_loop(self):
global loop
assert asyncio.get_running_loop() is loop
"""))
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=3)
def test_asyncio_mark_respects_the_loop_policy(
pytester: Pytester,
):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(
__init__="",
custom_policy=dedent("""\
import asyncio
class CustomEventLoopPolicy(asyncio.DefaultEventLoopPolicy):
pass
"""),
test_uses_custom_policy=dedent("""\
import asyncio
import pytest
from .custom_policy import CustomEventLoopPolicy
pytestmark = pytest.mark.asyncio(loop_scope="module")
@pytest.fixture(scope="module")
def event_loop_policy():
return CustomEventLoopPolicy()
async def test_uses_custom_event_loop_policy():
assert isinstance(
asyncio.get_event_loop_policy(),
CustomEventLoopPolicy,
)
"""),
test_does_not_use_custom_policy=dedent("""\
import asyncio
import pytest
from .custom_policy import CustomEventLoopPolicy
pytestmark = pytest.mark.asyncio(loop_scope="module")
async def test_does_not_use_custom_event_loop_policy():
assert not isinstance(
asyncio.get_event_loop_policy(),
CustomEventLoopPolicy,
)
"""),
)
pytest_args = ["--asyncio-mode=strict"]
if sys.version_info >= (3, 14):
pytest_args.extend(["-W", "default"])
result = pytester.runpytest(*pytest_args)
if sys.version_info >= (3, 14):
result.assert_outcomes(passed=2, warnings=4)
result.stdout.fnmatch_lines("*DefaultEventLoopPolicy*")
else:
result.assert_outcomes(passed=2)
def test_asyncio_mark_respects_parametrized_loop_policies(
pytester: Pytester,
):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
import asyncio
import pytest
pytestmark = pytest.mark.asyncio(loop_scope="module")
@pytest.fixture(
scope="module",
params=[
asyncio.DefaultEventLoopPolicy(),
asyncio.DefaultEventLoopPolicy(),
],
)
def event_loop_policy(request):
return request.param
async def test_parametrized_loop():
pass
"""))
pytest_args = ["--asyncio-mode=strict"]
if sys.version_info >= (3, 14):
pytest_args.extend(["-W", "default"])
result = pytester.runpytest(*pytest_args)
if sys.version_info >= (3, 14):
result.assert_outcomes(passed=2, warnings=4)
result.stdout.fnmatch_lines("*DefaultEventLoopPolicy*")
else:
result.assert_outcomes(passed=2)
def test_asyncio_mark_provides_module_scoped_loop_to_fixtures(
pytester: Pytester,
):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
import asyncio
import pytest
import pytest_asyncio
pytestmark = pytest.mark.asyncio(loop_scope="module")
loop: asyncio.AbstractEventLoop
@pytest_asyncio.fixture(loop_scope="module", scope="module")
async def my_fixture():
global loop
loop = asyncio.get_running_loop()
async def test_runs_is_same_loop_as_fixture(my_fixture):
global loop
assert asyncio.get_running_loop() is loop
"""))
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=1)
def test_asyncio_mark_allows_combining_module_scoped_fixture_with_class_scoped_test(
pytester: Pytester,
):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(
dedent("""\
import asyncio
import pytest
import pytest_asyncio
loop: asyncio.AbstractEventLoop
@pytest_asyncio.fixture(loop_scope="module", scope="module")
async def async_fixture():
global loop
loop = asyncio.get_running_loop()
@pytest.mark.asyncio(loop_scope="class")
class TestMixedScopes:
async def test_runs_in_different_loop_as_fixture(self, async_fixture):
global loop
assert asyncio.get_running_loop() is not loop
"""),
)
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=1)
def test_asyncio_mark_allows_combining_module_scoped_fixture_with_function_scoped_test(
pytester: Pytester,
):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(
__init__="",
test_mixed_scopes=dedent("""\
import asyncio
import pytest
import pytest_asyncio
loop: asyncio.AbstractEventLoop
@pytest_asyncio.fixture(loop_scope="module", scope="module")
async def async_fixture():
global loop
loop = asyncio.get_running_loop()
@pytest.mark.asyncio(loop_scope="function")
async def test_runs_in_different_loop_as_fixture(async_fixture):
global loop
assert asyncio.get_running_loop() is not loop
"""),
)
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=1)
def test_allows_combining_module_scoped_asyncgen_fixture_with_function_scoped_test(
pytester: Pytester,
):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(
dedent("""\
import asyncio
import pytest
import pytest_asyncio
loop: asyncio.AbstractEventLoop
@pytest_asyncio.fixture(loop_scope="module", scope="module")
async def async_fixture():
global loop
loop = asyncio.get_running_loop()
yield
@pytest.mark.asyncio(loop_scope="function")
async def test_runs_in_different_loop_as_fixture(async_fixture):
global loop
assert asyncio.get_running_loop() is not loop
"""),
)
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=1)
def test_asyncio_mark_handles_missing_event_loop_triggered_by_fixture(
pytester: Pytester,
):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
import pytest
import asyncio
@pytest.fixture(scope="module")
def sets_event_loop_to_none():
# asyncio.run() creates a new event loop without closing the existing
# one. For any test, but the first one, this leads to a ResourceWarning
# when the discarded loop is destroyed by the garbage collector.
# We close the current loop to avoid this
try:
asyncio.get_event_loop().close()
except RuntimeError:
pass
return asyncio.run(asyncio.sleep(0))
# asyncio.run() sets the current event loop to None when finished
@pytest.mark.asyncio(loop_scope="module")
# parametrization may impact fixture ordering
@pytest.mark.parametrize("n", (0, 1))
async def test_does_not_fail(sets_event_loop_to_none, n):
pass
"""))
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=2)
def test_standalone_test_does_not_trigger_warning_about_no_current_event_loop_being_set(
pytester: Pytester,
):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
import pytest
@pytest.mark.asyncio(loop_scope="module")
async def test_anything():
pass
"""))
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(warnings=0, passed=1)
pytest-dev-pytest-asyncio-dbe96ef/tests/markers/test_package_scope.py 0000664 0000000 0000000 00000024452 15205265572 0026420 0 ustar 00root root 0000000 0000000 from __future__ import annotations
import sys
from textwrap import dedent
from pytest import Pytester
def test_asyncio_mark_provides_package_scoped_loop_strict_mode(pytester: Pytester):
package_name = pytester.path.name
subpackage_name = "subpkg"
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(
__init__="",
shared_module=dedent("""\
import asyncio
loop: asyncio.AbstractEventLoop = None
"""),
test_module_one=dedent(f"""\
import asyncio
import pytest
from {package_name} import shared_module
@pytest.mark.asyncio(loop_scope="package")
async def test_remember_loop():
shared_module.loop = asyncio.get_running_loop()
"""),
test_module_two=dedent(f"""\
import asyncio
import pytest
from {package_name} import shared_module
pytestmark = pytest.mark.asyncio(loop_scope="package")
async def test_this_runs_in_same_loop():
assert asyncio.get_running_loop() is shared_module.loop
class TestClassA:
async def test_this_runs_in_same_loop(self):
assert asyncio.get_running_loop() is shared_module.loop
"""),
)
subpkg = pytester.mkpydir(subpackage_name)
subpkg.joinpath("__init__.py").touch()
subpkg.joinpath("test_subpkg.py").write_text(dedent(f"""\
import asyncio
import pytest
from {package_name} import shared_module
pytestmark = pytest.mark.asyncio(loop_scope="package")
async def test_subpackage_runs_in_different_loop():
assert asyncio.get_running_loop() is not shared_module.loop
"""))
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=4)
def test_asyncio_mark_respects_the_loop_policy(
pytester: Pytester,
):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(
__init__="",
conftest=dedent("""\
import pytest
from .custom_policy import CustomEventLoopPolicy
@pytest.fixture(scope="package")
def event_loop_policy():
return CustomEventLoopPolicy()
"""),
custom_policy=dedent("""\
import asyncio
class CustomEventLoopPolicy(asyncio.DefaultEventLoopPolicy):
pass
"""),
test_uses_custom_policy=dedent("""\
import asyncio
import pytest
from .custom_policy import CustomEventLoopPolicy
pytestmark = pytest.mark.asyncio(loop_scope="package")
async def test_uses_custom_event_loop_policy():
assert isinstance(
asyncio.get_event_loop_policy(),
CustomEventLoopPolicy,
)
"""),
test_also_uses_custom_policy=dedent("""\
import asyncio
import pytest
from .custom_policy import CustomEventLoopPolicy
pytestmark = pytest.mark.asyncio(loop_scope="package")
async def test_also_uses_custom_event_loop_policy():
assert isinstance(
asyncio.get_event_loop_policy(),
CustomEventLoopPolicy,
)
"""),
)
pytest_args = ["--asyncio-mode=strict"]
if sys.version_info >= (3, 14):
pytest_args.extend(["-W", "default"])
result = pytester.runpytest(*pytest_args)
if sys.version_info >= (3, 14):
result.assert_outcomes(passed=2, warnings=4)
result.stdout.fnmatch_lines("*DefaultEventLoopPolicy*")
else:
result.assert_outcomes(passed=2)
def test_asyncio_mark_respects_parametrized_loop_policies(
pytester: Pytester,
):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(
__init__="",
test_parametrization=dedent("""\
import asyncio
import pytest
pytestmark = pytest.mark.asyncio(loop_scope="package")
@pytest.fixture(
scope="package",
params=[
asyncio.DefaultEventLoopPolicy(),
asyncio.DefaultEventLoopPolicy(),
],
)
def event_loop_policy(request):
return request.param
async def test_parametrized_loop():
pass
"""),
)
pytest_args = ["--asyncio-mode=strict"]
if sys.version_info >= (3, 14):
pytest_args.extend(["-W", "default"])
result = pytester.runpytest(*pytest_args)
if sys.version_info >= (3, 14):
result.assert_outcomes(passed=2, warnings=4)
result.stdout.fnmatch_lines("*DefaultEventLoopPolicy*")
else:
result.assert_outcomes(passed=2)
def test_asyncio_mark_provides_package_scoped_loop_to_fixtures(
pytester: Pytester,
):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
package_name = pytester.path.name
pytester.makepyfile(
__init__="",
conftest=dedent(f"""\
import asyncio
import pytest_asyncio
from {package_name} import shared_module
@pytest_asyncio.fixture(loop_scope="package", scope="package")
async def my_fixture():
shared_module.loop = asyncio.get_running_loop()
"""),
shared_module=dedent("""\
import asyncio
loop: asyncio.AbstractEventLoop = None
"""),
test_fixture_runs_in_scoped_loop=dedent(f"""\
import asyncio
import pytest
import pytest_asyncio
from {package_name} import shared_module
pytestmark = pytest.mark.asyncio(loop_scope="package")
async def test_runs_in_same_loop_as_fixture(my_fixture):
assert asyncio.get_running_loop() is shared_module.loop
"""),
)
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=1)
def test_asyncio_mark_allows_combining_package_scoped_fixture_with_module_scoped_test(
pytester: Pytester,
):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(
__init__="",
test_mixed_scopes=dedent("""\
import asyncio
import pytest
import pytest_asyncio
loop: asyncio.AbstractEventLoop
@pytest_asyncio.fixture(loop_scope="package", scope="package")
async def async_fixture():
global loop
loop = asyncio.get_running_loop()
@pytest.mark.asyncio(loop_scope="module")
async def test_runs_in_different_loop_as_fixture(async_fixture):
global loop
assert asyncio.get_running_loop() is not loop
"""),
)
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=1)
def test_asyncio_mark_allows_combining_package_scoped_fixture_with_class_scoped_test(
pytester: Pytester,
):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(
__init__="",
test_mixed_scopes=dedent("""\
import asyncio
import pytest
import pytest_asyncio
loop: asyncio.AbstractEventLoop
@pytest_asyncio.fixture(loop_scope="package", scope="package")
async def async_fixture():
global loop
loop = asyncio.get_running_loop()
@pytest.mark.asyncio(loop_scope="class")
class TestMixedScopes:
async def test_runs_in_different_loop_as_fixture(self, async_fixture):
global loop
assert asyncio.get_running_loop() is not loop
"""),
)
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=1)
def test_asyncio_mark_allows_combining_package_scoped_fixture_with_function_scoped_test(
pytester: Pytester,
):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(
__init__="",
test_mixed_scopes=dedent("""\
import asyncio
import pytest
import pytest_asyncio
loop: asyncio.AbstractEventLoop
@pytest_asyncio.fixture(loop_scope="package", scope="package")
async def async_fixture():
global loop
loop = asyncio.get_running_loop()
@pytest.mark.asyncio
async def test_runs_in_different_loop_as_fixture(async_fixture):
global loop
assert asyncio.get_running_loop() is not loop
"""),
)
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=1)
def test_asyncio_mark_handles_missing_event_loop_triggered_by_fixture(
pytester: Pytester,
):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(
__init__="",
test_loop_is_none=dedent("""\
import pytest
import asyncio
@pytest.fixture(scope="package")
def sets_event_loop_to_none():
# asyncio.run() creates a new event loop without closing the existing
# one. For any test, but the first one, this leads to a ResourceWarning
# when the discarded loop is destroyed by the garbage collector.
# We close the current loop to avoid this
try:
asyncio.get_event_loop().close()
except RuntimeError:
pass
return asyncio.run(asyncio.sleep(0))
# asyncio.run() sets the current event loop to None when finished
@pytest.mark.asyncio(loop_scope="package")
# parametrization may impact fixture ordering
@pytest.mark.parametrize("n", (0, 1))
async def test_does_not_fail(sets_event_loop_to_none, n):
pass
"""),
)
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=2)
pytest-dev-pytest-asyncio-dbe96ef/tests/markers/test_session_scope.py 0000664 0000000 0000000 00000031272 15205265572 0026506 0 ustar 00root root 0000000 0000000 from __future__ import annotations
import sys
from textwrap import dedent
from pytest import Pytester
def test_asyncio_mark_provides_session_scoped_loop_strict_mode(pytester: Pytester):
package_name = pytester.path.name
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(
__init__="",
shared_module=dedent("""\
import asyncio
loop: asyncio.AbstractEventLoop = None
"""),
test_module_one=dedent(f"""\
import asyncio
import pytest
from {package_name} import shared_module
@pytest.mark.asyncio(loop_scope="session")
async def test_remember_loop():
shared_module.loop = asyncio.get_running_loop()
"""),
test_module_two=dedent(f"""\
import asyncio
import pytest
from {package_name} import shared_module
pytestmark = pytest.mark.asyncio(loop_scope="session")
async def test_this_runs_in_same_loop():
assert asyncio.get_running_loop() is shared_module.loop
class TestClassA:
async def test_this_runs_in_same_loop(self):
assert asyncio.get_running_loop() is shared_module.loop
"""),
)
# subpackage_name must alphabetically come after test_module_one.py
subpackage_name = "z_subpkg"
subpkg = pytester.mkpydir(subpackage_name)
subpkg.joinpath("test_subpkg.py").write_text(dedent(f"""\
import asyncio
import pytest
from {package_name} import shared_module
pytestmark = pytest.mark.asyncio(loop_scope="session")
async def test_subpackage_runs_in_same_loop():
assert asyncio.get_running_loop() is shared_module.loop
"""))
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=4)
def test_asyncio_mark_respects_the_loop_policy(
pytester: Pytester,
):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(
__init__="",
conftest=dedent("""\
import pytest
from .custom_policy import CustomEventLoopPolicy
@pytest.fixture(scope="session")
def event_loop_policy():
return CustomEventLoopPolicy()
"""),
custom_policy=dedent("""\
import asyncio
class CustomEventLoopPolicy(asyncio.DefaultEventLoopPolicy):
pass
"""),
test_uses_custom_policy=dedent("""\
import asyncio
import pytest
from .custom_policy import CustomEventLoopPolicy
pytestmark = pytest.mark.asyncio(loop_scope="session")
async def test_uses_custom_event_loop_policy():
assert isinstance(
asyncio.get_event_loop_policy(),
CustomEventLoopPolicy,
)
"""),
test_also_uses_custom_policy=dedent("""\
import asyncio
import pytest
from .custom_policy import CustomEventLoopPolicy
pytestmark = pytest.mark.asyncio(loop_scope="session")
async def test_also_uses_custom_event_loop_policy():
assert isinstance(
asyncio.get_event_loop_policy(),
CustomEventLoopPolicy,
)
"""),
)
pytest_args = ["--asyncio-mode=strict"]
if sys.version_info >= (3, 14):
pytest_args.extend(["-W", "default"])
result = pytester.runpytest(*pytest_args)
if sys.version_info >= (3, 14):
result.assert_outcomes(passed=2, warnings=4)
result.stdout.fnmatch_lines("*DefaultEventLoopPolicy*")
else:
result.assert_outcomes(passed=2)
def test_asyncio_mark_respects_parametrized_loop_policies(
pytester: Pytester,
):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(
__init__="",
test_parametrization=dedent("""\
import asyncio
import pytest
pytestmark = pytest.mark.asyncio(loop_scope="session")
@pytest.fixture(
scope="session",
params=[
asyncio.DefaultEventLoopPolicy(),
asyncio.DefaultEventLoopPolicy(),
],
)
def event_loop_policy(request):
return request.param
async def test_parametrized_loop():
pass
"""),
)
pytest_args = ["--asyncio-mode=strict"]
if sys.version_info >= (3, 14):
pytest_args.extend(["-W", "default"])
result = pytester.runpytest(*pytest_args)
if sys.version_info >= (3, 14):
result.assert_outcomes(passed=2, warnings=4)
result.stdout.fnmatch_lines("*DefaultEventLoopPolicy*")
else:
result.assert_outcomes(passed=2)
def test_asyncio_mark_provides_session_scoped_loop_to_fixtures(
pytester: Pytester,
):
package_name = pytester.path.name
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(
__init__="",
conftest=dedent(f"""\
import asyncio
import pytest_asyncio
from {package_name} import shared_module
@pytest_asyncio.fixture(loop_scope="session", scope="session")
async def my_fixture():
shared_module.loop = asyncio.get_running_loop()
"""),
shared_module=dedent("""\
import asyncio
loop: asyncio.AbstractEventLoop = None
"""),
)
subpackage_name = "subpkg"
subpkg = pytester.mkpydir(subpackage_name)
subpkg.joinpath("test_subpkg.py").write_text(dedent(f"""\
import asyncio
import pytest
import pytest_asyncio
from {package_name} import shared_module
pytestmark = pytest.mark.asyncio(loop_scope="session")
async def test_runs_in_same_loop_as_fixture(my_fixture):
assert asyncio.get_running_loop() is shared_module.loop
"""))
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=1)
def test_asyncio_mark_allows_combining_session_scoped_fixture_with_package_scoped_test(
pytester: Pytester,
):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(
__init__="",
test_mixed_scopes=dedent("""\
import asyncio
import pytest
import pytest_asyncio
loop: asyncio.AbstractEventLoop
@pytest_asyncio.fixture(loop_scope="session", scope="session")
async def async_fixture():
global loop
loop = asyncio.get_running_loop()
@pytest.mark.asyncio(loop_scope="package")
async def test_runs_in_different_loop_as_fixture(async_fixture):
global loop
assert asyncio.get_running_loop() is not loop
"""),
)
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=1)
def test_asyncio_mark_allows_combining_session_scoped_fixture_with_module_scoped_test(
pytester: Pytester,
):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(
__init__="",
test_mixed_scopes=dedent("""\
import asyncio
import pytest
import pytest_asyncio
loop: asyncio.AbstractEventLoop
@pytest_asyncio.fixture(loop_scope="session", scope="session")
async def async_fixture():
global loop
loop = asyncio.get_running_loop()
@pytest.mark.asyncio(loop_scope="module")
async def test_runs_in_different_loop_as_fixture(async_fixture):
global loop
assert asyncio.get_running_loop() is not loop
"""),
)
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=1)
def test_asyncio_mark_allows_combining_session_scoped_fixture_with_class_scoped_test(
pytester: Pytester,
):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(
__init__="",
test_mixed_scopes=dedent("""\
import asyncio
import pytest
import pytest_asyncio
loop: asyncio.AbstractEventLoop
@pytest_asyncio.fixture(loop_scope="session", scope="session")
async def async_fixture():
global loop
loop = asyncio.get_running_loop()
@pytest.mark.asyncio(loop_scope="class")
class TestMixedScopes:
async def test_runs_in_different_loop_as_fixture(self, async_fixture):
global loop
assert asyncio.get_running_loop() is not loop
"""),
)
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=1)
def test_asyncio_mark_allows_combining_session_scoped_fixture_with_function_scoped_test(
pytester: Pytester,
):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(
__init__="",
test_mixed_scopes=dedent("""\
import asyncio
import pytest
import pytest_asyncio
loop: asyncio.AbstractEventLoop
@pytest_asyncio.fixture(loop_scope="session", scope="session")
async def async_fixture():
global loop
loop = asyncio.get_running_loop()
@pytest.mark.asyncio
async def test_runs_in_different_loop_as_fixture(async_fixture):
global loop
assert asyncio.get_running_loop() is not loop
"""),
)
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=1)
def test_allows_combining_session_scoped_asyncgen_fixture_with_function_scoped_test(
pytester: Pytester,
):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(
__init__="",
test_mixed_scopes=dedent("""\
import asyncio
import pytest
import pytest_asyncio
loop: asyncio.AbstractEventLoop
@pytest_asyncio.fixture(loop_scope="session", scope="session")
async def async_fixture():
global loop
loop = asyncio.get_running_loop()
yield
@pytest.mark.asyncio
async def test_runs_in_different_loop_as_fixture(async_fixture):
global loop
assert asyncio.get_running_loop() is not loop
"""),
)
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=1)
def test_asyncio_mark_handles_missing_event_loop_triggered_by_fixture(
pytester: Pytester,
):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
import pytest
import asyncio
@pytest.fixture(scope="session")
def sets_event_loop_to_none():
# asyncio.run() creates a new event loop without closing the existing
# one. For any test, but the first one, this leads to a ResourceWarning
# when the discarded loop is destroyed by the garbage collector.
# We close the current loop to avoid this
try:
asyncio.get_event_loop().close()
except RuntimeError:
pass
return asyncio.run(asyncio.sleep(0))
# asyncio.run() sets the current event loop to None when finished
@pytest.mark.asyncio(loop_scope="session")
# parametrization may impact fixture ordering
@pytest.mark.parametrize("n", (0, 1))
async def test_does_not_fail(sets_event_loop_to_none, n):
pass
"""))
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=2)
def test_standalone_test_does_not_trigger_warning_about_no_current_event_loop_being_set(
pytester: Pytester,
):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
import pytest
@pytest.mark.asyncio(loop_scope="session")
async def test_anything():
pass
"""))
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(warnings=0, passed=1)
pytest-dev-pytest-asyncio-dbe96ef/tests/modes/ 0000775 0000000 0000000 00000000000 15205265572 0021657 5 ustar 00root root 0000000 0000000 pytest-dev-pytest-asyncio-dbe96ef/tests/modes/__init__.py 0000664 0000000 0000000 00000000000 15205265572 0023756 0 ustar 00root root 0000000 0000000 pytest-dev-pytest-asyncio-dbe96ef/tests/modes/test_auto_mode.py 0000664 0000000 0000000 00000006524 15205265572 0025253 0 ustar 00root root 0000000 0000000 from __future__ import annotations
from textwrap import dedent
from pytest import Pytester
def test_auto_mode_cmdline(pytester: Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
import asyncio
import pytest
pytest_plugins = 'pytest_asyncio'
async def test_a():
await asyncio.sleep(0)
"""))
result = pytester.runpytest("--asyncio-mode=auto")
result.assert_outcomes(passed=1)
def test_auto_mode_cfg(pytester: Pytester):
pytester.makeini(dedent("""\
[pytest]
asyncio_default_fixture_loop_scope = function
asyncio_mode = auto
"""))
pytester.makepyfile(dedent("""\
import asyncio
import pytest
pytest_plugins = 'pytest_asyncio'
async def test_a():
await asyncio.sleep(0)
"""))
result = pytester.runpytest("--asyncio-mode=auto")
result.assert_outcomes(passed=1)
def test_auto_mode_async_fixture(pytester: Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
import asyncio
import pytest
pytest_plugins = 'pytest_asyncio'
@pytest.fixture
async def fixture_a():
await asyncio.sleep(0)
return 1
async def test_a(fixture_a):
await asyncio.sleep(0)
assert fixture_a == 1
"""))
result = pytester.runpytest("--asyncio-mode=auto")
result.assert_outcomes(passed=1)
def test_auto_mode_method_fixture(pytester: Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
import asyncio
import pytest
pytest_plugins = 'pytest_asyncio'
class TestA:
@pytest.fixture
async def fixture_a(self):
await asyncio.sleep(0)
return 1
async def test_a(self, fixture_a):
await asyncio.sleep(0)
assert fixture_a == 1
"""))
result = pytester.runpytest("--asyncio-mode=auto")
result.assert_outcomes(passed=1)
def test_auto_mode_static_method(pytester: Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
import asyncio
pytest_plugins = 'pytest_asyncio'
class TestA:
@staticmethod
async def test_a():
await asyncio.sleep(0)
"""))
result = pytester.runpytest("--asyncio-mode=auto")
result.assert_outcomes(passed=1)
def test_auto_mode_static_method_fixture(pytester: Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
import asyncio
import pytest
pytest_plugins = 'pytest_asyncio'
class TestA:
@staticmethod
@pytest.fixture
async def fixture_a():
await asyncio.sleep(0)
return 1
@staticmethod
async def test_a(fixture_a):
await asyncio.sleep(0)
assert fixture_a == 1
"""))
result = pytester.runpytest("--asyncio-mode=auto")
result.assert_outcomes(passed=1)
pytest-dev-pytest-asyncio-dbe96ef/tests/modes/test_strict_mode.py 0000664 0000000 0000000 00000015177 15205265572 0025617 0 ustar 00root root 0000000 0000000 from __future__ import annotations
from textwrap import dedent
import pytest
from pytest import Pytester, version_tuple as pytest_version
def test_strict_mode_cmdline(pytester: Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
import asyncio
import pytest
pytest_plugins = 'pytest_asyncio'
@pytest.mark.asyncio
async def test_a():
await asyncio.sleep(0)
"""))
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=1)
def test_strict_mode_cfg(pytester: Pytester):
pytester.makeini(dedent("""\
[pytest]
asyncio_default_fixture_loop_scope = function
asyncio_mode = strict
"""))
pytester.makepyfile(dedent("""\
import asyncio
import pytest
pytest_plugins = 'pytest_asyncio'
@pytest.mark.asyncio
async def test_a():
await asyncio.sleep(0)
"""))
result = pytester.runpytest()
result.assert_outcomes(passed=1)
def test_strict_mode_method_fixture(pytester: Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
import asyncio
import pytest
import pytest_asyncio
pytest_plugins = 'pytest_asyncio'
class TestA:
@pytest_asyncio.fixture
async def fixture_a(self):
await asyncio.sleep(0)
return 1
@pytest.mark.asyncio
async def test_a(self, fixture_a):
await asyncio.sleep(0)
assert fixture_a == 1
"""))
result = pytester.runpytest("--asyncio-mode=auto")
result.assert_outcomes(passed=1)
def test_strict_mode_ignores_unmarked_coroutine(pytester: Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
import pytest
async def test_anything():
pass
"""))
result = pytester.runpytest("--asyncio-mode=strict", "-W default", "--assert=plain")
if pytest_version >= (8, 4, 0):
result.assert_outcomes(failed=1, skipped=0, warnings=0)
else:
result.assert_outcomes(skipped=1, warnings=1)
result.stdout.fnmatch_lines(["*async def functions are not natively supported*"])
@pytest.mark.skipif(
pytest_version >= (9, 1, 0),
reason="pytest >=9.1 converts unhandled async fixtures to errors",
)
def test_strict_mode_ignores_unmarked_fixture(pytester: Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
import pytest
# Not using pytest_asyncio.fixture
@pytest.fixture()
async def any_fixture():
raise RuntimeError()
async def test_anything(any_fixture):
pass
"""))
result = pytester.runpytest("--asyncio-mode=strict", "-W default", "--assert=plain")
if pytest_version >= (8, 4, 0):
result.assert_outcomes(failed=1, skipped=0, warnings=2)
else:
result.assert_outcomes(skipped=1, warnings=2)
result.stdout.fnmatch_lines(
[
"*async def functions are not natively supported*",
"*coroutine 'any_fixture' was never awaited*",
],
)
@pytest.mark.skipif(
pytest_version >= (9, 1, 0),
reason="pytest >=9.1 converts unhandled async fixtures to errors",
)
def test_strict_mode_marked_test_unmarked_fixture_warning(pytester: Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
import pytest
# Not using pytest_asyncio.fixture
@pytest.fixture()
async def any_fixture():
pass
@pytest.mark.asyncio
async def test_anything(any_fixture):
# suppress unawaited coroutine warning
try:
any_fixture.send(None)
except StopIteration:
pass
"""))
result = pytester.runpytest("--asyncio-mode=strict", "-W default", "--assert=plain")
if pytest_version >= (8, 4, 0):
result.assert_outcomes(passed=1, failed=0, skipped=0, warnings=2)
else:
result.assert_outcomes(passed=1, failed=0, skipped=0, warnings=1)
result.stdout.fnmatch_lines(
[
"*warnings summary*",
(
"test_strict_mode_marked_test_unmarked_fixture_warning.py::"
"test_anything"
),
(
"*/pytest_asyncio/plugin.py:*: PytestDeprecationWarning: "
"asyncio test 'test_anything' requested async "
"@pytest.fixture 'any_fixture' in strict mode. "
"You might want to use @pytest_asyncio.fixture or switch to "
"auto mode. "
"This will become an error in future versions of pytest-asyncio."
),
],
)
# autouse is not handled in any special way currently
@pytest.mark.skipif(
pytest_version >= (9, 1, 0),
reason="pytest >=9.1 converts unhandled async fixtures to errors",
)
def test_strict_mode_marked_test_unmarked_autouse_fixture_warning(pytester: Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
import pytest
# Not using pytest_asyncio.fixture
@pytest.fixture(autouse=True)
async def any_fixture():
pass
@pytest.mark.asyncio
async def test_anything(any_fixture):
# suppress unawaited coroutine warning
try:
any_fixture.send(None)
except StopIteration:
pass
"""))
result = pytester.runpytest("--asyncio-mode=strict", "-W default", "--assert=plain")
if pytest_version >= (8, 4, 0):
result.assert_outcomes(passed=1, warnings=2)
else:
result.assert_outcomes(passed=1, warnings=1)
result.stdout.fnmatch_lines(
[
"*warnings summary*",
(
"test_strict_mode_marked_test_unmarked_autouse_fixture_warning.py::"
"test_anything"
),
(
"*/pytest_asyncio/plugin.py:*: PytestDeprecationWarning: "
"*asyncio test 'test_anything' requested async "
"@pytest.fixture 'any_fixture' in strict mode. "
"You might want to use @pytest_asyncio.fixture or switch to "
"auto mode. "
"This will become an error in future versions of pytest-asyncio."
),
],
)
pytest-dev-pytest-asyncio-dbe96ef/tests/test_asyncio_debug.py 0000664 0000000 0000000 00000013151 15205265572 0024775 0 ustar 00root root 0000000 0000000 from __future__ import annotations
from textwrap import dedent
import pytest
from pytest import Pytester
def test_asyncio_debug_disabled_by_default(pytester: Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
import asyncio
import pytest
pytest_plugins = "pytest_asyncio"
@pytest.mark.asyncio
async def test_debug_mode_disabled():
loop = asyncio.get_running_loop()
assert not loop.get_debug()
"""))
result = pytester.runpytest()
result.assert_outcomes(passed=1)
def test_asyncio_debug_enabled_via_cli_option(pytester: Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
import asyncio
import pytest
pytest_plugins = "pytest_asyncio"
@pytest.mark.asyncio
async def test_debug_mode_enabled():
loop = asyncio.get_running_loop()
assert loop.get_debug()
"""))
result = pytester.runpytest("--asyncio-debug")
result.assert_outcomes(passed=1)
@pytest.mark.parametrize("config_value", ("true", "1"))
def test_asyncio_debug_enabled_via_config_option(pytester: Pytester, config_value: str):
pytester.makeini(dedent(f"""\
[pytest]
asyncio_default_fixture_loop_scope = function
asyncio_debug = {config_value}
"""))
pytester.makepyfile(dedent("""\
import asyncio
import pytest
pytest_plugins = "pytest_asyncio"
@pytest.mark.asyncio
async def test_debug_mode_enabled():
loop = asyncio.get_running_loop()
assert loop.get_debug()
"""))
result = pytester.runpytest()
result.assert_outcomes(passed=1)
@pytest.mark.parametrize("config_value", ("false", "0"))
def test_asyncio_debug_disabled_via_config_option(
pytester: Pytester,
config_value: str,
):
pytester.makeini(dedent(f"""\
[pytest]
asyncio_default_fixture_loop_scope = function
asyncio_debug = {config_value}
"""))
pytester.makepyfile(dedent("""\
import asyncio
import pytest
pytest_plugins = "pytest_asyncio"
@pytest.mark.asyncio
async def test_debug_mode_disabled():
loop = asyncio.get_running_loop()
assert not loop.get_debug()
"""))
result = pytester.runpytest()
result.assert_outcomes(passed=1)
def test_asyncio_debug_cli_option_overrides_config(pytester: Pytester):
pytester.makeini(
"[pytest]\nasyncio_default_fixture_loop_scope = function\nasyncio_debug = false"
)
pytester.makepyfile(dedent("""\
import asyncio
import pytest
pytest_plugins = "pytest_asyncio"
@pytest.mark.asyncio
async def test_debug_mode_enabled():
loop = asyncio.get_running_loop()
assert loop.get_debug()
"""))
result = pytester.runpytest("--asyncio-debug")
result.assert_outcomes(passed=1)
@pytest.mark.parametrize("loop_scope", ("function", "module", "session"))
def test_asyncio_debug_with_different_loop_scopes(pytester: Pytester, loop_scope: str):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent(f"""\
import asyncio
import pytest
pytest_plugins = "pytest_asyncio"
@pytest.mark.asyncio(loop_scope="{loop_scope}")
async def test_debug_mode_with_scope():
loop = asyncio.get_running_loop()
assert loop.get_debug()
"""))
result = pytester.runpytest("--asyncio-debug")
result.assert_outcomes(passed=1)
def test_asyncio_debug_with_async_fixtures(pytester: Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
import asyncio
import pytest
import pytest_asyncio
pytest_plugins = "pytest_asyncio"
@pytest_asyncio.fixture
async def async_fixture():
loop = asyncio.get_running_loop()
assert loop.get_debug()
return "fixture_value"
@pytest.mark.asyncio
async def test_debug_mode_with_fixture(async_fixture):
loop = asyncio.get_running_loop()
assert loop.get_debug()
assert async_fixture == "fixture_value"
"""))
result = pytester.runpytest("--asyncio-debug")
result.assert_outcomes(passed=1)
def test_asyncio_debug_multiple_test_functions(pytester: Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
import asyncio
import pytest
pytest_plugins = "pytest_asyncio"
@pytest.mark.asyncio
async def test_debug_first():
loop = asyncio.get_running_loop()
assert loop.get_debug()
@pytest.mark.asyncio
async def test_debug_second():
loop = asyncio.get_running_loop()
assert loop.get_debug()
@pytest.mark.asyncio
async def test_debug_third():
loop = asyncio.get_running_loop()
assert loop.get_debug()
"""))
result = pytester.runpytest("--asyncio-debug")
result.assert_outcomes(passed=3)
pytest-dev-pytest-asyncio-dbe96ef/tests/test_asyncio_fixture.py 0000664 0000000 0000000 00000003021 15205265572 0025370 0 ustar 00root root 0000000 0000000 from __future__ import annotations
import asyncio
from textwrap import dedent
import pytest
from pytest import Pytester
import pytest_asyncio
@pytest_asyncio.fixture
async def fixture_bare():
await asyncio.sleep(0)
return 1
@pytest.mark.asyncio
async def test_bare_fixture(fixture_bare):
await asyncio.sleep(0)
assert fixture_bare == 1
@pytest_asyncio.fixture(name="new_fixture_name")
async def fixture_with_name(request):
await asyncio.sleep(0)
return request.fixturename
@pytest.mark.asyncio
async def test_fixture_with_name(new_fixture_name):
await asyncio.sleep(0)
assert new_fixture_name == "new_fixture_name"
@pytest_asyncio.fixture(params=[2, 4])
async def fixture_with_params(request):
await asyncio.sleep(0)
return request.param
@pytest.mark.asyncio
async def test_fixture_with_params(fixture_with_params):
await asyncio.sleep(0)
assert fixture_with_params % 2 == 0
@pytest.mark.parametrize("mode", ("auto", "strict"))
def test_sync_function_uses_async_fixture(pytester: Pytester, mode):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
import pytest_asyncio
pytest_plugins = 'pytest_asyncio'
@pytest_asyncio.fixture
async def always_true():
return True
def test_sync_function_uses_async_fixture(always_true):
assert always_true is True
"""))
result = pytester.runpytest(f"--asyncio-mode={mode}")
result.assert_outcomes(passed=1)
pytest-dev-pytest-asyncio-dbe96ef/tests/test_asyncio_mark.py 0000664 0000000 0000000 00000014165 15205265572 0024647 0 ustar 00root root 0000000 0000000 from __future__ import annotations
from textwrap import dedent
from pytest import Pytester
def test_asyncio_mark_on_sync_function_emits_warning(pytester: Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
import pytest
@pytest.mark.asyncio
def test_a():
pass
"""))
result = pytester.runpytest("--asyncio-mode=strict", "-W default", "--assert=plain")
result.assert_outcomes(passed=1)
result.stdout.fnmatch_lines(
["*is marked with '@pytest.mark.asyncio' but it is not an async function.*"]
)
def test_asyncio_mark_on_async_generator_function_emits_warning_in_strict_mode(
pytester: Pytester,
):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
import pytest
@pytest.mark.asyncio
async def test_a():
yield
"""))
result = pytester.runpytest("--asyncio-mode=strict", "-W default", "--assert=plain")
result.assert_outcomes(xfailed=1, warnings=1)
result.stdout.fnmatch_lines(
["*Tests based on asynchronous generators are not supported*"]
)
def test_asyncio_mark_on_async_generator_function_emits_warning_in_auto_mode(
pytester: Pytester,
):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
async def test_a():
yield
"""))
result = pytester.runpytest("--asyncio-mode=auto", "-W default", "--assert=plain")
result.assert_outcomes(xfailed=1, warnings=1)
result.stdout.fnmatch_lines(
["*Tests based on asynchronous generators are not supported*"]
)
def test_asyncio_mark_on_async_generator_method_emits_warning_in_strict_mode(
pytester: Pytester,
):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
import pytest
class TestAsyncGenerator:
@pytest.mark.asyncio
async def test_a(self):
yield
"""))
result = pytester.runpytest("--asyncio-mode=strict", "-W default", "--assert=plain")
result.assert_outcomes(xfailed=1, warnings=1)
result.stdout.fnmatch_lines(
["*Tests based on asynchronous generators are not supported*"]
)
def test_asyncio_mark_on_async_generator_method_emits_warning_in_auto_mode(
pytester: Pytester,
):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
class TestAsyncGenerator:
@staticmethod
async def test_a():
yield
"""))
result = pytester.runpytest("--asyncio-mode=auto", "-W default", "--assert=plain")
result.assert_outcomes(xfailed=1, warnings=1)
result.stdout.fnmatch_lines(
["*Tests based on asynchronous generators are not supported*"]
)
def test_asyncio_mark_on_async_generator_staticmethod_emits_warning_in_strict_mode(
pytester: Pytester,
):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
import pytest
class TestAsyncGenerator:
@staticmethod
@pytest.mark.asyncio
async def test_a():
yield
"""))
result = pytester.runpytest("--asyncio-mode=strict", "-W default", "--assert=plain")
result.assert_outcomes(xfailed=1, warnings=1)
result.stdout.fnmatch_lines(
["*Tests based on asynchronous generators are not supported*"]
)
def test_asyncio_mark_on_async_generator_staticmethod_emits_warning_in_auto_mode(
pytester: Pytester,
):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
class TestAsyncGenerator:
@staticmethod
async def test_a():
yield
"""))
result = pytester.runpytest("--asyncio-mode=auto", "-W default", "--assert=plain")
result.assert_outcomes(xfailed=1, warnings=1)
result.stdout.fnmatch_lines(
["*Tests based on asynchronous generators are not supported*"]
)
def test_asyncio_marker_fallbacks_to_configured_default_loop_scope_if_not_set(
pytester: Pytester,
):
pytester.makeini(dedent("""\
[pytest]
asyncio_default_fixture_loop_scope = function
asyncio_default_test_loop_scope = session
"""))
pytester.makepyfile(dedent("""\
import asyncio
import pytest_asyncio
import pytest
loop: asyncio.AbstractEventLoop
@pytest_asyncio.fixture(loop_scope="session", scope="session")
async def session_loop_fixture():
global loop
loop = asyncio.get_running_loop()
async def test_a(session_loop_fixture):
global loop
assert asyncio.get_running_loop() is loop
"""))
result = pytester.runpytest("--asyncio-mode=auto")
result.assert_outcomes(passed=1)
def test_asyncio_marker_uses_marker_loop_scope_even_if_config_is_set(
pytester: Pytester,
):
pytester.makeini(dedent("""\
[pytest]
asyncio_default_fixture_loop_scope = function
asyncio_default_test_loop_scope = module
"""))
pytester.makepyfile(dedent("""\
import asyncio
import pytest_asyncio
import pytest
loop: asyncio.AbstractEventLoop
@pytest_asyncio.fixture(loop_scope="session", scope="session")
async def session_loop_fixture():
global loop
loop = asyncio.get_running_loop()
@pytest.mark.asyncio(loop_scope="session")
async def test_a(session_loop_fixture):
global loop
assert asyncio.get_running_loop() is loop
"""))
result = pytester.runpytest("--asyncio-mode=auto")
result.assert_outcomes(passed=1)
pytest-dev-pytest-asyncio-dbe96ef/tests/test_doctest.py 0000664 0000000 0000000 00000002232 15205265572 0023625 0 ustar 00root root 0000000 0000000 from __future__ import annotations
from textwrap import dedent
from pytest import Pytester
def test_plugin_does_not_interfere_with_doctest_collection(pytester: Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(
dedent('''\
def any_function():
"""
>>> 42
42
"""
'''),
)
result = pytester.runpytest("--asyncio-mode=strict", "--doctest-modules")
result.assert_outcomes(passed=1)
def test_plugin_does_not_interfere_with_doctest_textfile_collection(pytester: Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makefile(".txt", "") # collected as DoctestTextfile
pytester.makepyfile(
__init__="",
test_python_file=dedent("""\
import pytest
pytest_plugins = "pytest_asyncio"
@pytest.mark.asyncio
async def test_anything():
pass
"""),
)
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=1)
pytest-dev-pytest-asyncio-dbe96ef/tests/test_event_loop_fixture.py 0000664 0000000 0000000 00000005030 15205265572 0026077 0 ustar 00root root 0000000 0000000 from __future__ import annotations
from textwrap import dedent
from pytest import Pytester
def test_event_loop_fixture_handles_unclosed_async_gen(
pytester: Pytester,
):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
import asyncio
import pytest
pytest_plugins = 'pytest_asyncio'
@pytest.mark.asyncio
async def test_something():
async def generator_fn():
yield
yield
gen = generator_fn()
await gen.__anext__()
"""))
result = pytester.runpytest("--asyncio-mode=strict", "-W", "default")
result.assert_outcomes(passed=1, warnings=0)
def test_closing_event_loop_in_sync_fixture_teardown_raises_warning(
pytester: Pytester,
):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
import asyncio
import pytest
import pytest_asyncio
pytest_plugins = 'pytest_asyncio'
@pytest_asyncio.fixture
async def _event_loop():
return asyncio.get_running_loop()
@pytest.fixture
def close_event_loop(_event_loop):
yield
# fixture has its own cleanup code
_event_loop.close()
@pytest.mark.asyncio
async def test_something(close_event_loop):
await asyncio.sleep(0.01)
"""))
result = pytester.runpytest_subprocess("--asyncio-mode=strict", "--assert=plain")
result.assert_outcomes(passed=1, warnings=1)
result.stdout.fnmatch_lines(
["*An exception occurred during teardown of an asyncio.Runner*"]
)
def test_event_loop_fixture_asyncgen_error(
pytester: Pytester,
):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
import asyncio
import pytest
pytest_plugins = 'pytest_asyncio'
@pytest.mark.asyncio
async def test_something():
# mock shutdown_asyncgen failure
loop = asyncio.get_running_loop()
async def fail():
raise RuntimeError("mock error cleaning up...")
loop.shutdown_asyncgens = fail
"""))
result = pytester.runpytest("--asyncio-mode=strict", "-W", "default")
result.assert_outcomes(passed=1, warnings=1)
pytest-dev-pytest-asyncio-dbe96ef/tests/test_fixture_loop_scopes.py 0000664 0000000 0000000 00000010070 15205265572 0026252 0 ustar 00root root 0000000 0000000 from __future__ import annotations
from textwrap import dedent
import pytest
from pytest import Pytester
@pytest.mark.parametrize(
"fixture_scope", ("session", "package", "module", "class", "function")
)
def test_loop_scope_session_is_independent_of_fixture_scope(
pytester: Pytester,
fixture_scope: str,
):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent(f"""\
import asyncio
import pytest
import pytest_asyncio
loop: asyncio.AbstractEventLoop = None
@pytest_asyncio.fixture(scope="{fixture_scope}", loop_scope="session")
async def fixture():
global loop
loop = asyncio.get_running_loop()
@pytest.mark.asyncio(loop_scope="session")
async def test_runs_in_same_loop_as_fixture(fixture):
global loop
assert loop == asyncio.get_running_loop()
"""))
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=1)
@pytest.mark.parametrize("default_loop_scope", ("function", "module", "session"))
def test_default_loop_scope_config_option_changes_fixture_loop_scope(
pytester: Pytester,
default_loop_scope: str,
):
pytester.makeini(dedent(f"""\
[pytest]
asyncio_default_fixture_loop_scope = {default_loop_scope}
"""))
pytester.makepyfile(dedent(f"""\
import asyncio
import pytest
import pytest_asyncio
@pytest_asyncio.fixture
async def fixture_loop():
return asyncio.get_running_loop()
@pytest.mark.asyncio(loop_scope="{default_loop_scope}")
async def test_runs_in_fixture_loop(fixture_loop):
assert asyncio.get_running_loop() is fixture_loop
"""))
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=1)
def test_default_class_loop_scope_config_option_changes_fixture_loop_scope(
pytester: Pytester,
):
pytester.makeini(dedent("""\
[pytest]
asyncio_default_fixture_loop_scope = class
"""))
pytester.makepyfile(dedent("""\
import asyncio
import pytest
import pytest_asyncio
class TestClass:
@pytest_asyncio.fixture
async def fixture_loop(self):
return asyncio.get_running_loop()
@pytest.mark.asyncio(loop_scope="class")
async def test_runs_in_fixture_loop(self, fixture_loop):
assert asyncio.get_running_loop() is fixture_loop
"""))
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=1)
def test_default_package_loop_scope_config_option_changes_fixture_loop_scope(
pytester: Pytester,
):
pytester.makeini(dedent("""\
[pytest]
asyncio_default_fixture_loop_scope = package
"""))
pytester.makepyfile(
__init__="",
test_a=dedent("""\
import asyncio
import pytest
import pytest_asyncio
@pytest_asyncio.fixture
async def fixture_loop():
return asyncio.get_running_loop()
@pytest.mark.asyncio(loop_scope="package")
async def test_runs_in_fixture_loop(fixture_loop):
assert asyncio.get_running_loop() is fixture_loop
"""),
)
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=1)
def test_invalid_default_fixture_loop_scope_raises_error(pytester: Pytester):
pytester.makeini("""\
[pytest]
asyncio_default_fixture_loop_scope = invalid_scope
""")
result = pytester.runpytest("--assert=plain")
result.stderr.fnmatch_lines(
[
"ERROR: 'invalid_scope' is not a valid "
"asyncio_default_fixture_loop_scope. Valid scopes are: "
"function, class, module, package, session."
]
)
pytest-dev-pytest-asyncio-dbe96ef/tests/test_import.py 0000664 0000000 0000000 00000003077 15205265572 0023502 0 ustar 00root root 0000000 0000000 from __future__ import annotations
from textwrap import dedent
from pytest import Pytester
def test_import_warning_does_not_cause_internal_error(pytester: Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
raise ImportWarning()
async def test_errors_out():
pass
"""))
result = pytester.runpytest("--asyncio-mode=auto")
result.assert_outcomes(errors=1)
def test_import_warning_in_package_does_not_cause_internal_error(pytester: Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(
__init__=dedent("""\
raise ImportWarning()
"""),
test_a=dedent("""\
async def test_errors_out():
pass
"""),
)
result = pytester.runpytest("--asyncio-mode=auto")
result.assert_outcomes(errors=1)
def test_does_not_import_unrelated_packages(pytester: Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pkg_dir = pytester.mkpydir("mypkg")
pkg_dir.joinpath("__init__.py").write_text(
dedent("""\
raise ImportError()
"""),
)
test_dir = pytester.mkdir("tests")
test_dir.joinpath("test_a.py").write_text(
dedent("""\
async def test_passes():
pass
"""),
)
result = pytester.runpytest("--asyncio-mode=auto")
result.assert_outcomes(passed=1)
pytest-dev-pytest-asyncio-dbe96ef/tests/test_is_async_test.py 0000664 0000000 0000000 00000005527 15205265572 0025041 0 ustar 00root root 0000000 0000000 from __future__ import annotations
from textwrap import dedent
from pytest import Pytester
def test_returns_false_for_sync_item(pytester: Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
import pytest
import pytest_asyncio
def test_sync():
pass
def pytest_collection_modifyitems(items):
async_tests = [
item
for item in items
if pytest_asyncio.is_async_test(item)
]
assert len(async_tests) == 0
"""))
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=1)
def test_returns_true_for_marked_coroutine_item_in_strict_mode(pytester: Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
import pytest
import pytest_asyncio
@pytest.mark.asyncio
async def test_coro():
pass
def pytest_collection_modifyitems(items):
async_tests = [
item
for item in items
if pytest_asyncio.is_async_test(item)
]
assert len(async_tests) == 1
"""))
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=1)
def test_returns_false_for_unmarked_coroutine_item_in_strict_mode(pytester: Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
import pytest
import pytest_asyncio
async def test_coro():
pass
def pytest_collection_modifyitems(items):
async_tests = [
item
for item in items
if pytest_asyncio.is_async_test(item)
]
assert len(async_tests) == 0
"""))
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(failed=1)
def test_returns_true_for_unmarked_coroutine_item_in_auto_mode(pytester: Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
import pytest
import pytest_asyncio
async def test_coro():
pass
def pytest_collection_modifyitems(items):
async_tests = [
item
for item in items
if pytest_asyncio.is_async_test(item)
]
assert len(async_tests) == 1
"""))
result = pytester.runpytest("--asyncio-mode=auto")
result.assert_outcomes(passed=1)
pytest-dev-pytest-asyncio-dbe96ef/tests/test_loop_factory_parametrization.py 0000664 0000000 0000000 00000067774 15205265572 0030177 0 ustar 00root root 0000000 0000000 from __future__ import annotations
from textwrap import dedent
import pytest
from pytest import Pytester
@pytest.mark.skipif(
not hasattr(pytest, "HIDDEN_PARAM"),
reason="pytest.HIDDEN_PARAM requires pytest 8.4+",
)
def test_single_factory_does_not_add_suffix_to_test_name(
pytester: Pytester,
) -> None:
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makeconftest(dedent("""\
import asyncio
def pytest_asyncio_loop_factories(config, item):
return {"asyncio": asyncio.new_event_loop}
"""))
pytester.makepyfile(dedent("""\
import pytest
pytest_plugins = "pytest_asyncio"
@pytest.mark.asyncio
async def test_example():
assert True
"""))
result = pytester.runpytest("--asyncio-mode=strict", "--collect-only", "-q")
result.stdout.fnmatch_lines(
["test_single_factory_does_not_add_suffix_to_test_name.py::test_example"]
)
def test_named_hook_factories_apply_to_async_tests(pytester: Pytester) -> None:
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makeconftest(dedent("""\
import asyncio
class CustomEventLoop(asyncio.SelectorEventLoop):
pass
def pytest_asyncio_loop_factories(config, item):
return {"custom": CustomEventLoop}
"""))
pytester.makepyfile(dedent("""\
import asyncio
import pytest
pytest_plugins = "pytest_asyncio"
@pytest.mark.asyncio
async def test_uses_custom_loop():
assert type(asyncio.get_running_loop()).__name__ == "CustomEventLoop"
"""))
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=1)
def test_named_hook_factories_parametrize_async_tests(pytester: Pytester) -> None:
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makeconftest(dedent("""\
import asyncio
class CustomEventLoopA(asyncio.SelectorEventLoop):
pass
class CustomEventLoopB(asyncio.SelectorEventLoop):
pass
def pytest_asyncio_loop_factories(config, item):
return {
"factory_a": CustomEventLoopA,
"factory_b": CustomEventLoopB,
}
"""))
pytester.makepyfile(dedent("""\
import asyncio
import pytest
pytest_plugins = "pytest_asyncio"
@pytest.mark.asyncio
async def test_runs_once_per_factory():
loop_name = type(asyncio.get_running_loop()).__name__
assert loop_name in ("CustomEventLoopA", "CustomEventLoopB")
"""))
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=2)
def test_named_hook_factories_use_mapping_keys_as_test_ids(
pytester: Pytester,
) -> None:
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makeconftest(dedent("""\
import asyncio
def pytest_asyncio_loop_factories(config, item):
return {
"factory_a": asyncio.new_event_loop,
"factory_b": asyncio.new_event_loop,
}
"""))
pytester.makepyfile(dedent("""\
import pytest
pytest_plugins = "pytest_asyncio"
@pytest.mark.asyncio
async def test_runs_once_per_factory():
assert True
"""))
result = pytester.runpytest("--asyncio-mode=strict", "--collect-only", "-q")
result.stdout.fnmatch_lines(
[
"*test_runs_once_per_factory[[]factory_a[]]",
"*test_runs_once_per_factory[[]factory_b[]]",
]
)
def test_named_hook_factories_apply_to_async_fixtures(pytester: Pytester) -> None:
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makeconftest(dedent("""\
import asyncio
class CustomEventLoop(asyncio.SelectorEventLoop):
pass
def pytest_asyncio_loop_factories(config, item):
return {"custom": CustomEventLoop}
"""))
pytester.makepyfile(dedent("""\
import asyncio
import pytest
import pytest_asyncio
pytest_plugins = "pytest_asyncio"
@pytest_asyncio.fixture
async def loop_fixture():
return asyncio.get_running_loop()
@pytest.mark.asyncio
async def test_fixture_uses_custom_loop(loop_fixture):
assert type(loop_fixture).__name__ == "CustomEventLoop"
assert type(asyncio.get_running_loop()).__name__ == "CustomEventLoop"
"""))
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=1)
def test_sync_tests_are_not_parametrized_by_hook_factories(pytester: Pytester) -> None:
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makeconftest(dedent("""\
import asyncio
class CustomEventLoopA(asyncio.SelectorEventLoop):
pass
class CustomEventLoopB(asyncio.SelectorEventLoop):
pass
def pytest_asyncio_loop_factories(config, item):
return {
"factory_a": CustomEventLoopA,
"factory_b": CustomEventLoopB,
}
"""))
pytester.makepyfile(dedent("""\
import asyncio
import pytest
pytest_plugins = "pytest_asyncio"
def test_sync(request):
assert True
@pytest.mark.asyncio
async def test_async(request):
assert "_asyncio_loop_factory" in request.fixturenames
loop_name = type(asyncio.get_running_loop()).__name__
assert loop_name in ("CustomEventLoopA", "CustomEventLoopB")
"""))
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=3)
@pytest.mark.parametrize(
"hook_body",
(
"return None",
"return {}",
"return [CustomEventLoop]",
"return {'': CustomEventLoop}",
"return {'default': 1}",
),
)
def test_hook_requires_non_empty_mapping_of_named_callables(
pytester: Pytester,
hook_body: str,
) -> None:
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makeconftest(dedent(f"""\
import asyncio
class CustomEventLoop(asyncio.SelectorEventLoop):
pass
def pytest_asyncio_loop_factories(config, item):
{hook_body}
"""))
pytester.makepyfile(dedent("""\
import pytest
pytest_plugins = "pytest_asyncio"
@pytest.mark.asyncio
async def test_async():
assert True
"""))
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(errors=1)
result.stdout.fnmatch_lines(
[
"*pytest_asyncio_loop_factories must return a non-empty mapping of "
"factory*"
]
)
def test_hook_factories_use_first_non_none_result(pytester: Pytester) -> None:
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(
plugin_none=dedent("""\
import pytest
@pytest.hookimpl(tryfirst=True)
def pytest_asyncio_loop_factories(config, item):
return None
"""),
plugin_loop=dedent("""\
import asyncio
import pytest
class SecondaryCustomEventLoop(asyncio.SelectorEventLoop):
pass
@pytest.hookimpl(trylast=True)
def pytest_asyncio_loop_factories(config, item):
return {"secondary": SecondaryCustomEventLoop}
"""),
test_sample=dedent("""\
import asyncio
import pytest
pytest_plugins = ("pytest_asyncio", "plugin_none", "plugin_loop")
@pytest.mark.asyncio
async def test_uses_secondary_loop():
assert (
type(asyncio.get_running_loop()).__name__
== "SecondaryCustomEventLoop"
)
"""),
)
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=1)
def test_hook_factories_error_when_all_implementations_return_none(
pytester: Pytester,
) -> None:
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(
plugin_none_a=dedent("""\
import pytest
@pytest.hookimpl(tryfirst=True)
def pytest_asyncio_loop_factories(config, item):
return None
"""),
plugin_none_b=dedent("""\
import pytest
@pytest.hookimpl(trylast=True)
def pytest_asyncio_loop_factories(config, item):
return None
"""),
test_sample=dedent("""\
import pytest
pytest_plugins = ("pytest_asyncio", "plugin_none_a", "plugin_none_b")
@pytest.mark.asyncio
async def test_anything():
assert True
"""),
)
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(errors=1)
result.stdout.fnmatch_lines(
[
"*pytest_asyncio_loop_factories must return a non-empty mapping of "
"factory*"
]
)
def test_nested_conftest_hook_respects_conftest_locality(
pytester: Pytester,
) -> None:
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makeconftest(dedent("""\
import asyncio
class RootCustomEventLoop(asyncio.SelectorEventLoop):
pass
def pytest_asyncio_loop_factories(config, item):
return {"root": RootCustomEventLoop}
"""))
subdir = pytester.mkdir("subdir")
subdir.joinpath("conftest.py").write_text(
dedent("""\
import asyncio
class SubCustomEventLoop(asyncio.SelectorEventLoop):
pass
def pytest_asyncio_loop_factories(config, item):
return {"sub": SubCustomEventLoop}
"""),
)
pytester.makepyfile(
test_root=dedent("""\
import asyncio
import pytest
pytest_plugins = "pytest_asyncio"
@pytest.mark.asyncio
async def test_root_uses_root_loop():
assert (
type(asyncio.get_running_loop()).__name__ == "RootCustomEventLoop"
)
"""),
)
subdir.joinpath("test_sub.py").write_text(
dedent("""\
import asyncio
import pytest
pytest_plugins = "pytest_asyncio"
@pytest.mark.asyncio
async def test_sub_uses_sub_loop():
assert type(asyncio.get_running_loop()).__name__ == "SubCustomEventLoop"
"""),
)
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=2)
def test_asyncio_marker_loop_factories_select_subset(pytester: Pytester) -> None:
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makeconftest(dedent("""\
import asyncio
class MainCustomEventLoop(asyncio.SelectorEventLoop):
pass
class AlternativeCustomEventLoop(asyncio.SelectorEventLoop):
pass
def pytest_asyncio_loop_factories(config, item):
return {
"main": MainCustomEventLoop,
"alternative": AlternativeCustomEventLoop,
}
"""))
pytester.makepyfile(dedent("""\
import asyncio
import pytest
pytest_plugins = "pytest_asyncio"
@pytest.mark.asyncio(loop_factories=["alternative"])
async def test_runs_only_with_uvloop():
assert (
type(asyncio.get_running_loop()).__name__
== "AlternativeCustomEventLoop"
)
"""))
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=1)
def test_unavailable_factory_skips_with_reason(pytester: Pytester) -> None:
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makeconftest(dedent("""\
import asyncio
def pytest_asyncio_loop_factories(config, item):
return {"root": asyncio.new_event_loop}
"""))
pytester.makepyfile(dedent("""\
import pytest
pytest_plugins = "pytest_asyncio"
@pytest.mark.asyncio(loop_factories=["missing"])
async def test_skipped():
assert True
"""))
result = pytester.runpytest("--asyncio-mode=strict", "-rs")
result.assert_outcomes(skipped=1)
result.stdout.fnmatch_lines(["*SKIPPED*Loop factory 'missing' is not available*"])
def test_partial_intersection_runs_available_and_skips_missing(
pytester: Pytester,
) -> None:
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makeconftest(dedent("""\
import asyncio
class CustomEventLoop(asyncio.SelectorEventLoop):
pass
def pytest_asyncio_loop_factories(config, item):
return {
"available": CustomEventLoop,
"other": asyncio.new_event_loop,
}
"""))
pytester.makepyfile(dedent("""\
import asyncio
import pytest
pytest_plugins = "pytest_asyncio"
@pytest.mark.asyncio(loop_factories=["available", "missing"])
async def test_runs_with_available():
assert type(asyncio.get_running_loop()).__name__ == "CustomEventLoop"
"""))
result = pytester.runpytest("--asyncio-mode=strict", "-rs")
result.assert_outcomes(passed=1, skipped=1)
result.stdout.fnmatch_lines(["*SKIPPED*Loop factory 'missing' is not available*"])
def test_platform_conditional_factories(pytester: Pytester) -> None:
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makeconftest(dedent("""\
import asyncio
import sys
def pytest_asyncio_loop_factories(config, item):
factories = {"default": asyncio.new_event_loop}
if sys.platform == "a_platform_that_does_not_exist":
factories["exotic"] = asyncio.new_event_loop
return factories
"""))
pytester.makepyfile(dedent("""\
import pytest
pytest_plugins = "pytest_asyncio"
@pytest.mark.asyncio(loop_factories=["exotic"])
async def test_exotic_only():
assert True
@pytest.mark.asyncio(loop_factories=["default"])
async def test_default_only():
assert True
@pytest.mark.asyncio(loop_factories=["default", "exotic"])
async def test_both():
assert True
"""))
result = pytester.runpytest("--asyncio-mode=strict", "-rs")
result.assert_outcomes(passed=2, skipped=2)
result.stdout.fnmatch_lines(["*SKIPPED*Loop factory 'exotic' is not available*"])
def test_asyncio_marker_loop_factories_without_hook_errors(
pytester: Pytester,
) -> None:
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
import pytest
pytest_plugins = "pytest_asyncio"
@pytest.mark.asyncio(loop_factories=["missing"])
async def test_errors():
assert True
"""))
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(errors=1)
result.stdout.fnmatch_lines(
[
"*mark.asyncio 'loop_factories' requires at least one "
"pytest_asyncio_loop_factories hook implementation.*",
]
)
@pytest.mark.parametrize("default_test_loop_scope", ("function", "module"))
def test_hook_factories_can_vary_per_test_with_default_loop_scope(
pytester: Pytester,
default_test_loop_scope: str,
) -> None:
pytester.makeini(
"[pytest]\nasyncio_default_fixture_loop_scope = function\n"
f"asyncio_default_test_loop_scope = {default_test_loop_scope}"
)
pytester.makeconftest(dedent("""\
import asyncio
class CustomEventLoopA(asyncio.SelectorEventLoop):
pass
class CustomEventLoopB(asyncio.SelectorEventLoop):
pass
def pytest_asyncio_loop_factories(config, item):
if item.name.endswith("a"):
return {"factory_a": CustomEventLoopA}
else:
return {"factory_b": CustomEventLoopB}
"""))
pytester.makepyfile(dedent("""\
import asyncio
import pytest
pytest_plugins = "pytest_asyncio"
@pytest.mark.asyncio
async def test_a():
assert type(asyncio.get_running_loop()).__name__ == "CustomEventLoopA"
@pytest.mark.asyncio
async def test_b():
assert type(asyncio.get_running_loop()).__name__ == "CustomEventLoopB"
"""))
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=2)
def test_hook_factories_can_vary_per_test_with_session_scope_across_modules(
pytester: Pytester,
) -> None:
pytester.makeini(
"[pytest]\nasyncio_default_fixture_loop_scope = function\n"
"asyncio_default_test_loop_scope = session"
)
pytester.makeconftest(dedent("""\
import asyncio
class CustomEventLoopA(asyncio.SelectorEventLoop):
pass
class CustomEventLoopB(asyncio.SelectorEventLoop):
pass
def pytest_asyncio_loop_factories(config, item):
if "test_a.py::" in item.nodeid:
return {"factory_a": CustomEventLoopA}
else:
return {"factory_b": CustomEventLoopB}
"""))
pytester.makepyfile(
test_a=dedent("""\
import asyncio
import pytest
pytest_plugins = "pytest_asyncio"
@pytest.mark.asyncio
async def test_a():
assert type(asyncio.get_running_loop()).__name__ == "CustomEventLoopA"
"""),
test_b=dedent("""\
import asyncio
import pytest
pytest_plugins = "pytest_asyncio"
@pytest.mark.asyncio
async def test_b():
assert type(asyncio.get_running_loop()).__name__ == "CustomEventLoopB"
"""),
)
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=2)
def test_hook_factories_work_in_auto_mode(pytester: Pytester) -> None:
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makeconftest(dedent("""\
import asyncio
class CustomEventLoop(asyncio.SelectorEventLoop):
pass
def pytest_asyncio_loop_factories(config, item):
return {"custom": CustomEventLoop}
"""))
pytester.makepyfile(dedent("""\
import asyncio
pytest_plugins = "pytest_asyncio"
async def test_uses_custom_loop():
assert type(asyncio.get_running_loop()).__name__ == "CustomEventLoop"
"""))
result = pytester.runpytest("--asyncio-mode=auto")
result.assert_outcomes(passed=1)
def test_no_event_loop_leak_with_custom_factory(pytester: Pytester) -> None:
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makeconftest(dedent("""\
import asyncio
import pytest_asyncio
class CustomEventLoop(asyncio.SelectorEventLoop):
pass
def pytest_asyncio_loop_factories(config, item):
return {"custom": CustomEventLoop}
@pytest_asyncio.fixture(autouse=True, scope="session", loop_scope="session")
async def session_fixture():
yield
@pytest_asyncio.fixture(autouse=True)
def sync_fixture():
asyncio.get_event_loop()
"""))
pytester.makepyfile(dedent("""\
import pytest
pytest_plugins = "pytest_asyncio"
@pytest.mark.asyncio
async def test_passes():
assert True
"""))
result = pytester.runpytest_subprocess(
"--asyncio-mode=auto", "-W", "error::ResourceWarning"
)
result.assert_outcomes(passed=1)
result.stderr.no_fnmatch_line("*unclosed event loop*")
def test_function_loop_scope_allows_per_test_factories_with_session_default(
pytester: Pytester,
) -> None:
pytester.makeini(
"[pytest]\nasyncio_default_fixture_loop_scope = function\n"
"asyncio_default_test_loop_scope = session"
)
pytester.makeconftest(dedent("""\
import asyncio
class CustomEventLoopA(asyncio.SelectorEventLoop):
pass
class CustomEventLoopB(asyncio.SelectorEventLoop):
pass
def pytest_asyncio_loop_factories(config, item):
if item.name.endswith("a"):
return {"factory_a": CustomEventLoopA}
else:
return {"factory_b": CustomEventLoopB}
"""))
pytester.makepyfile(dedent("""\
import asyncio
import pytest
pytest_plugins = "pytest_asyncio"
@pytest.mark.asyncio(loop_scope="function")
async def test_a():
assert type(asyncio.get_running_loop()).__name__ == "CustomEventLoopA"
@pytest.mark.asyncio(loop_scope="function")
async def test_b():
assert type(asyncio.get_running_loop()).__name__ == "CustomEventLoopB"
"""))
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=2)
def test_sync_fixture_sees_same_loop_as_async_test_under_custom_factory(
pytester: Pytester,
) -> None:
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makeconftest(dedent("""\
import asyncio
class CustomEventLoop(asyncio.SelectorEventLoop):
pass
def pytest_asyncio_loop_factories(config, item):
return {"custom": CustomEventLoop}
"""))
pytester.makepyfile(dedent("""\
import asyncio
import pytest
import pytest_asyncio
pytest_plugins = "pytest_asyncio"
@pytest_asyncio.fixture(autouse=True)
def enable_debug_on_event_loop():
asyncio.get_event_loop().set_debug(True)
@pytest.mark.asyncio
async def test_debug_mode_visible():
assert asyncio.get_running_loop().get_debug()
"""))
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=1)
@pytest.mark.parametrize(
("fixture_scope", "wider_scope"),
[
("function", "module"),
("function", "package"),
("function", "session"),
("module", "session"),
("package", "session"),
],
)
def test_sync_fixture_sees_its_own_loop_when_wider_scoped_loop_active(
pytester: Pytester,
fixture_scope: str,
wider_scope: str,
) -> None:
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makeconftest(dedent(f"""\
import asyncio
import pytest_asyncio
class CustomEventLoop(asyncio.SelectorEventLoop):
pass
def pytest_asyncio_loop_factories(config, item):
return {{"custom": CustomEventLoop}}
@pytest_asyncio.fixture(
autouse=True,
scope="{wider_scope}",
loop_scope="{wider_scope}",
)
async def wider_scoped_fixture():
yield
@pytest_asyncio.fixture(
autouse=True,
scope="{fixture_scope}",
loop_scope="{fixture_scope}",
)
def sync_fixture_captures_loop():
return id(asyncio.get_event_loop())
"""))
pytester.makepyfile(dedent(f"""\
import asyncio
import pytest
pytest_plugins = "pytest_asyncio"
@pytest.mark.asyncio(loop_scope="{fixture_scope}")
async def test_sync_fixture_and_test_see_same_loop(sync_fixture_captures_loop):
assert sync_fixture_captures_loop == id(asyncio.get_running_loop())
"""))
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=1)
@pytest.mark.parametrize(
("fixture_scope", "wider_scope"),
[
("function", "module"),
("function", "session"),
("module", "session"),
],
)
def test_sync_generator_fixture_teardown_sees_own_loop(
pytester: Pytester,
fixture_scope: str,
wider_scope: str,
) -> None:
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makeconftest(dedent(f"""\
import asyncio
import pytest_asyncio
class CustomEventLoop(asyncio.SelectorEventLoop):
pass
def pytest_asyncio_loop_factories(config, item):
return {{"custom": CustomEventLoop}}
@pytest_asyncio.fixture(
autouse=True,
scope="{wider_scope}",
loop_scope="{wider_scope}",
)
async def wider_scoped_fixture():
yield
@pytest_asyncio.fixture(
autouse=True,
scope="{fixture_scope}",
loop_scope="{fixture_scope}",
)
def sync_generator_fixture():
loop_at_setup = id(asyncio.get_event_loop())
yield loop_at_setup
loop_at_teardown = id(asyncio.get_event_loop())
assert loop_at_setup == loop_at_teardown
"""))
pytester.makepyfile(dedent(f"""\
import asyncio
import pytest
pytest_plugins = "pytest_asyncio"
@pytest.mark.asyncio(loop_scope="{fixture_scope}")
async def test_generator_fixture_sees_correct_loop(sync_generator_fixture):
assert sync_generator_fixture == id(asyncio.get_running_loop())
"""))
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=1)
@pytest.mark.parametrize("loop_scope", ("module", "package", "session"))
def test_async_generator_fixture_teardown_runs_under_custom_factory(
pytester: Pytester,
loop_scope: str,
) -> None:
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makeconftest(dedent(f"""\
import asyncio
import pytest_asyncio
class CustomEventLoop(asyncio.SelectorEventLoop):
pass
def pytest_asyncio_loop_factories(config, item):
return {{"custom": CustomEventLoop}}
@pytest_asyncio.fixture(
autouse=True, scope="{loop_scope}", loop_scope="{loop_scope}"
)
async def fixture_with_teardown():
yield
print("TEARDOWN_EXECUTED")
"""))
pytester.makepyfile(dedent(f"""\
import pytest
pytest_plugins = "pytest_asyncio"
@pytest.mark.asyncio(loop_scope="{loop_scope}")
async def test_passes():
assert True
"""))
result = pytester.runpytest("--asyncio-mode=strict", "-s")
result.assert_outcomes(passed=1)
result.stdout.fnmatch_lines(["*TEARDOWN_EXECUTED*"])
@pytest.mark.parametrize("loop_scope", ("module", "package", "session"))
def test_async_fixture_recreated_per_loop_factory_variant(
pytester: Pytester,
loop_scope: str,
) -> None:
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makeconftest(dedent(f"""\
import asyncio
import pytest_asyncio
class CustomLoopA(asyncio.SelectorEventLoop):
pass
class CustomLoopB(asyncio.SelectorEventLoop):
pass
def pytest_asyncio_loop_factories(config, item):
return {{"loop_a": CustomLoopA, "loop_b": CustomLoopB}}
@pytest_asyncio.fixture(scope="{loop_scope}", loop_scope="{loop_scope}")
async def fixture_loop_type():
return type(asyncio.get_running_loop()).__name__
"""))
pytester.makepyfile(dedent(f"""\
import asyncio
import pytest
pytest_plugins = "pytest_asyncio"
@pytest.mark.asyncio(loop_scope="{loop_scope}")
async def test_fixture_matches_running_loop(fixture_loop_type):
running_loop_type = type(asyncio.get_running_loop()).__name__
assert fixture_loop_type == running_loop_type
"""))
result = pytester.runpytest("--asyncio-mode=strict", "-v")
result.assert_outcomes(passed=2)
pytest-dev-pytest-asyncio-dbe96ef/tests/test_package.py 0000664 0000000 0000000 00000000142 15205265572 0023551 0 ustar 00root root 0000000 0000000 import pytest_asyncio
def test_package_exposes_version():
assert pytest_asyncio.__version__
pytest-dev-pytest-asyncio-dbe96ef/tests/test_port_factories.py 0000664 0000000 0000000 00000013764 15205265572 0025217 0 ustar 00root root 0000000 0000000 from __future__ import annotations
from textwrap import dedent
from pytest import Pytester
import pytest_asyncio.plugin
def test_unused_tcp_port_selects_unused_port(pytester: Pytester):
pytester.makepyfile(dedent("""\
import asyncio
import pytest
@pytest.mark.asyncio
async def test_unused_port_fixture(unused_tcp_port):
async def closer(_, writer):
writer.close()
server1 = await asyncio.start_server(
closer, host="localhost", port=unused_tcp_port
)
with pytest.raises(IOError):
await asyncio.start_server(
closer, host="localhost", port=unused_tcp_port
)
server1.close()
await server1.wait_closed()
"""))
def test_unused_udp_port_selects_unused_port(pytester: Pytester):
pytester.makepyfile(dedent("""\
@pytest.mark.asyncio
async def test_unused_udp_port_fixture(unused_udp_port):
class Closer:
def connection_made(self, transport):
pass
def connection_lost(self, *arg, **kwd):
pass
event_loop = asyncio.get_running_loop()
transport1, _ = await event_loop.create_datagram_endpoint(
Closer,
local_addr=("127.0.0.1", unused_udp_port),
reuse_port=False,
)
with pytest.raises(IOError):
await event_loop.create_datagram_endpoint(
Closer,
local_addr=("127.0.0.1", unused_udp_port),
reuse_port=False,
)
transport1.abort()
"""))
def test_unused_tcp_port_factory_selects_unused_port(pytester: Pytester):
pytester.makepyfile(dedent("""\
@pytest.mark.asyncio
async def test_unused_port_factory_fixture(unused_tcp_port_factory):
async def closer(_, writer):
writer.close()
port1, port2, port3 = (
unused_tcp_port_factory(),
unused_tcp_port_factory(),
unused_tcp_port_factory(),
)
server1 = await asyncio.start_server(
closer, host="localhost", port=port1
)
server2 = await asyncio.start_server(
closer, host="localhost", port=port2
)
server3 = await asyncio.start_server(
closer, host="localhost", port=port3
)
for port in port1, port2, port3:
with pytest.raises(IOError):
await asyncio.start_server(closer, host="localhost", port=port)
server1.close()
await server1.wait_closed()
server2.close()
await server2.wait_closed()
server3.close()
await server3.wait_closed()
"""))
def test_unused_udp_port_factory_selects_unused_port(pytester: Pytester):
pytester.makepyfile(dedent("""\
@pytest.mark.asyncio
async def test_unused_udp_port_factory_fixture(unused_udp_port_factory):
class Closer:
def connection_made(self, transport):
pass
def connection_lost(self, *arg, **kwd):
pass
port1, port2, port3 = (
unused_udp_port_factory(),
unused_udp_port_factory(),
unused_udp_port_factory(),
)
event_loop = asyncio.get_running_loop()
transport1, _ = await event_loop.create_datagram_endpoint(
Closer,
local_addr=("127.0.0.1", port1),
reuse_port=False,
)
transport2, _ = await event_loop.create_datagram_endpoint(
Closer,
local_addr=("127.0.0.1", port2),
reuse_port=False,
)
transport3, _ = await event_loop.create_datagram_endpoint(
Closer,
local_addr=("127.0.0.1", port3),
reuse_port=False,
)
for port in port1, port2, port3:
with pytest.raises(IOError):
await event_loop.create_datagram_endpoint(
Closer,
local_addr=("127.0.0.1", port),
reuse_port=False,
)
transport1.abort()
transport2.abort()
transport3.abort()
"""))
def test_unused_port_factory_duplicate(unused_tcp_port_factory, monkeypatch):
"""Test correct avoidance of duplicate ports."""
counter = 0
def mock_unused_tcp_port(_ignored):
"""Force some duplicate ports."""
nonlocal counter
counter += 1
if counter < 5:
return 10000
else:
return 10000 + counter
monkeypatch.setattr(pytest_asyncio.plugin, "_unused_port", mock_unused_tcp_port)
assert unused_tcp_port_factory() == 10000
assert unused_tcp_port_factory() > 10000
def test_unused_udp_port_factory_duplicate(unused_udp_port_factory, monkeypatch):
"""Test correct avoidance of duplicate UDP ports."""
counter = 0
def mock_unused_udp_port(_ignored):
"""Force some duplicate ports."""
nonlocal counter
counter += 1
if counter < 5:
return 10000
else:
return 10000 + counter
monkeypatch.setattr(pytest_asyncio.plugin, "_unused_port", mock_unused_udp_port)
assert unused_udp_port_factory() == 10000
assert unused_udp_port_factory() > 10000
pytest-dev-pytest-asyncio-dbe96ef/tests/test_set_event_loop.py 0000664 0000000 0000000 00000032412 15205265572 0025210 0 ustar 00root root 0000000 0000000 from __future__ import annotations
import sys
from textwrap import dedent
import pytest
from pytest import Pytester
@pytest.mark.parametrize(
"test_loop_scope",
("function", "module", "package", "session"),
)
@pytest.mark.parametrize(
"loop_breaking_action",
[
"asyncio.set_event_loop(None)",
"asyncio.run(asyncio.sleep(0))",
pytest.param(
"with asyncio.Runner(): pass",
marks=pytest.mark.skipif(
sys.version_info < (3, 11),
reason="asyncio.Runner requires Python 3.11+",
),
),
],
)
def test_set_event_loop_none(
pytester: Pytester,
test_loop_scope: str,
loop_breaking_action: str,
):
pytester.makeini(dedent(f"""\
[pytest]
asyncio_default_test_loop_scope = {test_loop_scope}
asyncio_default_fixture_loop_scope = function
"""))
pytester.makepyfile(dedent(f"""\
import asyncio
import pytest
pytest_plugins = "pytest_asyncio"
@pytest.mark.asyncio
async def test_before():
pass
def test_set_event_loop_none():
{loop_breaking_action}
@pytest.mark.asyncio
async def test_after():
pass
"""))
result = pytester.runpytest_subprocess()
result.assert_outcomes(passed=3)
@pytest.mark.parametrize(
"loop_breaking_action",
[
"asyncio.set_event_loop(None)",
"asyncio.run(asyncio.sleep(0))",
pytest.param(
"with asyncio.Runner(): pass",
marks=pytest.mark.skipif(
sys.version_info < (3, 11),
reason="asyncio.Runner requires Python 3.11+",
),
),
],
)
def test_set_event_loop_none_class(pytester: Pytester, loop_breaking_action: str):
pytester.makeini(dedent("""\
[pytest]
asyncio_default_test_loop_scope = class
asyncio_default_fixture_loop_scope = function
"""))
pytester.makepyfile(dedent(f"""\
import asyncio
import pytest
pytest_plugins = "pytest_asyncio"
class TestClass:
@pytest.mark.asyncio
async def test_before(self):
pass
def test_set_event_loop_none(self):
{loop_breaking_action}
@pytest.mark.asyncio
async def test_after(self):
pass
"""))
result = pytester.runpytest_subprocess()
result.assert_outcomes(passed=3)
def test_asyncio_run_after_async_fixture_does_not_leak_loop(
pytester: Pytester,
):
pytester.makeini(dedent("""\
[pytest]
asyncio_default_fixture_loop_scope = function
"""))
pytester.makepyfile(dedent("""\
import asyncio
import gc
import pytest
import pytest_asyncio
pytest_plugins = "pytest_asyncio"
@pytest_asyncio.fixture
async def async_fixture():
yield
@pytest.mark.asyncio
async def test_async_function_uses_async_fixture(async_fixture):
pass
def test_collect_unclosed_loops():
async def amain():
pass
asyncio.run(amain())
gc.collect()
"""))
result = pytester.runpytest_subprocess("-W", "error")
result.assert_outcomes(passed=2)
@pytest.mark.parametrize("test_loop_scope", ("module", "package", "session"))
@pytest.mark.parametrize(
"loop_breaking_action",
[
"asyncio.set_event_loop(None)",
"asyncio.run(asyncio.sleep(0))",
pytest.param(
"with asyncio.Runner(): pass",
marks=pytest.mark.skipif(
sys.version_info < (3, 11),
reason="asyncio.Runner requires Python 3.11+",
),
),
],
)
def test_original_shared_loop_is_reinstated_not_fresh_loop(
pytester: Pytester,
test_loop_scope: str,
loop_breaking_action: str,
):
pytester.makeini(dedent(f"""\
[pytest]
asyncio_default_test_loop_scope = {test_loop_scope}
asyncio_default_fixture_loop_scope = function
"""))
pytester.makepyfile(dedent(f"""\
import asyncio
import pytest
pytest_plugins = "pytest_asyncio"
original_shared_loop: asyncio.AbstractEventLoop = None
@pytest.mark.asyncio
async def test_store_original_shared_loop():
global original_shared_loop
original_shared_loop = asyncio.get_running_loop()
original_shared_loop._custom_marker = "original_loop_marker"
def test_unset_event_loop():
{loop_breaking_action}
@pytest.mark.asyncio
async def test_verify_original_loop_reinstated():
global original_shared_loop
current_loop = asyncio.get_running_loop()
assert current_loop is original_shared_loop
assert hasattr(current_loop, '_custom_marker')
assert current_loop._custom_marker == "original_loop_marker"
"""))
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=3)
@pytest.mark.parametrize("test_loop_scope", ("module", "package", "session"))
@pytest.mark.parametrize(
"loop_breaking_action",
[
"asyncio.set_event_loop(None)",
"asyncio.run(asyncio.sleep(0))",
pytest.param(
"with asyncio.Runner(): pass",
marks=pytest.mark.skipif(
sys.version_info < (3, 11),
reason="asyncio.Runner requires Python 3.11+",
),
),
],
)
def test_shared_loop_with_fixture_preservation(
pytester: Pytester,
test_loop_scope: str,
loop_breaking_action: str,
):
pytester.makeini(dedent(f"""\
[pytest]
asyncio_default_test_loop_scope = {test_loop_scope}
asyncio_default_fixture_loop_scope = {test_loop_scope}
"""))
pytester.makepyfile(dedent(f"""\
import asyncio
import pytest
import pytest_asyncio
pytest_plugins = "pytest_asyncio"
fixture_loop: asyncio.AbstractEventLoop = None
long_running_task = None
@pytest_asyncio.fixture
async def webserver():
global fixture_loop, long_running_task
fixture_loop = asyncio.get_running_loop()
async def background_task():
while True:
await asyncio.sleep(1)
long_running_task = asyncio.create_task(background_task())
yield
long_running_task.cancel()
@pytest.mark.asyncio
async def test_before(webserver):
global fixture_loop, long_running_task
assert asyncio.get_running_loop() is fixture_loop
assert not long_running_task.done()
def test_set_event_loop_none():
{loop_breaking_action}
@pytest.mark.asyncio
async def test_after(webserver):
global fixture_loop, long_running_task
current_loop = asyncio.get_running_loop()
assert current_loop is fixture_loop
assert not long_running_task.done()
"""))
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=3)
@pytest.mark.parametrize(
"first_scope,second_scope",
[
("module", "session"),
("session", "module"),
("package", "session"),
("session", "package"),
("package", "module"),
("module", "package"),
],
)
@pytest.mark.parametrize(
"loop_breaking_action",
[
"asyncio.set_event_loop(None)",
"asyncio.run(asyncio.sleep(0))",
pytest.param(
"with asyncio.Runner(): pass",
marks=pytest.mark.skipif(
sys.version_info < (3, 11),
reason="asyncio.Runner requires Python 3.11+",
),
),
],
)
def test_shared_loop_with_multiple_fixtures_preservation(
pytester: Pytester,
first_scope: str,
second_scope: str,
loop_breaking_action: str,
):
pytester.makeini(dedent("""\
[pytest]
asyncio_default_test_loop_scope = session
asyncio_default_fixture_loop_scope = session
"""))
pytester.makepyfile(dedent(f"""\
import asyncio
import pytest
import pytest_asyncio
pytest_plugins = "pytest_asyncio"
first_fixture_loop: asyncio.AbstractEventLoop = None
second_fixture_loop: asyncio.AbstractEventLoop = None
first_long_running_task = None
second_long_running_task = None
@pytest_asyncio.fixture(scope="{first_scope}", loop_scope="{first_scope}")
async def first_webserver():
global first_fixture_loop, first_long_running_task
first_fixture_loop = asyncio.get_running_loop()
async def background_task():
while True:
await asyncio.sleep(0.1)
first_long_running_task = asyncio.create_task(background_task())
yield
first_long_running_task.cancel()
@pytest_asyncio.fixture(scope="{second_scope}", loop_scope="{second_scope}")
async def second_webserver():
global second_fixture_loop, second_long_running_task
second_fixture_loop = asyncio.get_running_loop()
async def background_task():
while True:
await asyncio.sleep(0.1)
second_long_running_task = asyncio.create_task(background_task())
yield
second_long_running_task.cancel()
@pytest.mark.asyncio(loop_scope="{first_scope}")
async def test_before_first(first_webserver):
global first_fixture_loop, first_long_running_task
assert asyncio.get_running_loop() is first_fixture_loop
assert not first_long_running_task.done()
@pytest.mark.asyncio(loop_scope="{second_scope}")
async def test_before_second(second_webserver):
global second_fixture_loop, second_long_running_task
assert asyncio.get_running_loop() is second_fixture_loop
assert not second_long_running_task.done()
def test_set_event_loop_none():
{loop_breaking_action}
@pytest.mark.asyncio(loop_scope="{first_scope}")
async def test_after_first(first_webserver):
global first_fixture_loop, first_long_running_task
current_loop = asyncio.get_running_loop()
assert current_loop is first_fixture_loop
assert not first_long_running_task.done()
@pytest.mark.asyncio(loop_scope="{second_scope}")
async def test_after_second(second_webserver):
global second_fixture_loop, second_long_running_task
current_loop = asyncio.get_running_loop()
assert current_loop is second_fixture_loop
assert not second_long_running_task.done()
"""))
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=5)
@pytest.mark.parametrize("test_loop_scope", ("module", "package", "session"))
@pytest.mark.parametrize(
"loop_breaking_action",
[
"asyncio.set_event_loop(None)",
"asyncio.run(asyncio.sleep(0))",
pytest.param(
"with asyncio.Runner(): pass",
marks=pytest.mark.skipif(
sys.version_info < (3, 11),
reason="asyncio.Runner requires Python 3.11+",
),
),
],
)
def test_sync_fixture_sees_correct_loop_after_loop_broken_with_factory(
pytester: Pytester,
test_loop_scope: str,
loop_breaking_action: str,
):
pytester.makeini(dedent(f"""\
[pytest]
asyncio_default_test_loop_scope = {test_loop_scope}
asyncio_default_fixture_loop_scope = function
"""))
pytester.makepyfile(dedent(f"""\
import asyncio
import pytest
import pytest_asyncio
pytest_plugins = "pytest_asyncio"
class CustomEventLoop(asyncio.SelectorEventLoop):
pass
def pytest_asyncio_loop_factories(config, item):
return {{"custom": CustomEventLoop}}
@pytest.mark.asyncio
async def test_before():
pass
def test_break_event_loop():
{loop_breaking_action}
@pytest_asyncio.fixture(loop_scope="{test_loop_scope}")
def sync_fixture_loop_id():
return id(asyncio.get_event_loop())
@pytest.mark.asyncio
async def test_sync_fixture_sees_correct_loop(sync_fixture_loop_id):
assert sync_fixture_loop_id == id(asyncio.get_running_loop())
"""))
result = pytester.runpytest_subprocess()
result.assert_outcomes(passed=3)
pytest-dev-pytest-asyncio-dbe96ef/tests/test_simple.py 0000664 0000000 0000000 00000005747 15205265572 0023467 0 ustar 00root root 0000000 0000000 """Quick'n'dirty unit tests for provided fixtures and markers."""
from __future__ import annotations
import asyncio
from textwrap import dedent
import pytest
from pytest import Pytester
async def async_coro():
await asyncio.sleep(0)
return "ok"
@pytest.mark.asyncio
async def test_asyncio_marker():
"""Test the asyncio pytest marker."""
await asyncio.sleep(0)
def test_asyncio_marker_compatibility_with_xfail(pytester: Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
import pytest
pytest_plugins = "pytest_asyncio"
@pytest.mark.xfail(reason="need a failure", strict=True)
@pytest.mark.asyncio
async def test_asyncio_marker_fail():
raise AssertionError
"""))
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(xfailed=1)
def test_asyncio_auto_mode_compatibility_with_xfail(pytester: Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
import pytest
pytest_plugins = "pytest_asyncio"
@pytest.mark.xfail(reason="need a failure", strict=True)
async def test_asyncio_marker_fail():
raise AssertionError
"""))
result = pytester.runpytest("--asyncio-mode=auto")
result.assert_outcomes(xfailed=1)
@pytest.mark.asyncio
async def test_asyncio_marker_with_default_param(a_param=None):
"""Test the asyncio pytest marker."""
await asyncio.sleep(0)
class TestMarkerInClassBasedTests:
"""Test that asyncio marked functions work for methods of test classes."""
@pytest.mark.asyncio
async def test_asyncio_marker_with_implicit_loop_fixture(self):
"""
Test the "asyncio" marker works on a method in
a class-based test with implicit loop fixture.
"""
ret = await async_coro()
assert ret == "ok"
class TestEventLoopStartedBeforeFixtures:
@pytest.fixture
async def loop(self):
return asyncio.get_event_loop()
@staticmethod
def foo():
return 1
@pytest.mark.asyncio
async def test_no_event_loop(self, loop):
assert await loop.run_in_executor(None, self.foo) == 1
@pytest.mark.asyncio
async def test_event_loop_after_fixture(self, loop):
assert await loop.run_in_executor(None, self.foo) == 1
@pytest.mark.asyncio
async def test_event_loop_before_fixture(self, loop):
assert await loop.run_in_executor(None, self.foo) == 1
def test_invalid_asyncio_mode(pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
result = pytester.runpytest("-o", "asyncio_mode=True")
result.stderr.no_fnmatch_line("INTERNALERROR> *")
result.stderr.fnmatch_lines(
"ERROR: 'True' is not a valid asyncio_mode. Valid modes: auto, strict."
)
pytest-dev-pytest-asyncio-dbe96ef/tests/test_skips.py 0000664 0000000 0000000 00000007232 15205265572 0023316 0 ustar 00root root 0000000 0000000 from __future__ import annotations
from textwrap import dedent
from pytest import Pytester
def test_asyncio_strict_mode_skip(pytester: Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
import pytest
pytest_plugins = "pytest_asyncio"
@pytest.mark.asyncio
async def test_no_warning_on_skip():
pytest.skip("Test a skip error inside asyncio")
"""))
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(skipped=1)
def test_asyncio_auto_mode_skip(pytester: Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
import pytest
pytest_plugins = "pytest_asyncio"
async def test_no_warning_on_skip():
pytest.skip("Test a skip error inside asyncio")
"""))
result = pytester.runpytest("--asyncio-mode=auto")
result.assert_outcomes(skipped=1)
def test_asyncio_strict_mode_module_level_skip(pytester: Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
import pytest
pytest.skip("Skip all tests", allow_module_level=True)
@pytest.mark.asyncio
async def test_is_skipped():
pass
"""))
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(skipped=1)
def test_asyncio_auto_mode_module_level_skip(pytester: Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
import pytest
pytest.skip("Skip all tests", allow_module_level=True)
async def test_is_skipped():
pass
"""))
result = pytester.runpytest("--asyncio-mode=auto")
result.assert_outcomes(skipped=1)
def test_asyncio_auto_mode_wrong_skip_usage(pytester: Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
import pytest
pytest.skip("Skip all tests")
async def test_is_skipped():
pass
"""))
result = pytester.runpytest("--asyncio-mode=auto")
result.assert_outcomes(errors=1)
def test_unittest_skiptest_compatibility(pytester: Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
from unittest import SkipTest
raise SkipTest("Skip all tests")
async def test_is_skipped():
pass
"""))
result = pytester.runpytest("--asyncio-mode=auto")
result.assert_outcomes(skipped=1)
def test_skip_in_module_does_not_skip_package(pytester: Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(
__init__="",
test_skip=dedent("""\
import pytest
pytest.skip("Skip all tests", allow_module_level=True)
def test_a():
pass
def test_b():
pass
"""),
test_something=dedent("""\
import pytest
@pytest.mark.asyncio
async def test_something():
pass
"""),
)
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=1, skipped=1)
pytest-dev-pytest-asyncio-dbe96ef/tests/test_subprocess.py 0000664 0000000 0000000 00000000621 15205265572 0024350 0 ustar 00root root 0000000 0000000 """Tests for using subprocesses in tests."""
from __future__ import annotations
import asyncio.subprocess
import sys
import pytest
@pytest.mark.asyncio
async def test_subprocess():
"""Starting a subprocess should be possible."""
proc = await asyncio.subprocess.create_subprocess_exec(
sys.executable, "--version", stdout=asyncio.subprocess.PIPE
)
await proc.communicate()
pytest-dev-pytest-asyncio-dbe96ef/tests/test_task_cleanup.py 0000664 0000000 0000000 00000001433 15205265572 0024633 0 ustar 00root root 0000000 0000000 from __future__ import annotations
from textwrap import dedent
from pytest import Pytester
def test_task_is_cancelled_when_abandoned_by_test(pytester: Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
import asyncio
import pytest
@pytest.mark.asyncio
async def test_create_task():
async def coroutine():
try:
while True:
await asyncio.sleep(0)
finally:
raise RuntimeError("The task should be cancelled at this point.")
asyncio.create_task(coroutine())
"""))
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=1)
pytest-dev-pytest-asyncio-dbe96ef/tools/ 0000775 0000000 0000000 00000000000 15205265572 0020546 5 ustar 00root root 0000000 0000000 pytest-dev-pytest-asyncio-dbe96ef/tools/get-version.py 0000664 0000000 0000000 00000000655 15205265572 0023370 0 ustar 00root root 0000000 0000000 from __future__ import annotations
import json
import sys
from importlib import metadata
from packaging.version import parse as parse_version
def main():
version_string = metadata.version("pytest-asyncio")
version = parse_version(version_string)
print(f"version={version}")
prerelease = json.dumps(version.is_prerelease)
print(f"prerelease={prerelease}")
if __name__ == "__main__":
sys.exit(main())
pytest-dev-pytest-asyncio-dbe96ef/tox.ini 0000664 0000000 0000000 00000004626 15205265572 0020731 0 ustar 00root root 0000000 0000000 [tox]
minversion = 4.28.0
envlist = build, py310, py311, py312, py313, py314, pytest-min, docs, pyright
isolated_build = true
passenv =
CI
[pkgenv]
constraints = constraints.txt
[testenv]
package = external
extras = testing
constraints = constraints.txt
commands = make test
allowlist_externals =
make
[testenv:.pkg_external]
deps = build
package_glob = {toxinidir}{/}dist{/}*.whl
commands =
python -c 'import shutil; shutil.rmtree("{toxinidir}{/}dist", ignore_errors=True)'
pyproject-build --outdir {toxinidir}{/}dist .
[testenv:build]
description = Check distribution files
deps =
check-wheel-contents
twine
commands =
check-wheel-contents {toxinidir}{/}dist
twine check {toxinidir}{/}dist{/}*
[testenv:pytest-min]
extras = testing
constraints = dependencies/pytest-min/constraints.txt
deps = -r dependencies/pytest-min/requirements.txt
commands = make test
allowlist_externals =
make
[testenv:pytest-dev]
description = Run tests against pytest main
constraints =
deps =
pytest @ git+https://github.com/pytest-dev/pytest.git
[testenv:docs]
allowlist_externals =
git
extras = docs
change_dir = docs
description = Build The Docs with {basepython}
commands =
# Retrieve possibly missing commits:
-git fetch --unshallow
-git fetch --tags
# Build the html docs with Sphinx:
{envpython} -Im sphinx \
-j auto \
{tty:--color} \
-a \
-T \
-n \
-W --keep-going \
-d "{temp_dir}{/}.doctrees" \
. \
{posargs:"{envdir}{/}docs_out" -b html}
# Print out the output docs dir and a way to serve html:
-{envpython} -c\
'import pathlib;\
docs_dir = pathlib.Path(r"{envdir}") / "docs_out";\
index_file = docs_dir / "index.html";\
print("\n" + "=" * 120 +\
f"\n\nOpen the documentation with:\n\n\
\t$ python3 -Im webbrowser \N\{QUOTATION MARK\}file://\{index_file\}\N\{QUOTATION MARK\}\n\n\
To serve docs, use\n\n\
\t$ python3 -Im http.server --directory \
\N\{QUOTATION MARK\}\{docs_dir\}\N\{QUOTATION MARK\} 0\n\n" +\
"=" * 120)'
changedir = {toxinidir}{/}docs
isolated_build = true
passenv =
SSH_AUTH_SOCK
skip_install = false
[testenv:pyright]
deps =
pyright[nodejs]
pytest
commands = pyright pytest_asyncio/
skip_install = true
[gh-actions]
python =
3.10: py310, pytest-min, build
3.11: py311
3.12: py312
3.13: py313, pyright
3.14-dev: py314
pypy3: pypy3