pax_global_header00006660000000000000000000000064147471563460014533gustar00rootroot0000000000000052 comment=253b1bea24e909d257c7650265ff34e22af6a3bc pytest-codspeed-3.2.0/000077500000000000000000000000001474715634600146515ustar00rootroot00000000000000pytest-codspeed-3.2.0/.github/000077500000000000000000000000001474715634600162115ustar00rootroot00000000000000pytest-codspeed-3.2.0/.github/workflows/000077500000000000000000000000001474715634600202465ustar00rootroot00000000000000pytest-codspeed-3.2.0/.github/workflows/ci.yml000066400000000000000000000042061474715634600213660ustar00rootroot00000000000000name: 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.yml000066400000000000000000000017401474715634600225610ustar00rootroot00000000000000name: 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.yml000066400000000000000000000052341474715634600224150ustar00rootroot00000000000000name: 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/.gitignore000066400000000000000000000060721474715634600166460ustar00rootroot00000000000000# 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/.gitmodules000066400000000000000000000002031474715634600170210ustar00rootroot00000000000000[submodule "tests/benchmarks/TheAlgorithms"] path = tests/benchmarks/TheAlgorithms url = git@github.com:TheAlgorithms/Python.git pytest-codspeed-3.2.0/.pre-commit-config.yaml000066400000000000000000000010571474715634600211350ustar00rootroot00000000000000# 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/000077500000000000000000000000001474715634600162125ustar00rootroot00000000000000pytest-codspeed-3.2.0/.vscode/launch.json000066400000000000000000000004111474715634600203530ustar00rootroot00000000000000{ "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.json000066400000000000000000000002001474715634600207350ustar00rootroot00000000000000{ "python.testing.pytestArgs": ["tests"], "python.testing.unittestEnabled": false, "python.testing.pytestEnabled": true } pytest-codspeed-3.2.0/CHANGELOG.md000066400000000000000000000273651474715634600164770ustar00rootroot00000000000000# 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/LICENSE000066400000000000000000000021041474715634600156530ustar00rootroot00000000000000The 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.md000066400000000000000000000064401474715634600161340ustar00rootroot00000000000000

pytest-codspeed

[![CI](https://github.com/CodSpeedHQ/pytest-codspeed/actions/workflows/ci.yml/badge.svg)](https://github.com/CodSpeedHQ/pytest-codspeed/actions/workflows/ci.yml) [![PyPi Version](https://img.shields.io/pypi/v/pytest-codspeed?color=%2334D058&label=pypi)](https://pypi.org/project/pytest-codspeed) ![Python Version](https://img.shields.io/badge/python-3.9%20|%203.10%20|%203.11%20|%203.12%20|%203.13-informational.svg) [![Discord](https://img.shields.io/badge/chat%20on-discord-7289da.svg)](https://discord.com/invite/MxpaCfKSqF) [![CodSpeed Badge](https://img.shields.io/endpoint?url=https://codspeed.io/badge.json)](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.toml000066400000000000000000000103531474715634600166330ustar00rootroot00000000000000# 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.toml000066400000000000000000000073001474715634600175650ustar00rootroot00000000000000[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/000077500000000000000000000000001474715634600163405ustar00rootroot00000000000000pytest-codspeed-3.2.0/scripts/post-release.sh000077500000000000000000000003251474715634600213020ustar00rootroot00000000000000#!/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.sh000077500000000000000000000011141474715634600211000ustar00rootroot00000000000000#!/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.py000066400000000000000000000032211474715634600163610ustar00rootroot00000000000000import 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/000077500000000000000000000000001474715634600154405ustar00rootroot00000000000000pytest-codspeed-3.2.0/src/pytest_codspeed/000077500000000000000000000000001474715634600206365ustar00rootroot00000000000000pytest-codspeed-3.2.0/src/pytest_codspeed/__init__.py000066400000000000000000000003521474715634600227470ustar00rootroot00000000000000__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/000077500000000000000000000000001474715634600232315ustar00rootroot00000000000000pytest-codspeed-3.2.0/src/pytest_codspeed/instruments/__init__.py000066400000000000000000000025651474715634600253520ustar00rootroot00000000000000from __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/000077500000000000000000000000001474715634600250375ustar00rootroot00000000000000pytest-codspeed-3.2.0/src/pytest_codspeed/instruments/valgrind/__init__.py000066400000000000000000000060121474715634600271470ustar00rootroot00000000000000from __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/000077500000000000000000000000001474715634600266565ustar00rootroot00000000000000pytest-codspeed-3.2.0/src/pytest_codspeed/instruments/valgrind/_wrapper/__init__.py000066400000000000000000000005271474715634600307730ustar00rootroot00000000000000from __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.py000066400000000000000000000007471474715634600303370ustar00rootroot00000000000000from 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.c000066400000000000000000000006101474715634600304770ustar00rootroot00000000000000#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.h000066400000000000000000000002251474715634600305060ustar00rootroot00000000000000void 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.pyi000066400000000000000000000005551474715634600310660ustar00rootroot00000000000000class 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.py000066400000000000000000000173311474715634600254260ustar00rootroot00000000000000from __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.py000066400000000000000000000270201474715634600225070ustar00rootroot00000000000000from __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.typed000066400000000000000000000000001474715634600223230ustar00rootroot00000000000000pytest-codspeed-3.2.0/src/pytest_codspeed/utils.py000066400000000000000000000040051474715634600223470ustar00rootroot00000000000000from __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/000077500000000000000000000000001474715634600160135ustar00rootroot00000000000000pytest-codspeed-3.2.0/tests/benchmarks/000077500000000000000000000000001474715634600201305ustar00rootroot00000000000000pytest-codspeed-3.2.0/tests/benchmarks/TheAlgorithms/000077500000000000000000000000001474715634600227025ustar00rootroot00000000000000pytest-codspeed-3.2.0/tests/benchmarks/TheAlgorithms_bench/000077500000000000000000000000001474715634600240415ustar00rootroot00000000000000pytest-codspeed-3.2.0/tests/benchmarks/TheAlgorithms_bench/__init__.py000066400000000000000000000000001474715634600261400ustar00rootroot00000000000000pytest-codspeed-3.2.0/tests/benchmarks/TheAlgorithms_bench/bit_manipulation.py000066400000000000000000000142631474715634600277570ustar00rootroot00000000000000import 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.py000066400000000000000000000021501474715634600314400ustar00rootroot00000000000000import 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.py000066400000000000000000000130271474715634600312370ustar00rootroot00000000000000import 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__.py000066400000000000000000000000001474715634600222270ustar00rootroot00000000000000pytest-codspeed-3.2.0/tests/benchmarks/test_bench_fibo.py000066400000000000000000000020141474715634600236140ustar00rootroot00000000000000def 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.py000066400000000000000000000077601474715634600245470ustar00rootroot00000000000000import 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.py000066400000000000000000000010661474715634600254260ustar00rootroot00000000000000def 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.py000066400000000000000000000050051474715634600202120ustar00rootroot00000000000000from __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/000077500000000000000000000000001474715634600176315ustar00rootroot00000000000000pytest-codspeed-3.2.0/tests/examples/__init__.py000066400000000000000000000000001474715634600217300ustar00rootroot00000000000000pytest-codspeed-3.2.0/tests/examples/test_addition_fixture.py000066400000000000000000000001401474715634600245760ustar00rootroot00000000000000def test_some_addition_performance(benchmark): @benchmark def _(): return 1 + 1 pytest-codspeed-3.2.0/tests/test_pytest_plugin.py000066400000000000000000000216111474715634600223330ustar00rootroot00000000000000import 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.py000066400000000000000000000067031474715634600265320ustar00rootroot00000000000000import 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.py000066400000000000000000000021521474715634600242300ustar00rootroot00000000000000import 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.py000066400000000000000000000022041474715634600205620ustar00rootroot00000000000000import 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.lock000066400000000000000000002067131474715634600161660ustar00rootroot00000000000000version = 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 }, ]