pax_global_header 0000666 0000000 0000000 00000000064 14747156346 0014533 g ustar 00root root 0000000 0000000 52 comment=253b1bea24e909d257c7650265ff34e22af6a3bc
pytest-codspeed-3.2.0/ 0000775 0000000 0000000 00000000000 14747156346 0014651 5 ustar 00root root 0000000 0000000 pytest-codspeed-3.2.0/.github/ 0000775 0000000 0000000 00000000000 14747156346 0016211 5 ustar 00root root 0000000 0000000 pytest-codspeed-3.2.0/.github/workflows/ 0000775 0000000 0000000 00000000000 14747156346 0020246 5 ustar 00root root 0000000 0000000 pytest-codspeed-3.2.0/.github/workflows/ci.yml 0000664 0000000 0000000 00000004206 14747156346 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
- name: Set up Python 3.11
uses: actions/setup-python@v5
with:
python-version: "3.11"
- uses: pre-commit/action@v3.0.0
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"
pytest-version:
- ">=8.1.1"
steps:
- uses: actions/checkout@v4
- 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 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-3.2.0/.github/workflows/codspeed.yml 0000664 0000000 0000000 00000001740 14747156346 0022561 0 ustar 00root root 0000000 0000000 name: CodSpeed
on:
push:
branches: [master]
pull_request:
branches: [master]
workflow_dispatch:
env:
PYTHON_VERSION: "3.12"
jobs:
benchmarks-instrumentation:
strategy:
matrix:
include:
- mode: "instrumentation"
runs-on: ubuntu-24.04
- mode: "walltime"
runs-on: codspeed-macro
name: Run ${{ matrix.mode }} benchmarks
runs-on: ${{ matrix.runs-on }}
steps:
- uses: actions/checkout@v4
with:
submodules: "recursive"
- uses: actions/setup-python@v2
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Install local version of pytest-codspeed
run: |
sudo apt-get install valgrind -y
pip install .
sudo apt-get remove valgrind -y
- name: Run benchmarks
uses: CodSpeedHQ/action@main
with:
run: pytest tests/benchmarks/ --codspeed
token: ${{ secrets.CODSPEED_TOKEN }}
pytest-codspeed-3.2.0/.github/workflows/release.yml 0000664 0000000 0000000 00000005234 14747156346 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
- name: Build wheels
uses: pypa/cibuildwheel@v2.22.0
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
- uses: astral-sh/setup-uv@v4
with:
version: "0.5.20"
- uses: actions/setup-python@v2
with:
python-version: "3.12"
- 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
- uses: astral-sh/setup-uv@v4
with:
version: "0.5.20"
- uses: actions/setup-python@v2
with:
python-version: "3.12"
- 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.12"
- 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-3.2.0/.gitignore 0000664 0000000 0000000 00000006072 14747156346 0016646 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-3.2.0/.gitmodules 0000664 0000000 0000000 00000000203 14747156346 0017021 0 ustar 00root root 0000000 0000000 [submodule "tests/benchmarks/TheAlgorithms"]
path = tests/benchmarks/TheAlgorithms
url = git@github.com:TheAlgorithms/Python.git
pytest-codspeed-3.2.0/.pre-commit-config.yaml 0000664 0000000 0000000 00000001057 14747156346 0021135 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.3.0
hooks:
- id: mypy
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.6.5
hooks:
- id: ruff
args: [--fix]
- id: ruff-format
pytest-codspeed-3.2.0/.vscode/ 0000775 0000000 0000000 00000000000 14747156346 0016212 5 ustar 00root root 0000000 0000000 pytest-codspeed-3.2.0/.vscode/launch.json 0000664 0000000 0000000 00000000411 14747156346 0020353 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-3.2.0/.vscode/settings.json 0000664 0000000 0000000 00000000200 14747156346 0020735 0 ustar 00root root 0000000 0000000 {
"python.testing.pytestArgs": ["tests"],
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true
}
pytest-codspeed-3.2.0/CHANGELOG.md 0000664 0000000 0000000 00000027365 14747156346 0016477 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).
## [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
### ๐๏ธ 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)
## [3.1.0-beta] - 2024-12-06
### ๐ 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
### โ๏ธ Internals
- 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
[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.1.0-beta..v3.1.0
[3.1.0-beta]: https://github.com/CodSpeedHQ/pytest-codspeed/compare/v3.0.0..v3.1.0-beta
[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-3.2.0/LICENSE 0000664 0000000 0000000 00000002104 14747156346 0015653 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-3.2.0/README.md 0000664 0000000 0000000 00000006440 14747156346 0016134 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
## Requirements
**Python**: 3.9 and later
**pytest**: any recent version
## Installation
```shell
pip install pytest-codspeed
```
## Usage
### Creating benchmarks
Creating benchmarks with `pytest-codspeed` is compatible with the standard `pytest-benchmark` API. So if you already have benchmarks written with it, you can start using `pytest-codspeed` right away.
#### Marking a whole test function as a benchmark with `pytest.mark.benchmark`
```python
import pytest
from statistics import median
@pytest.mark.benchmark
def test_median_performance():
return median([1, 2, 3, 4, 5])
```
#### Benchmarking selected lines of a test function with the `benchmark` fixture
```python
import pytest
from statistics import mean
def test_mean_performance(benchmark):
# Precompute some data useful for the benchmark but that should not be
# included in the benchmark time
data = [1, 2, 3, 4, 5]
# Benchmark the execution of the function
benchmark(lambda: mean(data))
def test_mean_and_median_performance(benchmark):
# Precompute some data useful for the benchmark but that should not be
# included in the benchmark time
data = [1, 2, 3, 4, 5]
# Benchmark the execution of the function:
# The `@benchmark` decorator will automatically call the function and
# measure its execution
@benchmark
def bench():
mean(data)
median(data)
```
### Running benchmarks
#### Testing the benchmarks locally
If you want to run only the benchmarks tests locally, you can use the `--codspeed` pytest flag:
```shell
pytest tests/ --codspeed
```
> **Note:** Running `pytest-codspeed` locally will not produce any performance reporting. It's only useful for making sure that your benchmarks are working as expected. If you want to get performance reporting, you should run the benchmarks in your CI.
#### 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.
Example workflow:
```yaml
name: CodSpeed
on:
push:
branches:
- "main" # or "master"
pull_request:
jobs:
benchmarks:
name: Run benchmarks
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install dependencies
run: pip install -r requirements.txt
- name: Run benchmarks
uses: CodSpeedHQ/action@v3
with:
token: ${{ secrets.CODSPEED_TOKEN }}
run: pytest tests/ --codspeed
```
pytest-codspeed-3.2.0/cliff.toml 0000664 0000000 0000000 00000010353 14747156346 0016633 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 -' },
]
# 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-3.2.0/pyproject.toml 0000664 0000000 0000000 00000007300 14747156346 0017565 0 ustar 00root root 0000000 0000000 [project.urls]
Homepage = "https://codspeed.io/"
Documentation = "https://docs.codspeed.io/"
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",
"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]
lint = ["mypy ~= 1.11.2", "ruff ~= 0.6.5"]
compat = [
"pytest-benchmark ~= 5.0.0",
"pytest-xdist ~= 3.6.1",
# "pytest-speed>=0.3.5",
]
test = ["pytest ~= 7.0", "pytest-cov ~= 4.0.0"]
[tool.uv.sources]
pytest-codspeed = { workspace = true }
[dependency-groups]
dev = ["pytest-codspeed"]
[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 = "3.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-3.2.0/scripts/ 0000775 0000000 0000000 00000000000 14747156346 0016340 5 ustar 00root root 0000000 0000000 pytest-codspeed-3.2.0/scripts/post-release.sh 0000775 0000000 0000000 00000000325 14747156346 0021302 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-3.2.0/scripts/pre-release.sh 0000775 0000000 0000000 00000001114 14747156346 0021100 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 -o CHANGELOG.md --tag $VERSION
git add CHANGELOG.md
fi
uv lock
git add uv.lock
pytest-codspeed-3.2.0/setup.py 0000664 0000000 0000000 00000003221 14747156346 0016361 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/valgrind/_wrapper/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/valgrind/_wrapper/*.h",
"instruments/valgrind/_wrapper/*.c",
]
},
ext_modules=(
[ffi_extension] if IS_EXTENSION_BUILDABLE and not SKIP_EXTENSION_BUILD else []
),
)
pytest-codspeed-3.2.0/src/ 0000775 0000000 0000000 00000000000 14747156346 0015440 5 ustar 00root root 0000000 0000000 pytest-codspeed-3.2.0/src/pytest_codspeed/ 0000775 0000000 0000000 00000000000 14747156346 0020636 5 ustar 00root root 0000000 0000000 pytest-codspeed-3.2.0/src/pytest_codspeed/__init__.py 0000664 0000000 0000000 00000000352 14747156346 0022747 0 ustar 00root root 0000000 0000000 __version__ = "3.2.0"
# We also have the semver version since __version__ is not semver compliant
__semver_version__ = "3.2.0"
from .plugin import BenchmarkFixture
__all__ = ["BenchmarkFixture", "__version__", "__semver_version__"]
pytest-codspeed-3.2.0/src/pytest_codspeed/instruments/ 0000775 0000000 0000000 00000000000 14747156346 0023231 5 ustar 00root root 0000000 0000000 pytest-codspeed-3.2.0/src/pytest_codspeed/instruments/__init__.py 0000664 0000000 0000000 00000002565 14747156346 0025352 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, ParamSpec, TypeVar
import pytest
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,
name: str,
uri: str,
fn: Callable[P, T],
*args: P.args,
**kwargs: P.kwargs,
) -> 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-3.2.0/src/pytest_codspeed/instruments/valgrind/ 0000775 0000000 0000000 00000000000 14747156346 0025037 5 ustar 00root root 0000000 0000000 pytest-codspeed-3.2.0/src/pytest_codspeed/instruments/valgrind/__init__.py 0000664 0000000 0000000 00000006012 14747156346 0027147 0 ustar 00root root 0000000 0000000 from __future__ import annotations
import os
import sys
from typing import TYPE_CHECKING
from pytest_codspeed import __semver_version__
from pytest_codspeed.instruments import Instrument
from pytest_codspeed.instruments.valgrind._wrapper import get_lib
if TYPE_CHECKING:
from typing import Any, Callable
from pytest import Session
from pytest_codspeed.instruments import P, T
from pytest_codspeed.instruments.valgrind._wrapper import LibType
from pytest_codspeed.plugin import CodSpeedConfig
SUPPORTS_PERF_TRAMPOLINE = sys.version_info >= (3, 12)
class ValgrindInstrument(Instrument):
instrument = "valgrind"
lib: LibType | None
def __init__(self, config: CodSpeedConfig) -> None:
self.benchmark_count = 0
self.should_measure = os.environ.get("CODSPEED_ENV") is not None
if self.should_measure:
self.lib = get_lib()
self.lib.dump_stats_at(
f"Metadata: pytest-codspeed {__semver_version__}".encode("ascii")
)
if SUPPORTS_PERF_TRAMPOLINE:
sys.activate_stack_trampoline("perf") # type: ignore
else:
self.lib = 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,
name: str,
uri: str,
fn: Callable[P, T],
*args: P.args,
**kwargs: P.kwargs,
) -> T:
self.benchmark_count += 1
if self.lib is None: # Thus should_measure is False
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__()
self.lib.zero_stats()
self.lib.start_instrumentation()
try:
return __codspeed_root_frame__()
finally:
# Ensure instrumentation is stopped even if the test failed
self.lib.stop_instrumentation()
self.lib.dump_stats_at(uri.encode("ascii"))
def report(self, session: Session) -> None:
reporter = session.config.pluginmanager.get_plugin("terminalreporter")
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-3.2.0/src/pytest_codspeed/instruments/valgrind/_wrapper/ 0000775 0000000 0000000 00000000000 14747156346 0026656 5 ustar 00root root 0000000 0000000 pytest-codspeed-3.2.0/src/pytest_codspeed/instruments/valgrind/_wrapper/__init__.py 0000664 0000000 0000000 00000000527 14747156346 0030773 0 ustar 00root root 0000000 0000000 from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from .wrapper import lib as LibType
def get_lib() -> LibType:
try:
from .dist_callgrind_wrapper import lib # type: ignore
return lib
except Exception as e:
raise Exception("Failed to get a compiled wrapper") from e
pytest-codspeed-3.2.0/src/pytest_codspeed/instruments/valgrind/_wrapper/build.py 0000664 0000000 0000000 00000000747 14747156346 0030337 0 ustar 00root root 0000000 0000000 from pathlib import Path
from cffi import FFI # type: ignore
wrapper_dir = Path(__file__).parent
ffibuilder = FFI()
ffibuilder.cdef((wrapper_dir / "wrapper.h").read_text())
ffibuilder.set_source(
"pytest_codspeed.instruments.valgrind._wrapper.dist_callgrind_wrapper",
'#include "wrapper.h"',
sources=["src/pytest_codspeed/instruments/valgrind/_wrapper/wrapper.c"],
include_dirs=[str(wrapper_dir)],
)
if __name__ == "__main__":
ffibuilder.compile(verbose=True)
pytest-codspeed-3.2.0/src/pytest_codspeed/instruments/valgrind/_wrapper/wrapper.c 0000664 0000000 0000000 00000000610 14747156346 0030477 0 ustar 00root root 0000000 0000000 #include
void start_instrumentation() {
CALLGRIND_START_INSTRUMENTATION;
}
void stop_instrumentation() {
CALLGRIND_STOP_INSTRUMENTATION;
}
void dump_stats() {
CALLGRIND_DUMP_STATS;
}
void dump_stats_at(char *s) {
CALLGRIND_DUMP_STATS_AT(s);
}
void zero_stats() {
CALLGRIND_ZERO_STATS;
}
void toggle_collect() {
CALLGRIND_TOGGLE_COLLECT;
}
pytest-codspeed-3.2.0/src/pytest_codspeed/instruments/valgrind/_wrapper/wrapper.h 0000664 0000000 0000000 00000000225 14747156346 0030506 0 ustar 00root root 0000000 0000000 void start_instrumentation();
void stop_instrumentation();
void dump_stats();
void dump_stats_at(char *s);
void zero_stats();
void toggle_collect();
pytest-codspeed-3.2.0/src/pytest_codspeed/instruments/valgrind/_wrapper/wrapper.pyi 0000664 0000000 0000000 00000000555 14747156346 0031066 0 ustar 00root root 0000000 0000000 class lib:
@staticmethod
def start_instrumentation() -> None: ...
@staticmethod
def stop_instrumentation() -> None: ...
@staticmethod
def dump_stats() -> None: ...
@staticmethod
def dump_stats_at(trigger: bytes) -> None: ...
@staticmethod
def zero_stats() -> None: ...
@staticmethod
def toggle_collect() -> None: ...
pytest-codspeed-3.2.0/src/pytest_codspeed/instruments/walltime.py 0000664 0000000 0000000 00000017331 14747156346 0025426 0 ustar 00root root 0000000 0000000 from __future__ import annotations
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.instruments import Instrument
if TYPE_CHECKING:
from typing import Any, Callable
from pytest import Session
from pytest_codspeed.instruments import P, T
from pytest_codspeed.plugin import 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 = 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(cls, config: CodSpeedConfig) -> BenchmarkConfig:
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=DEFAULT_MIN_ROUND_TIME_NS,
max_time_ns=config.max_time_ns
if config.max_time_ns is not None
else DEFAULT_MAX_TIME_NS,
max_rounds=config.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
def run_benchmark(
name: str, uri: str, fn: Callable[P, T], args, kwargs, config: BenchmarkConfig
) -> tuple[Benchmark, T]:
# Compute the actual result of the function
out = fn(*args, **kwargs)
# Warmup
times_per_round_ns: list[float] = []
warmup_start = start = perf_counter_ns()
while True:
start = perf_counter_ns()
fn(*args, **kwargs)
end = perf_counter_ns()
times_per_round_ns.append(end - start)
if end - warmup_start > 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(config.min_round_time_ns / warmup_mean_ns))
if warmup_mean_ns <= config.min_round_time_ns
else 1
)
if config.max_rounds is None:
round_time_ns = warmup_mean_ns * iter_per_round
rounds = int(config.max_time_ns / round_time_ns)
else:
rounds = config.max_rounds
rounds = max(1, rounds)
# Benchmark
iter_range = range(iter_per_round)
run_start = perf_counter_ns()
for _ in range(rounds):
start = perf_counter_ns()
for _ in iter_range:
fn(*args, **kwargs)
end = perf_counter_ns()
times_per_round_ns.append(end - start)
if end - run_start > config.max_time_ns:
# TODO: log something
break
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,
)
return Benchmark(name=name, uri=uri, config=config, stats=stats), out
class WallTimeInstrument(Instrument):
instrument = "walltime"
def __init__(self, config: CodSpeedConfig) -> None:
self.config = config
self.benchmarks: list[Benchmark] = []
def get_instrument_config_str_and_warns(self) -> tuple[str, list[str]]:
return f"mode: walltime, timer_resolution: {TIMER_RESOLUTION_NS:.1f}ns", []
def measure(
self,
name: str,
uri: str,
fn: Callable[P, T],
*args: P.args,
**kwargs: P.kwargs,
) -> T:
bench, out = run_benchmark(
name=name,
uri=uri,
fn=fn,
args=args,
kwargs=kwargs,
config=BenchmarkConfig.from_codspeed_config(self.config),
)
self.benchmarks.append(bench)
return out
def report(self, session: Session) -> None:
reporter = session.config.pluginmanager.get_plugin("terminalreporter")
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-3.2.0/src/pytest_codspeed/plugin.py 0000664 0000000 0000000 00000027020 14747156346 0022507 0 ustar 00root root 0000000 0000000 from __future__ import annotations
import functools
import gc
import importlib.util
import json
import os
import random
from dataclasses import dataclass, field
from pathlib import Path
from time import time
from typing import TYPE_CHECKING
import pytest
from _pytest.fixtures import FixtureManager
from pytest_codspeed.instruments import (
MeasurementMode,
get_instrument_from_mode,
)
from pytest_codspeed.utils import (
get_environment_metadata,
get_git_relative_uri_and_name,
)
from . import __version__
if TYPE_CHECKING:
from typing import Callable, ParamSpec, TypeVar
from pytest_codspeed.instruments import Instrument
T = TypeVar("T")
P = ParamSpec("P")
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)
@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(frozen=True)
class CodSpeedConfig:
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(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 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")
codspeedconfig = CodSpeedConfig.from_pytest_config(config)
plugin = CodSpeedPlugin(
disabled_plugins=tuple(disabled_plugins),
is_codspeed_enabled=is_codspeed_enabled,
mode=mode,
instrument=instrument(codspeedconfig),
config=codspeedconfig,
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 = 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 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,
nodeid: str,
config: pytest.Config,
fn: Callable[P, T],
*args: P.args,
**kwargs: P.kwargs,
) -> T:
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(nodeid, config.rootpath)
return plugin.instrument.measure(name, uri, fn, *args, **kwargs)
finally:
# Ensure GC is re-enabled even if the test failed
if is_gc_enabled:
gc.enable()
def wrap_runtest(
plugin: CodSpeedPlugin,
nodeid: str,
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, nodeid, config, 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.nodeid, 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
def __call__(self, func: Callable[P, T], *args: P.args, **kwargs: P.kwargs) -> T:
config = self._request.config
plugin = get_plugin(config)
if plugin.is_codspeed_enabled:
return _measure(
plugin, self._request.node.nodeid, config, func, *args, **kwargs
)
else:
return func(*args, **kwargs)
@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-3.2.0/src/pytest_codspeed/py.typed 0000664 0000000 0000000 00000000000 14747156346 0022323 0 ustar 00root root 0000000 0000000 pytest-codspeed-3.2.0/src/pytest_codspeed/utils.py 0000664 0000000 0000000 00000004005 14747156346 0022347 0 ustar 00root root 0000000 0000000 from __future__ import annotations
import os
import sys
import sysconfig
from pathlib import Path
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
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-3.2.0/tests/ 0000775 0000000 0000000 00000000000 14747156346 0016013 5 ustar 00root root 0000000 0000000 pytest-codspeed-3.2.0/tests/benchmarks/ 0000775 0000000 0000000 00000000000 14747156346 0020130 5 ustar 00root root 0000000 0000000 pytest-codspeed-3.2.0/tests/benchmarks/TheAlgorithms/ 0000775 0000000 0000000 00000000000 14747156346 0022702 5 ustar 00root root 0000000 0000000 pytest-codspeed-3.2.0/tests/benchmarks/TheAlgorithms_bench/ 0000775 0000000 0000000 00000000000 14747156346 0024041 5 ustar 00root root 0000000 0000000 pytest-codspeed-3.2.0/tests/benchmarks/TheAlgorithms_bench/__init__.py 0000664 0000000 0000000 00000000000 14747156346 0026140 0 ustar 00root root 0000000 0000000 pytest-codspeed-3.2.0/tests/benchmarks/TheAlgorithms_bench/bit_manipulation.py 0000664 0000000 0000000 00000014263 14747156346 0027757 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-3.2.0/tests/benchmarks/TheAlgorithms_bench/test_bench_audio_filters.py 0000664 0000000 0000000 00000002150 14747156346 0031440 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-3.2.0/tests/benchmarks/TheAlgorithms_bench/test_bench_backtracking.py 0000664 0000000 0000000 00000013027 14747156346 0031237 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-3.2.0/tests/benchmarks/__init__.py 0000664 0000000 0000000 00000000000 14747156346 0022227 0 ustar 00root root 0000000 0000000 pytest-codspeed-3.2.0/tests/benchmarks/test_bench_fibo.py 0000664 0000000 0000000 00000002014 14747156346 0023614 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-3.2.0/tests/benchmarks/test_bench_syscalls.py 0000664 0000000 0000000 00000007760 14747156346 0024547 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-3.2.0/tests/benchmarks/test_bench_various_noop.py 0000664 0000000 0000000 00000001066 14747156346 0025426 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-3.2.0/tests/conftest.py 0000664 0000000 0000000 00000005005 14747156346 0020212 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
if TYPE_CHECKING:
from _pytest.pytester import RunResult
pytest_plugins = ["pytester"]
IS_PYTEST_BENCHMARK_INSTALLED = importlib.util.find_spec("pytest_benchmark") is not None
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-3.2.0/tests/examples/ 0000775 0000000 0000000 00000000000 14747156346 0017631 5 ustar 00root root 0000000 0000000 pytest-codspeed-3.2.0/tests/examples/__init__.py 0000664 0000000 0000000 00000000000 14747156346 0021730 0 ustar 00root root 0000000 0000000 pytest-codspeed-3.2.0/tests/examples/test_addition_fixture.py 0000664 0000000 0000000 00000000140 14747156346 0024576 0 ustar 00root root 0000000 0000000 def test_some_addition_performance(benchmark):
@benchmark
def _():
return 1 + 1
pytest-codspeed-3.2.0/tests/test_pytest_plugin.py 0000664 0000000 0000000 00000021611 14747156346 0022333 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_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-codspeed-3.2.0/tests/test_pytest_plugin_cpu_instrumentation.py 0000664 0000000 0000000 00000006703 14747156346 0026532 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*"])
pytest-codspeed-3.2.0/tests/test_pytest_plugin_walltime.py 0000664 0000000 0000000 00000002152 14747156346 0024230 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*",
]
)
pytest-codspeed-3.2.0/tests/test_utils.py 0000664 0000000 0000000 00000002204 14747156346 0020562 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-3.2.0/uv.lock 0000664 0000000 0000000 00000206713 14747156346 0016166 0 ustar 00root root 0000000 0000000 version = 1
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 }
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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ url = "https://files.pythonhosted.org/packages/f8/fe/4d41c2f200c4a457933dbd98d3cf4e911870877bd94d9656cc0fcb390681/cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c", size = 171804 },
{ url = "https://files.pythonhosted.org/packages/d1/b6/0b0f5ab93b0df4acc49cae758c81fe4e5ef26c3ae2e10cc69249dfd8b3ab/cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15", size = 181299 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ url = "https://files.pythonhosted.org/packages/34/33/e1b8a1ba29025adbdcda5fb3a36f94c03d771c1b7b12f726ff7fef2ebe36/cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655", size = 171727 },
{ url = "https://files.pythonhosted.org/packages/3d/97/50228be003bb2802627d28ec0627837ac0bf35c90cf769812056f235b2d1/cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0", size = 181400 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ url = "https://files.pythonhosted.org/packages/86/c5/28b2d6f799ec0bdecf44dced2ec5ed43e0eb63097b0f58c293583b406582/cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65", size = 172448 },
{ url = "https://files.pythonhosted.org/packages/50/b9/db34c4755a7bd1cb2d1603ac3863f22bcecbd1ba29e5ee841a4bc510b294/cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903", size = 181976 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ url = "https://files.pythonhosted.org/packages/bf/ee/f94057fa6426481d663b88637a9a10e859e492c73d0384514a17d78ee205/cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", size = 172475 },
{ url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ url = "https://files.pythonhosted.org/packages/cb/b5/fd9f8b5a84010ca169ee49f4e4ad6f8c05f4e3545b72ee041dbbcb159882/cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7", size = 171820 },
{ url = "https://files.pythonhosted.org/packages/8c/52/b08750ce0bce45c143e1b5d7357ee8c55341b52bdef4b0f081af1eb248c2/cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662", size = 181290 },
]
[[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 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 },
]
[[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 }
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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ url = "https://files.pythonhosted.org/packages/ba/e2/1c5fb52eafcffeebaa9db084bff47e7c3cf4f97db752226c232cee4d530b/coverage-7.6.8-cp310-cp310-win32.whl", hash = "sha256:3988665ee376abce49613701336544041f2117de7b7fbfe91b93d8ff8b151c8e", size = 209669 },
{ url = "https://files.pythonhosted.org/packages/31/31/6a56469609a252549dd4b090815428d5521edd4642440d987573a450c069/coverage-7.6.8-cp310-cp310-win_amd64.whl", hash = "sha256:f56f49b2553d7dd85fd86e029515a221e5c1f8cb3d9c38b470bc38bde7b8445a", size = 210509 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ url = "https://files.pythonhosted.org/packages/f0/70/b05328901e4debe76e033717e1452d00246c458c44e9dbd893e7619c2967/coverage-7.6.8-cp311-cp311-win32.whl", hash = "sha256:629a1ba2115dce8bf75a5cce9f2486ae483cb89c0145795603d6554bdc83e801", size = 209638 },
{ url = "https://files.pythonhosted.org/packages/70/55/1efa24f960a2fa9fbc44a9523d3f3c50ceb94dd1e8cd732168ab2dc41b07/coverage-7.6.8-cp311-cp311-win_amd64.whl", hash = "sha256:fb9fc32399dca861584d96eccd6c980b69bbcd7c228d06fb74fe53e007aa8ef9", size = 210506 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ url = "https://files.pythonhosted.org/packages/0c/50/054f0b464fbae0483217186478eefa2e7df3a79917ed7f1d430b6da2cf0d/coverage-7.6.8-cp312-cp312-win32.whl", hash = "sha256:9e89d5c8509fbd6c03d0dd1972925b22f50db0792ce06324ba069f10787429ad", size = 209895 },
{ url = "https://files.pythonhosted.org/packages/df/d0/09ba870360a27ecf09e177ca2ff59d4337fc7197b456f22ceff85cffcfa5/coverage-7.6.8-cp312-cp312-win_amd64.whl", hash = "sha256:379c111d3558272a2cae3d8e57e6b6e6f4fe652905692d54bad5ea0ca37c5ad4", size = 210684 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ url = "https://files.pythonhosted.org/packages/8d/ac/687e9ba5e6d0979e9dab5c02e01c4f24ac58260ef82d88d3b433b3f84f1e/coverage-7.6.8-cp313-cp313-win32.whl", hash = "sha256:8aae5aea53cbfe024919715eca696b1a3201886ce83790537d1c3668459c7146", size = 209957 },
{ url = "https://files.pythonhosted.org/packages/2f/a3/b61cc8e3fcf075293fb0f3dee405748453c5ba28ac02ceb4a87f52bdb105/coverage-7.6.8-cp313-cp313-win_amd64.whl", hash = "sha256:ae270e79f7e169ccfe23284ff5ea2d52a6f401dc01b337efb54b3783e2ce3f28", size = 210711 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ url = "https://files.pythonhosted.org/packages/bd/37/e450c9f6b297c79bb9858407396ed3e084dcc22990dd110ab01d5ceb9770/coverage-7.6.8-cp313-cp313t-win32.whl", hash = "sha256:6535d996f6537ecb298b4e287a855f37deaf64ff007162ec0afb9ab8ba3b8b71", size = 210605 },
{ url = "https://files.pythonhosted.org/packages/44/79/7d0c7dd237c6905018e2936cd1055fe1d42e7eba2ebab3c00f4aad2a27d7/coverage-7.6.8-cp313-cp313t-win_amd64.whl", hash = "sha256:c79c0685f142ca53256722a384540832420dff4ab15fec1863d7e5bc8691bdcc", size = 211777 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ url = "https://files.pythonhosted.org/packages/f4/1d/d61d9b2d17628c4db834e9650b776663535b4258d0dc204ec475188b5b2a/coverage-7.6.8-cp39-cp39-win32.whl", hash = "sha256:4db3ed6a907b555e57cc2e6f14dc3a4c2458cdad8919e40b5357ab9b6db6c43e", size = 209695 },
{ url = "https://files.pythonhosted.org/packages/0f/d1/ef43852a998c41183dbffed4ab0dd658f9975d570c6106ea43fdcb5dcbf4/coverage-7.6.8-cp39-cp39-win_amd64.whl", hash = "sha256:428ac484592f780e8cd7b6b14eb568f7c85460c92e2a37cb0c0e5186e1a0d076", size = 210475 },
{ url = "https://files.pythonhosted.org/packages/32/df/0d2476121cd0bfb9ca2413efe02289c474b82c4b134863bef4b89ec7bcfa/coverage-7.6.8-pp39.pp310-none-any.whl", hash = "sha256:5c52a036535d12590c32c49209e79cabaad9f9ad8aa4cbd875b68c4d67a9cbce", size = 199230 },
]
[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 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453 },
]
[[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 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/43/09/2aea36ff60d16dd8879bdb2f5b3ee0ba8d08cbbdcdfe870e695ce3784385/execnet-2.1.1-py3-none-any.whl", hash = "sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc", size = 40612 },
]
[[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 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a0/d9/a1e041c5e7caa9a05c925f4bdbdfb7f006d1f74996af53467bc394c97be7/importlib_metadata-8.5.0-py3-none-any.whl", hash = "sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b", size = 26514 },
]
[[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 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 },
]
[[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 }
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 },
]
[[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 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 },
]
[[package]]
name = "mypy"
version = "1.11.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "mypy-extensions" },
{ name = "tomli", marker = "python_full_version < '3.11'" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/5c/86/5d7cbc4974fd564550b80fbb8103c05501ea11aa7835edf3351d90095896/mypy-1.11.2.tar.gz", hash = "sha256:7f9993ad3e0ffdc95c2a14b66dee63729f021968bff8ad911867579c65d13a79", size = 3078806 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/78/cd/815368cd83c3a31873e5e55b317551500b12f2d1d7549720632f32630333/mypy-1.11.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d42a6dd818ffce7be66cce644f1dff482f1d97c53ca70908dff0b9ddc120b77a", size = 10939401 },
{ url = "https://files.pythonhosted.org/packages/f1/27/e18c93a195d2fad75eb96e1f1cbc431842c332e8eba2e2b77eaf7313c6b7/mypy-1.11.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:801780c56d1cdb896eacd5619a83e427ce436d86a3bdf9112527f24a66618fef", size = 10111697 },
{ url = "https://files.pythonhosted.org/packages/dc/08/cdc1fc6d0d5a67d354741344cc4aa7d53f7128902ebcbe699ddd4f15a61c/mypy-1.11.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41ea707d036a5307ac674ea172875f40c9d55c5394f888b168033177fce47383", size = 12500508 },
{ url = "https://files.pythonhosted.org/packages/64/12/aad3af008c92c2d5d0720ea3b6674ba94a98cdb86888d389acdb5f218c30/mypy-1.11.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6e658bd2d20565ea86da7d91331b0eed6d2eee22dc031579e6297f3e12c758c8", size = 13020712 },
{ url = "https://files.pythonhosted.org/packages/03/e6/a7d97cc124a565be5e9b7d5c2a6ebf082379ffba99646e4863ed5bbcb3c3/mypy-1.11.2-cp310-cp310-win_amd64.whl", hash = "sha256:478db5f5036817fe45adb7332d927daa62417159d49783041338921dcf646fc7", size = 9567319 },
{ url = "https://files.pythonhosted.org/packages/e2/aa/cc56fb53ebe14c64f1fe91d32d838d6f4db948b9494e200d2f61b820b85d/mypy-1.11.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:75746e06d5fa1e91bfd5432448d00d34593b52e7e91a187d981d08d1f33d4385", size = 10859630 },
{ url = "https://files.pythonhosted.org/packages/04/c8/b19a760fab491c22c51975cf74e3d253b8c8ce2be7afaa2490fbf95a8c59/mypy-1.11.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a976775ab2256aadc6add633d44f100a2517d2388906ec4f13231fafbb0eccca", size = 10037973 },
{ url = "https://files.pythonhosted.org/packages/88/57/7e7e39f2619c8f74a22efb9a4c4eff32b09d3798335625a124436d121d89/mypy-1.11.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cd953f221ac1379050a8a646585a29574488974f79d8082cedef62744f0a0104", size = 12416659 },
{ url = "https://files.pythonhosted.org/packages/fc/a6/37f7544666b63a27e46c48f49caeee388bf3ce95f9c570eb5cfba5234405/mypy-1.11.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:57555a7715c0a34421013144a33d280e73c08df70f3a18a552938587ce9274f4", size = 12897010 },
{ url = "https://files.pythonhosted.org/packages/84/8b/459a513badc4d34acb31c736a0101c22d2bd0697b969796ad93294165cfb/mypy-1.11.2-cp311-cp311-win_amd64.whl", hash = "sha256:36383a4fcbad95f2657642a07ba22ff797de26277158f1cc7bd234821468b1b6", size = 9562873 },
{ url = "https://files.pythonhosted.org/packages/35/3a/ed7b12ecc3f6db2f664ccf85cb2e004d3e90bec928e9d7be6aa2f16b7cdf/mypy-1.11.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e8960dbbbf36906c5c0b7f4fbf2f0c7ffb20f4898e6a879fcf56a41a08b0d318", size = 10990335 },
{ url = "https://files.pythonhosted.org/packages/04/e4/1a9051e2ef10296d206519f1df13d2cc896aea39e8683302f89bf5792a59/mypy-1.11.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:06d26c277962f3fb50e13044674aa10553981ae514288cb7d0a738f495550b36", size = 10007119 },
{ url = "https://files.pythonhosted.org/packages/f3/3c/350a9da895f8a7e87ade0028b962be0252d152e0c2fbaafa6f0658b4d0d4/mypy-1.11.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6e7184632d89d677973a14d00ae4d03214c8bc301ceefcdaf5c474866814c987", size = 12506856 },
{ url = "https://files.pythonhosted.org/packages/b6/49/ee5adf6a49ff13f4202d949544d3d08abb0ea1f3e7f2a6d5b4c10ba0360a/mypy-1.11.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3a66169b92452f72117e2da3a576087025449018afc2d8e9bfe5ffab865709ca", size = 12952066 },
{ url = "https://files.pythonhosted.org/packages/27/c0/b19d709a42b24004d720db37446a42abadf844d5c46a2c442e2a074d70d9/mypy-1.11.2-cp312-cp312-win_amd64.whl", hash = "sha256:969ea3ef09617aff826885a22ece0ddef69d95852cdad2f60c8bb06bf1f71f70", size = 9664000 },
{ url = "https://files.pythonhosted.org/packages/16/64/bb5ed751487e2bea0dfaa6f640a7e3bb88083648f522e766d5ef4a76f578/mypy-1.11.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:801ca29f43d5acce85f8e999b1e431fb479cb02d0e11deb7d2abb56bdaf24fd6", size = 10937294 },
{ url = "https://files.pythonhosted.org/packages/a9/a3/67a0069abed93c3bf3b0bebb8857e2979a02828a4a3fd82f107f8f1143e8/mypy-1.11.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:af8d155170fcf87a2afb55b35dc1a0ac21df4431e7d96717621962e4b9192e70", size = 10107707 },
{ url = "https://files.pythonhosted.org/packages/2f/4d/0379daf4258b454b1f9ed589a9dabd072c17f97496daea7b72fdacf7c248/mypy-1.11.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f7821776e5c4286b6a13138cc935e2e9b6fde05e081bdebf5cdb2bb97c9df81d", size = 12498367 },
{ url = "https://files.pythonhosted.org/packages/3b/dc/3976a988c280b3571b8eb6928882dc4b723a403b21735a6d8ae6ed20e82b/mypy-1.11.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:539c570477a96a4e6fb718b8d5c3e0c0eba1f485df13f86d2970c91f0673148d", size = 13018014 },
{ url = "https://files.pythonhosted.org/packages/83/84/adffc7138fb970e7e2a167bd20b33bb78958370179853a4ebe9008139342/mypy-1.11.2-cp39-cp39-win_amd64.whl", hash = "sha256:3f14cd3d386ac4d05c5a39a51b84387403dadbd936e17cb35882134d4f8f0d24", size = 9568056 },
{ url = "https://files.pythonhosted.org/packages/42/3a/bdf730640ac523229dd6578e8a581795720a9321399de494374afc437ec5/mypy-1.11.2-py3-none-any.whl", hash = "sha256:b499bc07dbdcd3de92b0a8b29fdf592c111276f6a12fe29c30f6c417dd546d12", size = 2619625 },
]
[[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 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", size = 4695 },
]
[[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 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 },
]
[[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 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 },
]
[[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 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e0/a9/023730ba63db1e494a271cb018dcd361bd2c917ba7004c3e49d5daf795a2/py_cpuinfo-9.0.0-py3-none-any.whl", hash = "sha256:859625bc251f64e21f077d099d4162689c762b5d6a4c3c97553d56241c9674d5", size = 22335 },
]
[[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 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552 },
]
[[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 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f7/3f/01c8b82017c199075f8f788d0d906b9ffbbc5a47dc9918a945e13d5a2bda/pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a", size = 1205513 },
]
[[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 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/51/ff/f6e8b8f39e08547faece4bd80f89d5a8de68a38b2d179cc1c4490ffa3286/pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8", size = 325287 },
]
[[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 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f7/e2/c0da4989a933d6bac364f215217c47de37d2f641953aa69a37b66efd6d1b/pytest_benchmark-5.0.1-py3-none-any.whl", hash = "sha256:d75fec4cbf0d4fd91e020f425ce2d845e9c127c21bae35e77c84db8ed84bfaa6", size = 44062 },
]
[[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" },
]
lint = [
{ name = "mypy" },
{ name = "ruff" },
]
test = [
{ name = "pytest" },
{ name = "pytest-cov" },
]
[package.dev-dependencies]
dev = [
{ name = "pytest-codspeed" },
]
[package.metadata]
requires-dist = [
{ name = "cffi", specifier = ">=1.17.1" },
{ name = "importlib-metadata", marker = "python_full_version < '3.10'", specifier = ">=8.5.0" },
{ name = "mypy", marker = "extra == 'lint'", specifier = "~=1.11.2" },
{ name = "pytest", specifier = ">=3.8" },
{ name = "pytest", marker = "extra == 'test'", specifier = "~=7.0" },
{ name = "pytest-benchmark", marker = "extra == 'compat'", specifier = "~=5.0.0" },
{ name = "pytest-cov", marker = "extra == 'test'", specifier = "~=4.0.0" },
{ name = "pytest-xdist", marker = "extra == 'compat'", specifier = "~=3.6.1" },
{ name = "rich", specifier = ">=13.8.1" },
{ name = "ruff", marker = "extra == 'lint'", specifier = "~=0.6.5" },
]
[package.metadata.requires-dev]
dev = [{ name = "pytest-codspeed", editable = "." }]
[[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 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/fe/1f/9ec0ddd33bd2b37d6ec50bb39155bca4fe7085fa78b3b434c05459a860e3/pytest_cov-4.0.0-py3-none-any.whl", hash = "sha256:2feb1b751d66a8bd934e5edfa2e961d11309dc37b73b0eabe73b5945fee20f6b", size = 21554 },
]
[[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 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/6d/82/1d96bf03ee4c0fdc3c0cbe61470070e659ca78dc0086fb88b66c185e2449/pytest_xdist-3.6.1-py3-none-any.whl", hash = "sha256:9ed4adfb68a016610848639bb7e02c9352d5d9f03d04809919e2dafc3be4cca7", size = 46108 },
]
[[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 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/19/71/39c7c0d87f8d4e6c020a393182060eaefeeae6c01dab6a84ec346f2567df/rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90", size = 242424 },
]
[[package]]
name = "ruff"
version = "0.6.9"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/26/0d/6148a48dab5662ca1d5a93b7c0d13c03abd3cc7e2f35db08410e47cef15d/ruff-0.6.9.tar.gz", hash = "sha256:b076ef717a8e5bc819514ee1d602bbdca5b4420ae13a9cf61a0c0a4f53a2baa2", size = 3095355 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/6e/8f/f7a0a0ef1818662efb32ed6df16078c95da7a0a3248d64c2410c1e27799f/ruff-0.6.9-py3-none-linux_armv6l.whl", hash = "sha256:064df58d84ccc0ac0fcd63bc3090b251d90e2a372558c0f057c3f75ed73e1ccd", size = 10440526 },
{ url = "https://files.pythonhosted.org/packages/8b/69/b179a5faf936a9e2ab45bb412a668e4661eded964ccfa19d533f29463ef6/ruff-0.6.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:140d4b5c9f5fc7a7b074908a78ab8d384dd7f6510402267bc76c37195c02a7ec", size = 10034612 },
{ url = "https://files.pythonhosted.org/packages/c7/ef/fd1b4be979c579d191eeac37b5cfc0ec906de72c8bcd8595e2c81bb700c1/ruff-0.6.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:53fd8ca5e82bdee8da7f506d7b03a261f24cd43d090ea9db9a1dc59d9313914c", size = 9706197 },
{ url = "https://files.pythonhosted.org/packages/29/61/b376d775deb5851cb48d893c568b511a6d3625ef2c129ad5698b64fb523c/ruff-0.6.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645d7d8761f915e48a00d4ecc3686969761df69fb561dd914a773c1a8266e14e", size = 10751855 },
{ url = "https://files.pythonhosted.org/packages/13/d7/def9e5f446d75b9a9c19b24231a3a658c075d79163b08582e56fa5dcfa38/ruff-0.6.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eae02b700763e3847595b9d2891488989cac00214da7f845f4bcf2989007d577", size = 10200889 },
{ url = "https://files.pythonhosted.org/packages/6c/d6/7f34160818bcb6e84ce293a5966cba368d9112ff0289b273fbb689046047/ruff-0.6.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d5ccc9e58112441de8ad4b29dcb7a86dc25c5f770e3c06a9d57e0e5eba48829", size = 11038678 },
{ url = "https://files.pythonhosted.org/packages/13/34/a40ff8ae62fb1b26fb8e6fa7e64bc0e0a834b47317880de22edd6bfb54fb/ruff-0.6.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:417b81aa1c9b60b2f8edc463c58363075412866ae4e2b9ab0f690dc1e87ac1b5", size = 11808682 },
{ url = "https://files.pythonhosted.org/packages/2e/6d/25a4386ae4009fc798bd10ba48c942d1b0b3e459b5403028f1214b6dd161/ruff-0.6.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3c866b631f5fbce896a74a6e4383407ba7507b815ccc52bcedabb6810fdb3ef7", size = 11330446 },
{ url = "https://files.pythonhosted.org/packages/f7/f6/bdf891a9200d692c94ebcd06ae5a2fa5894e522f2c66c2a12dd5d8cb2654/ruff-0.6.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7b118afbb3202f5911486ad52da86d1d52305b59e7ef2031cea3425142b97d6f", size = 12483048 },
{ url = "https://files.pythonhosted.org/packages/a7/86/96f4252f41840e325b3fa6c48297e661abb9f564bd7dcc0572398c8daa42/ruff-0.6.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a67267654edc23c97335586774790cde402fb6bbdb3c2314f1fc087dee320bfa", size = 10936855 },
{ url = "https://files.pythonhosted.org/packages/45/87/801a52d26c8dbf73424238e9908b9ceac430d903c8ef35eab1b44fcfa2bd/ruff-0.6.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3ef0cc774b00fec123f635ce5c547dac263f6ee9fb9cc83437c5904183b55ceb", size = 10713007 },
{ url = "https://files.pythonhosted.org/packages/be/27/6f7161d90320a389695e32b6ebdbfbedde28ccbf52451e4b723d7ce744ad/ruff-0.6.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:12edd2af0c60fa61ff31cefb90aef4288ac4d372b4962c2864aeea3a1a2460c0", size = 10274594 },
{ url = "https://files.pythonhosted.org/packages/00/52/dc311775e7b5f5b19831563cb1572ecce63e62681bccc609867711fae317/ruff-0.6.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:55bb01caeaf3a60b2b2bba07308a02fca6ab56233302406ed5245180a05c5625", size = 10608024 },
{ url = "https://files.pythonhosted.org/packages/98/b6/be0a1ddcbac65a30c985cf7224c4fce786ba2c51e7efeb5178fe410ed3cf/ruff-0.6.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:925d26471fa24b0ce5a6cdfab1bb526fb4159952385f386bdcc643813d472039", size = 10982085 },
{ url = "https://files.pythonhosted.org/packages/bb/a4/c84bc13d0b573cf7bb7d17b16d6d29f84267c92d79b2f478d4ce322e8e72/ruff-0.6.9-py3-none-win32.whl", hash = "sha256:eb61ec9bdb2506cffd492e05ac40e5bc6284873aceb605503d8494180d6fc84d", size = 8522088 },
{ url = "https://files.pythonhosted.org/packages/74/be/fc352bd8ca40daae8740b54c1c3e905a7efe470d420a268cd62150248c91/ruff-0.6.9-py3-none-win_amd64.whl", hash = "sha256:785d31851c1ae91f45b3d8fe23b8ae4b5170089021fbb42402d811135f0b7117", size = 9359275 },
{ url = "https://files.pythonhosted.org/packages/3e/14/fd026bc74ded05e2351681545a5f626e78ef831f8edce064d61acd2e6ec7/ruff-0.6.9-py3-none-win_arm64.whl", hash = "sha256:a9641e31476d601f83cd602608739a0840e348bda93fec9f1ee816f8b6798b93", size = 8679879 },
]
[[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 }
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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310 },
{ url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875 },
{ url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724 },
{ url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383 },
{ url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257 },
]
[[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 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 },
]
[[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 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b7/1a/7e4798e9339adc931158c9d69ecc34f5e6791489d469f5e50ec15e35f458/zipp-3.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931", size = 9630 },
]