pax_global_header 0000666 0000000 0000000 00000000064 15076637504 0014527 g ustar 00root root 0000000 0000000 52 comment=3b0649bab359708a396eeb7ce190ba17b57b172a
pytest-codspeed-4.2.0/ 0000775 0000000 0000000 00000000000 15076637504 0014646 5 ustar 00root root 0000000 0000000 pytest-codspeed-4.2.0/.github/ 0000775 0000000 0000000 00000000000 15076637504 0016206 5 ustar 00root root 0000000 0000000 pytest-codspeed-4.2.0/.github/workflows/ 0000775 0000000 0000000 00000000000 15076637504 0020243 5 ustar 00root root 0000000 0000000 pytest-codspeed-4.2.0/.github/workflows/ci.yml 0000664 0000000 0000000 00000004425 15076637504 0021366 0 ustar 00root root 0000000 0000000 name: CI
on:
push:
branches: [master]
pull_request:
branches: [master]
workflow_dispatch:
concurrency:
group: ${{ github.ref }}
cancel-in-progress: true
jobs:
static-analysis:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: true
- name: Set up Python 3.14
uses: actions/setup-python@v5
with:
python-version: "3.14"
- uses: pre-commit/action@v3.0.1
with:
extra_args: --all-files
tests:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
config:
- headless
- pytest-benchmark-4
- pytest-benchmark-5
- valgrind
python-version:
- "3.9"
- "3.10"
- "3.11"
- "3.12"
- "3.13"
- "3.14"
pytest-version:
- ">=8.1.1"
steps:
- uses: actions/checkout@v4
with:
submodules: true
- uses: astral-sh/setup-uv@v4
with:
version: "0.5.20"
- name: "Set up Python ${{ matrix.python-version }}"
uses: actions/setup-python@v5
with:
python-version: "${{ matrix.python-version }}"
- if: matrix.config == 'valgrind' || matrix.config == 'pytest-benchmark'
name: Install valgrind
run: |
sudo apt-get update
sudo apt-get install valgrind -y
- name: Install dependencies with pytest${{ matrix.pytest-version }}
run: |
if [ "${{ matrix.config }}" == "valgrind" ]; then
export PYTEST_CODSPEED_FORCE_EXTENSION_BUILD=1
fi
uv sync --all-extras --dev --locked --verbose
uv pip install "pytest${{ matrix.pytest-version }}"
uv pip uninstall pytest-benchmark
- if: matrix.config == 'pytest-benchmark-4'
name: Install pytest-benchmark 4.0.0
run: uv pip install pytest-benchmark~=4.0.0
- if: matrix.config == 'pytest-benchmark-5'
name: Install pytest-benchmark 5.0.0
run: uv pip install pytest-benchmark~=5.0.0
- name: Run tests
run: uv run --no-sync pytest -vs
all-checks:
runs-on: ubuntu-latest
steps:
- run: echo "All CI checks passed."
needs:
- static-analysis
- tests
pytest-codspeed-4.2.0/.github/workflows/codspeed.yml 0000664 0000000 0000000 00000002516 15076637504 0022560 0 ustar 00root root 0000000 0000000 name: CodSpeed
on:
push:
branches: [master]
pull_request:
branches: [master]
workflow_dispatch:
env:
PYTHON_VERSION: "3.14"
SHARDS: 4
jobs:
benchmarks:
strategy:
matrix:
shard: [1, 2, 3, 4]
mode: ["instrumentation", "walltime"]
name: "Run ${{ matrix.mode }} benchmarks (Shard #${{ matrix.shard }})"
runs-on: ${{ matrix.mode == 'instrumentation' && 'ubuntu-24.04' || 'codspeed-macro' }}
steps:
- uses: actions/checkout@v5
with:
submodules: "recursive"
- name: Install required-version defined in uv.toml
uses: astral-sh/setup-uv@v7
- uses: actions/setup-python@v6
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Install local version of pytest-codspeed
run: |
sudo apt-get update
sudo apt-get install valgrind -y
uv sync --dev
sudo apt-get remove valgrind -y
- name: Run benchmarks
uses: CodSpeedHQ/action@main
with:
mode: ${{ matrix.mode }}
run: uv run pytest tests/benchmarks/ --codspeed --test-group=${{ matrix.shard }} --test-group-count=${{ env.SHARDS }}
token: ${{ secrets.CODSPEED_TOKEN }}
all-checks:
runs-on: ubuntu-latest
steps:
- run: echo "All CI checks passed."
needs:
- benchmarks
pytest-codspeed-4.2.0/.github/workflows/release.yml 0000664 0000000 0000000 00000005426 15076637504 0022415 0 ustar 00root root 0000000 0000000 name: Release on tag
on:
push:
tags:
- "v*"
workflow_dispatch:
permissions:
id-token: write
contents: write
jobs:
build-wheels:
strategy:
matrix:
platform:
- runs-on: ubuntu-24.04
arch: x86_64
- runs-on: buildjet-8vcpu-ubuntu-2204-arm
arch: aarch64
runs-on: ${{ matrix.platform.runs-on }}
steps:
- uses: actions/checkout@v4
with:
submodules: true
- name: Build wheels
uses: pypa/cibuildwheel@v3.2.1
env:
CIBW_ARCHS: ${{ matrix.platform.arch }}
with:
output-dir: wheelhouse
- uses: actions/upload-artifact@v4
with:
name: wheels-${{ matrix.platform.arch }}
path: wheelhouse/*.whl
build-py3-none-any:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
with:
submodules: true
- uses: astral-sh/setup-uv@v4
with:
version: "0.5.20"
- uses: actions/setup-python@v2
with:
python-version: "3.14"
- name: Build py3-none-any wheel
env:
PYTEST_CODSPEED_SKIP_EXTENSION_BUILD: "1"
run: uv build --wheel --out-dir dist/
- uses: actions/upload-artifact@v4
with:
name: wheels-py3-none-any
path: dist/*.whl
build-sdist:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
with:
submodules: true
- uses: astral-sh/setup-uv@v4
with:
version: "0.5.20"
- uses: actions/setup-python@v2
with:
python-version: "3.14"
- name: Build the source dist
run: uv build --sdist --out-dir dist/
- uses: actions/upload-artifact@v4
with:
name: sdist
path: dist/*.tar.gz
publish:
needs:
- build-wheels
- build-py3-none-any
- build-sdist
runs-on: ubuntu-24.04
steps:
- uses: actions/download-artifact@v4
with:
merge-multiple: true
path: dist/
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v4
with:
version: "0.5.20"
- uses: actions/setup-python@v2
with:
python-version: "3.14"
- uses: actions/download-artifact@v4
with:
merge-multiple: true
path: dist/
- name: List artifacts
run: ls -al dist/*
- if: github.event_name == 'push'
name: Publish to PyPI
run: uv publish --trusted-publishing=always dist/*
- if: github.event_name == 'push'
name: Create a draft release
run: |
VERSION="${{ github.ref_name }}"
gh release create $VERSION --title $VERSION --generate-notes -d
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
pytest-codspeed-4.2.0/.gitignore 0000664 0000000 0000000 00000006072 15076637504 0016643 0 ustar 00root root 0000000 0000000 # Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# 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/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/#use-with-ide
.pdm.toml
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
.venv.*
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
.venvubuntu
.python-version
*.o
.codspeed
pytest-codspeed-4.2.0/.gitmodules 0000664 0000000 0000000 00000000475 15076637504 0017031 0 ustar 00root root 0000000 0000000 [submodule "tests/benchmarks/TheAlgorithms"]
path = tests/benchmarks/TheAlgorithms
url = git@github.com:TheAlgorithms/Python.git
[submodule "src/pytest_codspeed/instruments/hooks/instrument-hooks"]
path = src/pytest_codspeed/instruments/hooks/instrument-hooks
url = https://github.com/CodSpeedHQ/instrument-hooks
pytest-codspeed-4.2.0/.pre-commit-config.yaml 0000664 0000000 0000000 00000001070 15076637504 0021125 0 ustar 00root root 0000000 0000000 # See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v2.4.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.18.2
hooks:
- id: mypy
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.11.12
hooks:
- id: ruff-check
args: [--fix]
- id: ruff-format
pytest-codspeed-4.2.0/.vscode/ 0000775 0000000 0000000 00000000000 15076637504 0016207 5 ustar 00root root 0000000 0000000 pytest-codspeed-4.2.0/.vscode/launch.json 0000664 0000000 0000000 00000000411 15076637504 0020350 0 ustar 00root root 0000000 0000000 {
"version": "0.2.0",
"configurations": [
{
"name": "Debug walltime benchmarks",
"type": "debugpy",
"request": "launch",
"module": "pytest",
"args": ["--codspeed", "--codspeed-mode", "walltime", "tests/benchmarks"]
}
]
}
pytest-codspeed-4.2.0/.vscode/settings.json 0000664 0000000 0000000 00000000200 15076637504 0020732 0 ustar 00root root 0000000 0000000 {
"python.testing.pytestArgs": ["tests"],
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true
}
pytest-codspeed-4.2.0/CHANGELOG.md 0000664 0000000 0000000 00000035377 15076637504 0016476 0 ustar 00root root 0000000 0000000 # Changelog
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [4.2.0] - 2025-10-24
### π Features
- Support python 3.14 by @adriencaccia in [#91](https://github.com/CodSpeedHQ/pytest-codspeed/pull/91)
### π Bug Fixes
- Throw error when instrument hooks failed to initialize under codspeed env by @adriencaccia
### βοΈ Internals
- Bump mypy by @adriencaccia
- Bump python to 3.14 in the ci by @adriencaccia
## [4.1.1] - 2025-10-07
### π Bug Fixes
- Wheel installation due to file system loop
- Use ParamSpec in instrument definition instead of tuple and dict by @art049
- Use ParamSpec in the plugin call definition by @flying-sheep
### π Documentation
- Add pull_request trigger comment by @adriencaccia
### βοΈ Internals
- Remove the beta versions from the changelog by @art049
- Exclude beta tags in changelog by @art049
- Bump instrument-hooks (#87) by @not-matthias in [#87](https://github.com/CodSpeedHQ/pytest-codspeed/pull/87)
- Switch to sharded upload (#75) by @art049 in [#75](https://github.com/CodSpeedHQ/pytest-codspeed/pull/75)
- Fix changelog generation by @art049
## [4.1.0] - 2025-10-06
### π Bug Fixes
- Use ParamSpec in instrument definition instead of tuple and dict by @art049
- Use ParamSpec in the plugin call definition by @flying-sheep
### π Documentation
- Add pull_request trigger comment by @adriencaccia
### βοΈ Internals
- Exclude beta tags in changelog by @art049
- Bump instrument-hooks (#87) by @not-matthias in [#87](https://github.com/CodSpeedHQ/pytest-codspeed/pull/87)
- Switch to sharded upload (#75) by @art049 in [#75](https://github.com/CodSpeedHQ/pytest-codspeed/pull/75)
- Fix changelog generation by @art049
## [4.0.0] - 2025-07-10
### π Features
- Update readme by @art049
- Support pytest-benchmark's pedantic API by @art049 in [#81](https://github.com/CodSpeedHQ/pytest-codspeed/pull/81)
- Make sure the benchmark fixture can only be called once per bench by @art049
- Support marker attributes to customize the walltime execution by @art049 in [#80](https://github.com/CodSpeedHQ/pytest-codspeed/pull/80)
- Use instrument hooks by @not-matthias
- Add instrument-hooks native module by @not-matthias
### π Bug Fixes
- Reenable walltime instrument hooks by @art049 in [#82](https://github.com/CodSpeedHQ/pytest-codspeed/pull/82)
- Fix native library typing by @art049
### π§ͺ Testing
- Add benches from the documentation's getting started by @art049 in [#71](https://github.com/CodSpeedHQ/pytest-codspeed/pull/71)
- Add simple python benches by @art049
### βοΈ Internals
- Remove pre-releases from git-cliff changelog by @art049
- Link to the documentation by @art049
- Improve reliability of perf trampoline compatibility checks by @art049
- Bump ruff by @art049
- Update release workflow to include submodules by @art049 in [#79](https://github.com/CodSpeedHQ/pytest-codspeed/pull/79)
- Remove valgrind wrapper by @not-matthias
- Update apt before installing packages by @art049
## [3.2.0] - 2025-01-31
### π Features
- Increase the min round time to a bigger value (+/- 1ms) by @art049
- Add benchmarks-walltime job to run additional performance benchmarks by @art049 in [#65](https://github.com/CodSpeedHQ/pytest-codspeed/pull/65)
- Fix the random seed while measuring with instruments by @art049 in [#48](https://github.com/CodSpeedHQ/pytest-codspeed/pull/48)
### π Bug Fixes
- Use time per iteration instead of total round time in stats by @art049
### ποΈ Refactor
- Replace hardcoded outlier factor for improved readability by @art049 in [#67](https://github.com/CodSpeedHQ/pytest-codspeed/pull/67)
### βοΈ Internals
- Fix self-dependency by @adriencaccia in [#66](https://github.com/CodSpeedHQ/pytest-codspeed/pull/66)
- Fix uv version in CI by @adriencaccia
## [3.1.2] - 2025-01-09
### π Bug Fixes
- Update package_data to include header and source files for valgrind wrapper by @art049 in [#64](https://github.com/CodSpeedHQ/pytest-codspeed/pull/64)
## [3.1.1] - 2025-01-07
### βοΈ Internals
- Fix tag num with bumpver by @art049 in [#61](https://github.com/CodSpeedHQ/pytest-codspeed/pull/61)
- Update uv lock before release by @art049
- Add a py3-none-any fallback wheel by @art049
## [3.1.0] - 2024-12-09
### π Features
- Check buildability and fallback when build doesn't work by @art049
- Compile the callgrind wrapper at build time by @art049
### π Bug Fixes
- Allow build on arm64 by @art049
### ποΈ Refactor
- Remove the scripted semver generation by @art049
### βοΈ Internals
- Fix typo in cibuildwheel config by @art049 in [#57](https://github.com/CodSpeedHQ/pytest-codspeed/pull/57)
- Build wheels with cibuildwheel by @art049
- Allow forcing integrated tests by @art049
- Fix release script by @art049
- Use bumpver to manage versions by @art049
- Add a changelog by @art049
- Force native extension build in CI by @art049
- Updated matrix release workflow by @art049
- Use a common python version in the codspeed job by @art049
- Fix the codspeed workflow by @art049
- Use uv in CI by @art049
- Commit uv lock file by @art049
## [3.0.0] - 2024-10-29
### π Bug Fixes
- Fix compatibility with pytest-benchmark 5.0.0 by @art049 in [#54](https://github.com/CodSpeedHQ/pytest-codspeed/pull/54)
### βοΈ Internals
- Drop support for python3.8 by @art049
- Expose type information (#53) by @Dreamsorcerer in [#53](https://github.com/CodSpeedHQ/pytest-codspeed/pull/53)
- Run the CI with ubuntu 24.04 by @art049
- Improve naming in workflow examples by @art049
- Bump actions/checkout to v4 (#47) by @fargito in [#47](https://github.com/CodSpeedHQ/pytest-codspeed/pull/47)
## [3.0.0b4] - 2024-09-27
### π Features
- Send more outlier data by @art049
### π Bug Fixes
- Fix display of parametrized tests by @art049
- Reenable gc logic by @art049
### π§ͺ Testing
- Add benches for various syscalls by @art049
## [3.0.0b3] - 2024-09-26
### π Features
- Also save the lower and upper fences in the json data by @art049 in [#46](https://github.com/CodSpeedHQ/pytest-codspeed/pull/46)
### π§ͺ Testing
- Refactor the algorithm benches using parametrization and add benches on bit_manipulation by @art049
## [3.0.0b2] - 2024-09-24
### π Features
- Also save the q1 and q3 in the json data by @art049 in [#45](https://github.com/CodSpeedHQ/pytest-codspeed/pull/45)
- Add the --codspeed-max-time flag by @art049
## [3.0.0b1] - 2024-09-20
### π Features
- Send the semver version to cospeed instead of the PEP440 one by @art049 in [#44](https://github.com/CodSpeedHQ/pytest-codspeed/pull/44)
- Also store the semver version by @art049
### π§ͺ Testing
- Add benches for TheAlgorithms/backtracking by @art049 in [#43](https://github.com/CodSpeedHQ/pytest-codspeed/pull/43)
## [3.0.0b0] - 2024-09-18
### π Features
- Improve table style when displaying results by @art049 in [#41](https://github.com/CodSpeedHQ/pytest-codspeed/pull/41)
- Add the total bench time to the collected stats by @art049
- Add configuration and split tests between instruments by @art049
- Add outlier detection in the walltime instrument by @art049
- Implement the walltime instrument by @art049
- Add bench of various python noop by @art049
- Avoid overriding pytest's default protocol (#32) by @kenodegard in [#32](https://github.com/CodSpeedHQ/pytest-codspeed/pull/32)
### π Bug Fixes
- Use importlib_metadata to keep backward compatibility by @art049
- Properly decide the mode depending on our env variable spec by @art049
- Disable pytest-speed when installed and codspeed is enabled by @art049
### ποΈ Refactor
- Differentiate the mode from the underlying instrument by @art049
- Move the instrumentation wrapper directly in the instrument by @art049
- Change Instrumentation to CPUInstrumentation by @art049
- Create an abstraction for each instrument by @art049
### π Documentation
- Update action version in the CI workflow configuration (#39) by @frgfm in [#39](https://github.com/CodSpeedHQ/pytest-codspeed/pull/39)
- Bump action versions in README by @adriencaccia
### π§ͺ Testing
- Add benches for TheAlgorithms/audio_filters by @art049 in [#42](https://github.com/CodSpeedHQ/pytest-codspeed/pull/42)
### βοΈ Internals
- Add a test on the walltime instrument by @art049
- Fix utils test using a fake git repo by @art049
- Update readme by @art049
- Support python 3.13 and drop 3.7 by @art049 in [#40](https://github.com/CodSpeedHQ/pytest-codspeed/pull/40)
- Add TCH, FA, and UP to ruff lints (#29) by @kenodegard in [#29](https://github.com/CodSpeedHQ/pytest-codspeed/pull/29)
## [2.2.1] - 2024-03-19
### π Features
- Support pytest 8.1.1 by @art049
### π Bug Fixes
- Loosen runtime requirements (#21) by @edgarrmondragon in [#21](https://github.com/CodSpeedHQ/pytest-codspeed/pull/21)
### βοΈ Internals
- Add all-checks job to CI workflow by @art049 in [#28](https://github.com/CodSpeedHQ/pytest-codspeed/pull/28)
- Switch from black to ruff format by @art049
- Update action version in README.md by @adriencaccia
- Add codspeed badge to the readme by @art049
## [2.2.0] - 2023-09-01
### π Features
- Avoid concurrent wrapper builds by @art049
- Add a test for pytest-xdist compatibility by @art049
### π Bug Fixes
- Fix xdist test output assertion by @art049
## [2.1.0] - 2023-07-27
### π Bug Fixes
- Fix relative git path when using working-directory by @art049 in [#15](https://github.com/CodSpeedHQ/pytest-codspeed/pull/15)
- Fix typo in release.yml (#14) by @art049 in [#14](https://github.com/CodSpeedHQ/pytest-codspeed/pull/14)
## [2.0.1] - 2023-07-22
### π Features
- Release the package from the CI with trusted provider by @art049
- Add a return type to the benchmark fixture by @art049 in [#13](https://github.com/CodSpeedHQ/pytest-codspeed/pull/13)
- Add support for returning values (#12) by @patrick91 in [#12](https://github.com/CodSpeedHQ/pytest-codspeed/pull/12)
### π Bug Fixes
- Fix setuptools installation with python3.12 by @art049
## [2.0.0] - 2023-07-04
### π Features
- Warmup performance map generation by @art049
- Add some details about the callgraph generation status in the header by @art049
- Test that perf maps are generated by @art049
- Add a local test matrix with hatch by @art049
- Test that benchmark selection with -k works by @art049
- Add support for CPython3.12 and perf trampoline by @art049
- Add introspection benchmarks by @art049 in [#9](https://github.com/CodSpeedHQ/pytest-codspeed/pull/9)
### π Bug Fixes
- Support benchmark.extra_info parameters on the fixture by @art049 in [#10](https://github.com/CodSpeedHQ/pytest-codspeed/pull/10)
- Filter out pytest-benchmark warnings in the tests by @art049
### ποΈ Refactor
- Use the pytest_run_protocol hook for better exec control by @art049
### βοΈ Internals
- Separate the benchmark workflow by @art049 in [#8](https://github.com/CodSpeedHQ/pytest-codspeed/pull/8)
- Bump version to 1.3.0 to trigger the callgraph generation by @art049
- Reuse same test code in the tests by @art049
- Bump linting dependencies by @art049
- Bump precommit in the CI by @art049
- Add python3.12 to the ci matrix by @art049
- Restructure dev dependencies by @art049
- Replace isort by ruff by @art049 in [#11](https://github.com/CodSpeedHQ/pytest-codspeed/pull/11)
- Add discord badge in the readme by @art049
## [1.2.2] - 2022-12-02
### π Features
- Add library metadata in the profile output by @art049 in [#5](https://github.com/CodSpeedHQ/pytest-codspeed/pull/5)
## [1.2.1] - 2022-11-28
### π Bug Fixes
- Support kwargs with the benchmark fixture by @art049 in [#4](https://github.com/CodSpeedHQ/pytest-codspeed/pull/4)
## [1.2.0] - 2022-11-22
### π Bug Fixes
- Avoid wrapping the callable to maintain existing results by @art049
- Disable automatic garbage collection to increase stability by @art049 in [#2](https://github.com/CodSpeedHQ/pytest-codspeed/pull/2)
- Update readme by @art049
### βοΈ Internals
- Update readme by @art049
## [1.1.0] - 2022-11-10
### π Features
- Allow running along with pytest-benchmarks by @art049
### π Bug Fixes
- Fix the release script by @art049
- Make the release script executable by @art049
- Match the test output in any order by @art049
### ποΈ Refactor
- Manage compatibility env in the conftest by @art049
### βοΈ Internals
- Add a pytest-benchmark compatibility test by @art049 in [#1](https://github.com/CodSpeedHQ/pytest-codspeed/pull/1)
- Add more details on the pytest run by @art049
- Continue running on matrix item error by @art049
- Add a CI configuration with pytest-benchmark installed by @art049
## [1.0.1] - 2022-11-05
[4.1.0]: https://github.com/CodSpeedHQ/pytest-codspeed/compare/v4.0.0..v4.1.0
[4.0.0]: https://github.com/CodSpeedHQ/pytest-codspeed/compare/v3.2.0..v4.0.0
[3.2.0]: https://github.com/CodSpeedHQ/pytest-codspeed/compare/v3.1.2..v3.2.0
[3.1.2]: https://github.com/CodSpeedHQ/pytest-codspeed/compare/v3.1.1..v3.1.2
[3.1.1]: https://github.com/CodSpeedHQ/pytest-codspeed/compare/v3.1.0..v3.1.1
[3.1.0]: https://github.com/CodSpeedHQ/pytest-codspeed/compare/v3.0.0..v3.1.0
[3.0.0]: https://github.com/CodSpeedHQ/pytest-codspeed/compare/v3.0.0b4..v3.0.0
[3.0.0b4]: https://github.com/CodSpeedHQ/pytest-codspeed/compare/v3.0.0b3..v3.0.0b4
[3.0.0b3]: https://github.com/CodSpeedHQ/pytest-codspeed/compare/v3.0.0b2..v3.0.0b3
[3.0.0b2]: https://github.com/CodSpeedHQ/pytest-codspeed/compare/v3.0.0b1..v3.0.0b2
[3.0.0b1]: https://github.com/CodSpeedHQ/pytest-codspeed/compare/v3.0.0b0..v3.0.0b1
[3.0.0b0]: https://github.com/CodSpeedHQ/pytest-codspeed/compare/v2.2.1..v3.0.0b0
[2.2.1]: https://github.com/CodSpeedHQ/pytest-codspeed/compare/v2.2.0..v2.2.1
[2.2.0]: https://github.com/CodSpeedHQ/pytest-codspeed/compare/v2.1.0..v2.2.0
[2.1.0]: https://github.com/CodSpeedHQ/pytest-codspeed/compare/v2.0.1..v2.1.0
[2.0.1]: https://github.com/CodSpeedHQ/pytest-codspeed/compare/v2.0.0..v2.0.1
[2.0.0]: https://github.com/CodSpeedHQ/pytest-codspeed/compare/v1.2.2..v2.0.0
[1.2.2]: https://github.com/CodSpeedHQ/pytest-codspeed/compare/v1.2.1..v1.2.2
[1.2.1]: https://github.com/CodSpeedHQ/pytest-codspeed/compare/v1.2.0..v1.2.1
[1.2.0]: https://github.com/CodSpeedHQ/pytest-codspeed/compare/v1.1.0..v1.2.0
[1.1.0]: https://github.com/CodSpeedHQ/pytest-codspeed/compare/v1.0.4..v1.1.0
pytest-codspeed-4.2.0/LICENSE 0000664 0000000 0000000 00000002104 15076637504 0015650 0 ustar 00root root 0000000 0000000 The MIT License (MIT)
Copyright (c) 2022 CodSpeed and contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
pytest-codspeed-4.2.0/README.md 0000664 0000000 0000000 00000010510 15076637504 0016122 0 ustar 00root root 0000000 0000000
pytest-codspeed
[](https://github.com/CodSpeedHQ/pytest-codspeed/actions/workflows/ci.yml)
[](https://pypi.org/project/pytest-codspeed)

[](https://discord.com/invite/MxpaCfKSqF)
[](https://codspeed.io/CodSpeedHQ/pytest-codspeed)
Pytest plugin to create CodSpeed benchmarks
---
**Documentation**: https://codspeed.io/docs/reference/pytest-codspeed
---
## Installation
```shell
pip install pytest-codspeed
```
## Usage
### Creating benchmarks
In a nutshell, `pytest-codspeed` offers two approaches to create performance benchmarks that integrate seamlessly with your existing test suite.
Use `@pytest.mark.benchmark` to measure entire test functions automatically:
```python
import pytest
from statistics import median
@pytest.mark.benchmark
def test_median_performance():
input = [1, 2, 3, 4, 5]
output = sum(i**2 for i in input)
assert output == 55
```
Since this measure the entire function, you might want to use the `benchmark` fixture for precise control over what code gets measured:
```python
def test_mean_performance(benchmark):
data = [1, 2, 3, 4, 5]
# Only the function call is measured
result = benchmark(lambda: sum(i**2 for i in data))
assert result == 55
```
Check out the [full documentation](https://codspeed.io/docs/reference/pytest-codspeed) for more details.
### Testing the benchmarks locally
If you want to run the benchmarks tests locally, you can use the `--codspeed` pytest flag:
```sh
$ pytest tests/ --codspeed
============================= test session starts ====================
platform darwin -- Python 3.13.0, pytest-7.4.4, pluggy-1.5.0
codspeed: 3.0.0 (enabled, mode: walltime, timer_resolution: 41.7ns)
rootdir: /home/user/codspeed-test, configfile: pytest.ini
plugins: codspeed-3.0.0
collected 1 items
tests/test_sum_squares.py . [ 100%]
Benchmark Results
ββββββββββββββββββ³ββββββββββββββ³ββββββββββββββ³βββββββββββ³βββββββββ
β Benchmark β Time (best) β Rel. StdDev β Run time β Iters β
β£βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ«
βtest_sum_squaresβ 1,873ns β 4.8% β 3.00s β 66,930 β
ββββββββββββββββββ»ββββββββββββββ»ββββββββββββββ»βββββββββββ»βββββββββ
=============================== 1 benchmarked ========================
=============================== 1 passed in 4.12s ====================
```
### Running the benchmarks in your CI
You can use the [CodSpeedHQ/action](https://github.com/CodSpeedHQ/action) to run the benchmarks in Github Actions and upload the results to CodSpeed.
Here is an example of a GitHub Actions workflow that runs the benchmarks and reports the results to CodSpeed on every push to the `main` branch and every pull request:
```yaml
name: CodSpeed
on:
push:
branches:
- "main" # or "master"
pull_request: # required to have reports on PRs
# `workflow_dispatch` allows CodSpeed to trigger backtest
# performance analysis in order to generate initial data.
workflow_dispatch:
jobs:
benchmarks:
name: Run benchmarks
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.13"
- name: Install dependencies
run: pip install -r requirements.txt
- name: Run benchmarks
uses: CodSpeedHQ/action@v4
with:
mode: instrumentation # or `walltime`
token: ${{ secrets.CODSPEED_TOKEN }}
run: pytest tests/ --codspeed
```
pytest-codspeed-4.2.0/cliff.toml 0000664 0000000 0000000 00000010404 15076637504 0016625 0 ustar 00root root 0000000 0000000 # git-cliff ~ default configuration file
# https://git-cliff.org/docs/configuration
#
# Lines starting with "#" are comments.
# Configuration options are organized into tables and keys.
# See documentation for more information on available options.
[remote.github]
owner = "CodSpeedHQ"
repo = "pytest-codspeed"
[changelog]
# template for the changelog header
header = """
# Changelog\n
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
\n
"""
# template for the changelog body
# https://keats.github.io/tera/docs/#introduction
body = """
{%- macro remote_url() -%}
https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }}
{%- endmacro -%}
{% if version -%}
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
{% else -%}
## [Unreleased]
{% endif -%}
{% for group, commits in commits | group_by(attribute="group") %}
### {{ group | upper_first }}
{%- for commit in commits %}
- {{ commit.message | split(pat="\n") | first | upper_first | trim }}\
{% if commit.remote.username %} by @{{ commit.remote.username }}{%- endif -%}
{% if commit.remote.pr_number %} in \
[#{{ commit.remote.pr_number }}]({{ self::remote_url() }}/pull/{{ commit.remote.pr_number }}) \
{%- endif -%}
{% endfor %}
{% endfor %}\n\n
"""
# template for the changelog footer
footer = """
{%- macro remote_url() -%}
https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }}
{%- endmacro -%}
{% for release in releases -%}
{% if release.version -%}
{% if release.previous.version -%}
[{{ release.version | trim_start_matches(pat="v") }}]: \
{{ self::remote_url() }}/compare/{{ release.previous.version }}..{{ release.version }}
{% endif -%}
{% else -%}
[unreleased]: {{ self::remote_url() }}/compare/{{ release.previous.version }}..HEAD
{% endif -%}
{% endfor %}
"""
# remove the leading and trailing s
trim = true
# postprocessors
postprocessors = [
# { pattern = '', replace = "https://github.com/orhun/git-cliff" }, # replace repository URL
]
# render body even when there are no releases to process
# render_always = true
# output file path
# output = "test.md"
[git]
# parse the commits based on https://www.conventionalcommits.org
conventional_commits = true
# filter out the commits that are not conventional
filter_unconventional = true
# process each line of a commit as an individual commit
split_commits = false
# regex for preprocessing the commit messages
commit_preprocessors = [
# Replace issue numbers
#{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](/issues/${2}))"},
# Check spelling of the commit with https://github.com/crate-ci/typos
# If the spelling is incorrect, it will be automatically fixed.
#{ pattern = '.*', replace_command = 'typos --write-changes -' },
]
ignore_tags = ".*beta.*"
# regex for parsing and grouping commits
commit_parsers = [
{ message = "^feat", group = "π Features" },
{ message = "^fix", group = "π Bug Fixes" },
{ message = "^doc", group = "π Documentation" },
{ message = "^perf", group = "β‘ Performance" },
{ message = "^refactor", group = "ποΈ Refactor" },
{ message = "^style", group = "π¨ Styling" },
{ message = "^test", group = "π§ͺ Testing" },
{ message = "^chore\\(release\\): prepare for", skip = true },
{ message = "^chore: Release", skip = true },
{ message = "^chore\\(deps.*\\)", skip = true },
{ message = "^chore\\(pr\\)", skip = true },
{ message = "^chore\\(pull\\)", skip = true },
{ message = "^chore|^ci", group = "βοΈ Internals" },
{ body = ".*security", group = "π‘οΈ Security" },
{ message = "^revert", group = "βοΈ Revert" },
{ message = ".*", group = "πΌ Other" },
]
# filter out the commits that are not matched by commit parsers
filter_commits = false
# sort the tags topologically
topo_order = false
# sort the commits inside sections by oldest/newest order
sort_commits = "newest"
pytest-codspeed-4.2.0/pyproject.toml 0000664 0000000 0000000 00000007461 15076637504 0017572 0 ustar 00root root 0000000 0000000 [project.urls]
Homepage = "https://codspeed.io/"
Documentation = "https://codspeed.io/docs/reference/pytest-codspeed"
Source = "https://github.com/CodSpeedHQ/pytest-codspeed"
[project]
name = "pytest-codspeed"
dynamic = ["version"]
description = "Pytest plugin to create CodSpeed benchmarks"
readme = "README.md"
license = { file = "LICENSE" }
requires-python = ">=3.9"
authors = [{ name = "Arthur Pastel", email = "arthur@codspeed.io" }]
keywords = ["codspeed", "benchmark", "performance", "pytest"]
classifiers = [
"Development Status :: 5 - Production/Stable",
"Framework :: Pytest",
"Intended Audience :: Developers",
"Intended Audience :: Information Technology",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.9",
"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",
"Topic :: System :: Benchmark",
"Topic :: Utilities",
"Typing :: Typed",
]
dependencies = [
"cffi >= 1.17.1",
"pytest>=3.8",
"rich>=13.8.1",
"importlib-metadata>=8.5.0; python_version < '3.10'",
]
[project.optional-dependencies]
compat = [
"pytest-benchmark ~= 5.0.0",
"pytest-xdist ~= 3.6.1",
# "pytest-speed>=0.3.5",
]
[tool.uv.sources]
pytest-codspeed = { workspace = true }
[dependency-groups]
dev = [
"pytest-codspeed",
"mypy ~= 1.18.2",
"ruff ~= 0.11.12",
"pytest ~= 7.0",
"pytest-cov ~= 4.0.0",
"pytest-test-groups>=1.1.0",
]
[project.entry-points]
pytest11 = { codspeed = "pytest_codspeed.plugin" }
[build-system]
requires = ["setuptools >= 61", "cffi >= 1.17.1"]
build-backend = "setuptools.build_meta"
[tool.setuptools]
license-files = [] # Workaround of https://github.com/astral-sh/uv/issues/9513
[tool.setuptools.dynamic]
version = { attr = "pytest_codspeed.__version__" }
[tool.bumpver]
current_version = "4.2.0"
version_pattern = "MAJOR.MINOR.PATCH[-TAG[NUM]]"
commit_message = "Release v{new_version} π"
tag_message = "Release v{new_version} π"
tag_scope = "default"
allow_dirty = false
pre_commit_hook = "./scripts/pre-release.sh"
post_commit_hook = "./scripts/post-release.sh"
commit = true
tag = false
push = false
[tool.bumpver.file_patterns]
"pyproject.toml" = ['current_version = "{version}"']
"src/pytest_codspeed/__init__.py" = [
'__version__ = "{pep440_version}"',
'__semver_version__ = "{version}"',
]
[tool.cibuildwheel]
build = "cp*manylinux*"
test-extras = ["build", "test", "compat"]
test-command = "pytest -v --ignore={project}/tests/benchmarks {project}/tests"
[tool.cibuildwheel.linux]
environment = { PYTEST_CODSPEED_FORCE_EXTENSION_BUILD = "1", PYTEST_CODSPEED_FORCE_VALGRIND_TESTS = "1" }
manylinux-x86_64-image = "manylinux_2_28"
manylinux-aarch64-image = "manylinux_2_28"
before-all = "yum -y install valgrind-devel"
[tool.mypy]
python_version = "3.12"
[tool.ruff]
target-version = "py37"
[tool.ruff.lint]
select = ["E", "F", "I", "C", "TCH", "FA", "UP"]
flake8-type-checking = { exempt-modules = [], strict = true }
[tool.isort]
line_length = 88
multi_line_output = 3
include_trailing_comma = true
use_parentheses = true
force_grid_wrap = 0
float_to_top = true
[tool.pytest.ini_options]
addopts = "--ignore=tests/benchmarks --ignore=tests/examples --ignore=tests/benchmarks/TheAlgorithms"
filterwarnings = ["ignore::DeprecationWarning:pytest_benchmark.utils.*:"]
pythonpath = ["tests/benchmarks/TheAlgorithms", "./scripts"]
[tool.coverage.run]
branch = true
[tool.coverage.report]
include = ["src/*", "tests/*"]
omit = ["**/conftest.py"]
exclude_lines = [
"pragma: no cover",
"if TYPE_CHECKING:",
"@pytest.mark.skip",
"@abstractmethod",
]
pytest-codspeed-4.2.0/scripts/ 0000775 0000000 0000000 00000000000 15076637504 0016335 5 ustar 00root root 0000000 0000000 pytest-codspeed-4.2.0/scripts/post-release.sh 0000775 0000000 0000000 00000000325 15076637504 0021277 0 ustar 00root root 0000000 0000000 #!/bin/bash
set -e
VERSION=$BUMPVER_NEW_VERSION
# We handle tagging here since bumpver doesn't allow custom
# tagnames and we want a v prefix
git tag v$VERSION -m "Release v$VERSION π"
git push --follow-tags
pytest-codspeed-4.2.0/scripts/pre-release.sh 0000775 0000000 0000000 00000001140 15076637504 0021074 0 ustar 00root root 0000000 0000000 #!/bin/bash
set -e
VERSION=v$BUMPVER_NEW_VERSION
# Skip alpha/beta/rc changelog generation
if [[ $VERSION == *"alpha"* ]] || [[ $VERSION == *"beta"* ]] || [[ $VERSION == *"rc"* ]]; then
echo "Skipping changelog generation for alpha/beta/rc release"
else
echo "Generating changelog for $VERSION"
# Check that GITHUB_TOKEN is set
if [ -z "$GITHUB_TOKEN" ]; then
echo "GITHUB_TOKEN is not set. Trying to fetch it from gh"
GITHUB_TOKEN=$(gh auth token)
fi
git cliff --unreleased --tag $VERSION --prepend CHANGELOG.md
git add CHANGELOG.md
fi
uv lock
git add uv.lock
pytest-codspeed-4.2.0/setup.py 0000664 0000000 0000000 00000003225 15076637504 0016362 0 ustar 00root root 0000000 0000000 import importlib.util
import os
import platform
from pathlib import Path
from setuptools import setup
build_path = Path(__file__).parent / "src/pytest_codspeed/instruments/hooks/build.py"
spec = importlib.util.spec_from_file_location("build", build_path)
assert spec is not None, "The spec should be initialized"
build = importlib.util.module_from_spec(spec)
assert spec.loader is not None, "The loader should be initialized"
spec.loader.exec_module(build)
system = platform.system()
current_arch = platform.machine()
print(f"System: {system} ({current_arch})")
IS_EXTENSION_BUILDABLE = system == "Linux" and current_arch in [
"x86_64",
"aarch64",
]
IS_EXTENSION_REQUIRED = (
os.environ.get("PYTEST_CODSPEED_FORCE_EXTENSION_BUILD") is not None
)
SKIP_EXTENSION_BUILD = (
os.environ.get("PYTEST_CODSPEED_SKIP_EXTENSION_BUILD") is not None
)
if SKIP_EXTENSION_BUILD and IS_EXTENSION_REQUIRED:
raise ValueError("Extension build required but the build requires to skip it")
if IS_EXTENSION_REQUIRED and not IS_EXTENSION_BUILDABLE:
raise ValueError(
"The extension is required but the current platform is not supported"
)
ffi_extension = build.ffibuilder.distutils_extension()
ffi_extension.optional = not IS_EXTENSION_REQUIRED
print(
"CodSpeed native extension is "
+ ("required" if IS_EXTENSION_REQUIRED else "optional")
)
setup(
package_data={
"pytest_codspeed": [
"instruments/hooks/instrument-hooks/includes/*.h",
"instruments/hooks/instrument-hooks/dist/*.c",
]
},
ext_modules=(
[ffi_extension] if IS_EXTENSION_BUILDABLE and not SKIP_EXTENSION_BUILD else []
),
)
pytest-codspeed-4.2.0/src/ 0000775 0000000 0000000 00000000000 15076637504 0015435 5 ustar 00root root 0000000 0000000 pytest-codspeed-4.2.0/src/pytest_codspeed/ 0000775 0000000 0000000 00000000000 15076637504 0020633 5 ustar 00root root 0000000 0000000 pytest-codspeed-4.2.0/src/pytest_codspeed/__init__.py 0000664 0000000 0000000 00000000352 15076637504 0022744 0 ustar 00root root 0000000 0000000 __version__ = "4.2.0"
# We also have the semver version since __version__ is not semver compliant
__semver_version__ = "4.2.0"
from .plugin import BenchmarkFixture
__all__ = ["BenchmarkFixture", "__version__", "__semver_version__"]
pytest-codspeed-4.2.0/src/pytest_codspeed/config.py 0000664 0000000 0000000 00000007514 15076637504 0022461 0 ustar 00root root 0000000 0000000 from __future__ import annotations
import dataclasses
from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Generic, TypeVar
T = TypeVar("T")
if TYPE_CHECKING:
from typing import Any, Callable
import pytest
@dataclass(frozen=True)
class CodSpeedConfig:
"""
The configuration for the codspeed plugin.
Usually created from the command line arguments.
"""
warmup_time_ns: int | None = None
max_time_ns: int | None = None
max_rounds: int | None = None
@classmethod
def from_pytest_config(cls, config: pytest.Config) -> CodSpeedConfig:
warmup_time = config.getoption("--codspeed-warmup-time", None)
warmup_time_ns = (
int(warmup_time * 1_000_000_000) if warmup_time is not None else None
)
max_time = config.getoption("--codspeed-max-time", None)
max_time_ns = int(max_time * 1_000_000_000) if max_time is not None else None
return cls(
warmup_time_ns=warmup_time_ns,
max_rounds=config.getoption("--codspeed-max-rounds", None),
max_time_ns=max_time_ns,
)
@dataclass(frozen=True)
class BenchmarkMarkerOptions:
group: str | None = None
"""The group name to use for the benchmark."""
min_time: int | None = None
"""
The minimum time of a round (in seconds).
Only available in walltime mode.
"""
max_time: int | None = None
"""
The maximum time to run the benchmark for (in seconds).
Only available in walltime mode.
"""
max_rounds: int | None = None
"""
The maximum number of rounds to run the benchmark for.
Takes precedence over max_time. Only available in walltime mode.
"""
@classmethod
def from_pytest_item(cls, item: pytest.Item) -> BenchmarkMarkerOptions:
marker = item.get_closest_marker(
"codspeed_benchmark"
) or item.get_closest_marker("benchmark")
if marker is None:
return cls()
if len(marker.args) > 0:
raise ValueError(
"Positional arguments are not allowed in the benchmark marker"
)
kwargs = marker.kwargs
unknown_kwargs = set(kwargs.keys()) - {
field.name for field in dataclasses.fields(cls)
}
if unknown_kwargs:
raise ValueError(
"Unknown kwargs passed to benchmark marker: "
+ ", ".join(sorted(unknown_kwargs))
)
return cls(**kwargs)
@dataclass(frozen=True)
class PedanticOptions(Generic[T]):
"""Parameters for running a benchmark using the pedantic fixture API."""
target: Callable[..., T]
setup: Callable[[], Any | None] | None
teardown: Callable[..., Any | None] | None
rounds: int
warmup_rounds: int
iterations: int
args: tuple[Any, ...] = field(default_factory=tuple)
kwargs: dict[str, Any] = field(default_factory=dict)
def __post_init__(self) -> None:
if self.rounds < 0:
raise ValueError("rounds must be positive")
if self.warmup_rounds < 0:
raise ValueError("warmup_rounds must be non-negative")
if self.iterations <= 0:
raise ValueError("iterations must be positive")
if self.iterations > 1 and self.setup is not None:
raise ValueError(
"setup cannot be used with multiple iterations, use multiple rounds"
)
def setup_and_get_args_kwargs(self) -> tuple[tuple[Any, ...], dict[str, Any]]:
if self.setup is None:
return self.args, self.kwargs
maybe_result = self.setup(*self.args, **self.kwargs)
if maybe_result is not None:
if len(self.args) > 0 or len(self.kwargs) > 0:
raise ValueError("setup cannot return a value when args are provided")
return maybe_result
return self.args, self.kwargs
pytest-codspeed-4.2.0/src/pytest_codspeed/instruments/ 0000775 0000000 0000000 00000000000 15076637504 0023226 5 ustar 00root root 0000000 0000000 pytest-codspeed-4.2.0/src/pytest_codspeed/instruments/__init__.py 0000664 0000000 0000000 00000003345 15076637504 0025344 0 ustar 00root root 0000000 0000000 from __future__ import annotations
from abc import ABCMeta, abstractmethod
from enum import Enum
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from typing import Any, Callable, ClassVar, TypeVar
import pytest
from typing_extensions import ParamSpec
from pytest_codspeed.config import BenchmarkMarkerOptions, PedanticOptions
from pytest_codspeed.plugin import CodSpeedConfig
T = TypeVar("T")
P = ParamSpec("P")
class Instrument(metaclass=ABCMeta):
instrument: ClassVar[str]
@abstractmethod
def __init__(self, config: CodSpeedConfig): ...
@abstractmethod
def get_instrument_config_str_and_warns(self) -> tuple[str, list[str]]: ...
@abstractmethod
def measure(
self,
marker_options: BenchmarkMarkerOptions,
name: str,
uri: str,
fn: Callable[P, T],
*args: P.args,
**kwargs: P.kwargs,
) -> T: ...
@abstractmethod
def measure_pedantic(
self,
marker_options: BenchmarkMarkerOptions,
pedantic_options: PedanticOptions[T],
name: str,
uri: str,
) -> T: ...
@abstractmethod
def report(self, session: pytest.Session) -> None: ...
@abstractmethod
def get_result_dict(
self,
) -> dict[str, Any]: ...
class MeasurementMode(str, Enum):
Instrumentation = "instrumentation"
WallTime = "walltime"
def get_instrument_from_mode(mode: MeasurementMode) -> type[Instrument]:
from pytest_codspeed.instruments.valgrind import (
ValgrindInstrument,
)
from pytest_codspeed.instruments.walltime import WallTimeInstrument
if mode == MeasurementMode.Instrumentation:
return ValgrindInstrument
else:
return WallTimeInstrument
pytest-codspeed-4.2.0/src/pytest_codspeed/instruments/hooks/ 0000775 0000000 0000000 00000000000 15076637504 0024351 5 ustar 00root root 0000000 0000000 pytest-codspeed-4.2.0/src/pytest_codspeed/instruments/hooks/__init__.py 0000664 0000000 0000000 00000005626 15076637504 0026473 0 ustar 00root root 0000000 0000000 from __future__ import annotations
import os
import sys
import warnings
from typing import TYPE_CHECKING
from pytest_codspeed.utils import SUPPORTS_PERF_TRAMPOLINE
if TYPE_CHECKING:
from .dist_instrument_hooks import InstrumentHooksPointer, LibType
class InstrumentHooks:
"""Zig library wrapper class providing benchmark measurement functionality."""
lib: LibType
instance: InstrumentHooksPointer
def __init__(self) -> None:
if os.environ.get("CODSPEED_ENV") is None:
raise RuntimeError(
"Can't run benchmarks outside of CodSpeed environment."
"Please set the CODSPEED_ENV environment variable."
)
try:
from .dist_instrument_hooks import lib # type: ignore
except ImportError as e:
raise RuntimeError(f"Failed to load instrument hooks library: {e}") from e
self.lib = lib
self.instance = self.lib.instrument_hooks_init()
if self.instance == 0:
raise RuntimeError("Failed to initialize CodSpeed instrumentation library.")
if SUPPORTS_PERF_TRAMPOLINE:
sys.activate_stack_trampoline("perf") # type: ignore
def __del__(self):
if hasattr(self, "lib") and hasattr(self, "instance"):
self.lib.instrument_hooks_deinit(self.instance)
def start_benchmark(self) -> None:
"""Start a new benchmark measurement."""
ret = self.lib.instrument_hooks_start_benchmark(self.instance)
if ret != 0:
warnings.warn("Failed to start benchmark measurement", RuntimeWarning)
def stop_benchmark(self) -> None:
"""Stop the current benchmark measurement."""
ret = self.lib.instrument_hooks_stop_benchmark(self.instance)
if ret != 0:
warnings.warn("Failed to stop benchmark measurement", RuntimeWarning)
def set_executed_benchmark(self, uri: str, pid: int | None = None) -> None:
"""Set the executed benchmark URI and process ID.
Args:
uri: The benchmark URI string identifier
pid: Optional process ID (defaults to current process)
"""
if pid is None:
pid = os.getpid()
ret = self.lib.instrument_hooks_set_executed_benchmark(
self.instance, pid, uri.encode("ascii")
)
if ret != 0:
warnings.warn("Failed to set executed benchmark", RuntimeWarning)
def set_integration(self, name: str, version: str) -> None:
"""Set the integration name and version."""
ret = self.lib.instrument_hooks_set_integration(
self.instance, name.encode("ascii"), version.encode("ascii")
)
if ret != 0:
warnings.warn("Failed to set integration name and version", RuntimeWarning)
def is_instrumented(self) -> bool:
"""Check if instrumentation is active."""
return self.lib.instrument_hooks_is_instrumented(self.instance)
pytest-codspeed-4.2.0/src/pytest_codspeed/instruments/hooks/build.py 0000664 0000000 0000000 00000003203 15076637504 0026020 0 ustar 00root root 0000000 0000000 from pathlib import Path
from cffi import FFI # type: ignore
ffibuilder = FFI()
includes_dir = Path(__file__).parent.joinpath("instrument-hooks/includes")
header_text = (includes_dir / "core.h").read_text()
# Manually copied from `instrument-hooks/includes/core.h` to avoid parsing issues
ffibuilder.cdef("""
typedef uint64_t *InstrumentHooks;
InstrumentHooks *instrument_hooks_init(void);
void instrument_hooks_deinit(InstrumentHooks *);
bool instrument_hooks_is_instrumented(InstrumentHooks *);
uint8_t instrument_hooks_start_benchmark(InstrumentHooks *);
uint8_t instrument_hooks_stop_benchmark(InstrumentHooks *);
uint8_t instrument_hooks_set_executed_benchmark(InstrumentHooks *, int32_t pid,
const char *uri);
uint8_t instrument_hooks_set_integration(InstrumentHooks *, const char *name,
const char *version);
#define MARKER_TYPE_SAMPLE_START 0
#define MARKER_TYPE_SAMPLE_END 1
#define MARKER_TYPE_BENCHMARK_START 2
#define MARKER_TYPE_BENCHMARK_END 3
uint8_t instrument_hooks_add_marker(InstrumentHooks *, uint32_t pid,
uint8_t marker_type, uint64_t timestamp);
uint64_t instrument_hooks_current_timestamp(void);
void callgrind_start_instrumentation();
void callgrind_stop_instrumentation();
""")
ffibuilder.set_source(
"pytest_codspeed.instruments.hooks.dist_instrument_hooks",
"""
#include "core.h"
""",
sources=[
"src/pytest_codspeed/instruments/hooks/instrument-hooks/dist/core.c",
],
include_dirs=[str(includes_dir)],
)
if __name__ == "__main__":
ffibuilder.compile(verbose=True)
pytest-codspeed-4.2.0/src/pytest_codspeed/instruments/hooks/dist_instrument_hooks.pyi 0000664 0000000 0000000 00000002271 15076637504 0031534 0 ustar 00root root 0000000 0000000 InstrumentHooksPointer = object
class lib:
@staticmethod
def instrument_hooks_init() -> InstrumentHooksPointer: ...
@staticmethod
def instrument_hooks_deinit(hooks: InstrumentHooksPointer) -> None: ...
@staticmethod
def instrument_hooks_is_instrumented(hooks: InstrumentHooksPointer) -> bool: ...
@staticmethod
def instrument_hooks_start_benchmark(hooks: InstrumentHooksPointer) -> int: ...
@staticmethod
def instrument_hooks_stop_benchmark(hooks: InstrumentHooksPointer) -> int: ...
@staticmethod
def instrument_hooks_set_executed_benchmark(
hooks: InstrumentHooksPointer, pid: int, uri: bytes
) -> int: ...
@staticmethod
def instrument_hooks_set_integration(
hooks: InstrumentHooksPointer, name: bytes, version: bytes
) -> int: ...
@staticmethod
def instrument_hooks_add_marker(
hooks: InstrumentHooksPointer, pid: int, marker_type: int, timestamp: int
) -> int: ...
@staticmethod
def instrument_hooks_current_timestamp() -> int: ...
@staticmethod
def callgrind_start_instrumentation() -> int: ...
@staticmethod
def callgrind_stop_instrumentation() -> int: ...
LibType = type[lib]
pytest-codspeed-4.2.0/src/pytest_codspeed/instruments/hooks/instrument-hooks/ 0000775 0000000 0000000 00000000000 15076637504 0027702 5 ustar 00root root 0000000 0000000 pytest-codspeed-4.2.0/src/pytest_codspeed/instruments/valgrind.py 0000664 0000000 0000000 00000012205 15076637504 0025406 0 ustar 00root root 0000000 0000000 from __future__ import annotations
import os
import warnings
from typing import TYPE_CHECKING
from pytest_codspeed import __semver_version__
from pytest_codspeed.instruments import Instrument
from pytest_codspeed.instruments.hooks import InstrumentHooks
from pytest_codspeed.utils import SUPPORTS_PERF_TRAMPOLINE
if TYPE_CHECKING:
from typing import Any, Callable
from pytest import Session
from pytest_codspeed.config import PedanticOptions
from pytest_codspeed.instruments import P, T
from pytest_codspeed.plugin import BenchmarkMarkerOptions, CodSpeedConfig
class ValgrindInstrument(Instrument):
instrument = "valgrind"
instrument_hooks: InstrumentHooks | None
def __init__(self, config: CodSpeedConfig) -> None:
self.benchmark_count = 0
try:
self.instrument_hooks = InstrumentHooks()
self.instrument_hooks.set_integration("pytest-codspeed", __semver_version__)
except RuntimeError as e:
if os.environ.get("CODSPEED_ENV") is not None:
raise Exception(
"Failed to initialize CPU simulation instrument hooks"
) from e
self.instrument_hooks = None
self.should_measure = self.instrument_hooks is not None
def get_instrument_config_str_and_warns(self) -> tuple[str, list[str]]:
config = (
f"mode: instrumentation, "
f"callgraph: {'enabled' if SUPPORTS_PERF_TRAMPOLINE else 'not supported'}"
)
warnings = []
if not self.should_measure:
warnings.append(
"\033[1m"
"NOTICE: codspeed is enabled, but no performance measurement"
" will be made since it's running in an unknown environment."
"\033[0m"
)
return config, warnings
def measure(
self,
marker_options: BenchmarkMarkerOptions,
name: str,
uri: str,
fn: Callable[P, T],
*args: P.args,
**kwargs: P.kwargs,
) -> T:
self.benchmark_count += 1
if not self.instrument_hooks:
return fn(*args, **kwargs)
def __codspeed_root_frame__() -> T:
return fn(*args, **kwargs)
if SUPPORTS_PERF_TRAMPOLINE:
# Warmup CPython performance map cache
__codspeed_root_frame__()
# Manually call the library function to avoid an extra stack frame. Also
# call the callgrind markers directly to avoid extra overhead.
self.instrument_hooks.lib.callgrind_start_instrumentation()
try:
return __codspeed_root_frame__()
finally:
# Ensure instrumentation is stopped even if the test failed
self.instrument_hooks.lib.callgrind_stop_instrumentation()
self.instrument_hooks.set_executed_benchmark(uri)
def measure_pedantic(
self,
marker_options: BenchmarkMarkerOptions,
pedantic_options: PedanticOptions[T],
name: str,
uri: str,
) -> T:
if pedantic_options.rounds != 1 or pedantic_options.iterations != 1:
warnings.warn(
"Valgrind instrument ignores rounds and iterations settings "
"in pedantic mode"
)
if not self.instrument_hooks:
args, kwargs = pedantic_options.setup_and_get_args_kwargs()
out = pedantic_options.target(*args, **kwargs)
if pedantic_options.teardown is not None:
pedantic_options.teardown(*args, **kwargs)
return out
def __codspeed_root_frame__(*args, **kwargs) -> T:
return pedantic_options.target(*args, **kwargs)
# Warmup
warmup_rounds = max(
pedantic_options.warmup_rounds, 1 if SUPPORTS_PERF_TRAMPOLINE else 0
)
for _ in range(warmup_rounds):
args, kwargs = pedantic_options.setup_and_get_args_kwargs()
__codspeed_root_frame__(*args, **kwargs)
if pedantic_options.teardown is not None:
pedantic_options.teardown(*args, **kwargs)
# Compute the actual result of the function
args, kwargs = pedantic_options.setup_and_get_args_kwargs()
self.instrument_hooks.lib.callgrind_start_instrumentation()
try:
out = __codspeed_root_frame__(*args, **kwargs)
finally:
self.instrument_hooks.lib.callgrind_stop_instrumentation()
self.instrument_hooks.set_executed_benchmark(uri)
if pedantic_options.teardown is not None:
pedantic_options.teardown(*args, **kwargs)
return out
def report(self, session: Session) -> None:
reporter = session.config.pluginmanager.get_plugin("terminalreporter")
assert reporter is not None, "terminalreporter not found"
count_suffix = "benchmarked" if self.should_measure else "benchmark tested"
reporter.write_sep(
"=",
f"{self.benchmark_count} {count_suffix}",
)
def get_result_dict(self) -> dict[str, Any]:
return {
"instrument": {"type": self.instrument},
# bench results will be dumped by valgrind
}
pytest-codspeed-4.2.0/src/pytest_codspeed/instruments/walltime.py 0000664 0000000 0000000 00000030143 15076637504 0025417 0 ustar 00root root 0000000 0000000 from __future__ import annotations
import os
import warnings
from dataclasses import asdict, dataclass
from math import ceil
from statistics import mean, quantiles, stdev
from time import get_clock_info, perf_counter_ns
from typing import TYPE_CHECKING
from rich.console import Console
from rich.markup import escape
from rich.table import Table
from rich.text import Text
from pytest_codspeed import __semver_version__
from pytest_codspeed.instruments import Instrument
from pytest_codspeed.instruments.hooks import InstrumentHooks
from pytest_codspeed.utils import SUPPORTS_PERF_TRAMPOLINE
if TYPE_CHECKING:
from typing import Any, Callable
from pytest import Session
from pytest_codspeed.config import PedanticOptions
from pytest_codspeed.instruments import P, T
from pytest_codspeed.plugin import BenchmarkMarkerOptions, CodSpeedConfig
DEFAULT_WARMUP_TIME_NS = 1_000_000_000
DEFAULT_MAX_TIME_NS = 3_000_000_000
TIMER_RESOLUTION_NS = get_clock_info("perf_counter").resolution * 1e9
DEFAULT_MIN_ROUND_TIME_NS = int(TIMER_RESOLUTION_NS * 1_000_000)
IQR_OUTLIER_FACTOR = 1.5
STDEV_OUTLIER_FACTOR = 3
@dataclass
class BenchmarkConfig:
warmup_time_ns: int
min_round_time_ns: float
max_time_ns: int
max_rounds: int | None
@classmethod
def from_codspeed_config_and_marker_data(
cls, config: CodSpeedConfig, marker_data: BenchmarkMarkerOptions
) -> BenchmarkConfig:
if marker_data.max_time is not None:
max_time_ns = int(marker_data.max_time * 1e9)
elif config.max_time_ns is not None:
max_time_ns = config.max_time_ns
else:
max_time_ns = DEFAULT_MAX_TIME_NS
if marker_data.max_rounds is not None:
max_rounds = marker_data.max_rounds
elif config.max_rounds is not None:
max_rounds = config.max_rounds
else:
max_rounds = None
if marker_data.min_time is not None:
min_round_time_ns = int(marker_data.min_time * 1e9)
else:
min_round_time_ns = DEFAULT_MIN_ROUND_TIME_NS
return cls(
warmup_time_ns=config.warmup_time_ns
if config.warmup_time_ns is not None
else DEFAULT_WARMUP_TIME_NS,
min_round_time_ns=min_round_time_ns,
max_time_ns=max_time_ns,
max_rounds=max_rounds,
)
@dataclass
class BenchmarkStats:
min_ns: float
max_ns: float
mean_ns: float
stdev_ns: float
q1_ns: float
median_ns: float
q3_ns: float
rounds: int
total_time: float
iqr_outlier_rounds: int
stdev_outlier_rounds: int
iter_per_round: int
warmup_iters: int
@classmethod
def from_list(
cls,
times_per_round_ns: list[float],
*,
rounds: int,
iter_per_round: int,
warmup_iters: int,
total_time: float,
) -> BenchmarkStats:
times_ns = [t / iter_per_round for t in times_per_round_ns]
stdev_ns = stdev(times_ns) if len(times_ns) > 1 else 0
mean_ns = mean(times_ns)
if len(times_ns) > 1:
q1_ns, median_ns, q3_ns = quantiles(times_ns, n=4)
else:
q1_ns, median_ns, q3_ns = (
mean_ns,
mean_ns,
mean_ns,
)
iqr_ns = q3_ns - q1_ns
iqr_outlier_rounds = sum(
1
for t in times_ns
if t < q1_ns - IQR_OUTLIER_FACTOR * iqr_ns
or t > q3_ns + IQR_OUTLIER_FACTOR * iqr_ns
)
stdev_outlier_rounds = sum(
1
for t in times_ns
if t < mean_ns - STDEV_OUTLIER_FACTOR * stdev_ns
or t > mean_ns + STDEV_OUTLIER_FACTOR * stdev_ns
)
return cls(
min_ns=min(times_ns),
max_ns=max(times_ns),
stdev_ns=stdev_ns,
mean_ns=mean_ns,
q1_ns=q1_ns,
median_ns=median_ns,
q3_ns=q3_ns,
rounds=rounds,
total_time=total_time,
iqr_outlier_rounds=iqr_outlier_rounds,
stdev_outlier_rounds=stdev_outlier_rounds,
iter_per_round=iter_per_round,
warmup_iters=warmup_iters,
)
@dataclass
class Benchmark:
name: str
uri: str
config: BenchmarkConfig
stats: BenchmarkStats
class WallTimeInstrument(Instrument):
instrument = "walltime"
instrument_hooks: InstrumentHooks | None
def __init__(self, config: CodSpeedConfig) -> None:
try:
self.instrument_hooks = InstrumentHooks()
self.instrument_hooks.set_integration("pytest-codspeed", __semver_version__)
except RuntimeError as e:
if os.environ.get("CODSPEED_ENV") is not None:
warnings.warn(
f"Failed to initialize instrument hooks: {e}", RuntimeWarning
)
self.instrument_hooks = None
self.config = config
self.benchmarks: list[Benchmark] = []
def get_instrument_config_str_and_warns(self) -> tuple[str, list[str]]:
config_str = (
f"mode: walltime, "
f"callgraph: "
f"{'enabled' if SUPPORTS_PERF_TRAMPOLINE else 'not supported'}, "
f"timer_resolution: {TIMER_RESOLUTION_NS:.1f}ns"
)
return config_str, []
def measure(
self,
marker_options: BenchmarkMarkerOptions,
name: str,
uri: str,
fn: Callable[P, T],
*args: P.args,
**kwargs: P.kwargs,
) -> T:
benchmark_config = BenchmarkConfig.from_codspeed_config_and_marker_data(
self.config, marker_options
)
def __codspeed_root_frame__() -> T:
return fn(*args, **kwargs)
# Compute the actual result of the function
out = __codspeed_root_frame__()
# Warmup
times_per_round_ns: list[float] = []
warmup_start = start = perf_counter_ns()
while True:
start = perf_counter_ns()
__codspeed_root_frame__()
end = perf_counter_ns()
times_per_round_ns.append(end - start)
if end - warmup_start > benchmark_config.warmup_time_ns:
break
# Round sizing
warmup_mean_ns = mean(times_per_round_ns)
warmup_iters = len(times_per_round_ns)
times_per_round_ns.clear()
iter_per_round = (
int(ceil(benchmark_config.min_round_time_ns / warmup_mean_ns))
if warmup_mean_ns <= benchmark_config.min_round_time_ns
else 1
)
if benchmark_config.max_rounds is None:
round_time_ns = warmup_mean_ns * iter_per_round
rounds = int(benchmark_config.max_time_ns / round_time_ns)
else:
rounds = benchmark_config.max_rounds
rounds = max(1, rounds)
# Benchmark
iter_range = range(iter_per_round)
run_start = perf_counter_ns()
if self.instrument_hooks:
self.instrument_hooks.start_benchmark()
for _ in range(rounds):
start = perf_counter_ns()
for _ in iter_range:
__codspeed_root_frame__()
end = perf_counter_ns()
times_per_round_ns.append(end - start)
if end - run_start > benchmark_config.max_time_ns:
# TODO: log something
break
if self.instrument_hooks:
self.instrument_hooks.stop_benchmark()
self.instrument_hooks.set_executed_benchmark(uri)
benchmark_end = perf_counter_ns()
total_time = (benchmark_end - run_start) / 1e9
stats = BenchmarkStats.from_list(
times_per_round_ns,
rounds=rounds,
total_time=total_time,
iter_per_round=iter_per_round,
warmup_iters=warmup_iters,
)
self.benchmarks.append(
Benchmark(name=name, uri=uri, config=benchmark_config, stats=stats)
)
return out
def measure_pedantic( # noqa: C901
self,
marker_options: BenchmarkMarkerOptions,
pedantic_options: PedanticOptions[T],
name: str,
uri: str,
) -> T:
benchmark_config = BenchmarkConfig.from_codspeed_config_and_marker_data(
self.config, marker_options
)
def __codspeed_root_frame__(*args, **kwargs) -> T:
return pedantic_options.target(*args, **kwargs)
iter_range = range(pedantic_options.iterations)
# Warmup
for _ in range(pedantic_options.warmup_rounds):
args, kwargs = pedantic_options.setup_and_get_args_kwargs()
for _ in iter_range:
__codspeed_root_frame__(*args, **kwargs)
if pedantic_options.teardown is not None:
pedantic_options.teardown(*args, **kwargs)
# Benchmark
times_per_round_ns: list[float] = []
benchmark_start = perf_counter_ns()
if self.instrument_hooks:
self.instrument_hooks.start_benchmark()
for _ in range(pedantic_options.rounds):
start = perf_counter_ns()
args, kwargs = pedantic_options.setup_and_get_args_kwargs()
for _ in iter_range:
__codspeed_root_frame__(*args, **kwargs)
end = perf_counter_ns()
times_per_round_ns.append(end - start)
if pedantic_options.teardown is not None:
pedantic_options.teardown(*args, **kwargs)
if self.instrument_hooks:
self.instrument_hooks.stop_benchmark()
self.instrument_hooks.set_executed_benchmark(uri)
benchmark_end = perf_counter_ns()
total_time = (benchmark_end - benchmark_start) / 1e9
stats = BenchmarkStats.from_list(
times_per_round_ns,
rounds=pedantic_options.rounds,
total_time=total_time,
iter_per_round=pedantic_options.iterations,
warmup_iters=pedantic_options.warmup_rounds,
)
# Compute the actual result of the function
args, kwargs = pedantic_options.setup_and_get_args_kwargs()
out = __codspeed_root_frame__(*args, **kwargs)
if pedantic_options.teardown is not None:
pedantic_options.teardown(*args, **kwargs)
self.benchmarks.append(
Benchmark(name=name, uri=uri, config=benchmark_config, stats=stats)
)
return out
def report(self, session: Session) -> None:
reporter = session.config.pluginmanager.get_plugin("terminalreporter")
assert reporter is not None, "terminalreporter not found"
if len(self.benchmarks) == 0:
reporter.write_sep(
"=",
f"{len(self.benchmarks)} benchmarked",
)
return
self._print_benchmark_table()
reporter.write_sep(
"=",
f"{len(self.benchmarks)} benchmarked",
)
def _print_benchmark_table(self) -> None:
table = Table(title="Benchmark Results")
table.add_column("Benchmark", justify="right", style="cyan", no_wrap=True)
table.add_column("Time (best)", justify="right", style="green bold")
table.add_column(
"Rel. StdDev",
justify="right",
)
table.add_column("Run time", justify="right")
table.add_column("Iters", justify="right")
for bench in self.benchmarks:
rsd = bench.stats.stdev_ns / bench.stats.mean_ns
rsd_text = Text(f"{rsd * 100:.1f}%")
if rsd > 0.1:
rsd_text.stylize("red bold")
table.add_row(
escape(bench.name),
f"{bench.stats.min_ns / bench.stats.iter_per_round:,.0f}ns",
rsd_text,
f"{bench.stats.total_time:,.2f}s",
f"{bench.stats.iter_per_round * bench.stats.rounds:,}",
)
console = Console()
print("\n")
console.print(table)
def get_result_dict(self) -> dict[str, Any]:
return {
"instrument": {
"type": self.instrument,
"clock_info": get_clock_info("perf_counter").__dict__,
},
"benchmarks": [asdict(bench) for bench in self.benchmarks],
}
pytest-codspeed-4.2.0/src/pytest_codspeed/plugin.py 0000664 0000000 0000000 00000031330 15076637504 0022503 0 ustar 00root root 0000000 0000000 from __future__ import annotations
import functools
import gc
import json
import os
import random
from dataclasses import dataclass, field
from pathlib import Path
from time import time
from typing import TYPE_CHECKING, cast
import pytest
from _pytest.fixtures import FixtureManager
from pytest_codspeed.config import (
BenchmarkMarkerOptions,
CodSpeedConfig,
PedanticOptions,
)
from pytest_codspeed.instruments import MeasurementMode, get_instrument_from_mode
from pytest_codspeed.utils import (
BEFORE_PYTEST_8_1_1,
IS_PYTEST_BENCHMARK_INSTALLED,
IS_PYTEST_SPEED_INSTALLED,
get_environment_metadata,
get_git_relative_uri_and_name,
)
from . import __version__
if TYPE_CHECKING:
from typing import Any, Callable, ParamSpec, TypeVar
from pytest_codspeed.instruments import Instrument
T = TypeVar("T")
P = ParamSpec("P")
@pytest.hookimpl(trylast=True)
def pytest_addoption(parser: pytest.Parser):
group = parser.getgroup("CodSpeed benchmarking")
group.addoption(
"--codspeed",
action="store_true",
default=False,
help="Enable codspeed (not required when using the CodSpeed action)",
)
group.addoption(
"--codspeed-mode",
action="store",
choices=[mode.value for mode in MeasurementMode],
help="The measurement tool to use for measuring performance",
)
group.addoption(
"--codspeed-warmup-time",
action="store",
type=float,
help=(
"The time to warm up the benchmark for (in seconds), only for walltime mode"
),
)
group.addoption(
"--codspeed-max-time",
action="store",
type=float,
help=(
"The maximum time to run a benchmark for (in seconds), "
"only for walltime mode"
),
)
group.addoption(
"--codspeed-max-rounds",
action="store",
type=int,
help=(
"The maximum number of rounds to run a benchmark for"
", only for walltime mode"
),
)
@dataclass(unsafe_hash=True)
class CodSpeedPlugin:
is_codspeed_enabled: bool
mode: MeasurementMode
instrument: Instrument
config: CodSpeedConfig
disabled_plugins: tuple[str, ...]
profile_folder: Path | None
benchmark_count: int = field(default=0, hash=False, compare=False)
PLUGIN_NAME = "codspeed_plugin"
def get_plugin(config: pytest.Config) -> CodSpeedPlugin:
return cast("CodSpeedPlugin", config.pluginmanager.get_plugin(PLUGIN_NAME))
@pytest.hookimpl(tryfirst=True)
def pytest_configure(config: pytest.Config):
config.addinivalue_line(
"markers", "codspeed_benchmark: mark an entire test for codspeed benchmarking"
)
config.addinivalue_line(
"markers", "benchmark: mark an entire test for codspeed benchmarking"
)
is_codspeed_enabled = (
config.getoption("--codspeed") or os.environ.get("CODSPEED_ENV") is not None
)
if os.environ.get("CODSPEED_ENV") is not None:
if os.environ.get("CODSPEED_RUNNER_MODE") == "walltime":
default_mode = MeasurementMode.WallTime.value
else:
default_mode = MeasurementMode.Instrumentation.value
else:
default_mode = MeasurementMode.WallTime.value
mode = MeasurementMode(config.getoption("--codspeed-mode", None) or default_mode)
instrument = get_instrument_from_mode(mode)
disabled_plugins: list[str] = []
if is_codspeed_enabled:
if IS_PYTEST_BENCHMARK_INSTALLED:
# Disable pytest-benchmark
object.__setattr__(config.option, "benchmark_disable", True)
config.pluginmanager.set_blocked("pytest_benchmark")
config.pluginmanager.set_blocked("pytest-benchmark")
disabled_plugins.append("pytest-benchmark")
if IS_PYTEST_SPEED_INSTALLED:
# Disable pytest-speed
config.pluginmanager.set_blocked("speed")
disabled_plugins.append("pytest-speed")
profile_folder = os.environ.get("CODSPEED_PROFILE_FOLDER")
codspeed_config = CodSpeedConfig.from_pytest_config(config)
plugin = CodSpeedPlugin(
disabled_plugins=tuple(disabled_plugins),
is_codspeed_enabled=is_codspeed_enabled,
mode=mode,
instrument=instrument(codspeed_config),
config=codspeed_config,
profile_folder=Path(profile_folder) if profile_folder else None,
)
config.pluginmanager.register(plugin, PLUGIN_NAME)
@pytest.hookimpl()
def pytest_plugin_registered(plugin, manager: pytest.PytestPluginManager):
"""
Patch the benchmark fixture to use the codspeed one if codspeed is enabled and an
alternative benchmark fixture is available
"""
if (IS_PYTEST_BENCHMARK_INSTALLED or IS_PYTEST_SPEED_INSTALLED) and isinstance(
plugin, FixtureManager
):
fixture_manager = plugin
codspeed_plugin: CodSpeedPlugin = cast(
"CodSpeedPlugin", manager.get_plugin(PLUGIN_NAME)
)
if codspeed_plugin.is_codspeed_enabled:
codspeed_benchmark_fixtures = plugin.getfixturedefs(
"codspeed_benchmark",
fixture_manager.session.nodeid
if BEFORE_PYTEST_8_1_1
else cast("str", fixture_manager.session),
)
assert codspeed_benchmark_fixtures is not None
# Archive the alternative benchmark fixture
fixture_manager._arg2fixturedefs["__benchmark"] = (
fixture_manager._arg2fixturedefs["benchmark"]
)
# Replace the alternative fixture with the codspeed one
fixture_manager._arg2fixturedefs["benchmark"] = codspeed_benchmark_fixtures
@pytest.hookimpl(trylast=True)
def pytest_report_header(config: pytest.Config):
plugin = get_plugin(config)
config_str, warns = plugin.instrument.get_instrument_config_str_and_warns()
out = [
(
f"codspeed: {__version__} ("
f"{'enabled' if plugin.is_codspeed_enabled else 'disabled'}, {config_str}"
")"
),
*warns,
]
if len(plugin.disabled_plugins) > 0:
out.append(
"\033[93mCodSpeed had to disable the following plugins: "
f"{', '.join(plugin.disabled_plugins)}\033[0m"
)
return "\n".join(out)
def has_benchmark_fixture(item: pytest.Item) -> bool:
item_fixtures = getattr(item, "fixturenames", [])
return "benchmark" in item_fixtures or "codspeed_benchmark" in item_fixtures
def has_benchmark_marker(item: pytest.Item) -> bool:
return (
item.get_closest_marker("codspeed_benchmark") is not None
or item.get_closest_marker("benchmark") is not None
)
def should_benchmark_item(item: pytest.Item) -> bool:
return has_benchmark_fixture(item) or has_benchmark_marker(item)
@pytest.hookimpl(trylast=True)
def pytest_collection_modifyitems(
session: pytest.Session, config: pytest.Config, items: list[pytest.Item]
):
"""Filter out items that should not be benchmarked when codspeed is enabled"""
plugin = get_plugin(config)
if plugin.is_codspeed_enabled:
deselected = []
selected = []
for item in items:
if should_benchmark_item(item):
selected.append(item)
else:
deselected.append(item)
config.hook.pytest_deselected(items=deselected)
items[:] = selected
def _measure(
plugin: CodSpeedPlugin,
node: pytest.Item,
config: pytest.Config,
pedantic_options: PedanticOptions | None,
fn: Callable[..., T],
args: tuple[Any, ...],
kwargs: dict[str, Any],
) -> T:
marker_options = BenchmarkMarkerOptions.from_pytest_item(node)
random.seed(0)
is_gc_enabled = gc.isenabled()
if is_gc_enabled:
gc.collect()
gc.disable()
try:
uri, name = get_git_relative_uri_and_name(node.nodeid, config.rootpath)
if pedantic_options is None:
return plugin.instrument.measure(
marker_options, name, uri, fn, *args, **kwargs
)
else:
return plugin.instrument.measure_pedantic(
marker_options, pedantic_options, name, uri
)
finally:
# Ensure GC is re-enabled even if the test failed
if is_gc_enabled:
gc.enable()
def wrap_runtest(
plugin: CodSpeedPlugin,
node: pytest.Item,
config: pytest.Config,
fn: Callable[P, T],
) -> Callable[P, T]:
@functools.wraps(fn)
def wrapped(*args: P.args, **kwargs: P.kwargs) -> T:
return _measure(plugin, node, config, None, fn, args, kwargs)
return wrapped
@pytest.hookimpl(tryfirst=True)
def pytest_runtest_protocol(item: pytest.Item, nextitem: pytest.Item | None):
plugin = get_plugin(item.config)
if not plugin.is_codspeed_enabled or not should_benchmark_item(item):
# Defer to the default test protocol since no benchmarking is needed
return None
if has_benchmark_fixture(item):
# Instrumentation is handled by the fixture
return None
# Wrap runtest and defer to default protocol
item.runtest = wrap_runtest(plugin, item, item.config, item.runtest)
return None
@pytest.hookimpl()
def pytest_sessionfinish(session: pytest.Session, exitstatus):
plugin = get_plugin(session.config)
if plugin.is_codspeed_enabled:
plugin.instrument.report(session)
if plugin.profile_folder:
result_path = plugin.profile_folder / "results" / f"{os.getpid()}.json"
else:
result_path = (
session.config.rootpath / f".codspeed/results_{time() * 1000:.0f}.json"
)
data = {**get_environment_metadata(), **plugin.instrument.get_result_dict()}
result_path.parent.mkdir(parents=True, exist_ok=True)
result_path.write_text(json.dumps(data, indent=2))
class BenchmarkFixture:
"""The fixture that can be used to benchmark a function."""
@property # type: ignore
def __class__(self):
# Bypass the pytest-benchmark fixture class check
# https://github.com/ionelmc/pytest-benchmark/commit/d6511e3474931feb4e862948128e0c389acfceec
if IS_PYTEST_BENCHMARK_INSTALLED:
from pytest_benchmark.fixture import (
BenchmarkFixture as PytestBenchmarkFixture,
)
return PytestBenchmarkFixture
return BenchmarkFixture
def __init__(self, request: pytest.FixtureRequest):
self.extra_info: dict = {}
self._request = request
self._config = self._request.config
self._plugin = get_plugin(self._config)
self._called = False
def __call__(self, target: Callable[P, T], *args: P.args, **kwargs: P.kwargs) -> T:
if self._called:
raise RuntimeError("The benchmark fixture can only be used once per test")
self._called = True
if self._plugin.is_codspeed_enabled:
return _measure(
self._plugin,
self._request.node,
self._config,
None,
target,
args,
kwargs,
)
else:
return target(*args, **kwargs)
def pedantic(
self,
target: Callable[..., T],
args: tuple[Any, ...] = (),
kwargs: dict[str, Any] = {},
setup: Callable | None = None,
teardown: Callable | None = None,
rounds: int = 1,
warmup_rounds: int = 0,
iterations: int = 1,
):
if self._called:
raise RuntimeError("The benchmark fixture can only be used once per test")
self._called = True
pedantic_options = PedanticOptions(
target=target,
args=args,
kwargs=kwargs,
setup=setup,
teardown=teardown,
rounds=rounds,
warmup_rounds=warmup_rounds,
iterations=iterations,
)
if self._plugin.is_codspeed_enabled:
return _measure(
self._plugin,
self._request.node,
self._config,
pedantic_options,
target,
args,
kwargs,
)
else:
args, kwargs = pedantic_options.setup_and_get_args_kwargs()
result = target(*args, **kwargs)
if pedantic_options.teardown is not None:
pedantic_options.teardown(*args, **kwargs)
return result
@pytest.fixture(scope="function")
def codspeed_benchmark(request: pytest.FixtureRequest) -> Callable:
return BenchmarkFixture(request)
if not IS_PYTEST_BENCHMARK_INSTALLED:
@pytest.fixture(scope="function")
def benchmark(codspeed_benchmark, request: pytest.FixtureRequest):
"""
Compatibility with pytest-benchmark
"""
return codspeed_benchmark
pytest-codspeed-4.2.0/src/pytest_codspeed/py.typed 0000664 0000000 0000000 00000000000 15076637504 0022320 0 ustar 00root root 0000000 0000000 pytest-codspeed-4.2.0/src/pytest_codspeed/utils.py 0000664 0000000 0000000 00000004541 15076637504 0022351 0 ustar 00root root 0000000 0000000 from __future__ import annotations
import importlib.util
import os
import sys
import sysconfig
from pathlib import Path
import pytest
from pytest_codspeed import __semver_version__
if sys.version_info < (3, 10):
import importlib_metadata as importlib_metadata
else:
import importlib.metadata as importlib_metadata
IS_PYTEST_BENCHMARK_INSTALLED = importlib.util.find_spec("pytest_benchmark") is not None
IS_PYTEST_SPEED_INSTALLED = importlib.util.find_spec("pytest_speed") is not None
BEFORE_PYTEST_8_1_1 = pytest.version_tuple < (8, 1, 1)
SUPPORTS_PERF_TRAMPOLINE = sysconfig.get_config_var("PY_HAVE_PERF_TRAMPOLINE") == 1
def get_git_relative_path(abs_path: Path) -> Path:
"""Get the path relative to the git root directory. If the path is not
inside a git repository, the original path itself is returned.
"""
git_path = Path(abs_path).resolve()
while (
git_path != git_path.parent
): # stops at root since parent of root is root itself
if (git_path / ".git").exists():
return abs_path.resolve().relative_to(git_path)
git_path = git_path.parent
return abs_path
def get_git_relative_uri_and_name(nodeid: str, pytest_rootdir: Path) -> tuple[str, str]:
"""Get the benchmark uri relative to the git root dir and the benchmark name.
Args:
nodeid (str): the pytest nodeid, for example:
testing/test_excinfo.py::TestFormattedExcinfo::test_repr_source
pytest_rootdir (str): the pytest root dir, for example:
/home/user/gitrepo/folder
Returns:
str: the benchmark uri relative to the git root dir, for example:
folder/testing/test_excinfo.py::TestFormattedExcinfo::test_repr_source
"""
file_path, bench_name = nodeid.split("::", 1)
absolute_file_path = pytest_rootdir / Path(file_path)
relative_git_path = get_git_relative_path(absolute_file_path)
return (f"{str(relative_git_path)}::{bench_name}", bench_name)
def get_environment_metadata() -> dict[str, dict]:
return {
"creator": {
"name": "pytest-codspeed",
"version": __semver_version__,
"pid": os.getpid(),
},
"python": {
"sysconfig": sysconfig.get_config_vars(),
"dependencies": {
d.name: d.version for d in importlib_metadata.distributions()
},
},
}
pytest-codspeed-4.2.0/tests/ 0000775 0000000 0000000 00000000000 15076637504 0016010 5 ustar 00root root 0000000 0000000 pytest-codspeed-4.2.0/tests/benchmarks/ 0000775 0000000 0000000 00000000000 15076637504 0020125 5 ustar 00root root 0000000 0000000 pytest-codspeed-4.2.0/tests/benchmarks/TheAlgorithms/ 0000775 0000000 0000000 00000000000 15076637504 0022677 5 ustar 00root root 0000000 0000000 pytest-codspeed-4.2.0/tests/benchmarks/TheAlgorithms_bench/ 0000775 0000000 0000000 00000000000 15076637504 0024036 5 ustar 00root root 0000000 0000000 pytest-codspeed-4.2.0/tests/benchmarks/TheAlgorithms_bench/__init__.py 0000664 0000000 0000000 00000000000 15076637504 0026135 0 ustar 00root root 0000000 0000000 pytest-codspeed-4.2.0/tests/benchmarks/TheAlgorithms_bench/bit_manipulation.py 0000664 0000000 0000000 00000014263 15076637504 0027754 0 ustar 00root root 0000000 0000000 import pytest
from bit_manipulation.binary_and_operator import binary_and
from bit_manipulation.binary_coded_decimal import binary_coded_decimal
from bit_manipulation.binary_count_setbits import binary_count_setbits
from bit_manipulation.binary_count_trailing_zeros import binary_count_trailing_zeros
from bit_manipulation.binary_or_operator import binary_or
from bit_manipulation.binary_shifts import (
arithmetic_right_shift,
logical_left_shift,
logical_right_shift,
)
from bit_manipulation.binary_twos_complement import twos_complement
from bit_manipulation.binary_xor_operator import binary_xor
from bit_manipulation.count_1s_brian_kernighan_method import get_1s_count
from bit_manipulation.excess_3_code import excess_3_code
from bit_manipulation.find_previous_power_of_two import find_previous_power_of_two
from bit_manipulation.gray_code_sequence import gray_code
from bit_manipulation.highest_set_bit import get_highest_set_bit_position
from bit_manipulation.is_even import is_even
from bit_manipulation.largest_pow_of_two_le_num import largest_pow_of_two_le_num
from bit_manipulation.missing_number import find_missing_number
from bit_manipulation.numbers_different_signs import different_signs
from bit_manipulation.power_of_4 import power_of_4
from bit_manipulation.reverse_bits import reverse_bit
from bit_manipulation.single_bit_manipulation_operations import (
clear_bit,
flip_bit,
get_bit,
is_bit_set,
set_bit,
)
from bit_manipulation.swap_all_odd_and_even_bits import swap_odd_even_bits
@pytest.mark.parametrize("a, b", [(25, 32), (37, 50), (21, 30), (58, 73)])
def test_binary_and(benchmark, a, b):
benchmark(binary_and, a, b)
@pytest.mark.parametrize("a, b", [(25, 32), (37, 50), (21, 30), (58, 73)])
def test_binary_or(benchmark, a, b):
benchmark(binary_or, a, b)
@pytest.mark.parametrize("a, b", [(25, 32), (37, 50), (21, 30), (58, 73)])
def test_binary_xor(benchmark, a, b):
benchmark(binary_xor, a, b)
@pytest.mark.parametrize("a", [25, 36, 16, 58, 4294967295, 0])
def test_binary_count_setbits(benchmark, a):
benchmark(binary_count_setbits, a)
@pytest.mark.parametrize("a", [25, 36, 16, 58, 4294967296, 0])
def test_binary_count_trailing_zeros(benchmark, a):
benchmark(binary_count_trailing_zeros, a)
@pytest.mark.parametrize("a", [-1, -5, -17, -207])
def test_twos_complement(benchmark, a):
benchmark(twos_complement, a)
@pytest.mark.parametrize("a", [25, 37, 21, 58, 0, 256])
def test_get_1s_count(benchmark, a):
benchmark(get_1s_count, a)
@pytest.mark.parametrize("a", [25, 37, 21, 58, 0, 256])
def test_reverse_bit(benchmark, a):
benchmark(reverse_bit, a)
@pytest.mark.parametrize("number, position", [(0b1101, 1), (0b0, 5), (0b1111, 1)])
def test_set_bit(benchmark, number, position):
benchmark(set_bit, number, position)
@pytest.mark.parametrize("number, position", [(0b10010, 1), (0b0, 5)])
def test_clear_bit(benchmark, number, position):
benchmark(clear_bit, number, position)
@pytest.mark.parametrize("number, position", [(0b101, 1), (0b101, 0)])
def test_flip_bit(benchmark, number, position):
benchmark(flip_bit, number, position)
@pytest.mark.parametrize(
"number, position", [(0b1010, 0), (0b1010, 1), (0b1010, 2), (0b1010, 3), (0b0, 17)]
)
def test_is_bit_set(benchmark, number, position):
benchmark(is_bit_set, number, position)
@pytest.mark.parametrize(
"number, position", [(0b1010, 0), (0b1010, 1), (0b1010, 2), (0b1010, 3)]
)
def test_get_bit(benchmark, number, position):
benchmark(get_bit, number, position)
@pytest.mark.parametrize(
"number, shift_amount", [(0, 1), (1, 1), (1, 5), (17, 2), (1983, 4)]
)
def test_logical_left_shift(benchmark, number, shift_amount):
benchmark(logical_left_shift, number, shift_amount)
@pytest.mark.parametrize(
"number, shift_amount", [(0, 1), (1, 1), (1, 5), (17, 2), (1983, 4)]
)
def test_logical_right_shift(benchmark, number, shift_amount):
benchmark(logical_right_shift, number, shift_amount)
@pytest.mark.parametrize(
"number, shift_amount", [(0, 1), (1, 1), (-1, 1), (17, 2), (-17, 2), (-1983, 4)]
)
def test_arithmetic_right_shift(benchmark, number, shift_amount):
benchmark(arithmetic_right_shift, number, shift_amount)
@pytest.mark.parametrize("number", [0, 3, 2, 12, 987])
def test_binary_coded_decimal(benchmark, number):
benchmark(binary_coded_decimal, number)
@pytest.mark.parametrize("number", [0, 3, 2, 20, 120])
def test_excess_3_code(benchmark, number):
benchmark(excess_3_code, number)
@pytest.mark.parametrize("number", [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 16, 17])
def test_find_previous_power_of_two(benchmark, number):
benchmark(find_previous_power_of_two, number)
@pytest.mark.parametrize("bit_count", [1, 2, 3])
def test_gray_code(benchmark, bit_count):
benchmark(gray_code, bit_count)
@pytest.mark.parametrize("number", [25, 37, 1, 4, 0])
def test_get_highest_set_bit_position(benchmark, number):
benchmark(get_highest_set_bit_position, number)
@pytest.mark.parametrize("number", [1, 4, 9, 15, 40, 100, 101])
def test_is_even(benchmark, number):
benchmark(is_even, number)
@pytest.mark.parametrize("number", [0, 1, 3, 15, 99, 178, 999999])
def test_largest_pow_of_two_le_num(benchmark, number):
benchmark(largest_pow_of_two_le_num, number)
@pytest.mark.parametrize(
"nums",
[
[0, 1, 3, 4],
[4, 3, 1, 0],
[-4, -3, -1, 0],
[-2, 2, 1, 3, 0],
[1, 3, 4, 5, 6],
[6, 5, 4, 2, 1],
[6, 1, 5, 3, 4],
],
)
def test_find_missing_number(benchmark, nums):
benchmark(find_missing_number, nums)
@pytest.mark.parametrize(
"num1, num2",
[
(1, -1),
(1, 1),
(1000000000000000000000000000, -1000000000000000000000000000),
(-1000000000000000000000000000, 1000000000000000000000000000),
(50, 278),
(0, 2),
(2, 0),
],
)
def test_different_signs(benchmark, num1, num2):
benchmark(different_signs, num1, num2)
@pytest.mark.parametrize("number", [1, 2, 4, 6, 8, 17, 64])
def test_power_of_4(benchmark, number):
benchmark(power_of_4, number)
@pytest.mark.parametrize("number", [0, 1, 2, 3, 4, 5, 6, 23, 24])
def test_swap_odd_even_bits(benchmark, number):
benchmark(swap_odd_even_bits, number)
pytest-codspeed-4.2.0/tests/benchmarks/TheAlgorithms_bench/test_bench_audio_filters.py 0000664 0000000 0000000 00000002150 15076637504 0031435 0 ustar 00root root 0000000 0000000 import pytest
from audio_filters.butterworth_filter import (
make_allpass,
make_bandpass,
make_highpass,
make_highshelf,
make_lowpass,
make_lowshelf,
make_peak,
)
from audio_filters.iir_filter import IIRFilter
def test_make_lowpass(benchmark):
benchmark(make_lowpass, 1000, 48000)
def test_make_highpass(benchmark):
benchmark(make_highpass, 1000, 48000)
def test_make_bandpass(benchmark):
benchmark(make_bandpass, 1000, 48000)
def test_make_allpass(benchmark):
benchmark(make_allpass, 1000, 48000)
def test_make_peak(benchmark):
benchmark(make_peak, 1000, 48000, 6)
def test_make_lowshelf(benchmark):
benchmark(make_lowshelf, 1000, 48000, 6)
def test_make_highshelf(benchmark):
benchmark(make_highshelf, 1000, 48000, 6)
@pytest.mark.parametrize("a_coeffs, b_coeffs", [([1.0, -1.8, 0.81], [0.9, -1.8, 0.81])])
def test_iir_filter_set_coefficients(benchmark, a_coeffs, b_coeffs):
filt = IIRFilter(2)
benchmark(filt.set_coefficients, a_coeffs, b_coeffs)
def test_iir_filter_process(benchmark):
filt = IIRFilter(2)
benchmark(filt.process, 0)
pytest-codspeed-4.2.0/tests/benchmarks/TheAlgorithms_bench/test_bench_backtracking.py 0000664 0000000 0000000 00000013027 15076637504 0031234 0 ustar 00root root 0000000 0000000 import math
import pytest
from backtracking.all_combinations import combination_lists, generate_all_combinations
from backtracking.all_permutations import generate_all_permutations
from backtracking.all_subsequences import generate_all_subsequences
from backtracking.coloring import color
from backtracking.combination_sum import combination_sum
from backtracking.crossword_puzzle_solver import solve_crossword
from backtracking.generate_parentheses import generate_parenthesis
from backtracking.hamiltonian_cycle import hamilton_cycle
from backtracking.knight_tour import get_valid_pos, is_complete, open_knight_tour
from backtracking.match_word_pattern import match_word_pattern
from backtracking.minimax import minimax
from backtracking.n_queens import is_safe
from backtracking.n_queens import solve as n_queens_solve
from backtracking.n_queens_math import depth_first_search
from backtracking.power_sum import solve
from backtracking.rat_in_maze import solve_maze
from backtracking.sudoku import sudoku
from backtracking.sum_of_subsets import generate_sum_of_subsets_soln
from backtracking.word_search import word_exists
@pytest.mark.parametrize("sequence", [[1, 2, 3], ["A", "B", "C"]])
def test_generate_all_permutations(benchmark, sequence):
benchmark(generate_all_permutations, sequence)
@pytest.mark.parametrize("n, k", [(4, 2), (0, 0), (5, 4)])
def test_combination_lists(benchmark, n, k):
benchmark(combination_lists, n, k)
@pytest.mark.parametrize("n, k", [(4, 2), (0, 0), (5, 4)])
def test_generate_all_combinations(benchmark, n, k):
benchmark(generate_all_combinations, n, k)
@pytest.mark.parametrize("sequence", [[3, 2, 1], ["A", "B"]])
def test_generate_all_subsequences(benchmark, sequence):
benchmark(generate_all_subsequences, sequence)
@pytest.mark.parametrize("candidates, target", [([2, 3, 5], 8)])
def test_combination_sum(benchmark, candidates, target):
benchmark(combination_sum, candidates, target)
@pytest.mark.parametrize(
"initial_grid",
[
[
[3, 0, 6, 5, 0, 8, 4, 0, 0],
[5, 2, 0, 0, 0, 0, 0, 0, 0],
[0, 8, 7, 0, 0, 0, 0, 3, 1],
[0, 0, 3, 0, 1, 0, 0, 8, 0],
[9, 0, 0, 8, 6, 3, 0, 0, 5],
[0, 5, 0, 0, 9, 0, 6, 0, 0],
[1, 3, 0, 0, 0, 0, 2, 5, 0],
[0, 0, 0, 0, 0, 0, 0, 7, 4],
[0, 0, 5, 2, 0, 6, 3, 0, 0],
]
],
)
def test_sudoku(benchmark, initial_grid):
benchmark(sudoku, initial_grid)
@pytest.mark.parametrize("nums, max_sum", [([3, 34, 4, 12, 5, 2], 9)])
def test_generate_sum_of_subsets_soln(benchmark, nums, max_sum):
benchmark(generate_sum_of_subsets_soln, nums, max_sum)
@pytest.mark.parametrize("scores", [[90, 23, 6, 33, 21, 65, 123, 34423]])
def test_minimax(benchmark, scores):
height = math.log(len(scores), 2)
benchmark(minimax, 0, 0, True, scores, height)
@pytest.mark.parametrize(
"graph, max_colors",
[
(
[
[0, 1, 0, 0, 0],
[1, 0, 1, 0, 1],
[0, 1, 0, 1, 0],
[0, 1, 1, 0, 0],
[0, 1, 0, 0, 0],
],
3,
)
],
)
def test_color(benchmark, graph, max_colors):
benchmark(color, graph, max_colors)
@pytest.mark.parametrize("n", [3])
def test_generate_parenthesis(benchmark, n):
benchmark(generate_parenthesis, n)
@pytest.mark.parametrize("x, n", [(13, 2)])
def test_solve_power_sum(benchmark, x, n):
benchmark(solve, x, n)
@pytest.mark.parametrize("board, row, col", [([[0, 0, 0], [0, 0, 0], [0, 0, 0]], 1, 1)])
def test_is_safe(benchmark, board, row, col):
benchmark(is_safe, board, row, col)
@pytest.mark.parametrize("board", [[[0 for i in range(4)] for j in range(4)]])
def test_n_queens_solve(benchmark, board):
benchmark(n_queens_solve, board, 0)
@pytest.mark.parametrize("pattern, string", [("aba", "GraphTreesGraph")])
def test_match_word_pattern(benchmark, pattern, string):
benchmark(match_word_pattern, pattern, string)
@pytest.mark.parametrize("pos, board_size", [((1, 3), 4)])
def test_get_valid_pos(benchmark, pos, board_size):
benchmark(get_valid_pos, pos, board_size)
@pytest.mark.parametrize("board", [[[1]]])
def test_is_complete(benchmark, board):
benchmark(is_complete, board)
@pytest.mark.parametrize("board_size", [1])
def test_open_knight_tour(benchmark, board_size):
benchmark(open_knight_tour, board_size)
@pytest.mark.parametrize(
"graph",
[
[
[0, 1, 0, 1, 0],
[1, 0, 1, 1, 1],
[0, 1, 0, 0, 1],
[1, 1, 0, 0, 1],
[0, 1, 1, 1, 0],
]
],
)
def test_hamilton_cycle(benchmark, graph):
benchmark(hamilton_cycle, graph)
@pytest.mark.parametrize(
"maze",
[
[
[0, 1, 0, 1, 1],
[0, 0, 0, 0, 0],
[1, 0, 1, 0, 1],
[0, 0, 1, 0, 0],
[1, 0, 0, 1, 0],
]
],
)
def test_solve_maze(benchmark, maze):
benchmark(solve_maze, maze, 0, 0, len(maze) - 1, len(maze) - 1)
@pytest.mark.parametrize(
"board, word",
[([["A", "B", "C", "E"], ["S", "F", "C", "S"], ["A", "D", "E", "E"]], "ABCCED")],
)
def test_word_exists(benchmark, board, word):
benchmark(word_exists, board, word)
@pytest.mark.parametrize(
"puzzle, words", [([[""] * 3 for _ in range(3)], ["cat", "dog", "car"])]
)
def test_solve_crossword(benchmark, puzzle, words):
benchmark(solve_crossword, puzzle, words)
@pytest.mark.parametrize("n", [4])
def test_depth_first_search(benchmark, n):
boards = []
benchmark(depth_first_search, [], [], [], boards, n)
pytest-codspeed-4.2.0/tests/benchmarks/__init__.py 0000664 0000000 0000000 00000000000 15076637504 0022224 0 ustar 00root root 0000000 0000000 pytest-codspeed-4.2.0/tests/benchmarks/test_bench_doc.py 0000664 0000000 0000000 00000000771 15076637504 0023447 0 ustar 00root root 0000000 0000000 """Benches from the CodSpeed Getting Started Documentation."""
import pytest
def sum_of_squares_fast(arr) -> int:
total = 0
for x in arr:
total += x * x
return total
def sum_of_squares_slow(arr) -> int:
return sum(map(lambda x: x**2, arr)) # noqa: C417
@pytest.mark.benchmark
def test_sum_squares_fast():
assert sum_of_squares_fast(range(1000)) == 332833500
@pytest.mark.benchmark
def test_sum_squares_slow():
assert sum_of_squares_slow(range(1000)) == 332833500
pytest-codspeed-4.2.0/tests/benchmarks/test_bench_fibo.py 0000664 0000000 0000000 00000002014 15076637504 0023611 0 ustar 00root root 0000000 0000000 def recursive_fibonacci(n: int) -> int:
if n in [0, 1]:
return n
return recursive_fibonacci(n - 1) + recursive_fibonacci(n - 2)
def recursive_cached_fibonacci(n: int) -> int:
cache = {0: 0, 1: 1}
def fibo(n) -> int:
if n in cache:
return cache[n]
cache[n] = fibo(n - 1) + fibo(n - 2)
return cache[n]
return fibo(n)
def iterative_fibonacci(n: int) -> int:
a, b = 0, 1
for _ in range(n):
a, b = b, a + b
return a
def test_iterative_fibo_10(benchmark):
@benchmark
def _():
iterative_fibonacci(10)
def test_recursive_fibo_10(benchmark):
@benchmark
def _():
recursive_fibonacci(10)
def test_recursive_fibo_20(benchmark):
@benchmark
def _():
recursive_fibonacci(20)
def test_recursive_cached_fibo_10(benchmark):
@benchmark
def _():
recursive_cached_fibonacci(10)
def test_recursive_cached_fibo_100(benchmark):
@benchmark
def _():
recursive_cached_fibonacci(100)
pytest-codspeed-4.2.0/tests/benchmarks/test_bench_misc.py 0000664 0000000 0000000 00000003042 15076637504 0023627 0 ustar 00root root 0000000 0000000 import pytest
def count_even_fast(arr):
"""Count the number of even numbers in an array."""
even = 0
for x in arr:
if x % 2 == 0:
even += 1
return even
def count_even_slow(arr):
"""Count the number of even numbers in an array."""
return sum(1 for x in arr if x % 2 == 0)
@pytest.mark.parametrize(
"func",
[
count_even_fast,
count_even_slow,
],
)
def test_count_even(func, benchmark):
assert benchmark(func, range(10_000)) == 5000
def sum_of_squares_for_loop_product(arr) -> int:
total = 0
for x in arr:
total += x * x
return total
def sum_of_squares_for_loop_power(arr) -> int:
total = 0
for x in arr:
total += x**2
return total
def sum_of_squares_sum_labmda_product(arr) -> int:
return sum(map(lambda x: x * x, arr)) # noqa: C417
def sum_of_squares_sum_labmda_power(arr) -> int:
return sum(map(lambda x: x**2, arr)) # noqa: C417
def sum_of_squares_sum_comprehension_product(arr) -> int:
return sum(x * x for x in arr)
def sum_of_squares_sum_comprehension_power(arr) -> int:
return sum(x**2 for x in arr)
@pytest.mark.parametrize(
"func",
[
sum_of_squares_for_loop_product,
sum_of_squares_for_loop_power,
sum_of_squares_sum_labmda_product,
sum_of_squares_sum_labmda_power,
sum_of_squares_sum_comprehension_product,
sum_of_squares_sum_comprehension_power,
],
)
@pytest.mark.benchmark
def test_sum_of_squares(func):
assert func(range(1000)) == 332833500
pytest-codspeed-4.2.0/tests/benchmarks/test_bench_syscalls.py 0000664 0000000 0000000 00000007760 15076637504 0024544 0 ustar 00root root 0000000 0000000 import concurrent.futures
import mmap
import multiprocessing
import os
import socket
from socket import gethostbyname
from tempfile import NamedTemporaryFile
from time import sleep
import pytest
@pytest.mark.parametrize("sleep_time", [0.001, 0.01, 0.05, 0.1])
def test_sleep(benchmark, sleep_time):
benchmark(sleep, sleep_time)
@pytest.mark.parametrize("array_size", [100, 1_000, 10_000, 100_000])
def test_array_alloc(benchmark, array_size):
benchmark(lambda: [0] * array_size)
@pytest.mark.parametrize("num_fds", [10, 100, 1000])
def test_open_close_fd(benchmark, num_fds):
def open_close_fds():
fds = [os.open("/dev/null", os.O_RDONLY) for _ in range(num_fds)]
for fd in fds:
os.close(fd)
benchmark(open_close_fds)
def test_dup_fd(benchmark):
def dup_fd():
fd = os.open("/dev/null", os.O_RDONLY)
new_fd = os.dup(fd)
os.close(new_fd)
os.close(fd)
benchmark(dup_fd)
@pytest.mark.parametrize("content_length", [100, 1000, 10_000, 100_000, 1_000_000])
def test_fs_write(benchmark, content_length):
content = "a" * content_length
f = NamedTemporaryFile(mode="w")
@benchmark
def write_to_file():
f.write(content)
f.flush()
f.close()
@pytest.mark.parametrize("content_length", [100, 1000, 10_000, 100_000, 1_000_000])
def test_fs_read(benchmark, content_length):
with open("/dev/urandom", "rb") as f:
benchmark(f.read, content_length)
@pytest.mark.parametrize(
"host",
["localhost", "127.0.0.1", "1.1.1.1", "8.8.8.8", "google.com", "amazon.com"],
)
def test_hostname_resolution(benchmark, host):
benchmark(gethostbyname, host)
@pytest.mark.parametrize(
"host, port",
[("8.8.8.8", 53), ("1.1.1.1", 53), ("google.com", 443), ("wikipedia.org", 443)],
)
def test_tcp_connection(benchmark, host, port):
def connect():
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
sock.connect((host, port))
finally:
sock.close()
benchmark(connect)
@pytest.mark.parametrize("command", ["echo hello", "ls -l", "cat /dev/null"])
def test_process_creation(benchmark, command):
def create_process():
process = os.popen(command)
process.read()
process.close()
benchmark(create_process)
@pytest.mark.parametrize("message_size", [10, 100, 1000, 10000])
def test_pipe_communication(benchmark, message_size):
def pipe_comm():
r, w = os.pipe()
pid = os.fork()
if pid == 0: # child process
os.close(r)
os.write(w, b"x" * message_size)
os._exit(0)
else: # parent process
os.close(w)
os.read(r, message_size)
os.waitpid(pid, 0)
os.close(r)
benchmark(pipe_comm)
@pytest.mark.parametrize("map_size", [4096, 40960, 409600])
def test_mmap_operation(benchmark, map_size):
# Create a temporary file outside the benchmarked function
temp_file = NamedTemporaryFile(mode="w+b", delete=False)
temp_file.write(b"\0" * map_size)
temp_file.flush()
temp_file.close()
mfd = os.open(temp_file.name, os.O_RDONLY)
def mmap_op():
mm = mmap.mmap(mfd, map_size, access=mmap.ACCESS_READ)
mm.read(map_size)
benchmark(mmap_op)
os.close(mfd)
def multi_task(x):
"""Multiprocessing need this function to be defined at the top level."""
return x * x
@pytest.mark.parametrize("num_tasks", [10, 100, 1000, 10000, 100000])
def test_threadpool_map(benchmark, num_tasks):
def threadpool_map():
with concurrent.futures.ThreadPoolExecutor(max_workers=8) as executor:
list(executor.map(multi_task, range(num_tasks)))
benchmark(threadpool_map)
@pytest.mark.parametrize("num_tasks", [10, 100, 1000, 10000, 100000])
def test_multiprocessing_map(benchmark, num_tasks):
def multiprocessing_map():
with multiprocessing.Pool(processes=8) as pool:
list(pool.map(multi_task, range(num_tasks)))
benchmark(multiprocessing_map)
pytest-codspeed-4.2.0/tests/benchmarks/test_bench_various_noop.py 0000664 0000000 0000000 00000001066 15076637504 0025423 0 ustar 00root root 0000000 0000000 def noop_pass():
pass
def noop_ellipsis(): ...
def noop_lambda():
(lambda: None)()
def test_noop_pass(benchmark):
benchmark(noop_pass)
def test_noop_ellipsis(benchmark):
benchmark(noop_ellipsis)
def test_noop_lambda(benchmark):
benchmark(noop_lambda)
def test_noop_pass_decorated(benchmark):
@benchmark
def _():
noop_pass()
def test_noop_ellipsis_decorated(benchmark):
@benchmark
def _():
noop_ellipsis()
def test_noop_lambda_decorated(benchmark):
@benchmark
def _():
noop_lambda()
pytest-codspeed-4.2.0/tests/conftest.py 0000664 0000000 0000000 00000004754 15076637504 0020221 0 ustar 00root root 0000000 0000000 from __future__ import annotations
import importlib.util
import os
import shutil
import sys
from contextlib import contextmanager
from typing import TYPE_CHECKING
import pytest
from pytest_codspeed.instruments import MeasurementMode
from pytest_codspeed.utils import IS_PYTEST_BENCHMARK_INSTALLED
if TYPE_CHECKING:
from _pytest.pytester import RunResult
pytest_plugins = ["pytester"]
skip_without_pytest_benchmark = pytest.mark.skipif(
not IS_PYTEST_BENCHMARK_INSTALLED, reason="pytest_benchmark not installed"
)
skip_with_pytest_benchmark = pytest.mark.skipif(
IS_PYTEST_BENCHMARK_INSTALLED, reason="pytest_benchmark installed"
)
if IS_PYTEST_BENCHMARK_INSTALLED:
pytest_plugins.append("pytest_benchmark")
print(
"NOTICE: Testing with pytest-benchmark compatibility",
file=sys.stderr,
flush=True,
)
IS_VALGRIND_INSTALLED = shutil.which("valgrind") is not None
skip_without_valgrind = pytest.mark.skipif(
"PYTEST_CODSPEED_FORCE_VALGRIND_TESTS" not in os.environ
and not IS_VALGRIND_INSTALLED,
reason="valgrind not installed",
)
if IS_VALGRIND_INSTALLED:
print("NOTICE: Testing with valgrind compatibility", file=sys.stderr, flush=True)
IS_PERF_TRAMPOLINE_SUPPORTED = sys.version_info >= (3, 12)
skip_without_perf_trampoline = pytest.mark.skipif(
not IS_PERF_TRAMPOLINE_SUPPORTED, reason="perf trampoline is not supported"
)
skip_with_perf_trampoline = pytest.mark.skipif(
IS_PERF_TRAMPOLINE_SUPPORTED, reason="perf trampoline is supported"
)
# The name for the pytest-xdist plugin is just "xdist"
IS_PYTEST_XDIST_INSTALLED = importlib.util.find_spec("xdist") is not None
skip_without_pytest_xdist = pytest.mark.skipif(
not IS_PYTEST_XDIST_INSTALLED,
reason="pytest_xdist not installed",
)
@pytest.fixture(scope="function")
def codspeed_env(monkeypatch):
@contextmanager
def ctx_manager():
monkeypatch.setenv("CODSPEED_ENV", "1")
try:
yield
finally:
monkeypatch.delenv("CODSPEED_ENV", raising=False)
return ctx_manager
def run_pytest_codspeed_with_mode(
pytester: pytest.Pytester, mode: MeasurementMode, *args, **kwargs
) -> RunResult:
csargs = [
"--codspeed",
f"--codspeed-mode={mode.value}",
]
if mode == MeasurementMode.WallTime:
# Run only 1 round to speed up the test times
csargs.extend(["--codspeed-warmup-time=0", "--codspeed-max-rounds=2"])
return pytester.runpytest(
*csargs,
*args,
**kwargs,
)
pytest-codspeed-4.2.0/tests/examples/ 0000775 0000000 0000000 00000000000 15076637504 0017626 5 ustar 00root root 0000000 0000000 pytest-codspeed-4.2.0/tests/examples/__init__.py 0000664 0000000 0000000 00000000000 15076637504 0021725 0 ustar 00root root 0000000 0000000 pytest-codspeed-4.2.0/tests/examples/test_addition_fixture.py 0000664 0000000 0000000 00000000140 15076637504 0024573 0 ustar 00root root 0000000 0000000 def test_some_addition_performance(benchmark):
@benchmark
def _():
return 1 + 1
pytest-codspeed-4.2.0/tests/test_pytest_plugin.py 0000664 0000000 0000000 00000031477 15076637504 0022343 0 ustar 00root root 0000000 0000000 import pytest
from conftest import (
IS_PERF_TRAMPOLINE_SUPPORTED,
MeasurementMode,
run_pytest_codspeed_with_mode,
skip_with_perf_trampoline,
skip_without_pytest_benchmark,
skip_without_valgrind,
)
@pytest.mark.parametrize("mode", [*MeasurementMode])
def test_plugin_enabled_with_kwargs(
pytester: pytest.Pytester, mode: MeasurementMode
) -> None:
pytester.makepyfile(
"""
def test_arg_kwarg_addition(benchmark):
def fn(arg, kwarg=None):
assert arg + kwarg == 40
benchmark(fn, 25, kwarg=15)
"""
)
result = run_pytest_codspeed_with_mode(pytester, mode)
result.assert_outcomes(passed=1)
@skip_without_valgrind
@skip_with_perf_trampoline
def test_bench_enabled_header_without_perf(
pytester: pytest.Pytester, codspeed_env
) -> None:
pytester.copy_example("tests/examples/test_addition_fixture.py")
with codspeed_env():
result = pytester.runpytest()
result.stdout.fnmatch_lines(
["codspeed: * (enabled, mode: instrumentation, callgraph: not supported)"]
)
@skip_without_valgrind
def test_plugin_enabled_by_env(pytester: pytest.Pytester, codspeed_env) -> None:
pytester.copy_example("tests/examples/test_addition_fixture.py")
with codspeed_env():
result = pytester.runpytest()
result.stdout.fnmatch_lines(["*1 benchmarked*", "*1 passed*"])
@skip_without_valgrind
def test_plugin_enabled_and_env(pytester: pytest.Pytester, codspeed_env) -> None:
pytester.copy_example("tests/examples/test_addition_fixture.py")
with codspeed_env():
result = pytester.runpytest("--codspeed")
result.stdout.fnmatch_lines(["*1 benchmarked*", "*1 passed*"])
@skip_without_valgrind
def test_plugin_enabled_and_env_bench_run_once(
pytester: pytest.Pytester, codspeed_env
) -> None:
pytester.makepyfile(
"""
import pytest
@pytest.mark.benchmark
def test_noisy_bench_marked():
print() # make sure noise is on its own line
print("I'm noisy marked!!!")
print()
def test_noisy_bench_fxt(benchmark):
@benchmark
def _():
print() # make sure noise is on its own line
print("I'm noisy fixtured!!!")
print()
"""
)
EXPECTED_OUTPUT_COUNT = 2 if IS_PERF_TRAMPOLINE_SUPPORTED else 1
with codspeed_env():
run_result = pytester.runpytest("--codspeed", "-s")
print(run_result.stdout.str())
assert run_result.outlines.count("I'm noisy marked!!!") == EXPECTED_OUTPUT_COUNT
assert (
run_result.outlines.count("I'm noisy fixtured!!!") == EXPECTED_OUTPUT_COUNT
)
@pytest.mark.parametrize("mode", [*MeasurementMode])
def test_plugin_enabled_and_env_bench_hierachy_called(
pytester: pytest.Pytester, mode: MeasurementMode
) -> None:
pytester.makepyfile(
"""
import pytest
import time
class TestGroup:
def setup_method(self):
print(); print("Setup called")
def teardown_method(self):
print(); print("Teardown called")
@pytest.mark.benchmark
def test_child(self):
time.sleep(0.1) # Avoids the test being too fast
print(); print("Test called")
"""
)
result = run_pytest_codspeed_with_mode(pytester, mode, "-s")
result.stdout.fnmatch_lines(
[
"Setup called",
"Test called",
"Teardown called",
]
)
def test_plugin_disabled(pytester: pytest.Pytester) -> None:
pytester.copy_example("tests/examples/test_addition_fixture.py")
result = pytester.runpytest()
result.stdout.fnmatch_lines(["*1 passed*"])
@skip_without_valgrind
def test_plugin_enabled_nothing_to_benchmark(
pytester: pytest.Pytester, codspeed_env
) -> None:
pytester.makepyfile(
"""
def test_some_addition_performance():
return 1 + 1
"""
)
with codspeed_env():
result = pytester.runpytest("--codspeed")
result.stdout.fnmatch_lines(["*0 benchmarked*", "*1 deselected*"])
@pytest.mark.parametrize("mode", [*MeasurementMode])
def test_plugin_only_benchmark_collection(
pytester: pytest.Pytester, mode: MeasurementMode
) -> None:
pytester.makepyfile(
"""
import pytest
@pytest.mark.codspeed_benchmark
def test_some_addition_performance():
return 1 + 1
@pytest.mark.benchmark
def test_some_addition_performance_shorthand():
return 1 + 1
def test_some_wrapped_benchmark(benchmark):
@benchmark
def _():
hello = "hello"
def test_another_useless_thing():
assert True
"""
)
collection_result = run_pytest_codspeed_with_mode(pytester, mode, "--collect-only")
collection_result.stdout.fnmatch_lines_random(
[
"**",
"**",
"**",
],
)
collection_result.assert_outcomes(
deselected=1,
)
collection_result = run_pytest_codspeed_with_mode(
pytester, mode, "--collect-only", "-k", "test_some_wrapped_benchmark"
)
collection_result.stdout.fnmatch_lines_random(
[
"**",
],
)
collection_result.assert_outcomes(
deselected=3,
)
@skip_without_pytest_benchmark
def test_pytest_benchmark_compatibility(pytester: pytest.Pytester) -> None:
pytester.makepyfile(
"""
def test_some_wrapped_benchmark(benchmark):
@benchmark
def _():
hello = "hello"
"""
)
result = pytester.runpytest(
"--benchmark-only",
"--benchmark-max-time=0",
"--benchmark-warmup-iterations=1",
)
result.stdout.fnmatch_lines_random(
[
"*benchmark: 1 tests*",
"*Name*",
"*test_some_wrapped_benchmark*",
"*Legend:*",
"*Outliers:*",
"*OPS: Operations Per Second*",
"*Outliers:*",
"*1 passed*",
]
)
def test_codspeed_marker_unexpected_args(pytester: pytest.Pytester) -> None:
pytester.makepyfile(
"""
import pytest
@pytest.mark.codspeed_benchmark(
"positional_arg"
)
def test_bench():
pass
"""
)
result = pytester.runpytest("--codspeed")
assert result.ret == 1
result.stdout.fnmatch_lines_random(
["*ValueError: Positional arguments are not allowed in the benchmark marker*"],
)
def test_codspeed_marker_unexpected_kwargs(pytester: pytest.Pytester) -> None:
pytester.makepyfile(
"""
import pytest
@pytest.mark.codspeed_benchmark(
not_allowed=True
)
def test_bench():
pass
"""
)
result = pytester.runpytest("--codspeed")
assert result.ret == 1
result.stdout.fnmatch_lines_random(
[
"*ValueError: Unknown kwargs passed to benchmark marker: not_allowed*",
],
)
def test_pytest_benchmark_extra_info(pytester: pytest.Pytester) -> None:
"""https://pytest-benchmark.readthedocs.io/en/latest/usage.html#extra-info"""
pytester.makepyfile(
"""
import time
def test_my_stuff(benchmark):
benchmark.extra_info['foo'] = 'bar'
benchmark(time.sleep, 0.02)
"""
)
result = pytester.runpytest("--codspeed")
assert result.ret == 0, "the run should have succeeded"
@pytest.mark.parametrize("mode", [*MeasurementMode])
def test_pytest_benchmark_return_value(
pytester: pytest.Pytester, mode: MeasurementMode
) -> None:
pytester.makepyfile(
"""
def calculate_something():
return 1 + 1
def test_my_stuff(benchmark):
value = benchmark(calculate_something)
assert value == 2
"""
)
result = run_pytest_codspeed_with_mode(pytester, mode)
assert result.ret == 0, "the run should have succeeded"
@pytest.mark.parametrize("mode", [*MeasurementMode])
def test_print(pytester: pytest.Pytester, mode: MeasurementMode) -> None:
"""Test print statements are captured by pytest (i.e., not printed to terminal in
the middle of the progress bar) and only displayed after test run (on failures)."""
pytester.makepyfile(
"""
import pytest, sys
@pytest.mark.benchmark
def test_print():
print("print to stdout")
print("print to stderr", file=sys.stderr)
"""
)
result = run_pytest_codspeed_with_mode(pytester, mode)
assert result.ret == 0, "the run should have succeeded"
result.assert_outcomes(passed=1)
result.stdout.no_fnmatch_line("*print to stdout*")
result.stderr.no_fnmatch_line("*print to stderr*")
@pytest.mark.parametrize("mode", [*MeasurementMode])
def test_capsys(pytester: pytest.Pytester, mode: MeasurementMode):
"""Test print statements are captured by capsys (i.e., not printed to terminal in
the middle of the progress bar) and can be inspected within test."""
pytester.makepyfile(
"""
import pytest, sys
@pytest.mark.benchmark
def test_capsys(capsys):
print("print to stdout")
print("print to stderr", file=sys.stderr)
stdout, stderr = capsys.readouterr()
assert stdout == "print to stdout\\n"
assert stderr == "print to stderr\\n"
"""
)
result = run_pytest_codspeed_with_mode(pytester, mode)
assert result.ret == 0, "the run should have succeeded"
result.assert_outcomes(passed=1)
result.stdout.no_fnmatch_line("*print to stdout*")
result.stderr.no_fnmatch_line("*print to stderr*")
@pytest.mark.xfail(reason="not supported by pytest-benchmark, see #78")
@pytest.mark.parametrize("mode", [*MeasurementMode])
def test_stateful_warmup_fixture(
pytester: pytest.Pytester, mode: MeasurementMode
) -> None:
"""Test that the stateful warmup works correctly."""
pytester.makepyfile(
"""
import pytest
def test_stateful_warmup(benchmark):
has_run = False
def b():
nonlocal has_run
assert not has_run, "Benchmark ran multiple times without setup"
has_run = True
benchmark(b)
"""
)
result = run_pytest_codspeed_with_mode(pytester, mode)
assert result.ret == 0, "the run should have succeeded"
result.assert_outcomes(passed=1)
@pytest.mark.xfail(reason="not supported by pytest-benchmark, see #78")
@pytest.mark.parametrize("mode", [*MeasurementMode])
def test_stateful_warmup_marker(
pytester: pytest.Pytester, mode: MeasurementMode
) -> None:
"""Test that the stateful warmup marker works correctly."""
pytester.makepyfile(
"""
import pytest
has_run = False
@pytest.fixture(autouse=True)
def fixture():
global has_run
has_run = False
@pytest.mark.benchmark
def test_stateful_warmup_marker():
global has_run
assert not has_run, "Benchmark ran multiple times without setup"
has_run = True
"""
)
result = run_pytest_codspeed_with_mode(pytester, mode)
assert result.ret == 0, "the run should have succeeded"
result.assert_outcomes(passed=1)
@pytest.mark.parametrize("mode", [*MeasurementMode])
def test_benchmark_fixture_used_twice(
pytester: pytest.Pytester, mode: MeasurementMode
) -> None:
"""Test that using the benchmark fixture twice in a test raises an error."""
pytester.makepyfile(
"""
def test_benchmark_used_twice(benchmark):
def foo():
pass
benchmark(foo)
benchmark(foo)
"""
)
result = run_pytest_codspeed_with_mode(pytester, mode)
assert result.ret == 1, "the run should have failed"
result.stdout.fnmatch_lines(
["*RuntimeError: The benchmark fixture can only be used once per test*"]
)
@pytest.mark.parametrize("mode", [*MeasurementMode])
def test_benchmark_fixture_used_normal_pedantic(
pytester: pytest.Pytester, mode: MeasurementMode
) -> None:
"""Test that using the benchmark fixture twice in a test raises an error."""
pytester.makepyfile(
"""
def test_benchmark_used_twice(benchmark):
def foo():
pass
benchmark(foo)
benchmark.pedantic(foo)
"""
)
result = run_pytest_codspeed_with_mode(pytester, mode)
assert result.ret == 1, "the run should have failed"
result.stdout.fnmatch_lines(
["*RuntimeError: The benchmark fixture can only be used once per test*"]
)
pytest-codspeed-4.2.0/tests/test_pytest_plugin_cpu_instrumentation.py 0000664 0000000 0000000 00000013502 15076637504 0026522 0 ustar 00root root 0000000 0000000 import os
import pytest
from conftest import (
run_pytest_codspeed_with_mode,
skip_with_pytest_benchmark,
skip_without_perf_trampoline,
skip_without_pytest_xdist,
skip_without_valgrind,
)
from pytest_codspeed.instruments import MeasurementMode
@skip_without_valgrind
@skip_without_perf_trampoline
def test_bench_enabled_header_with_perf(
pytester: pytest.Pytester, codspeed_env
) -> None:
pytester.copy_example("tests/examples/test_addition_fixture.py")
with codspeed_env():
result = pytester.runpytest()
result.stdout.fnmatch_lines(
["codspeed: * (enabled, mode: instrumentation, callgraph: enabled)"]
)
def test_plugin_enabled_cpu_instrumentation_without_env(
pytester: pytest.Pytester,
) -> None:
pytester.makepyfile(
"""
def test_some_addition_performance(benchmark):
@benchmark
def _():
return 1 + 1
"""
)
result = run_pytest_codspeed_with_mode(pytester, MeasurementMode.Instrumentation)
result.stdout.fnmatch_lines(
[
(
"*NOTICE: codspeed is enabled, but no "
"performance measurement will be made*"
),
"*1 benchmark tested*",
"*1 passed*",
]
)
@skip_without_valgrind
@skip_without_perf_trampoline
def test_perf_maps_generation(pytester: pytest.Pytester, codspeed_env) -> None:
pytester.makepyfile(
"""
import pytest
@pytest.mark.benchmark
def test_some_addition_marked():
assert 1 + 1
def test_some_addition_fixtured(benchmark):
@benchmark
def fixtured_child():
assert 1 + 1
"""
)
with codspeed_env():
result = pytester.runpytest("--codspeed")
result.stdout.fnmatch_lines(["*2 benchmarked*", "*2 passed*"])
current_pid = os.getpid()
perf_filepath = f"/tmp/perf-{current_pid}.map"
print(perf_filepath)
with open(perf_filepath) as perf_file:
lines = perf_file.readlines()
assert any(
"py::ValgrindInstrument.measure..__codspeed_root_frame__" in line
for line in lines
), "No root frame found in perf map"
assert any("py::test_some_addition_marked" in line for line in lines), (
"No marked test frame found in perf map"
)
assert any("py::test_some_addition_fixtured" in line for line in lines), (
"No fixtured test frame found in perf map"
)
assert any(
"py::test_some_addition_fixtured..fixtured_child" in line
for line in lines
), "No fixtured child test frame found in perf map"
@skip_without_valgrind
@skip_with_pytest_benchmark
@skip_without_pytest_xdist
def test_pytest_xdist_concurrency_compatibility(
pytester: pytest.Pytester, codspeed_env
) -> None:
pytester.makepyfile(
"""
import time, pytest
def do_something():
time.sleep(1)
@pytest.mark.parametrize("i", range(256))
def test_my_stuff(benchmark, i):
benchmark(do_something)
"""
)
# Run the test multiple times to reduce the chance of a false positive
ITERATIONS = 5
for i in range(ITERATIONS):
with codspeed_env():
result = pytester.runpytest("--codspeed", "-n", "128")
assert result.ret == 0, "the run should have succeeded"
result.stdout.fnmatch_lines(["*256 passed*"])
def test_valgrind_pedantic_warning(pytester: pytest.Pytester) -> None:
"""
Test that using pedantic mode with Valgrind instrumentation shows a warning about
ignoring rounds and iterations.
"""
pytester.makepyfile(
"""
def test_benchmark_pedantic(benchmark):
def foo():
return 1 + 1
benchmark.pedantic(foo, rounds=10, iterations=100)
"""
)
result = run_pytest_codspeed_with_mode(pytester, MeasurementMode.Instrumentation)
result.stdout.fnmatch_lines(
[
"*UserWarning: Valgrind instrument ignores rounds and iterations settings "
"in pedantic mode*"
]
)
result.assert_outcomes(passed=1)
@skip_without_valgrind
@skip_without_perf_trampoline
def test_benchmark_pedantic_instrumentation(
pytester: pytest.Pytester, codspeed_env
) -> None:
"""Test that pedantic mode works with instrumentation mode."""
pytester.makepyfile(
"""
def test_pedantic_full_features(benchmark):
setup_calls = 0
teardown_calls = 0
target_calls = 0
def setup():
nonlocal setup_calls
setup_calls += 1
return (1, 2), {"c": 3}
def teardown(a, b, c):
nonlocal teardown_calls
teardown_calls += 1
assert a == 1
assert b == 2
assert c == 3
def target(a, b, c):
nonlocal target_calls
target_calls += 1
assert a == 1
assert b == 2
assert c == 3
return a + b + c
result = benchmark.pedantic(
target,
setup=setup,
teardown=teardown,
rounds=3,
warmup_rounds=3
)
# Verify the results
# Instrumentation ignores rounds but is called during warmup
assert result == 6 # 1 + 2 + 3
assert setup_calls == 1 + 3
assert teardown_calls == 1 + 3
assert target_calls == 1 + 3
"""
)
with codspeed_env():
result = run_pytest_codspeed_with_mode(
pytester, MeasurementMode.Instrumentation
)
assert result.ret == 0, "the run should have succeeded"
result.assert_outcomes(passed=1)
pytest-codspeed-4.2.0/tests/test_pytest_plugin_walltime.py 0000664 0000000 0000000 00000005121 15076637504 0024224 0 ustar 00root root 0000000 0000000 import pytest
from conftest import run_pytest_codspeed_with_mode
from pytest_codspeed.instruments import MeasurementMode
def test_bench_enabled_header_with_perf(
pytester: pytest.Pytester,
) -> None:
pytester.copy_example("tests/examples/test_addition_fixture.py")
result = run_pytest_codspeed_with_mode(pytester, MeasurementMode.WallTime)
result.stdout.fnmatch_lines(["*test_some_addition_performance*", "*1 benchmarked*"])
def test_parametrization_naming(
pytester: pytest.Pytester,
) -> None:
pytester.makepyfile(
"""
import time, pytest
@pytest.mark.parametrize("inp", ["toto", 12, 58.3])
def test_my_stuff(benchmark, inp):
benchmark(lambda: time.sleep(0.01))
"""
)
result = run_pytest_codspeed_with_mode(pytester, MeasurementMode.WallTime)
# Make sure the parametrization is not broken
print(result.outlines)
result.stdout.fnmatch_lines_random(
[
"*test_my_stuff[[]toto[]]*",
"*test_my_stuff[[]12[]]*",
"*test_my_stuff[[]58.3[]]*",
"*3 benchmarked*",
]
)
def test_benchmark_pedantic_walltime(
pytester: pytest.Pytester,
) -> None:
"""Test that pedantic mode works with walltime mode."""
pytester.makepyfile(
"""
def test_pedantic_full_features(benchmark):
setup_calls = 0
teardown_calls = 0
target_calls = 0
def setup():
nonlocal setup_calls
setup_calls += 1
return (1, 2), {"c": 3}
def teardown(a, b, c):
nonlocal teardown_calls
teardown_calls += 1
assert a == 1
assert b == 2
assert c == 3
def target(a, b, c):
nonlocal target_calls
target_calls += 1
assert a == 1
assert b == 2
assert c == 3
return a + b + c
result = benchmark.pedantic(
target,
setup=setup,
teardown=teardown,
rounds=3,
warmup_rounds=1
)
# Verify the results
assert result == 6 # 1 + 2 + 3
assert setup_calls == 5 # 3 rounds + 1 warmup + 1 calibration
assert teardown_calls == 5
assert target_calls == 5
"""
)
result = run_pytest_codspeed_with_mode(pytester, MeasurementMode.WallTime)
assert result.ret == 0, "the run should have succeeded"
result.assert_outcomes(passed=1)
pytest-codspeed-4.2.0/tests/test_utils.py 0000664 0000000 0000000 00000002204 15076637504 0020557 0 ustar 00root root 0000000 0000000 import tempfile
from contextlib import contextmanager
from pathlib import Path
from pytest_codspeed.utils import get_git_relative_path, get_git_relative_uri_and_name
@contextmanager
def TemporaryGitRepo():
with tempfile.TemporaryDirectory() as tmpdirname:
(Path(tmpdirname) / ".git").mkdir(parents=True)
yield tmpdirname
def test_get_git_relative_path_found():
with TemporaryGitRepo() as tmp_repo:
path = Path(tmp_repo) / "folder/nested_folder"
assert get_git_relative_path(path) == Path("folder/nested_folder")
def test_get_git_relative_path_not_found():
with tempfile.TemporaryDirectory() as tmp_dir:
path = Path(tmp_dir) / "folder"
assert get_git_relative_path(path) == path
def test_get_git_relative_uri():
with TemporaryGitRepo() as tmp_repo:
pytest_rootdir = Path(tmp_repo) / "pytest_root"
uri = "testing/test_excinfo.py::TestFormattedExcinfo::test_fn"
assert get_git_relative_uri_and_name(uri, pytest_rootdir) == (
"pytest_root/testing/test_excinfo.py::TestFormattedExcinfo::test_fn",
"TestFormattedExcinfo::test_fn",
)
pytest-codspeed-4.2.0/uv.lock 0000664 0000000 0000000 00000247356 15076637504 0016173 0 ustar 00root root 0000000 0000000 version = 1
revision = 2
requires-python = ">=3.9"
[[package]]
name = "cffi"
version = "1.17.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pycparser" },
]
sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621, upload-time = "2024-09-04T20:45:21.852Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/90/07/f44ca684db4e4f08a3fdc6eeb9a0d15dc6883efc7b8c90357fdbf74e186c/cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14", size = 182191, upload-time = "2024-09-04T20:43:30.027Z" },
{ url = "https://files.pythonhosted.org/packages/08/fd/cc2fedbd887223f9f5d170c96e57cbf655df9831a6546c1727ae13fa977a/cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67", size = 178592, upload-time = "2024-09-04T20:43:32.108Z" },
{ url = "https://files.pythonhosted.org/packages/de/cc/4635c320081c78d6ffc2cab0a76025b691a91204f4aa317d568ff9280a2d/cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382", size = 426024, upload-time = "2024-09-04T20:43:34.186Z" },
{ url = "https://files.pythonhosted.org/packages/b6/7b/3b2b250f3aab91abe5f8a51ada1b717935fdaec53f790ad4100fe2ec64d1/cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702", size = 448188, upload-time = "2024-09-04T20:43:36.286Z" },
{ url = "https://files.pythonhosted.org/packages/d3/48/1b9283ebbf0ec065148d8de05d647a986c5f22586b18120020452fff8f5d/cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3", size = 455571, upload-time = "2024-09-04T20:43:38.586Z" },
{ url = "https://files.pythonhosted.org/packages/40/87/3b8452525437b40f39ca7ff70276679772ee7e8b394934ff60e63b7b090c/cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6", size = 436687, upload-time = "2024-09-04T20:43:40.084Z" },
{ url = "https://files.pythonhosted.org/packages/8d/fb/4da72871d177d63649ac449aec2e8a29efe0274035880c7af59101ca2232/cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17", size = 446211, upload-time = "2024-09-04T20:43:41.526Z" },
{ url = "https://files.pythonhosted.org/packages/ab/a0/62f00bcb411332106c02b663b26f3545a9ef136f80d5df746c05878f8c4b/cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8", size = 461325, upload-time = "2024-09-04T20:43:43.117Z" },
{ url = "https://files.pythonhosted.org/packages/36/83/76127035ed2e7e27b0787604d99da630ac3123bfb02d8e80c633f218a11d/cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e", size = 438784, upload-time = "2024-09-04T20:43:45.256Z" },
{ url = "https://files.pythonhosted.org/packages/21/81/a6cd025db2f08ac88b901b745c163d884641909641f9b826e8cb87645942/cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be", size = 461564, upload-time = "2024-09-04T20:43:46.779Z" },
{ url = "https://files.pythonhosted.org/packages/f8/fe/4d41c2f200c4a457933dbd98d3cf4e911870877bd94d9656cc0fcb390681/cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c", size = 171804, upload-time = "2024-09-04T20:43:48.186Z" },
{ url = "https://files.pythonhosted.org/packages/d1/b6/0b0f5ab93b0df4acc49cae758c81fe4e5ef26c3ae2e10cc69249dfd8b3ab/cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15", size = 181299, upload-time = "2024-09-04T20:43:49.812Z" },
{ url = "https://files.pythonhosted.org/packages/6b/f4/927e3a8899e52a27fa57a48607ff7dc91a9ebe97399b357b85a0c7892e00/cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401", size = 182264, upload-time = "2024-09-04T20:43:51.124Z" },
{ url = "https://files.pythonhosted.org/packages/6c/f5/6c3a8efe5f503175aaddcbea6ad0d2c96dad6f5abb205750d1b3df44ef29/cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf", size = 178651, upload-time = "2024-09-04T20:43:52.872Z" },
{ url = "https://files.pythonhosted.org/packages/94/dd/a3f0118e688d1b1a57553da23b16bdade96d2f9bcda4d32e7d2838047ff7/cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4", size = 445259, upload-time = "2024-09-04T20:43:56.123Z" },
{ url = "https://files.pythonhosted.org/packages/2e/ea/70ce63780f096e16ce8588efe039d3c4f91deb1dc01e9c73a287939c79a6/cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41", size = 469200, upload-time = "2024-09-04T20:43:57.891Z" },
{ url = "https://files.pythonhosted.org/packages/1c/a0/a4fa9f4f781bda074c3ddd57a572b060fa0df7655d2a4247bbe277200146/cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1", size = 477235, upload-time = "2024-09-04T20:44:00.18Z" },
{ url = "https://files.pythonhosted.org/packages/62/12/ce8710b5b8affbcdd5c6e367217c242524ad17a02fe5beec3ee339f69f85/cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6", size = 459721, upload-time = "2024-09-04T20:44:01.585Z" },
{ url = "https://files.pythonhosted.org/packages/ff/6b/d45873c5e0242196f042d555526f92aa9e0c32355a1be1ff8c27f077fd37/cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d", size = 467242, upload-time = "2024-09-04T20:44:03.467Z" },
{ url = "https://files.pythonhosted.org/packages/1a/52/d9a0e523a572fbccf2955f5abe883cfa8bcc570d7faeee06336fbd50c9fc/cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6", size = 477999, upload-time = "2024-09-04T20:44:05.023Z" },
{ url = "https://files.pythonhosted.org/packages/44/74/f2a2460684a1a2d00ca799ad880d54652841a780c4c97b87754f660c7603/cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f", size = 454242, upload-time = "2024-09-04T20:44:06.444Z" },
{ url = "https://files.pythonhosted.org/packages/f8/4a/34599cac7dfcd888ff54e801afe06a19c17787dfd94495ab0c8d35fe99fb/cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b", size = 478604, upload-time = "2024-09-04T20:44:08.206Z" },
{ url = "https://files.pythonhosted.org/packages/34/33/e1b8a1ba29025adbdcda5fb3a36f94c03d771c1b7b12f726ff7fef2ebe36/cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655", size = 171727, upload-time = "2024-09-04T20:44:09.481Z" },
{ url = "https://files.pythonhosted.org/packages/3d/97/50228be003bb2802627d28ec0627837ac0bf35c90cf769812056f235b2d1/cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0", size = 181400, upload-time = "2024-09-04T20:44:10.873Z" },
{ url = "https://files.pythonhosted.org/packages/5a/84/e94227139ee5fb4d600a7a4927f322e1d4aea6fdc50bd3fca8493caba23f/cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4", size = 183178, upload-time = "2024-09-04T20:44:12.232Z" },
{ url = "https://files.pythonhosted.org/packages/da/ee/fb72c2b48656111c4ef27f0f91da355e130a923473bf5ee75c5643d00cca/cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c", size = 178840, upload-time = "2024-09-04T20:44:13.739Z" },
{ url = "https://files.pythonhosted.org/packages/cc/b6/db007700f67d151abadf508cbfd6a1884f57eab90b1bb985c4c8c02b0f28/cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", size = 454803, upload-time = "2024-09-04T20:44:15.231Z" },
{ url = "https://files.pythonhosted.org/packages/1a/df/f8d151540d8c200eb1c6fba8cd0dfd40904f1b0682ea705c36e6c2e97ab3/cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5", size = 478850, upload-time = "2024-09-04T20:44:17.188Z" },
{ url = "https://files.pythonhosted.org/packages/28/c0/b31116332a547fd2677ae5b78a2ef662dfc8023d67f41b2a83f7c2aa78b1/cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff", size = 485729, upload-time = "2024-09-04T20:44:18.688Z" },
{ url = "https://files.pythonhosted.org/packages/91/2b/9a1ddfa5c7f13cab007a2c9cc295b70fbbda7cb10a286aa6810338e60ea1/cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99", size = 471256, upload-time = "2024-09-04T20:44:20.248Z" },
{ url = "https://files.pythonhosted.org/packages/b2/d5/da47df7004cb17e4955df6a43d14b3b4ae77737dff8bf7f8f333196717bf/cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93", size = 479424, upload-time = "2024-09-04T20:44:21.673Z" },
{ url = "https://files.pythonhosted.org/packages/0b/ac/2a28bcf513e93a219c8a4e8e125534f4f6db03e3179ba1c45e949b76212c/cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3", size = 484568, upload-time = "2024-09-04T20:44:23.245Z" },
{ url = "https://files.pythonhosted.org/packages/d4/38/ca8a4f639065f14ae0f1d9751e70447a261f1a30fa7547a828ae08142465/cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8", size = 488736, upload-time = "2024-09-04T20:44:24.757Z" },
{ url = "https://files.pythonhosted.org/packages/86/c5/28b2d6f799ec0bdecf44dced2ec5ed43e0eb63097b0f58c293583b406582/cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65", size = 172448, upload-time = "2024-09-04T20:44:26.208Z" },
{ url = "https://files.pythonhosted.org/packages/50/b9/db34c4755a7bd1cb2d1603ac3863f22bcecbd1ba29e5ee841a4bc510b294/cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903", size = 181976, upload-time = "2024-09-04T20:44:27.578Z" },
{ url = "https://files.pythonhosted.org/packages/8d/f8/dd6c246b148639254dad4d6803eb6a54e8c85c6e11ec9df2cffa87571dbe/cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", size = 182989, upload-time = "2024-09-04T20:44:28.956Z" },
{ url = "https://files.pythonhosted.org/packages/8b/f1/672d303ddf17c24fc83afd712316fda78dc6fce1cd53011b839483e1ecc8/cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", size = 178802, upload-time = "2024-09-04T20:44:30.289Z" },
{ url = "https://files.pythonhosted.org/packages/0e/2d/eab2e858a91fdff70533cab61dcff4a1f55ec60425832ddfdc9cd36bc8af/cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", size = 454792, upload-time = "2024-09-04T20:44:32.01Z" },
{ url = "https://files.pythonhosted.org/packages/75/b2/fbaec7c4455c604e29388d55599b99ebcc250a60050610fadde58932b7ee/cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", size = 478893, upload-time = "2024-09-04T20:44:33.606Z" },
{ url = "https://files.pythonhosted.org/packages/4f/b7/6e4a2162178bf1935c336d4da8a9352cccab4d3a5d7914065490f08c0690/cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", size = 485810, upload-time = "2024-09-04T20:44:35.191Z" },
{ url = "https://files.pythonhosted.org/packages/c7/8a/1d0e4a9c26e54746dc08c2c6c037889124d4f59dffd853a659fa545f1b40/cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", size = 471200, upload-time = "2024-09-04T20:44:36.743Z" },
{ url = "https://files.pythonhosted.org/packages/26/9f/1aab65a6c0db35f43c4d1b4f580e8df53914310afc10ae0397d29d697af4/cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", size = 479447, upload-time = "2024-09-04T20:44:38.492Z" },
{ url = "https://files.pythonhosted.org/packages/5f/e4/fb8b3dd8dc0e98edf1135ff067ae070bb32ef9d509d6cb0f538cd6f7483f/cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", size = 484358, upload-time = "2024-09-04T20:44:40.046Z" },
{ url = "https://files.pythonhosted.org/packages/f1/47/d7145bf2dc04684935d57d67dff9d6d795b2ba2796806bb109864be3a151/cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", size = 488469, upload-time = "2024-09-04T20:44:41.616Z" },
{ url = "https://files.pythonhosted.org/packages/bf/ee/f94057fa6426481d663b88637a9a10e859e492c73d0384514a17d78ee205/cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", size = 172475, upload-time = "2024-09-04T20:44:43.733Z" },
{ url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009, upload-time = "2024-09-04T20:44:45.309Z" },
{ url = "https://files.pythonhosted.org/packages/b9/ea/8bb50596b8ffbc49ddd7a1ad305035daa770202a6b782fc164647c2673ad/cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16", size = 182220, upload-time = "2024-09-04T20:45:01.577Z" },
{ url = "https://files.pythonhosted.org/packages/ae/11/e77c8cd24f58285a82c23af484cf5b124a376b32644e445960d1a4654c3a/cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36", size = 178605, upload-time = "2024-09-04T20:45:03.837Z" },
{ url = "https://files.pythonhosted.org/packages/ed/65/25a8dc32c53bf5b7b6c2686b42ae2ad58743f7ff644844af7cdb29b49361/cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8", size = 424910, upload-time = "2024-09-04T20:45:05.315Z" },
{ url = "https://files.pythonhosted.org/packages/42/7a/9d086fab7c66bd7c4d0f27c57a1b6b068ced810afc498cc8c49e0088661c/cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576", size = 447200, upload-time = "2024-09-04T20:45:06.903Z" },
{ url = "https://files.pythonhosted.org/packages/da/63/1785ced118ce92a993b0ec9e0d0ac8dc3e5dbfbcaa81135be56c69cabbb6/cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87", size = 454565, upload-time = "2024-09-04T20:45:08.975Z" },
{ url = "https://files.pythonhosted.org/packages/74/06/90b8a44abf3556599cdec107f7290277ae8901a58f75e6fe8f970cd72418/cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0", size = 435635, upload-time = "2024-09-04T20:45:10.64Z" },
{ url = "https://files.pythonhosted.org/packages/bd/62/a1f468e5708a70b1d86ead5bab5520861d9c7eacce4a885ded9faa7729c3/cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3", size = 445218, upload-time = "2024-09-04T20:45:12.366Z" },
{ url = "https://files.pythonhosted.org/packages/5b/95/b34462f3ccb09c2594aa782d90a90b045de4ff1f70148ee79c69d37a0a5a/cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595", size = 460486, upload-time = "2024-09-04T20:45:13.935Z" },
{ url = "https://files.pythonhosted.org/packages/fc/fc/a1e4bebd8d680febd29cf6c8a40067182b64f00c7d105f8f26b5bc54317b/cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a", size = 437911, upload-time = "2024-09-04T20:45:15.696Z" },
{ url = "https://files.pythonhosted.org/packages/e6/c3/21cab7a6154b6a5ea330ae80de386e7665254835b9e98ecc1340b3a7de9a/cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e", size = 460632, upload-time = "2024-09-04T20:45:17.284Z" },
{ url = "https://files.pythonhosted.org/packages/cb/b5/fd9f8b5a84010ca169ee49f4e4ad6f8c05f4e3545b72ee041dbbcb159882/cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7", size = 171820, upload-time = "2024-09-04T20:45:18.762Z" },
{ url = "https://files.pythonhosted.org/packages/8c/52/b08750ce0bce45c143e1b5d7357ee8c55341b52bdef4b0f081af1eb248c2/cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662", size = 181290, upload-time = "2024-09-04T20:45:20.226Z" },
]
[[package]]
name = "colorama"
version = "0.4.6"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
]
[[package]]
name = "coverage"
version = "7.6.8"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/ab/75/aecfd0a3adbec6e45753976bc2a9fed62b42cea9a206d10fd29244a77953/coverage-7.6.8.tar.gz", hash = "sha256:8b2b8503edb06822c86d82fa64a4a5cb0760bb8f31f26e138ec743f422f37cfc", size = 801425, upload-time = "2024-11-24T00:32:04.471Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/31/86/6ed22e101badc8eedf181f0c2f65500df5929c44c79991cf45b9bf741424/coverage-7.6.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b39e6011cd06822eb964d038d5dff5da5d98652b81f5ecd439277b32361a3a50", size = 206988, upload-time = "2024-11-24T00:30:16.044Z" },
{ url = "https://files.pythonhosted.org/packages/3b/04/16853c58bacc02b3ff5405193dfc6c66632442d931b23dd7b9452dc55cf3/coverage-7.6.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:63c19702db10ad79151a059d2d6336fe0c470f2e18d0d4d1a57f7f9713875dcf", size = 207418, upload-time = "2024-11-24T00:30:18.918Z" },
{ url = "https://files.pythonhosted.org/packages/f8/eb/8a91520d04215eb549d6a7d7d3a79cbb1d78b5dd0814f4b23bf97521d580/coverage-7.6.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3985b9be361d8fb6b2d1adc9924d01dec575a1d7453a14cccd73225cb79243ee", size = 235860, upload-time = "2024-11-24T00:30:20.444Z" },
{ url = "https://files.pythonhosted.org/packages/00/10/bf1ede5b54ae1bbf39921a5dd4cc84aee79041ed301ec8955064785ddb90/coverage-7.6.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:644ec81edec0f4ad17d51c838a7d01e42811054543b76d4ba2c5d6af741ce2a6", size = 233766, upload-time = "2024-11-24T00:30:21.855Z" },
{ url = "https://files.pythonhosted.org/packages/5c/ea/741d9233eb502906e0d18ccf4c15c4fb74ff0e85fd8ee967590194b889a1/coverage-7.6.8-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f188a2402f8359cf0c4b1fe89eea40dc13b52e7b4fd4812450da9fcd210181d", size = 234924, upload-time = "2024-11-24T00:30:23.261Z" },
{ url = "https://files.pythonhosted.org/packages/18/43/b2cfd4413a5b64ab27c289228b0c45b4527d1b99381cc9d6a00bfd515da4/coverage-7.6.8-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e19122296822deafce89a0c5e8685704c067ae65d45e79718c92df7b3ec3d331", size = 234019, upload-time = "2024-11-24T00:30:24.639Z" },
{ url = "https://files.pythonhosted.org/packages/8e/95/8b2fbb9d1a79277963b6095cd51a90fb7088cd3618faf75550038331f78b/coverage-7.6.8-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:13618bed0c38acc418896005732e565b317aa9e98d855a0e9f211a7ffc2d6638", size = 232481, upload-time = "2024-11-24T00:30:26.159Z" },
{ url = "https://files.pythonhosted.org/packages/4d/d7/9e939508a39ef67605b715ca89c6522214aceb27c2db9152ae3ae1cf8626/coverage-7.6.8-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:193e3bffca48ad74b8c764fb4492dd875038a2f9925530cb094db92bb5e47bed", size = 233609, upload-time = "2024-11-24T00:30:27.912Z" },
{ url = "https://files.pythonhosted.org/packages/ba/e2/1c5fb52eafcffeebaa9db084bff47e7c3cf4f97db752226c232cee4d530b/coverage-7.6.8-cp310-cp310-win32.whl", hash = "sha256:3988665ee376abce49613701336544041f2117de7b7fbfe91b93d8ff8b151c8e", size = 209669, upload-time = "2024-11-24T00:30:29.298Z" },
{ url = "https://files.pythonhosted.org/packages/31/31/6a56469609a252549dd4b090815428d5521edd4642440d987573a450c069/coverage-7.6.8-cp310-cp310-win_amd64.whl", hash = "sha256:f56f49b2553d7dd85fd86e029515a221e5c1f8cb3d9c38b470bc38bde7b8445a", size = 210509, upload-time = "2024-11-24T00:30:31.122Z" },
{ url = "https://files.pythonhosted.org/packages/ab/9f/e98211980f6e2f439e251737482aa77906c9b9c507824c71a2ce7eea0402/coverage-7.6.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:86cffe9c6dfcfe22e28027069725c7f57f4b868a3f86e81d1c62462764dc46d4", size = 207093, upload-time = "2024-11-24T00:30:33.128Z" },
{ url = "https://files.pythonhosted.org/packages/fd/c7/8bab83fb9c20f7f8163c5a20dcb62d591b906a214a6dc6b07413074afc80/coverage-7.6.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d82ab6816c3277dc962cfcdc85b1efa0e5f50fb2c449432deaf2398a2928ab94", size = 207536, upload-time = "2024-11-24T00:30:34.783Z" },
{ url = "https://files.pythonhosted.org/packages/1e/d6/00243df625f1b282bb25c83ce153ae2c06f8e7a796a8d833e7235337b4d9/coverage-7.6.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13690e923a3932e4fad4c0ebfb9cb5988e03d9dcb4c5150b5fcbf58fd8bddfc4", size = 239482, upload-time = "2024-11-24T00:30:36.272Z" },
{ url = "https://files.pythonhosted.org/packages/1e/07/faf04b3eeb55ffc2a6f24b65dffe6e0359ec3b283e6efb5050ea0707446f/coverage-7.6.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4be32da0c3827ac9132bb488d331cb32e8d9638dd41a0557c5569d57cf22c9c1", size = 236886, upload-time = "2024-11-24T00:30:37.764Z" },
{ url = "https://files.pythonhosted.org/packages/43/23/c79e497bf4d8fcacd316bebe1d559c765485b8ec23ac4e23025be6bfce09/coverage-7.6.8-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44e6c85bbdc809383b509d732b06419fb4544dca29ebe18480379633623baafb", size = 238749, upload-time = "2024-11-24T00:30:40.164Z" },
{ url = "https://files.pythonhosted.org/packages/b5/e5/791bae13be3c6451e32ef7af1192e711c6a319f3c597e9b218d148fd0633/coverage-7.6.8-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:768939f7c4353c0fac2f7c37897e10b1414b571fd85dd9fc49e6a87e37a2e0d8", size = 237679, upload-time = "2024-11-24T00:30:41.663Z" },
{ url = "https://files.pythonhosted.org/packages/05/c6/bbfdfb03aada601fb8993ced17468c8c8e0b4aafb3097026e680fabb7ce1/coverage-7.6.8-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e44961e36cb13c495806d4cac67640ac2866cb99044e210895b506c26ee63d3a", size = 236317, upload-time = "2024-11-24T00:30:43.061Z" },
{ url = "https://files.pythonhosted.org/packages/67/f9/f8e5a4b2ce96d1b0e83ae6246369eb8437001dc80ec03bb51c87ff557cd8/coverage-7.6.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3ea8bb1ab9558374c0ab591783808511d135a833c3ca64a18ec927f20c4030f0", size = 237084, upload-time = "2024-11-24T00:30:44.473Z" },
{ url = "https://files.pythonhosted.org/packages/f0/70/b05328901e4debe76e033717e1452d00246c458c44e9dbd893e7619c2967/coverage-7.6.8-cp311-cp311-win32.whl", hash = "sha256:629a1ba2115dce8bf75a5cce9f2486ae483cb89c0145795603d6554bdc83e801", size = 209638, upload-time = "2024-11-24T00:30:46.313Z" },
{ url = "https://files.pythonhosted.org/packages/70/55/1efa24f960a2fa9fbc44a9523d3f3c50ceb94dd1e8cd732168ab2dc41b07/coverage-7.6.8-cp311-cp311-win_amd64.whl", hash = "sha256:fb9fc32399dca861584d96eccd6c980b69bbcd7c228d06fb74fe53e007aa8ef9", size = 210506, upload-time = "2024-11-24T00:30:48.385Z" },
{ url = "https://files.pythonhosted.org/packages/76/ce/3edf581c8fe429ed8ced6e6d9ac693c25975ef9093413276dab6ed68a80a/coverage-7.6.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e683e6ecc587643f8cde8f5da6768e9d165cd31edf39ee90ed7034f9ca0eefee", size = 207285, upload-time = "2024-11-24T00:30:49.872Z" },
{ url = "https://files.pythonhosted.org/packages/09/9c/cf102ab046c9cf8895c3f7aadcde6f489a4b2ec326757e8c6e6581829b5e/coverage-7.6.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1defe91d41ce1bd44b40fabf071e6a01a5aa14de4a31b986aa9dfd1b3e3e414a", size = 207522, upload-time = "2024-11-24T00:30:51.344Z" },
{ url = "https://files.pythonhosted.org/packages/39/06/42aa6dd13dbfca72e1fd8ffccadbc921b6e75db34545ebab4d955d1e7ad3/coverage-7.6.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7ad66e8e50225ebf4236368cc43c37f59d5e6728f15f6e258c8639fa0dd8e6d", size = 240543, upload-time = "2024-11-24T00:30:52.807Z" },
{ url = "https://files.pythonhosted.org/packages/a0/20/2932971dc215adeca8eeff446266a7fef17a0c238e881ffedebe7bfa0669/coverage-7.6.8-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3fe47da3e4fda5f1abb5709c156eca207eacf8007304ce3019eb001e7a7204cb", size = 237577, upload-time = "2024-11-24T00:30:54.88Z" },
{ url = "https://files.pythonhosted.org/packages/ac/85/4323ece0cd5452c9522f4b6e5cc461e6c7149a4b1887c9e7a8b1f4e51146/coverage-7.6.8-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:202a2d645c5a46b84992f55b0a3affe4f0ba6b4c611abec32ee88358db4bb649", size = 239646, upload-time = "2024-11-24T00:30:56.414Z" },
{ url = "https://files.pythonhosted.org/packages/77/52/b2537487d8f36241e518e84db6f79e26bc3343b14844366e35b090fae0d4/coverage-7.6.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4674f0daa1823c295845b6a740d98a840d7a1c11df00d1fd62614545c1583787", size = 239128, upload-time = "2024-11-24T00:30:58.241Z" },
{ url = "https://files.pythonhosted.org/packages/7c/99/7f007762012186547d0ecc3d328da6b6f31a8c99f05dc1e13dcd929918cd/coverage-7.6.8-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:74610105ebd6f33d7c10f8907afed696e79c59e3043c5f20eaa3a46fddf33b4c", size = 237434, upload-time = "2024-11-24T00:30:59.694Z" },
{ url = "https://files.pythonhosted.org/packages/97/53/e9b5cf0682a1cab9352adfac73caae0d77ae1d65abc88975d510f7816389/coverage-7.6.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:37cda8712145917105e07aab96388ae76e787270ec04bcb9d5cc786d7cbb8443", size = 239095, upload-time = "2024-11-24T00:31:01.106Z" },
{ url = "https://files.pythonhosted.org/packages/0c/50/054f0b464fbae0483217186478eefa2e7df3a79917ed7f1d430b6da2cf0d/coverage-7.6.8-cp312-cp312-win32.whl", hash = "sha256:9e89d5c8509fbd6c03d0dd1972925b22f50db0792ce06324ba069f10787429ad", size = 209895, upload-time = "2024-11-24T00:31:02.978Z" },
{ url = "https://files.pythonhosted.org/packages/df/d0/09ba870360a27ecf09e177ca2ff59d4337fc7197b456f22ceff85cffcfa5/coverage-7.6.8-cp312-cp312-win_amd64.whl", hash = "sha256:379c111d3558272a2cae3d8e57e6b6e6f4fe652905692d54bad5ea0ca37c5ad4", size = 210684, upload-time = "2024-11-24T00:31:04.451Z" },
{ url = "https://files.pythonhosted.org/packages/9a/84/6f0ccf94a098ac3d6d6f236bd3905eeac049a9e0efcd9a63d4feca37ac4b/coverage-7.6.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0b0c69f4f724c64dfbfe79f5dfb503b42fe6127b8d479b2677f2b227478db2eb", size = 207313, upload-time = "2024-11-24T00:31:06.515Z" },
{ url = "https://files.pythonhosted.org/packages/db/2b/e3b3a3a12ebec738c545897ac9f314620470fcbc368cdac88cf14974ba20/coverage-7.6.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c15b32a7aca8038ed7644f854bf17b663bc38e1671b5d6f43f9a2b2bd0c46f63", size = 207574, upload-time = "2024-11-24T00:31:08.831Z" },
{ url = "https://files.pythonhosted.org/packages/db/c0/5bf95d42b6a8d21dfce5025ce187f15db57d6460a59b67a95fe8728162f1/coverage-7.6.8-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63068a11171e4276f6ece913bde059e77c713b48c3a848814a6537f35afb8365", size = 240090, upload-time = "2024-11-24T00:31:10.318Z" },
{ url = "https://files.pythonhosted.org/packages/57/b8/d6fd17d1a8e2b0e1a4e8b9cb1f0f261afd422570735899759c0584236916/coverage-7.6.8-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f4548c5ead23ad13fb7a2c8ea541357474ec13c2b736feb02e19a3085fac002", size = 237237, upload-time = "2024-11-24T00:31:12.582Z" },
{ url = "https://files.pythonhosted.org/packages/d4/e4/a91e9bb46809c8b63e68fc5db5c4d567d3423b6691d049a4f950e38fbe9d/coverage-7.6.8-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b4b4299dd0d2c67caaaf286d58aef5e75b125b95615dda4542561a5a566a1e3", size = 239225, upload-time = "2024-11-24T00:31:14.807Z" },
{ url = "https://files.pythonhosted.org/packages/31/9c/9b99b0591ec4555b7292d271e005f27b465388ce166056c435b288db6a69/coverage-7.6.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c9ebfb2507751f7196995142f057d1324afdab56db1d9743aab7f50289abd022", size = 238888, upload-time = "2024-11-24T00:31:16.883Z" },
{ url = "https://files.pythonhosted.org/packages/a6/85/285c2df9a04bc7c31f21fd9d4a24d19e040ec5e2ff06e572af1f6514c9e7/coverage-7.6.8-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c1b4474beee02ede1eef86c25ad4600a424fe36cff01a6103cb4533c6bf0169e", size = 236974, upload-time = "2024-11-24T00:31:18.394Z" },
{ url = "https://files.pythonhosted.org/packages/cb/a1/95ec8522206f76cdca033bf8bb61fff56429fb414835fc4d34651dfd29fc/coverage-7.6.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d9fd2547e6decdbf985d579cf3fc78e4c1d662b9b0ff7cc7862baaab71c9cc5b", size = 238815, upload-time = "2024-11-24T00:31:19.976Z" },
{ url = "https://files.pythonhosted.org/packages/8d/ac/687e9ba5e6d0979e9dab5c02e01c4f24ac58260ef82d88d3b433b3f84f1e/coverage-7.6.8-cp313-cp313-win32.whl", hash = "sha256:8aae5aea53cbfe024919715eca696b1a3201886ce83790537d1c3668459c7146", size = 209957, upload-time = "2024-11-24T00:31:21.592Z" },
{ url = "https://files.pythonhosted.org/packages/2f/a3/b61cc8e3fcf075293fb0f3dee405748453c5ba28ac02ceb4a87f52bdb105/coverage-7.6.8-cp313-cp313-win_amd64.whl", hash = "sha256:ae270e79f7e169ccfe23284ff5ea2d52a6f401dc01b337efb54b3783e2ce3f28", size = 210711, upload-time = "2024-11-24T00:31:23.209Z" },
{ url = "https://files.pythonhosted.org/packages/ee/4b/891c8b9acf1b62c85e4a71dac142ab9284e8347409b7355de02e3f38306f/coverage-7.6.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:de38add67a0af869b0d79c525d3e4588ac1ffa92f39116dbe0ed9753f26eba7d", size = 208053, upload-time = "2024-11-24T00:31:24.789Z" },
{ url = "https://files.pythonhosted.org/packages/18/a9/9e330409b291cc002723d339346452800e78df1ce50774ca439ade1d374f/coverage-7.6.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b07c25d52b1c16ce5de088046cd2432b30f9ad5e224ff17c8f496d9cb7d1d451", size = 208329, upload-time = "2024-11-24T00:31:26.834Z" },
{ url = "https://files.pythonhosted.org/packages/9c/0d/33635fd429f6589c6e1cdfc7bf581aefe4c1792fbff06383f9d37f59db60/coverage-7.6.8-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62a66ff235e4c2e37ed3b6104d8b478d767ff73838d1222132a7a026aa548764", size = 251052, upload-time = "2024-11-24T00:31:29.053Z" },
{ url = "https://files.pythonhosted.org/packages/23/32/8a08da0e46f3830bbb9a5b40614241b2e700f27a9c2889f53122486443ed/coverage-7.6.8-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09b9f848b28081e7b975a3626e9081574a7b9196cde26604540582da60235fdf", size = 246765, upload-time = "2024-11-24T00:31:30.661Z" },
{ url = "https://files.pythonhosted.org/packages/56/3f/3b86303d2c14350fdb1c6c4dbf9bc76000af2382f42ca1d4d99c6317666e/coverage-7.6.8-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:093896e530c38c8e9c996901858ac63f3d4171268db2c9c8b373a228f459bbc5", size = 249125, upload-time = "2024-11-24T00:31:32.769Z" },
{ url = "https://files.pythonhosted.org/packages/36/cb/c4f081b9023f9fd8646dbc4ef77be0df090263e8f66f4ea47681e0dc2cff/coverage-7.6.8-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9a7b8ac36fd688c8361cbc7bf1cb5866977ece6e0b17c34aa0df58bda4fa18a4", size = 248615, upload-time = "2024-11-24T00:31:34.646Z" },
{ url = "https://files.pythonhosted.org/packages/32/ee/53bdbf67760928c44b57b2c28a8c0a4bf544f85a9ee129a63ba5c78fdee4/coverage-7.6.8-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:38c51297b35b3ed91670e1e4efb702b790002e3245a28c76e627478aa3c10d83", size = 246507, upload-time = "2024-11-24T00:31:36.992Z" },
{ url = "https://files.pythonhosted.org/packages/57/49/5a57910bd0af6d8e802b4ca65292576d19b54b49f81577fd898505dee075/coverage-7.6.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2e4e0f60cb4bd7396108823548e82fdab72d4d8a65e58e2c19bbbc2f1e2bfa4b", size = 247785, upload-time = "2024-11-24T00:31:38.723Z" },
{ url = "https://files.pythonhosted.org/packages/bd/37/e450c9f6b297c79bb9858407396ed3e084dcc22990dd110ab01d5ceb9770/coverage-7.6.8-cp313-cp313t-win32.whl", hash = "sha256:6535d996f6537ecb298b4e287a855f37deaf64ff007162ec0afb9ab8ba3b8b71", size = 210605, upload-time = "2024-11-24T00:31:40.543Z" },
{ url = "https://files.pythonhosted.org/packages/44/79/7d0c7dd237c6905018e2936cd1055fe1d42e7eba2ebab3c00f4aad2a27d7/coverage-7.6.8-cp313-cp313t-win_amd64.whl", hash = "sha256:c79c0685f142ca53256722a384540832420dff4ab15fec1863d7e5bc8691bdcc", size = 211777, upload-time = "2024-11-24T00:31:42.193Z" },
{ url = "https://files.pythonhosted.org/packages/2e/db/5c7008bcd8858c2dea02702ef0fee761f23780a6be7cd1292840f3e165b1/coverage-7.6.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3ac47fa29d8d41059ea3df65bd3ade92f97ee4910ed638e87075b8e8ce69599e", size = 206983, upload-time = "2024-11-24T00:31:43.791Z" },
{ url = "https://files.pythonhosted.org/packages/1c/30/e1be5b6802baa55967e83bdf57bd51cd2763b72cdc591a90aa0b465fffee/coverage-7.6.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:24eda3a24a38157eee639ca9afe45eefa8d2420d49468819ac5f88b10de84f4c", size = 207422, upload-time = "2024-11-24T00:31:45.352Z" },
{ url = "https://files.pythonhosted.org/packages/f6/df/19c0e12f9f7b976cd7b92ae8200d26f5b6cd3f322d17ac7b08d48fbf5bc5/coverage-7.6.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4c81ed2820b9023a9a90717020315e63b17b18c274a332e3b6437d7ff70abe0", size = 235455, upload-time = "2024-11-24T00:31:47.72Z" },
{ url = "https://files.pythonhosted.org/packages/e8/7a/a80b0c4fb48e8bce92bcfe3908e47e6c7607fb8f618a4e0de78218e42d9b/coverage-7.6.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd55f8fc8fa494958772a2a7302b0354ab16e0b9272b3c3d83cdb5bec5bd1779", size = 233376, upload-time = "2024-11-24T00:31:49.645Z" },
{ url = "https://files.pythonhosted.org/packages/8c/0e/1a4ecee734d70b78fc458ff611707f804605721467ef45fc1f1a684772ad/coverage-7.6.8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f39e2f3530ed1626c66e7493be7a8423b023ca852aacdc91fb30162c350d2a92", size = 234509, upload-time = "2024-11-24T00:31:51.624Z" },
{ url = "https://files.pythonhosted.org/packages/24/42/6eadd73adc0163cb18dee4fef80baefeb3faa11a1e217a2db80e274e784d/coverage-7.6.8-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:716a78a342679cd1177bc8c2fe957e0ab91405bd43a17094324845200b2fddf4", size = 233659, upload-time = "2024-11-24T00:31:53.514Z" },
{ url = "https://files.pythonhosted.org/packages/68/5f/10b825f39ecfe6fc5ee3120205daaa0950443948f0d0a538430f386fdf58/coverage-7.6.8-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:177f01eeaa3aee4a5ffb0d1439c5952b53d5010f86e9d2667963e632e30082cc", size = 232138, upload-time = "2024-11-24T00:31:55.234Z" },
{ url = "https://files.pythonhosted.org/packages/56/72/ad92bdad934de103e19a128a349ef4a0560892fd33d62becb1140885e44c/coverage-7.6.8-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:912e95017ff51dc3d7b6e2be158dedc889d9a5cc3382445589ce554f1a34c0ea", size = 233131, upload-time = "2024-11-24T00:31:56.938Z" },
{ url = "https://files.pythonhosted.org/packages/f4/1d/d61d9b2d17628c4db834e9650b776663535b4258d0dc204ec475188b5b2a/coverage-7.6.8-cp39-cp39-win32.whl", hash = "sha256:4db3ed6a907b555e57cc2e6f14dc3a4c2458cdad8919e40b5357ab9b6db6c43e", size = 209695, upload-time = "2024-11-24T00:31:58.63Z" },
{ url = "https://files.pythonhosted.org/packages/0f/d1/ef43852a998c41183dbffed4ab0dd658f9975d570c6106ea43fdcb5dcbf4/coverage-7.6.8-cp39-cp39-win_amd64.whl", hash = "sha256:428ac484592f780e8cd7b6b14eb568f7c85460c92e2a37cb0c0e5186e1a0d076", size = 210475, upload-time = "2024-11-24T00:32:00.415Z" },
{ url = "https://files.pythonhosted.org/packages/32/df/0d2476121cd0bfb9ca2413efe02289c474b82c4b134863bef4b89ec7bcfa/coverage-7.6.8-pp39.pp310-none-any.whl", hash = "sha256:5c52a036535d12590c32c49209e79cabaad9f9ad8aa4cbd875b68c4d67a9cbce", size = 199230, upload-time = "2024-11-24T00:32:02.343Z" },
]
[package.optional-dependencies]
toml = [
{ name = "tomli", marker = "python_full_version <= '3.11'" },
]
[[package]]
name = "exceptiongroup"
version = "1.2.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/09/35/2495c4ac46b980e4ca1f6ad6db102322ef3ad2410b79fdde159a4b0f3b92/exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc", size = 28883, upload-time = "2024-07-12T22:26:00.161Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453, upload-time = "2024-07-12T22:25:58.476Z" },
]
[[package]]
name = "execnet"
version = "2.1.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/bb/ff/b4c0dc78fbe20c3e59c0c7334de0c27eb4001a2b2017999af398bf730817/execnet-2.1.1.tar.gz", hash = "sha256:5189b52c6121c24feae288166ab41b32549c7e2348652736540b9e6e7d4e72e3", size = 166524, upload-time = "2024-04-08T09:04:19.245Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/43/09/2aea36ff60d16dd8879bdb2f5b3ee0ba8d08cbbdcdfe870e695ce3784385/execnet-2.1.1-py3-none-any.whl", hash = "sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc", size = 40612, upload-time = "2024-04-08T09:04:17.414Z" },
]
[[package]]
name = "importlib-metadata"
version = "8.5.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "zipp" },
]
sdist = { url = "https://files.pythonhosted.org/packages/cd/12/33e59336dca5be0c398a7482335911a33aa0e20776128f038019f1a95f1b/importlib_metadata-8.5.0.tar.gz", hash = "sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7", size = 55304, upload-time = "2024-09-11T14:56:08.937Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a0/d9/a1e041c5e7caa9a05c925f4bdbdfb7f006d1f74996af53467bc394c97be7/importlib_metadata-8.5.0-py3-none-any.whl", hash = "sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b", size = 26514, upload-time = "2024-09-11T14:56:07.019Z" },
]
[[package]]
name = "iniconfig"
version = "2.0.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646, upload-time = "2023-01-07T11:08:11.254Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892, upload-time = "2023-01-07T11:08:09.864Z" },
]
[[package]]
name = "markdown-it-py"
version = "3.0.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "mdurl" },
]
sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596, upload-time = "2023-06-03T06:41:14.443Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528, upload-time = "2023-06-03T06:41:11.019Z" },
]
[[package]]
name = "mdurl"
version = "0.1.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" },
]
[[package]]
name = "mypy"
version = "1.18.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "mypy-extensions" },
{ name = "pathspec" },
{ name = "tomli", marker = "python_full_version < '3.11'" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c0/77/8f0d0001ffad290cef2f7f216f96c814866248a0b92a722365ed54648e7e/mypy-1.18.2.tar.gz", hash = "sha256:06a398102a5f203d7477b2923dda3634c36727fa5c237d8f859ef90c42a9924b", size = 3448846, upload-time = "2025-09-19T00:11:10.519Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/03/6f/657961a0743cff32e6c0611b63ff1c1970a0b482ace35b069203bf705187/mypy-1.18.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c1eab0cf6294dafe397c261a75f96dc2c31bffe3b944faa24db5def4e2b0f77c", size = 12807973, upload-time = "2025-09-19T00:10:35.282Z" },
{ url = "https://files.pythonhosted.org/packages/10/e9/420822d4f661f13ca8900f5fa239b40ee3be8b62b32f3357df9a3045a08b/mypy-1.18.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7a780ca61fc239e4865968ebc5240bb3bf610ef59ac398de9a7421b54e4a207e", size = 11896527, upload-time = "2025-09-19T00:10:55.791Z" },
{ url = "https://files.pythonhosted.org/packages/aa/73/a05b2bbaa7005f4642fcfe40fb73f2b4fb6bb44229bd585b5878e9a87ef8/mypy-1.18.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:448acd386266989ef11662ce3c8011fd2a7b632e0ec7d61a98edd8e27472225b", size = 12507004, upload-time = "2025-09-19T00:11:05.411Z" },
{ url = "https://files.pythonhosted.org/packages/4f/01/f6e4b9f0d031c11ccbd6f17da26564f3a0f3c4155af344006434b0a05a9d/mypy-1.18.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f9e171c465ad3901dc652643ee4bffa8e9fef4d7d0eece23b428908c77a76a66", size = 13245947, upload-time = "2025-09-19T00:10:46.923Z" },
{ url = "https://files.pythonhosted.org/packages/d7/97/19727e7499bfa1ae0773d06afd30ac66a58ed7437d940c70548634b24185/mypy-1.18.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:592ec214750bc00741af1f80cbf96b5013d81486b7bb24cb052382c19e40b428", size = 13499217, upload-time = "2025-09-19T00:09:39.472Z" },
{ url = "https://files.pythonhosted.org/packages/9f/4f/90dc8c15c1441bf31cf0f9918bb077e452618708199e530f4cbd5cede6ff/mypy-1.18.2-cp310-cp310-win_amd64.whl", hash = "sha256:7fb95f97199ea11769ebe3638c29b550b5221e997c63b14ef93d2e971606ebed", size = 9766753, upload-time = "2025-09-19T00:10:49.161Z" },
{ url = "https://files.pythonhosted.org/packages/88/87/cafd3ae563f88f94eec33f35ff722d043e09832ea8530ef149ec1efbaf08/mypy-1.18.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:807d9315ab9d464125aa9fcf6d84fde6e1dc67da0b6f80e7405506b8ac72bc7f", size = 12731198, upload-time = "2025-09-19T00:09:44.857Z" },
{ url = "https://files.pythonhosted.org/packages/0f/e0/1e96c3d4266a06d4b0197ace5356d67d937d8358e2ee3ffac71faa843724/mypy-1.18.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:776bb00de1778caf4db739c6e83919c1d85a448f71979b6a0edd774ea8399341", size = 11817879, upload-time = "2025-09-19T00:09:47.131Z" },
{ url = "https://files.pythonhosted.org/packages/72/ef/0c9ba89eb03453e76bdac5a78b08260a848c7bfc5d6603634774d9cd9525/mypy-1.18.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1379451880512ffce14505493bd9fe469e0697543717298242574882cf8cdb8d", size = 12427292, upload-time = "2025-09-19T00:10:22.472Z" },
{ url = "https://files.pythonhosted.org/packages/1a/52/ec4a061dd599eb8179d5411d99775bec2a20542505988f40fc2fee781068/mypy-1.18.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1331eb7fd110d60c24999893320967594ff84c38ac6d19e0a76c5fd809a84c86", size = 13163750, upload-time = "2025-09-19T00:09:51.472Z" },
{ url = "https://files.pythonhosted.org/packages/c4/5f/2cf2ceb3b36372d51568f2208c021870fe7834cf3186b653ac6446511839/mypy-1.18.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3ca30b50a51e7ba93b00422e486cbb124f1c56a535e20eff7b2d6ab72b3b2e37", size = 13351827, upload-time = "2025-09-19T00:09:58.311Z" },
{ url = "https://files.pythonhosted.org/packages/c8/7d/2697b930179e7277529eaaec1513f8de622818696857f689e4a5432e5e27/mypy-1.18.2-cp311-cp311-win_amd64.whl", hash = "sha256:664dc726e67fa54e14536f6e1224bcfce1d9e5ac02426d2326e2bb4e081d1ce8", size = 9757983, upload-time = "2025-09-19T00:10:09.071Z" },
{ url = "https://files.pythonhosted.org/packages/07/06/dfdd2bc60c66611dd8335f463818514733bc763e4760dee289dcc33df709/mypy-1.18.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:33eca32dd124b29400c31d7cf784e795b050ace0e1f91b8dc035672725617e34", size = 12908273, upload-time = "2025-09-19T00:10:58.321Z" },
{ url = "https://files.pythonhosted.org/packages/81/14/6a9de6d13a122d5608e1a04130724caf9170333ac5a924e10f670687d3eb/mypy-1.18.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a3c47adf30d65e89b2dcd2fa32f3aeb5e94ca970d2c15fcb25e297871c8e4764", size = 11920910, upload-time = "2025-09-19T00:10:20.043Z" },
{ url = "https://files.pythonhosted.org/packages/5f/a9/b29de53e42f18e8cc547e38daa9dfa132ffdc64f7250e353f5c8cdd44bee/mypy-1.18.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d6c838e831a062f5f29d11c9057c6009f60cb294fea33a98422688181fe2893", size = 12465585, upload-time = "2025-09-19T00:10:33.005Z" },
{ url = "https://files.pythonhosted.org/packages/77/ae/6c3d2c7c61ff21f2bee938c917616c92ebf852f015fb55917fd6e2811db2/mypy-1.18.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01199871b6110a2ce984bde85acd481232d17413868c9807e95c1b0739a58914", size = 13348562, upload-time = "2025-09-19T00:10:11.51Z" },
{ url = "https://files.pythonhosted.org/packages/4d/31/aec68ab3b4aebdf8f36d191b0685d99faa899ab990753ca0fee60fb99511/mypy-1.18.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a2afc0fa0b0e91b4599ddfe0f91e2c26c2b5a5ab263737e998d6817874c5f7c8", size = 13533296, upload-time = "2025-09-19T00:10:06.568Z" },
{ url = "https://files.pythonhosted.org/packages/9f/83/abcb3ad9478fca3ebeb6a5358bb0b22c95ea42b43b7789c7fb1297ca44f4/mypy-1.18.2-cp312-cp312-win_amd64.whl", hash = "sha256:d8068d0afe682c7c4897c0f7ce84ea77f6de953262b12d07038f4d296d547074", size = 9828828, upload-time = "2025-09-19T00:10:28.203Z" },
{ url = "https://files.pythonhosted.org/packages/5f/04/7f462e6fbba87a72bc8097b93f6842499c428a6ff0c81dd46948d175afe8/mypy-1.18.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:07b8b0f580ca6d289e69209ec9d3911b4a26e5abfde32228a288eb79df129fcc", size = 12898728, upload-time = "2025-09-19T00:10:01.33Z" },
{ url = "https://files.pythonhosted.org/packages/99/5b/61ed4efb64f1871b41fd0b82d29a64640f3516078f6c7905b68ab1ad8b13/mypy-1.18.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ed4482847168439651d3feee5833ccedbf6657e964572706a2adb1f7fa4dfe2e", size = 11910758, upload-time = "2025-09-19T00:10:42.607Z" },
{ url = "https://files.pythonhosted.org/packages/3c/46/d297d4b683cc89a6e4108c4250a6a6b717f5fa96e1a30a7944a6da44da35/mypy-1.18.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3ad2afadd1e9fea5cf99a45a822346971ede8685cc581ed9cd4d42eaf940986", size = 12475342, upload-time = "2025-09-19T00:11:00.371Z" },
{ url = "https://files.pythonhosted.org/packages/83/45/4798f4d00df13eae3bfdf726c9244bcb495ab5bd588c0eed93a2f2dd67f3/mypy-1.18.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a431a6f1ef14cf8c144c6b14793a23ec4eae3db28277c358136e79d7d062f62d", size = 13338709, upload-time = "2025-09-19T00:11:03.358Z" },
{ url = "https://files.pythonhosted.org/packages/d7/09/479f7358d9625172521a87a9271ddd2441e1dab16a09708f056e97007207/mypy-1.18.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7ab28cc197f1dd77a67e1c6f35cd1f8e8b73ed2217e4fc005f9e6a504e46e7ba", size = 13529806, upload-time = "2025-09-19T00:10:26.073Z" },
{ url = "https://files.pythonhosted.org/packages/71/cf/ac0f2c7e9d0ea3c75cd99dff7aec1c9df4a1376537cb90e4c882267ee7e9/mypy-1.18.2-cp313-cp313-win_amd64.whl", hash = "sha256:0e2785a84b34a72ba55fb5daf079a1003a34c05b22238da94fcae2bbe46f3544", size = 9833262, upload-time = "2025-09-19T00:10:40.035Z" },
{ url = "https://files.pythonhosted.org/packages/5a/0c/7d5300883da16f0063ae53996358758b2a2df2a09c72a5061fa79a1f5006/mypy-1.18.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:62f0e1e988ad41c2a110edde6c398383a889d95b36b3e60bcf155f5164c4fdce", size = 12893775, upload-time = "2025-09-19T00:10:03.814Z" },
{ url = "https://files.pythonhosted.org/packages/50/df/2cffbf25737bdb236f60c973edf62e3e7b4ee1c25b6878629e88e2cde967/mypy-1.18.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8795a039bab805ff0c1dfdb8cd3344642c2b99b8e439d057aba30850b8d3423d", size = 11936852, upload-time = "2025-09-19T00:10:51.631Z" },
{ url = "https://files.pythonhosted.org/packages/be/50/34059de13dd269227fb4a03be1faee6e2a4b04a2051c82ac0a0b5a773c9a/mypy-1.18.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6ca1e64b24a700ab5ce10133f7ccd956a04715463d30498e64ea8715236f9c9c", size = 12480242, upload-time = "2025-09-19T00:11:07.955Z" },
{ url = "https://files.pythonhosted.org/packages/5b/11/040983fad5132d85914c874a2836252bbc57832065548885b5bb5b0d4359/mypy-1.18.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d924eef3795cc89fecf6bedc6ed32b33ac13e8321344f6ddbf8ee89f706c05cb", size = 13326683, upload-time = "2025-09-19T00:09:55.572Z" },
{ url = "https://files.pythonhosted.org/packages/e9/ba/89b2901dd77414dd7a8c8729985832a5735053be15b744c18e4586e506ef/mypy-1.18.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:20c02215a080e3a2be3aa50506c67242df1c151eaba0dcbc1e4e557922a26075", size = 13514749, upload-time = "2025-09-19T00:10:44.827Z" },
{ url = "https://files.pythonhosted.org/packages/25/bc/cc98767cffd6b2928ba680f3e5bc969c4152bf7c2d83f92f5a504b92b0eb/mypy-1.18.2-cp314-cp314-win_amd64.whl", hash = "sha256:749b5f83198f1ca64345603118a6f01a4e99ad4bf9d103ddc5a3200cc4614adf", size = 9982959, upload-time = "2025-09-19T00:10:37.344Z" },
{ url = "https://files.pythonhosted.org/packages/3f/a6/490ff491d8ecddf8ab91762d4f67635040202f76a44171420bcbe38ceee5/mypy-1.18.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:25a9c8fb67b00599f839cf472713f54249a62efd53a54b565eb61956a7e3296b", size = 12807230, upload-time = "2025-09-19T00:09:49.471Z" },
{ url = "https://files.pythonhosted.org/packages/eb/2e/60076fc829645d167ece9e80db9e8375648d210dab44cc98beb5b322a826/mypy-1.18.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c2b9c7e284ee20e7598d6f42e13ca40b4928e6957ed6813d1ab6348aa3f47133", size = 11895666, upload-time = "2025-09-19T00:10:53.678Z" },
{ url = "https://files.pythonhosted.org/packages/97/4a/1e2880a2a5dda4dc8d9ecd1a7e7606bc0b0e14813637eeda40c38624e037/mypy-1.18.2-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d6985ed057513e344e43a26cc1cd815c7a94602fb6a3130a34798625bc2f07b6", size = 12499608, upload-time = "2025-09-19T00:09:36.204Z" },
{ url = "https://files.pythonhosted.org/packages/00/81/a117f1b73a3015b076b20246b1f341c34a578ebd9662848c6b80ad5c4138/mypy-1.18.2-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22f27105f1525ec024b5c630c0b9f36d5c1cc4d447d61fe51ff4bd60633f47ac", size = 13244551, upload-time = "2025-09-19T00:10:17.531Z" },
{ url = "https://files.pythonhosted.org/packages/9b/61/b9f48e1714ce87c7bf0358eb93f60663740ebb08f9ea886ffc670cea7933/mypy-1.18.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:030c52d0ea8144e721e49b1f68391e39553d7451f0c3f8a7565b59e19fcb608b", size = 13491552, upload-time = "2025-09-19T00:10:13.753Z" },
{ url = "https://files.pythonhosted.org/packages/c9/66/b2c0af3b684fa80d1b27501a8bdd3d2daa467ea3992a8aa612f5ca17c2db/mypy-1.18.2-cp39-cp39-win_amd64.whl", hash = "sha256:aa5e07ac1a60a253445797e42b8b2963c9675563a94f11291ab40718b016a7a0", size = 9765635, upload-time = "2025-09-19T00:10:30.993Z" },
{ url = "https://files.pythonhosted.org/packages/87/e3/be76d87158ebafa0309946c4a73831974d4d6ab4f4ef40c3b53a385a66fd/mypy-1.18.2-py3-none-any.whl", hash = "sha256:22a1748707dd62b58d2ae53562ffc4d7f8bcc727e8ac7cbc69c053ddc874d47e", size = 2352367, upload-time = "2025-09-19T00:10:15.489Z" },
]
[[package]]
name = "mypy-extensions"
version = "1.0.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/98/a4/1ab47638b92648243faf97a5aeb6ea83059cc3624972ab6b8d2316078d3f/mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782", size = 4433, upload-time = "2023-02-04T12:11:27.157Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", size = 4695, upload-time = "2023-02-04T12:11:25.002Z" },
]
[[package]]
name = "packaging"
version = "24.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950, upload-time = "2024-11-08T09:47:47.202Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451, upload-time = "2024-11-08T09:47:44.722Z" },
]
[[package]]
name = "pathspec"
version = "0.12.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" },
]
[[package]]
name = "pluggy"
version = "1.5.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955, upload-time = "2024-04-20T21:34:42.531Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556, upload-time = "2024-04-20T21:34:40.434Z" },
]
[[package]]
name = "py-cpuinfo"
version = "9.0.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/37/a8/d832f7293ebb21690860d2e01d8115e5ff6f2ae8bbdc953f0eb0fa4bd2c7/py-cpuinfo-9.0.0.tar.gz", hash = "sha256:3cdbbf3fac90dc6f118bfd64384f309edeadd902d7c8fb17f02ffa1fc3f49690", size = 104716, upload-time = "2022-10-25T20:38:06.303Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e0/a9/023730ba63db1e494a271cb018dcd361bd2c917ba7004c3e49d5daf795a2/py_cpuinfo-9.0.0-py3-none-any.whl", hash = "sha256:859625bc251f64e21f077d099d4162689c762b5d6a4c3c97553d56241c9674d5", size = 22335, upload-time = "2022-10-25T20:38:27.636Z" },
]
[[package]]
name = "pycparser"
version = "2.22"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", size = 172736, upload-time = "2024-03-30T13:22:22.564Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552, upload-time = "2024-03-30T13:22:20.476Z" },
]
[[package]]
name = "pygments"
version = "2.18.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/8e/62/8336eff65bcbc8e4cb5d05b55faf041285951b6e80f33e2bff2024788f31/pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199", size = 4891905, upload-time = "2024-05-04T13:42:02.013Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f7/3f/01c8b82017c199075f8f788d0d906b9ffbbc5a47dc9918a945e13d5a2bda/pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a", size = 1205513, upload-time = "2024-05-04T13:41:57.345Z" },
]
[[package]]
name = "pytest"
version = "7.4.4"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" },
{ name = "exceptiongroup", marker = "python_full_version < '3.11'" },
{ name = "iniconfig" },
{ name = "packaging" },
{ name = "pluggy" },
{ name = "tomli", marker = "python_full_version < '3.11'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/80/1f/9d8e98e4133ffb16c90f3b405c43e38d3abb715bb5d7a63a5a684f7e46a3/pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280", size = 1357116, upload-time = "2023-12-31T12:00:18.035Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/51/ff/f6e8b8f39e08547faece4bd80f89d5a8de68a38b2d179cc1c4490ffa3286/pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8", size = 325287, upload-time = "2023-12-31T12:00:13.963Z" },
]
[[package]]
name = "pytest-benchmark"
version = "5.0.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "py-cpuinfo" },
{ name = "pytest" },
]
sdist = { url = "https://files.pythonhosted.org/packages/a3/48/b79272b2b8938513a66a62204a0649ef730dcf6cb52c812f4dc4daa62cd5/pytest-benchmark-5.0.1.tar.gz", hash = "sha256:8138178618c85586ce056c70cc5e92f4283c2e6198e8422c2c825aeb3ace6afd", size = 337310, upload-time = "2024-10-30T01:12:16.991Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f7/e2/c0da4989a933d6bac364f215217c47de37d2f641953aa69a37b66efd6d1b/pytest_benchmark-5.0.1-py3-none-any.whl", hash = "sha256:d75fec4cbf0d4fd91e020f425ce2d845e9c127c21bae35e77c84db8ed84bfaa6", size = 44062, upload-time = "2024-10-30T01:12:13.716Z" },
]
[[package]]
name = "pytest-codspeed"
source = { editable = "." }
dependencies = [
{ name = "cffi" },
{ name = "importlib-metadata", marker = "python_full_version < '3.10'" },
{ name = "pytest" },
{ name = "rich" },
]
[package.optional-dependencies]
compat = [
{ name = "pytest-benchmark" },
{ name = "pytest-xdist" },
]
[package.dev-dependencies]
dev = [
{ name = "mypy" },
{ name = "pytest" },
{ name = "pytest-codspeed" },
{ name = "pytest-cov" },
{ name = "pytest-test-groups" },
{ name = "ruff" },
]
[package.metadata]
requires-dist = [
{ name = "cffi", specifier = ">=1.17.1" },
{ name = "importlib-metadata", marker = "python_full_version < '3.10'", specifier = ">=8.5.0" },
{ name = "pytest", specifier = ">=3.8" },
{ name = "pytest-benchmark", marker = "extra == 'compat'", specifier = "~=5.0.0" },
{ name = "pytest-xdist", marker = "extra == 'compat'", specifier = "~=3.6.1" },
{ name = "rich", specifier = ">=13.8.1" },
]
provides-extras = ["compat"]
[package.metadata.requires-dev]
dev = [
{ name = "mypy", specifier = "~=1.18.2" },
{ name = "pytest", specifier = "~=7.0" },
{ name = "pytest-codspeed", editable = "." },
{ name = "pytest-cov", specifier = "~=4.0.0" },
{ name = "pytest-test-groups", specifier = ">=1.1.0" },
{ name = "ruff", specifier = "~=0.11.12" },
]
[[package]]
name = "pytest-cov"
version = "4.0.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "coverage", extra = ["toml"] },
{ name = "pytest" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ea/70/da97fd5f6270c7d2ce07559a19e5bf36a76f0af21500256f005a69d9beba/pytest-cov-4.0.0.tar.gz", hash = "sha256:996b79efde6433cdbd0088872dbc5fb3ed7fe1578b68cdbba634f14bb8dd0470", size = 62013, upload-time = "2022-09-28T18:39:22.927Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/fe/1f/9ec0ddd33bd2b37d6ec50bb39155bca4fe7085fa78b3b434c05459a860e3/pytest_cov-4.0.0-py3-none-any.whl", hash = "sha256:2feb1b751d66a8bd934e5edfa2e961d11309dc37b73b0eabe73b5945fee20f6b", size = 21554, upload-time = "2022-09-28T18:39:21.138Z" },
]
[[package]]
name = "pytest-test-groups"
version = "1.2.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pytest" },
]
sdist = { url = "https://files.pythonhosted.org/packages/9f/5a/c7874fe15e03d86a1109a3274b57a2473edb8a1dda4a4d27f25d848b6ff5/pytest_test_groups-1.2.1.tar.gz", hash = "sha256:67576b295522fc144b3a42fa1801f50ae962389e984b48bab4336686d09032f1", size = 8137, upload-time = "2025-05-08T16:28:19.627Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/31/ff/7ff0ca5e8051931bf7fb65e31f085f7c0577615bf3a4776fb583cb471800/pytest_test_groups-1.2.1-py3-none-any.whl", hash = "sha256:8c7a016448f9ad347fb69a62f417f0a2358ecbf129fe44bc44ee991918a0bb73", size = 5278, upload-time = "2025-05-08T16:28:18.077Z" },
]
[[package]]
name = "pytest-xdist"
version = "3.6.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "execnet" },
{ name = "pytest" },
]
sdist = { url = "https://files.pythonhosted.org/packages/41/c4/3c310a19bc1f1e9ef50075582652673ef2bfc8cd62afef9585683821902f/pytest_xdist-3.6.1.tar.gz", hash = "sha256:ead156a4db231eec769737f57668ef58a2084a34b2e55c4a8fa20d861107300d", size = 84060, upload-time = "2024-04-28T19:29:54.414Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/6d/82/1d96bf03ee4c0fdc3c0cbe61470070e659ca78dc0086fb88b66c185e2449/pytest_xdist-3.6.1-py3-none-any.whl", hash = "sha256:9ed4adfb68a016610848639bb7e02c9352d5d9f03d04809919e2dafc3be4cca7", size = 46108, upload-time = "2024-04-28T19:29:52.813Z" },
]
[[package]]
name = "rich"
version = "13.9.4"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "markdown-it-py" },
{ name = "pygments" },
{ name = "typing-extensions", marker = "python_full_version < '3.11'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ab/3a/0316b28d0761c6734d6bc14e770d85506c986c85ffb239e688eeaab2c2bc/rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098", size = 223149, upload-time = "2024-11-01T16:43:57.873Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/19/71/39c7c0d87f8d4e6c020a393182060eaefeeae6c01dab6a84ec346f2567df/rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90", size = 242424, upload-time = "2024-11-01T16:43:55.817Z" },
]
[[package]]
name = "ruff"
version = "0.11.13"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/ed/da/9c6f995903b4d9474b39da91d2d626659af3ff1eeb43e9ae7c119349dba6/ruff-0.11.13.tar.gz", hash = "sha256:26fa247dc68d1d4e72c179e08889a25ac0c7ba4d78aecfc835d49cbfd60bf514", size = 4282054, upload-time = "2025-06-05T21:00:15.721Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/7d/ce/a11d381192966e0b4290842cc8d4fac7dc9214ddf627c11c1afff87da29b/ruff-0.11.13-py3-none-linux_armv6l.whl", hash = "sha256:4bdfbf1240533f40042ec00c9e09a3aade6f8c10b6414cf11b519488d2635d46", size = 10292516, upload-time = "2025-06-05T20:59:32.944Z" },
{ url = "https://files.pythonhosted.org/packages/78/db/87c3b59b0d4e753e40b6a3b4a2642dfd1dcaefbff121ddc64d6c8b47ba00/ruff-0.11.13-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:aef9c9ed1b5ca28bb15c7eac83b8670cf3b20b478195bd49c8d756ba0a36cf48", size = 11106083, upload-time = "2025-06-05T20:59:37.03Z" },
{ url = "https://files.pythonhosted.org/packages/77/79/d8cec175856ff810a19825d09ce700265f905c643c69f45d2b737e4a470a/ruff-0.11.13-py3-none-macosx_11_0_arm64.whl", hash = "sha256:53b15a9dfdce029c842e9a5aebc3855e9ab7771395979ff85b7c1dedb53ddc2b", size = 10436024, upload-time = "2025-06-05T20:59:39.741Z" },
{ url = "https://files.pythonhosted.org/packages/8b/5b/f6d94f2980fa1ee854b41568368a2e1252681b9238ab2895e133d303538f/ruff-0.11.13-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab153241400789138d13f362c43f7edecc0edfffce2afa6a68434000ecd8f69a", size = 10646324, upload-time = "2025-06-05T20:59:42.185Z" },
{ url = "https://files.pythonhosted.org/packages/6c/9c/b4c2acf24ea4426016d511dfdc787f4ce1ceb835f3c5fbdbcb32b1c63bda/ruff-0.11.13-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6c51f93029d54a910d3d24f7dd0bb909e31b6cd989a5e4ac513f4eb41629f0dc", size = 10174416, upload-time = "2025-06-05T20:59:44.319Z" },
{ url = "https://files.pythonhosted.org/packages/f3/10/e2e62f77c65ede8cd032c2ca39c41f48feabedb6e282bfd6073d81bb671d/ruff-0.11.13-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1808b3ed53e1a777c2ef733aca9051dc9bf7c99b26ece15cb59a0320fbdbd629", size = 11724197, upload-time = "2025-06-05T20:59:46.935Z" },
{ url = "https://files.pythonhosted.org/packages/bb/f0/466fe8469b85c561e081d798c45f8a1d21e0b4a5ef795a1d7f1a9a9ec182/ruff-0.11.13-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:d28ce58b5ecf0f43c1b71edffabe6ed7f245d5336b17805803312ec9bc665933", size = 12511615, upload-time = "2025-06-05T20:59:49.534Z" },
{ url = "https://files.pythonhosted.org/packages/17/0e/cefe778b46dbd0cbcb03a839946c8f80a06f7968eb298aa4d1a4293f3448/ruff-0.11.13-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55e4bc3a77842da33c16d55b32c6cac1ec5fb0fbec9c8c513bdce76c4f922165", size = 12117080, upload-time = "2025-06-05T20:59:51.654Z" },
{ url = "https://files.pythonhosted.org/packages/5d/2c/caaeda564cbe103bed145ea557cb86795b18651b0f6b3ff6a10e84e5a33f/ruff-0.11.13-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:633bf2c6f35678c56ec73189ba6fa19ff1c5e4807a78bf60ef487b9dd272cc71", size = 11326315, upload-time = "2025-06-05T20:59:54.469Z" },
{ url = "https://files.pythonhosted.org/packages/75/f0/782e7d681d660eda8c536962920c41309e6dd4ebcea9a2714ed5127d44bd/ruff-0.11.13-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ffbc82d70424b275b089166310448051afdc6e914fdab90e08df66c43bb5ca9", size = 11555640, upload-time = "2025-06-05T20:59:56.986Z" },
{ url = "https://files.pythonhosted.org/packages/5d/d4/3d580c616316c7f07fb3c99dbecfe01fbaea7b6fd9a82b801e72e5de742a/ruff-0.11.13-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:4a9ddd3ec62a9a89578c85842b836e4ac832d4a2e0bfaad3b02243f930ceafcc", size = 10507364, upload-time = "2025-06-05T20:59:59.154Z" },
{ url = "https://files.pythonhosted.org/packages/5a/dc/195e6f17d7b3ea6b12dc4f3e9de575db7983db187c378d44606e5d503319/ruff-0.11.13-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d237a496e0778d719efb05058c64d28b757c77824e04ffe8796c7436e26712b7", size = 10141462, upload-time = "2025-06-05T21:00:01.481Z" },
{ url = "https://files.pythonhosted.org/packages/f4/8e/39a094af6967faa57ecdeacb91bedfb232474ff8c3d20f16a5514e6b3534/ruff-0.11.13-py3-none-musllinux_1_2_i686.whl", hash = "sha256:26816a218ca6ef02142343fd24c70f7cd8c5aa6c203bca284407adf675984432", size = 11121028, upload-time = "2025-06-05T21:00:04.06Z" },
{ url = "https://files.pythonhosted.org/packages/5a/c0/b0b508193b0e8a1654ec683ebab18d309861f8bd64e3a2f9648b80d392cb/ruff-0.11.13-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:51c3f95abd9331dc5b87c47ac7f376db5616041173826dfd556cfe3d4977f492", size = 11602992, upload-time = "2025-06-05T21:00:06.249Z" },
{ url = "https://files.pythonhosted.org/packages/7c/91/263e33ab93ab09ca06ce4f8f8547a858cc198072f873ebc9be7466790bae/ruff-0.11.13-py3-none-win32.whl", hash = "sha256:96c27935418e4e8e77a26bb05962817f28b8ef3843a6c6cc49d8783b5507f250", size = 10474944, upload-time = "2025-06-05T21:00:08.459Z" },
{ url = "https://files.pythonhosted.org/packages/46/f4/7c27734ac2073aae8efb0119cae6931b6fb48017adf048fdf85c19337afc/ruff-0.11.13-py3-none-win_amd64.whl", hash = "sha256:29c3189895a8a6a657b7af4e97d330c8a3afd2c9c8f46c81e2fc5a31866517e3", size = 11548669, upload-time = "2025-06-05T21:00:11.147Z" },
{ url = "https://files.pythonhosted.org/packages/ec/bf/b273dd11673fed8a6bd46032c0ea2a04b2ac9bfa9c628756a5856ba113b0/ruff-0.11.13-py3-none-win_arm64.whl", hash = "sha256:b4385285e9179d608ff1d2fb9922062663c658605819a6876d8beef0c30b7f3b", size = 10683928, upload-time = "2025-06-05T21:00:13.758Z" },
]
[[package]]
name = "tomli"
version = "2.2.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175, upload-time = "2024-11-27T22:38:36.873Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077, upload-time = "2024-11-27T22:37:54.956Z" },
{ url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429, upload-time = "2024-11-27T22:37:56.698Z" },
{ url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067, upload-time = "2024-11-27T22:37:57.63Z" },
{ url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030, upload-time = "2024-11-27T22:37:59.344Z" },
{ url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898, upload-time = "2024-11-27T22:38:00.429Z" },
{ url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894, upload-time = "2024-11-27T22:38:02.094Z" },
{ url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319, upload-time = "2024-11-27T22:38:03.206Z" },
{ url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273, upload-time = "2024-11-27T22:38:04.217Z" },
{ url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310, upload-time = "2024-11-27T22:38:05.908Z" },
{ url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309, upload-time = "2024-11-27T22:38:06.812Z" },
{ url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762, upload-time = "2024-11-27T22:38:07.731Z" },
{ url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453, upload-time = "2024-11-27T22:38:09.384Z" },
{ url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486, upload-time = "2024-11-27T22:38:10.329Z" },
{ url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349, upload-time = "2024-11-27T22:38:11.443Z" },
{ url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159, upload-time = "2024-11-27T22:38:13.099Z" },
{ url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243, upload-time = "2024-11-27T22:38:14.766Z" },
{ url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645, upload-time = "2024-11-27T22:38:15.843Z" },
{ url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584, upload-time = "2024-11-27T22:38:17.645Z" },
{ url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875, upload-time = "2024-11-27T22:38:19.159Z" },
{ url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418, upload-time = "2024-11-27T22:38:20.064Z" },
{ url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708, upload-time = "2024-11-27T22:38:21.659Z" },
{ url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582, upload-time = "2024-11-27T22:38:22.693Z" },
{ url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543, upload-time = "2024-11-27T22:38:24.367Z" },
{ url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691, upload-time = "2024-11-27T22:38:26.081Z" },
{ url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170, upload-time = "2024-11-27T22:38:27.921Z" },
{ url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530, upload-time = "2024-11-27T22:38:29.591Z" },
{ url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666, upload-time = "2024-11-27T22:38:30.639Z" },
{ url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954, upload-time = "2024-11-27T22:38:31.702Z" },
{ url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724, upload-time = "2024-11-27T22:38:32.837Z" },
{ url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383, upload-time = "2024-11-27T22:38:34.455Z" },
{ url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" },
]
[[package]]
name = "typing-extensions"
version = "4.12.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321, upload-time = "2024-06-07T18:52:15.995Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438, upload-time = "2024-06-07T18:52:13.582Z" },
]
[[package]]
name = "zipp"
version = "3.21.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/3f/50/bad581df71744867e9468ebd0bcd6505de3b275e06f202c2cb016e3ff56f/zipp-3.21.0.tar.gz", hash = "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4", size = 24545, upload-time = "2024-11-10T15:05:20.202Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b7/1a/7e4798e9339adc931158c9d69ecc34f5e6791489d469f5e50ec15e35f458/zipp-3.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931", size = 9630, upload-time = "2024-11-10T15:05:19.275Z" },
]