pax_global_header00006660000000000000000000000064152710753760014527gustar00rootroot0000000000000052 comment=673564f01fd322c5b19918908a91d365a9bba392 deprecated-3.0.1/000077500000000000000000000000001527107537600136305ustar00rootroot00000000000000deprecated-3.0.1/.editorconfig000066400000000000000000000003671527107537600163130ustar00rootroot00000000000000# see http://editorconfig.org root = true [*] end_of_line = lf trim_trailing_whitespace = true insert_final_newline = true indent_style = space indent_size = 4 charset = utf-8 [*.{yml,yaml}] indent_size = 2 [*.{bat,cmd,ps1}] end_of_line = crlf deprecated-3.0.1/.github/000077500000000000000000000000001527107537600151705ustar00rootroot00000000000000deprecated-3.0.1/.github/CODE_OF_CONDUCT.md000066400000000000000000000063241527107537600177740ustar00rootroot00000000000000# Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at [Laurent LAPORTE](mailto:laurent.laporte.pro@gmail.com). All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html [homepage]: https://www.contributor-covenant.org deprecated-3.0.1/.github/ISSUE_TEMPLATE.md000066400000000000000000000015051527107537600176760ustar00rootroot00000000000000**This issue tracker is a tool to address bugs in Deprecated itself. Please use Stack Overflow for general questions about using Deprecated or issues not related to Deprecated Library.** If you'd like to report a bug in Deprecated, fill out the template below. Provide any extra information that may be useful/related to your problem. Ideally, create an [Minimal, Complete, and Verifiable example](http://stackoverflow.com/help/mcve), which helps us understand the problem and helps check that it is not caused by something in your code. --- ### Expected Behavior Tell us what should happen. ```python # Paste a minimal example that causes the problem. ``` ### Actual Behavior Tell us what happens instead. ```pytb Paste the full traceback if there was an exception. ``` ### Environment * Python version: * Deprecated version: deprecated-3.0.1/.github/PULL_REQUEST_TEMPLATE.md000066400000000000000000000010001527107537600207600ustar00rootroot00000000000000Describe what this patch does to fix the issue. Link to any relevant issues or pull requests. deprecated-3.0.1/.github/dependabot.yml000066400000000000000000000012261527107537600200210ustar00rootroot00000000000000# Dependabot version updates: GitHub Actions and the uv lockfile (development tools). # See: https://docs.github.com/en/code-security/dependabot/working-with-dependabot/dependabot-options-reference version: 2 updates: - package-ecosystem: "github-actions" directory: "/" target-branch: "develop" schedule: interval: "monthly" commit-message: prefix: "ci" groups: github-actions: patterns: ["*"] - package-ecosystem: "uv" directory: "/" target-branch: "develop" schedule: interval: "monthly" commit-message: prefix: "build" groups: dev-dependencies: patterns: ["*"] deprecated-3.0.1/.github/workflows/000077500000000000000000000000001527107537600172255ustar00rootroot00000000000000deprecated-3.0.1/.github/workflows/codeql-analysis.yml000066400000000000000000000016221527107537600230410ustar00rootroot00000000000000# CodeQL code scanning (security and quality queries) of the Python sources. # See: https://docs.github.com/en/code-security/code-scanning name: "CodeQL" on: push: branches: ['master', 'develop'] pull_request: branches: ['master', 'develop'] schedule: - cron: '22 8 * * 1' permissions: contents: read jobs: analyze: name: Analyze (Python) runs-on: ubuntu-latest permissions: actions: read contents: read security-events: write steps: - name: Checkout repository uses: actions/checkout@v7 # Python is an interpreted language: no build step is needed. - name: Initialize CodeQL uses: github/codeql-action/init@v4 with: languages: python build-mode: none - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v4 with: category: "/language:python" deprecated-3.0.1/.github/workflows/python-package.yml000066400000000000000000000067101527107537600226660ustar00rootroot00000000000000# Run the test suite and the quality checks with uv and Hatch. # See: https://docs.astral.sh/uv/guides/integration/github/ and https://hatch.pypa.io/latest/ name: Python package on: push: branches: ['master', 'main', 'develop'] pull_request: branches: ['master', 'main', 'develop', 'release/**', 'hotfix/**'] permissions: contents: read concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true env: UV_VERSION: "0.12.19" HATCH_VERSION: "1.18.1" jobs: quality: name: Quality checks (hatch check) runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 - name: Set up Python uses: actions/setup-python@v7 with: python-version: "3.12" - name: Set up uv uses: astral-sh/setup-uv@v10.2.0 with: version: ${{ env.UV_VERSION }} enable-cache: true - name: Check the lockfile is up to date run: uv lock --check - name: Install Hatch run: uv tool install "hatch==${HATCH_VERSION}" - name: Lint (hatch check code) run: hatch check code - name: Formatting (hatch check fmt) run: hatch check fmt - name: Typing (hatch check types) run: hatch check types pytest: name: Tests (Python ${{ matrix.python-version }}, ${{ matrix.platform }}) strategy: fail-fast: false matrix: platform: [ubuntu-latest, macos-latest, windows-latest] python-version: ["3.12", "3.13", "3.14"] runs-on: ${{ matrix.platform }} steps: - uses: actions/checkout@v7 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v7 with: python-version: ${{ matrix.python-version }} - name: Set up uv uses: astral-sh/setup-uv@v10.2.0 with: version: ${{ env.UV_VERSION }} enable-cache: true - name: Install dependencies run: uv sync --locked --python ${{ matrix.python-version }} - name: Test with pytest run: uv run --locked --python ${{ matrix.python-version }} pytest --cov --cov-report=term-missing --cov-report=lcov # The coverage badge of the README is published by Coveralls (one upload is enough). - name: Upload the coverage to Coveralls if: matrix.platform == 'ubuntu-latest' && matrix.python-version == '3.12' uses: coverallsapp/github-action@v2 with: file: coverage.lcov format: lcov fail-on-error: false docs: name: Documentation (Sphinx + MyST) runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 - name: Set up uv uses: astral-sh/setup-uv@v10.2.0 with: version: ${{ env.UV_VERSION }} enable-cache: true - name: Build the documentation (warnings are errors) run: make docs-check wrapt-compat: name: Tests (wrapt compatibility matrix) runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 - name: Set up uv uses: astral-sh/setup-uv@v10.2.0 with: version: ${{ env.UV_VERSION }} enable-cache: true - name: Install Hatch run: uv tool install "hatch==${HATCH_VERSION}" - name: Test all Python / wrapt combinations (hatch test --all) run: hatch test --all - name: Test with the pure Python implementation of wrapt env: WRAPT_DISABLE_EXTENSIONS: "1" run: uv run --locked pytest deprecated-3.0.1/.gitignore000066400000000000000000000070221527107537600156210ustar00rootroot00000000000000### Python template # 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 # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .nox/ .coverage .coverage.* .cache nosetests.xml coverage.xml coverage.lcov *.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/latest/usage/project/#working-with-version-control .pdm.toml .pdm-python .pdm-build/ # 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 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/ ### macOS template # General .DS_Store .AppleDouble .LSOverride # Icon must end with two \r Icon? ![iI]con[_a-zA-Z0-9] # Thumbnails ._* # Files that might appear in the root of a volume .DocumentRevisions-V100 .fseventsd .Spotlight-V100 .TemporaryItems .Trashes .VolumeIcon.icns .com.apple.timemachine.donotpresent # Directories potentially created on remote AFP share .AppleDB .AppleDesktop Network Trash Folder Temporary Items .apdisk deprecated-3.0.1/.packit.yml000066400000000000000000000006351527107537600157100ustar00rootroot00000000000000files_to_sync: - python-deprecated.spec - .packit.yml upstream_package_name: Deprecated upstream_tag_template: v{version} downstream_package_name: python-deprecated jobs: - job: propose_downstream trigger: release metadata: dist_git_branches: - fedora-all - job: copr_build trigger: pull_request metadata: targets: - fedora-all srpm_build_deps: - python3-pip - python3-hatchling deprecated-3.0.1/.readthedocs.yaml000066400000000000000000000017361527107537600170660ustar00rootroot00000000000000# Read the Docs configuration file for Sphinx projects # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details version: 2 build: os: ubuntu-24.04 tools: python: "3.12" # The documentation dependencies are installed with uv, from the `docs` dependency group # of `pyproject.toml` (locked in `uv.lock`). # See https://docs.readthedocs.io/en/stable/build-customization.html#install-dependencies-with-uv jobs: pre_create_environment: - asdf plugin add uv - asdf install uv 0.12.19 - asdf global uv 0.12.19 create_environment: - uv venv "${READTHEDOCS_VIRTUALENV_PATH}" install: - UV_PROJECT_ENVIRONMENT="${READTHEDOCS_VIRTUALENV_PATH}" uv sync --locked --no-default-groups --group docs sphinx: configuration: docs/source/conf.py # The warnings are checked by the CI (`make docs-check`): an unreachable intersphinx # inventory must not prevent the publication of the documentation. # fail_on_warning: true deprecated-3.0.1/AGENTS.md000066400000000000000000000170061527107537600151370ustar00rootroot00000000000000# AGENTS.md Guidance for AI coding agents working in this repository. **Deprecated** is a small, published open-source library (`pip install Deprecated`) that provides the `@deprecated` decorator for classes, functions and methods. It is a Python 3.12+ project managed with **uv** (environment, lockfile) and **Hatch** (version, quality checks, test matrix). Its only runtime dependency is `wrapt >= 1.16, < 3`. Everything in `src/deprecated/` is **public API used by many downstream projects**: keep changes backward compatible unless a major release is explicitly being prepared. ## Commands ```sh uv sync # create .venv with the locked dev dependencies (make install) uv run pytest # test suite, current Python (make test) uv run pytest tests/test_deprecated.py::test_classic_deprecated_function__warns # a single test uv run pytest -k sphinx # tests matching a keyword make cov # tests with coverage (terminal + htmlcov/) make test-all # hatch test --all: every Python x wrapt combination WRAPT_DISABLE_EXTENSIONS=1 uv run pytest # tests with the pure Python wrapt (run in CI) make check # uv lock --check + hatch check code / fmt / types (what CI runs) make fix # ruff --fix and ruff format make docs-check # Sphinx build, warnings are errors (what CI runs) make docs-live # docs preview with auto-rebuild (sphinx-autobuild) make build # sdist + wheel ``` `hatch check code|fmt|types` run Ruff and mypy with the versions pinned in `pyproject.toml` (identical to the `dev` group of `uv.lock`); `uv run ruff check .` and `uv run mypy` give the same results from the project venv. Never edit `uv.lock` by hand: use `uv lock` / `uv add`. ## Architecture Six modules in `src/deprecated/` (src layout, `py.typed` shipped): - `classic.py` — `ClassicAdapter` (a `wrapt.AdapterFactory`) and the `deprecated()` decorator. `deprecated` accepts three call forms (`@deprecated`, `@deprecated("reason")`, `@deprecated(reason=..., version=..., action=..., category=..., extra_stacklevel=...)`), typed with `@overload`. The adapter behaves differently by target: - **routines** are wrapped with `wrapt.decorator` (transparent proxy, keeps the signature); - **classes** are *not* wrapped: their `__new__` is patched in place to emit the warning. - `warn()` uses `warnings.warn(..., skip_file_prefixes=SKIP_FILE_PREFIXES)` so the warning location always points at user code, whatever the wrapt implementation (C extension or pure Python) and the nesting depth. Anything touching stack levels must keep that invariant (tests check the reported file/line). - `sphinx.py` — `SphinxAdapter(ClassicAdapter)` plus `versionadded`, `versionchanged` and `deprecated`. It rewrites the docstring by appending a `.. directive:: version` block (text wrapped at `line_length`); only `deprecated` also emits a warning (via the parent adapter), and `get_deprecated_msg` strips Sphinx roles (`:func:` ...) from the message. - `google.py` / `numpy.py` — `GoogleAdapter` / `NumpyAdapter(ClassicAdapter)` plus `versionadded`, `versionchanged` and `deprecated`, built like `sphinx.py`: they add (or extend) a `Version added` / `Version changed` / `Deprecated` section to Google-style (`Header:`) or NumPy-style (hyphen-underlined header) docstrings. - `params.py` — `deprecated_params` (alias of the `DeprecatedParams` class) warns when deprecated *parameters* are passed. Stacked `@deprecated_params` decorators are merged through the `__deprecated_params__` attribute (`_DecoratorStack`) so each parameter warns once. It uses `functools.wraps`, not wrapt. - `__init__.py` — exports `deprecated` and `deprecated_params`, and holds `__version__`, the **single source of the version** (read by Hatch, updated with `hatch version`). Tests live in `tests/` (`test*.py`, pytest); `tests/deprecated_params/` holds the demo scenarios of `deprecated_params`. Documentation is Sphinx + MyST Markdown in `docs/source/`; the scripts in `docs/source/` (`tutorial/`, `sphinx/`, `google/`, `numpydoc/`) are executed examples whose output (including warning line numbers) is quoted in the pages, which is why Ruff does not reformat `*.md`. ## Python conventions Enforced by `pyproject.toml` (Ruff + mypy strict); CI fails on any violation. - Python **3.12+** syntax: PEP 695 generics (`def f[T: Deprecatable](...)`, `type X = ...`), built-in generics, `X | None` unions, `collections.abc` types for parameters. - **Never** `from __future__ import annotations` (banned by a Ruff `TID` rule). - Every function in `src/` is fully annotated (`ANN` rules, mypy `strict`, `warn_unreachable`). `*args: Any, **kwargs: Any` is accepted for pass-through wrappers. Every `# type: ignore` / `# noqa` must carry its error code and ideally a reason. Test code is exempt from annotation rules. - Ruff: line length **100**, double quotes, **one import per line** (`force-single-line`), no `print()` outside tests and docs. - Naming: `snake_case` functions/modules, `PascalCase` classes, `SCREAMING_SNAKE_CASE` module constants; private helpers prefixed with `_`. - Errors: raise a precise built-in exception (`TypeError`, `ValueError`...), never bare `Exception`. The library emits `warnings` (default category `DeprecationWarning`); it does not log. - Docstrings are reStructuredText (Sphinx autodoc): document `:param:` / `:return:`, and add `.. versionadded::`, `.. versionchanged::` or `.. deprecated::` with the upcoming version to any public behaviour you change. - Bug fixes come with a test that fails without the fix; behaviour must hold on every supported Python (3.12–3.14) and wrapt version (1.16, 1.17, 2.x) of the Hatch matrix. ## Git workflow - Branches: `develop` is the integration branch and receives the pull requests; `master` holds the releases. Work on short-lived branches created from `develop`: `feature/`, `fix/` (optionally prefixed by the GitHub issue number, e.g. `fix/123-nested-class-stacklevel`). `release/X.Y.Z` and `hotfix/X.Y.Z` branches target `master` (see `docs/source/release.md`). - Commits follow **Conventional Commits**: `(): `, imperative, lowercase, e.g. `fix: emit one warning per parameter with stacked @deprecated_params`, `build(docs): use Sphinx 9.1`. Types: `feat`, `fix`, `refactor`, `test`, `docs`, `chore`, `build`, `ci`, `perf`, `revert`. - Pull requests target `develop`, follow `.github/PULL_REQUEST_TEMPLATE.md` (what the patch does, linked issues), and add a `CHANGELOG.md` entry for code changes. The CI (`.github/workflows/python-package.yml`: quality checks, tests on Linux/macOS/Windows, wrapt matrix, docs) must be green before merging. - Never force-push `develop` or `master`; never rewrite someone else's branch. - Releases: bump with `hatch version ` (never edit the version elsewhere except `python-deprecated.spec`, the logo (`docs/source/_static/logo.svg`) and the social preview image (`docs/source/_static/social-preview.svg`), as listed in the release guide). Tags use the **`vX.Y.Z`** form and are created by GitHub when the release is published, not locally. ## Changelog `CHANGELOG.md` (3.x; older series in `CHANGELOG-1.*.md`) follows Keep a Changelog. Add entries under the topmost `## vX.Y.Z (unreleased)` section, in the right category (`Added`, `Changed`, `Deprecated`, `Removed`, `Fixed`, `Security`, `Documentation`, `Other`), written for library users in plain GitHub Markdown (no MyST roles; `> [!WARNING]` for breaking changes). deprecated-3.0.1/CHANGELOG-1.1.md000066400000000000000000000071611527107537600157430ustar00rootroot00000000000000# Changelog 1.1.x and 1.0.x All notable changes for the 1.1.x and 1.0.x releases. 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). ## v1.1.5 (2019-02-28) Bug fix release ### Fix - Fix #6: Use [`inspect.isroutine()`](https://docs.python.org/3/library/inspect.html#inspect.isroutine) to check if the wrapped object is a user-defined or built-in function or method. ### Other - Upgrade Tox configuration to add support for Python 3.7. Also, fix PyTest version for Python 2.7 and 3.4 (limited support). Remove dependency 'requests[security]': useless to build documentation. - Upgrade project configuration (`setup.py`) to add support for Python 3.7. ## v1.1.4 (2018-11-03) Bug fix release ### Fix - Fix #4: Correct the function [`deprecated()`](https://deprecated.readthedocs.io/en/latest/api.html#deprecated.deprecated): Don't pass arguments to [`object.__new__()`](https://docs.python.org/3/reference/datamodel.html#object.__new__) (other than *cls*). ### Other - Change the configuration for TravisCI and AppVeyor: drop configuration for Python **2.6** and **3.3**. add configuration for Python **3.7**. > [!NOTE] > Deprecated is no more tested with Python **2.6** and **3.3**. > Those Python versions are EOL for some time now and incur incompatibilities > with Continuous Integration tools like TravisCI and AppVeyor. > However, this library should still work perfectly... ## v1.1.3 (2018-09-03) Bug fix release ### Fix - Fix #2: a deprecated class is a class (not a function). Any subclass of a deprecated class is also deprecated. ## v1.1.2 (2018-08-27) Bug fix release ### Fix - Add a `MANIFEST.in` file to package additional files like "LICENSE.rst" in the source distribution. ## v1.1.1 (2018-04-02) Bug fix release ### Fix - Minor correction in `CONTRIBUTING.rst` for Sphinx builds: add the `-d` option to put apart the `doctrees` from the generated documentation and avoid warnings with epub generator. - Fix in documentation configuration: remove hyphens in `epub_identifier` (ISBN number has no hyphens). - Fix in Tox configuration: set the versions interval of each dependency. ### Other - Change in documentation: improve sentence phrasing in the Tutorial. - Restore the epub title to "Python Deprecated Library v1.1 Documentation" (required for Lulu.com). ## v1.1.0 (2017-11-06) Minor release ### Added - Change in [`deprecated.deprecated()`](https://deprecated.readthedocs.io/en/latest/api.html#deprecated.deprecated) decorator: you can give a "reason" message to help the developer choose another class, function or method. - Add support for Universal Wheel (Python versions 2.6, 2.7, 3.3, 3.4, 3.5, 3.6 and PyPy). - Add missing `__doc__` and `__version__` attributes to [`deprecated`](https://deprecated.readthedocs.io/en/latest/api.html#module-deprecated) module. - Add an extensive documentation of Deprecated Library. ### Other - Improve [Travis](https://www.travis-ci.com/) configuration file (compatibility from Python 2.6 to 3.7-dev, and PyPy). - Add [AppVeyor](https://www.appveyor.com/docs/) configuration file. - Add [Tox](https://tox.readthedocs.io/en/latest/) configuration file. - Add [BumpVersion](https://github.com/peritus/bumpversion) configuration file. - Improve project settings: add a long description for the project. Set the **license** and the **development status** in the classifiers property. - Add the `CONTRIBUTING.rst` file: "How to contribute to Deprecated Library". ## v1.0.0 (2016-08-30) Major release ### Added - **deprecated**: Created **@deprecated** decorator deprecated-3.0.1/CHANGELOG-1.2.md000066400000000000000000000243721527107537600157470ustar00rootroot00000000000000# Changelog 1.2.x All notable changes for the 1.2.x releases. 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). > [!NOTE] > The library **"Python-Deprecated"** was renamed **"Deprecated"**, simply! > This project is more consistent because now, the name of the library is the same as the name of the Python package. > > - In your `setup.py`, you can replace the "Python-Deprecated" dependency with "Deprecated". > - In your source code, nothing has changed, you will always use `import deprecated`, as before. > - I decided to keep the same version number because there is really no change in the source code > (only in comment or documentation). ## v1.2.18 (2024-01-25) This version does not bring any change in the source code, but fixes the build anomaly on Fedora (Packit). The package must be published on PyPi using [twine](https://pypi.org/project/twine/) to correctly deal with the license file. ## v1.2.17 (2024-01-25) Bug fix release ## v1.2.16 (2025-01-24) Bug fix release ### Fix - Fix #78: Fix configuration for Packit 1.0.0 - Fix #79: Fix the configuration for the intersphinx mapping in the Sphinx documentation. See [How to link to other documentation projects with Intersphinx](https://docs.readthedocs.io/en/stable/guides/intersphinx.html). ### Other - Drop support for Python older than 3.7 in GitHub Actions. ## v1.2.15 (2024-11-15) Bug fix release ### Fix - Resolve Python 2.7 support issue introduced in v1.2.14 in `sphinx.py`. - Fix #69: Add `extra_stacklevel` argument for interoperating with other wrapper functions (refer to #68 for a concrete use case). - Fix #73: Update class method deprecation warnings for Python 3.13. - Fix #75: Update GitHub workflows and fix development dependencies for Python 3.12. ### Other - Fix #66: discontinue TravisCI and AppVeyor due to end of free support. ## v1.2.14 (2023-05-27) Bug fix release ### Fix - Fix #60: return a correctly dedented docstring when long docstring are using the D212 or D213 format. ### Other - Add support for Python 3.11. - Drop support for Python older than 3.7 in build systems like pytest and tox, while ensuring the library remains production-compatible. - Update GitHub workflow to run in recent Python versions. ## v1.2.13 (2021-09-05) Bug fix release ### Fix - Fix #45: Change the signature of the [`deprecated()`](https://deprecated.readthedocs.io/en/latest/api.html#deprecated.sphinx.deprecated) decorator to reflect the valid use cases. - Fix #48: Fix `versionadded` and `versionchanged` decorators: do not return a decorator factory, but a Wrapt adapter. ### Other - Fix configuration for AppVeyor: simplify the test scripts and set the version format to match the current version. - Change configuration for Tox: - change the requirements for `pip` to "pip >= 9.0.3, < 21" (Python 2.7, 3.4 and 3.5). - install `typing` when building on Python 3.4 (required by Pytest->Attrs). - run unit tests on Wrapt 1.13 (release candidate). - Migrating project to [travis-ci.com](https://www.travis-ci.com/github/tantale/deprecated). ## v1.2.12 (2021-03-13) Bug fix release ### Fix - Avoid "Explicit markup ends without a blank line" when the decorated function has no docstring. - Fix #40: 'version' argument is required in Sphinx directives. - Fix #41: [`deprecated.sphinx`](https://deprecated.readthedocs.io/en/latest/api.html#module-deprecated.sphinx): strip Sphinx cross-referencing syntax from warning message. ### Other - Change in Tox and Travis CI configurations: enable unit testing on Python 3.10. ## v1.2.11 (2021-01-17) Bug fix release ### Fix - Fix packit configuration: use `upstream_tag_template: v{version}`. - Fix #33: Change the class [`SphinxAdapter`](https://deprecated.readthedocs.io/en/latest/api.html#deprecated.sphinx.SphinxAdapter): add the `line_length` keyword argument to the constructor to specify the max line length of the directive text. Sphinx decorators also accept the `line_length` argument. - Fix #34: `versionadded` and `versionchanged` decorators don't emit `DeprecationWarning` anymore on decorated classes. ### Other - Change the Tox configuration to run tests on Python 2.7, Python 3.4 and above (and PyPy 2.7 & 3.6). - Update the classifiers in `setup.py`. - Replace `bumpversion` by [bump2version](https://pypi.org/project/bump2version/) in `setup.py` and documentation. - Update configuration for Black and iSort. - Fix the development requirement versions in `setup.py` for Python 2.7 EOL. ## v1.2.10 (2020-05-13) Bug fix release ### Fix - Fix #25: `@deprecated` respects global warning filters with actions other than "ignore" and "always" on Python 3. ### Other - Change the configuration for TravisCI to build on pypy and pypy3. - Change the configuration for TravisCI and AppVeyor: drop configuration for Python **3.4** and add **3.8**. ## v1.2.9 (2020-04-10) Bug fix release ### Fix - Fix #20: Set the [`warnings.warn()`](https://docs.python.org/3/library/warnings.html#warnings.warn) stacklevel to 2 if the Python implementation is [PyPy](https://pypy.org/). - Fix packit configuration: use `dist-git-branch: fedora-all`. ### Other - Change the Tox configuration to run tests on PyPy v2.7 and 3.6. ## v1.2.8 (2020-04-05) Bug fix release ### Fix - Fix #15: The `@deprecated` decorator doesn't set a warning filter if the *action* keyword argument is not provided or `None`. In consequences, the warning messages are only emitted if the global filter allow it. For more information, see [The Warning Filter](https://docs.python.org/3/library/warnings.html#the-warnings-filter) in the Python documentation. - Fix #13: Warning displays the correct filename and line number when decorating a class if wrapt does not have the compiled c extension. ### Documentation - The [API](https://deprecated.readthedocs.io/en/latest/api.html) documentation and the [Tutorial](https://deprecated.readthedocs.io/en/latest/tutorial.html) is improved to explain how to use custom warning categories and local filtering (warning filtering at function call). - Fix #17: Customize the sidebar to add links to the documentation to the source in GitHub and to the Bug tracker. Add a logo in the sidebar and change the logo in the main page to see the library version. - Add a detailed documentation about [The “Sphinx” decorators](https://deprecated.readthedocs.io/en/latest/sphinx_deco.html). ### Other - Change the Tox configuration to test the library with Wrapt 1.12.x. ## v1.2.7 (2019-11-11) Bug fix release ### Fix - Fix #13: Warning displays the correct filename and line number when decorating a function if wrapt does not have the compiled c extension. ### Other - Support packit for Pull Request tests and sync to Fedora (thanks to Petr Hráček). Supported since v1.2.6. - Add [Black](https://black.readthedocs.io/en/latest/) configuration file. ## v1.2.6 (2019-07-06) Bug fix release ### Fix - Fix #9: Change the project's configuration: reinforce the constraint to the Wrapt requirement. ### Other - Upgrade project configuration (`setup.py`) to add the *project_urls* property: Documentation, Source and Bug Tracker URLs. - Change the Tox configuration to test the library against different Wrapt versions. - Fix an issue with the AppVeyor build: upgrade setuptools version in `appveyor.yml`, change the Tox configuration: set `py27,py34,py35: pip >= 9.0.3, < 19.2`. ## v1.2.5 (2019-02-28) Bug fix release ### Fix - Fix #6: Use [`inspect.isroutine()`](https://docs.python.org/3/library/inspect.html#inspect.isroutine) to check if the wrapped object is a user-defined or built-in function or method. ### Other - Upgrade Tox configuration to add support for Python 3.7. Also, fix PyTest version for Python 2.7 and 3.4 (limited support). Remove dependency 'requests[security]': useless to build documentation. - Upgrade project configuration (`setup.py`) to add support for Python 3.7. ## v1.2.4 (2018-11-03) Bug fix release ### Fix - Fix #4: Correct the class [`ClassicAdapter`](https://deprecated.readthedocs.io/en/latest/api.html#deprecated.classic.ClassicAdapter): Don't pass arguments to [`object.__new__()`](https://docs.python.org/3/reference/datamodel.html#object.__new__) (other than *cls*). ### Other - Add missing docstring to the classes [`ClassicAdapter`](https://deprecated.readthedocs.io/en/latest/api.html#deprecated.classic.ClassicAdapter) and [`SphinxAdapter`](https://deprecated.readthedocs.io/en/latest/api.html#deprecated.sphinx.SphinxAdapter). - Change the configuration for TravisCI and AppVeyor: drop configuration for Python **2.6** and **3.3**. add configuration for Python **3.7** (if available). > [!NOTE] > Deprecated is no more tested with Python **2.6** and **3.3**. > Those Python versions are EOL for some time now and incur incompatibilities > with Continuous Integration tools like TravisCI and AppVeyor. > However, this library should still work perfectly... ## v1.2.3 (2018-09-12) Bug fix release ### Fix - Fix #3: `deprecated.sphinx` decorators don't update the docstring. ## v1.2.2 (2018-09-04) Bug fix release ### Fix - Fix #2: a deprecated class is a class (not a function). Any subclass of a deprecated class is also deprecated. - Minor fix: add missing documentation in [`deprecated.sphinx`](https://deprecated.readthedocs.io/en/latest/api.html#module-deprecated.sphinx) module. ## v1.2.1 (2018-08-27) Bug fix release ### Fix - Add a `MANIFEST.in` file to package additional files like "LICENSE.rst" in the source distribution. ## v1.2.0 (2018-04-02) Minor release ### Added - Add decorators for Sphinx directive integration: `versionadded`, `versionchanged`, `deprecated`. That way, the developer can document the changes. ### Changed - Add the `version` parameter to the `@deprecated` decorator: used to specify the starting version number of the deprecation. - Add a way to choose a `DeprecationWarning` subclass. ### Removed - Deprecated no longer supports Python **2.6** and **3.3**. Those Python versions are EOL for some time now and incur maintenance and compatibility costs on the Deprecated core team, and following up with the rest of the community we decided that they will no longer be supported starting on this version. Users which still require those versions should pin Deprecated to `< 1.2`. deprecated-3.0.1/CHANGELOG-1.3.md000066400000000000000000000024251527107537600157430ustar00rootroot00000000000000# Changelog 1.3.x All notable changes for the 1.3.x releases. 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). ## v1.3.1 (2025-10-30) Patch release: Packaging fix ### Fixed - Restore missing source distribution (`.tar.gz`) that was not included in v1.3.0. ## v1.3.0 (2025-10-29) > [!NOTE] > This release was **yanked** on PyPI due to a missing source distribution (`.tar.gz`). > See issue #94: > It has been replaced by version 1.3.1. Minor release: Parameters deprecation ### Added - Add compatibility tests and adjustments for Wrapt v2.0. See PR #88 (musicinmybrain). - Add experimental `@deprecated_params` decorator to mark function parameters as deprecated at call-time; emits warnings when deprecated parameters are used with optional messages and configurable warning categories. See PR #93. ### Documentation - Update the Wrapt compatibility matrix to include Python 3.13 and 3.14. See PR #91 ### Changed - Limit test coverage collection to the dedicated `coverage` tox environment to avoid collecting coverage across all test environments and reduce cross-environment coverage noise. See PR #92. deprecated-3.0.1/CHANGELOG.md000066400000000000000000000212761527107537600154510ustar00rootroot00000000000000# Changelog 3.x All notable changes to this project will be documented in this file. 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). ## v3.0.1 (2026-10-30) Patch release: preserve the constructor signatures of deprecated classes ### Fixed - Preserve constructor signatures on deprecated classes, including in Sphinx autodoc, instead of displaying the warning wrapper's generic arguments ([#86](https://github.com/laurent-laporte-pro/deprecated/issues/86)). See PR #115 (Mirochill). ### Other - Bump the dev-dependencies group (Dependabot). See PR #114. - Add the social preview image of the repository and document its release steps. See PR #113. ## v3.0.0 (2026-09-26) Major release: Python 3.12+ modernization > [!WARNING] > This release drops the support of Python 2.7 and of Python 3 versions older than 3.12. > Use Deprecated 1.3.x on these versions: pip keeps resolving to 1.3.x on Python < 3.12. > [!IMPORTANT] > **Upgrading from 1.x** — the public API (`@deprecated`, `@deprecated_params`, > `deprecated.sphinx`, `ClassicAdapter`) is unchanged, but check the following points: > > - wrapt >= 1.16 is required (was >= 1.10); > - `deprecated.__date__` and `deprecated.classic.string_types` are removed; > - `from deprecated import *` only exports `deprecated` and `deprecated_params`; > - stacked `@deprecated_params` decorators emit one warning per deprecated parameter; > - the warnings of deprecated subclasses now refer to the user code: tests which assert > the file name or the line number of a warning may need to be updated. > [!NOTE] > There is no 2.x release: the major version 3 was chosen to match Python 3. ### Changed - Require Python >= 3.12 and wrapt >= 1.16 (first wrapt release supporting Python 3.12). See PR #102. - Migrate the packaging from setuptools (`setup.py`/`setup.cfg`/`MANIFEST.in`) to `pyproject.toml` with the Hatchling build backend; the version is read from `src/deprecated/__init__.py`. - Adopt the `src` layout: the package moves to `src/deprecated` (the tests stay in `tests` and the documentation in `docs`). - Manage the project with uv (`uv.lock`) and Hatch: tox is dropped and replaced by the `hatch test` matrix. - Modernize the `Makefile` (uv/Hatch based targets, run `make help`). - Replace bump2version by `hatch version `: the version is only defined in `src/deprecated/__init__.py`, and the git tag is created by GitHub when the release is published. - Update the Fedora RPM spec file to the `%pyproject_*` macros. ### Removed - Remove the `inspect2` dependency (it was only used on Python 2). See PR #102. - Remove the Python 2 compatibility constructs (`u""` literals, `coding` cookies, `class Foo(object)`, `super(Class, self)`, `OrderedDict`...). See PR #102. - Remove the undocumented `deprecated.classic.string_types` compatibility tuple. A `bytes` positional *reason* is still accepted by `@deprecated`, as before. See PR #102. - Remove the `deprecated.__date__` attribute (the release dates are in the changelog). See PR #102. ### Added - Add the `deprecated.google` module: the `@deprecated`, `@versionadded` and `@versionchanged` decorators insert a `Deprecated:`, `Version added:` or `Version changed:` section in [Google style](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings) docstrings (see the ["Google" decorators](https://deprecated.readthedocs.io/en/latest/google_deco.html) page). See PR #105. - Add the `deprecated.numpy` module: the `@deprecated`, `@versionadded` and `@versionchanged` decorators insert a `Deprecated`, `Version added` or `Version changed` section in [NumPy style](https://numpydoc.readthedocs.io/en/latest/format.html) docstrings (see the ["NumPy" decorators](https://deprecated.readthedocs.io/en/latest/numpy_deco.html) page). See PR #106. - Add the `ClassicAdapter.warn()` method used to emit the deprecation warning (can be overridden by custom adapters). See PR #102. - Add Python 3.12 type annotations to the public API and ship the `py.typed` marker (PEP 561): the decorators preserve the signature of the decorated functions and classes. See PR #102. - Add `__all__` to the `deprecated` package: `from deprecated import *` only exports `deprecated` and `deprecated_params`. See PR #102. ### Fixed - Deprecated functions and methods can now be pickled (by reference, like regular functions), so they can be used with `multiprocessing` and the *spawn* start method (the default one on Windows and macOS), for instance as a `Process` target or with `Pool.map()`. Previously, pickling failed with `NotImplementedError: object proxy must define __reduce_ex__()` ([#16](https://github.com/laurent-laporte-pro/deprecated/issues/16)). See PR #108 and PR #111 (a function decorated with `@deprecated` above `@staticmethod` could not be pickled). - When a deprecated class inherits from a deprecated class, all the deprecation warnings now refer to the user code (previously, the warning of the parent class referred to `classic.py`). The warnings are emitted with `skip_file_prefixes` (Python 3.12+): the frames of this library and of wrapt are skipped, whatever the wrapt implementation (C extension or pure Python). See PR #102. - Stacked `@deprecated_params` decorators now emit only one warning for each deprecated parameter: the message (and category) of the outermost decorator wins, as documented in the tutorial. The warnings refer to the caller. See PR #102. ### Documentation - Convert the documentation from reStructuredText to Markdown (MyST), built with Sphinx 9.1 and MyST-Parser 5.1. The API page remains in reStructuredText (autodoc), and the docstrings are still written in reStructuredText. See PR #103. - Convert the root files to plain Markdown, readable on GitHub: `CHANGELOG.md`, `CONTRIBUTING.md` and `LICENSE.md`. - Declare the documentation dependencies in the `docs` dependency group of `pyproject.toml` (`docs/requirements.txt` is removed); ReadTheDocs installs them with uv. - Check the documentation build in the CI (`make docs-check`: warnings are errors). - Rewrite the white paper: state of the art of deprecation in Python (warning categories and stack level, PEP 702 and the type checkers, the CPython policy, practices of major Open Source projects, best practices), and how to combine `warnings.deprecated` with this library. See PR #104. - Remove the ebook, LaTeX, manual page and Texinfo configuration of the documentation (HTML only), with the files used by the ebook (cover, title page and blurb). - Rework the README: a short presentation, the warning emitted, the options of `@deprecated`, `@deprecated_params`, and how the library complements `warnings.deprecated` (PEP 702). The "Authors" section now tells who started the library and who maintains it. - New logo (a "best before: next major" price tag), shown in the README and on the home page of the documentation; it replaces the rusty tools image. See PR #109. - Switch the documentation to the [Furo](https://pradyunsg.me/furo/) theme, with light, dark and automatic (system preference) modes, and the red of the logo as accent color. The code is highlighted with the Lovelace (light) and Gruvbox dark (dark) Pygments styles, tuned for contrast (WCAG AA). See PR #110. - Add `make docs-live` to preview the documentation while editing it: the pages are rebuilt and the browser is reloaded on each change (sphinx-autobuild, `docs-live` dependency group). See PR #110. - Present the Google and NumPy docstring decorators in the documentation. See PR #107. - Document the release process (`docs/source/release.md`). - Fix the outputs quoted in the tutorial and in the Sphinx page, stale references and typos of the documentation and of the docstrings. See PR #111. ### Other - Run the CI with uv and Hatch on Python 3.12, 3.13 and 3.14 (Linux, macOS and Windows), plus the wrapt compatibility matrix (wrapt 1.16, 1.17 and 2.x on every supported Python) and the pure Python implementation of wrapt. See PR #102 and PR #111. - Add the `hatch check code` (Ruff lint), `hatch check fmt` (Ruff format) and `hatch check types` (mypy, strict mode) quality checks. See PR #102. - Add non-regression tests for the behaviours which differ between Python 2 and Python 3, and make the `test_sphinx_metaclass` tests test the Sphinx decorator. See PR #102. - Bump the GitHub Actions versions. See PR #99. - Run CodeQL on the `develop` and `master` branches, and add Dependabot version updates (GitHub Actions and `uv.lock`). See PR #111. - Publish the coverage to Coveralls (README badge), and fail the tests below 95% of coverage. See PR #111. - Add `AGENTS.md` and `CLAUDE.md`: guidance for AI coding agents. deprecated-3.0.1/CLAUDE.md000066400000000000000000000003261527107537600151100ustar00rootroot00000000000000# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. The project guidance shared by all AI coding agents lives in [AGENTS.md](AGENTS.md): @AGENTS.md deprecated-3.0.1/CONTRIBUTING.md000066400000000000000000000137061527107537600160700ustar00rootroot00000000000000# How to contribute to Deprecated Library Thank you for considering contributing to Deprecated! ## Support questions Please, don't use the issue tracker for this. Use one of the following resources for questions about your own code: - Ask on [Stack Overflow]. Search with Google first using: `site:stackoverflow.com deprecated decorator {search term, exception message, etc.}` ## Reporting issues - Describe what you expected to happen. - If possible, include a [minimal, complete, and verifiable example] to help us identify the issue. This also helps check that the issue is not with your own code. - Describe what actually happened. Include the full traceback if there was an exception. - List your Python, Deprecated and wrapt versions. If possible, check if this issue is already fixed in the repository. ## Submitting patches - Include tests if your patch is supposed to solve a bug, and explain clearly under which circumstances the bug happens. Make sure the test fails without your patch. - Follow [PEP8]: the code is linted and formatted with Ruff (line length: 100). ### First time setup - Download and install the [latest version of git]. - Configure git with your [username] and [email]: ``` git config --global user.name 'your name' git config --global user.email 'your email' ``` - Make sure you have a [GitHub account]. - Fork Deprecated to your GitHub account by clicking the [Fork] button. - [Clone] your GitHub fork locally: ``` git clone https://github.com/{username}/deprecated.git cd deprecated ``` - Add the main repository as a remote to update later: ``` git remote add upstream https://github.com/laurent-laporte-pro/deprecated.git git fetch upstream ``` - Install [uv] and [Hatch] (Python 3.12 or newer is required): ``` curl -LsSf https://astral.sh/uv/install.sh | sh uv tool install hatch ``` - Create the virtual environment and install Deprecated in editable mode with the development dependencies (locked in `uv.lock`): ``` uv sync ``` ### Start coding - Create a branch to identify the issue you would like to work on (e.g. `2287-dry-test-suite`) - Using your favorite editor, make your changes, [committing as you go]. - Follow [PEP8]: the code is linted and formatted with Ruff (line length: 100). - Include tests that cover any code changes you make. Make sure the test fails without your patch. See [Running the tests](#running-the-tests). - Push your commits to GitHub and [create a pull request]. - Celebrate 🎉 ### Running the tests Run the basic test suite with: ``` uv run pytest ``` This only runs the tests for the current environment. Whether this is relevant depends on which part of Deprecated you're working on. GitHub Actions will run the full suite when you submit your pull request. The full test suite tests multiple combinations of Python (3.12 and newer) and wrapt versions. Hatch downloads the missing Python interpreters with uv. Run: ``` hatch test --all ``` ### Running the quality checks The following checks are run by the CI and must pass before merging: ``` hatch check code # lint with Ruff hatch check fmt # verify the formatting with Ruff (use --fix to reformat) hatch check types # type-check with mypy (strict mode) ``` ### Running test coverage Generating a report of lines that do not have test coverage can indicate where to start contributing. Run `pytest` using `coverage` and generate a report on the terminal and as an interactive HTML document: ``` uv run pytest --cov --cov-report term-missing --cov-report html # then open htmlcov/index.html ``` Read more about [coverage](https://coverage.readthedocs.io). ### `make` targets Deprecated provides a `Makefile` with various shortcuts, based on uv and Hatch (run `make help` to list them): - `make install` creates the virtual environment with the locked dependencies - `make test` runs the basic test suite with `pytest` - `make cov` runs the basic test suite with `coverage` - `make test-all` runs the full test suite with `hatch test --all` - `make check` verifies the lockfile and runs the quality checks with `hatch check` - `make fix` fixes the lint errors and reformats the code - `make docs` builds the HTML documentation - `make docs-live` serves the HTML documentation, rebuilt and reloaded on each change - `make build` builds the source distribution and the wheel - `make version` shows the current version, and `make bump-major`, `make bump-minor`, `make bump-patch` bump it with `hatch version` (see the [release process](docs/source/release.md)) - `make clean` removes the build artifacts and the caches ### Generating the documentation The documentation is automatically generated with ReadTheDocs for each git push on master. You can also generate it manually using Sphinx. To generate the HTML documentation, run: ``` uv run --group docs sphinx-build -b html -d dist/docs/doctrees docs/source/ dist/docs/html/ ``` To preview the documentation while editing it, run `make docs-live` and open : the pages are rebuilt and the browser is reloaded on each change of the documentation or of the docstrings (`src/deprecated/`). [clone]: https://help.github.com/articles/fork-a-repo/#step-2-create-a-local-clone-of-your-fork [committing as you go]: http://dont-be-afraid-to-commit.readthedocs.io/en/latest/git/commandlinegit.html#commit-your-changes [create a pull request]: https://help.github.com/articles/creating-a-pull-request/ [email]: https://help.github.com/articles/setting-your-commit-email-address-in-git/ [fork]: https://github.com/laurent-laporte-pro/deprecated/fork [github account]: https://github.com/join [hatch]: https://hatch.pypa.io/latest/ [latest version of git]: https://git-scm.com/downloads [minimal, complete, and verifiable example]: https://stackoverflow.com/help/mcve [pep8]: https://pep8.org/ [stack overflow]: https://stackoverflow.com/search?q=python+deprecated+decorator [username]: https://help.github.com/articles/setting-your-username-in-git/ [uv]: https://docs.astral.sh/uv/ deprecated-3.0.1/LICENSE.md000066400000000000000000000020721527107537600152350ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2017 Laurent LAPORTE 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. deprecated-3.0.1/Makefile000066400000000000000000000052261527107537600152750ustar00rootroot00000000000000# Development shortcuts: every target relies on uv (environment, dependencies) # and Hatch (quality checks, test matrix). Run `make help` to list the targets. UV ?= uv HATCH ?= hatch DOCS_BUILD_DIR ?= dist/docs SPHINX_BUILD = $(UV) run --group docs sphinx-build .DEFAULT_GOAL := help .PHONY: help help: ## Show this help @awk 'BEGIN {FS = ":.*## "} /^[a-zA-Z_-]+:.*## / {printf " \033[36m%-12s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST) .PHONY: install install: ## Create the virtual environment with the locked dependencies $(UV) sync --locked .PHONY: lock lock: ## Update the lockfile (uv.lock) $(UV) lock .PHONY: test test: install ## Run the test suite (current Python version) $(UV) run pytest .PHONY: cov cov: install ## Run the test suite with coverage (terminal and HTML reports) $(UV) run pytest --cov --cov-report=term-missing --cov-report=html .PHONY: test-all test-all: ## Run the test suite on all Python and wrapt versions $(HATCH) test --all .PHONY: check check: ## Run the quality checks (lint, format, types) and verify the lockfile $(UV) lock --check $(HATCH) check code $(HATCH) check fmt $(HATCH) check types .PHONY: fix fix: ## Fix the lint errors and reformat the code $(HATCH) check code --fix $(HATCH) check fmt --fix .PHONY: docs docs: ## Build the HTML documentation $(SPHINX_BUILD) -b html -d $(DOCS_BUILD_DIR)/doctrees docs/source/ $(DOCS_BUILD_DIR)/html .PHONY: docs-check docs-check: ## Build the HTML documentation, warnings are errors (used by the CI) $(SPHINX_BUILD) -W --keep-going -E -b html -d $(DOCS_BUILD_DIR)/doctrees docs/source/ $(DOCS_BUILD_DIR)/html .PHONY: docs-live docs-live: ## Serve the HTML documentation, rebuilt and reloaded on each change $(UV) run --group docs-live sphinx-autobuild --watch src/deprecated -d $(DOCS_BUILD_DIR)/doctrees docs/source/ $(DOCS_BUILD_DIR)/html .PHONY: docs-linkcheck docs-linkcheck: ## Check the external links of the documentation $(SPHINX_BUILD) -b linkcheck -d $(DOCS_BUILD_DIR)/doctrees docs/source/ $(DOCS_BUILD_DIR)/linkcheck .PHONY: build build: ## Build the source distribution and the wheel $(UV) build .PHONY: version version: ## Show the current version $(HATCH) version .PHONY: bump-major bump-minor bump-patch bump-major: ## Bump the major version (see docs/source/release.md) $(HATCH) version major bump-minor: ## Bump the minor version $(HATCH) version minor bump-patch: ## Bump the patch version $(HATCH) version patch .PHONY: clean clean: ## Remove the build artifacts and the caches rm -rf dist/ build/ htmlcov/ .coverage .coverage.* coverage.lcov .pytest_cache/ .mypy_cache/ .ruff_cache/ find . -type d -name __pycache__ -not -path "./.venv/*" -prune -exec rm -rf {} + deprecated-3.0.1/README.md000066400000000000000000000116711527107537600151150ustar00rootroot00000000000000# Deprecated Decorator ![Deprecated: a price tag reading "@deprecated, best before: next major"](https://raw.githubusercontent.com/laurent-laporte-pro/deprecated/master/docs/source/_static/logo.svg) Python `@deprecated` decorator to deprecate old python classes, functions or methods. [![license](https://img.shields.io/badge/license-MIT-blue?logo=opensourceinitiative&logoColor=white)](https://raw.githubusercontent.com/laurent-laporte-pro/deprecated/master/LICENSE.md) [![GitHub release](https://img.shields.io/github/v/release/laurent-laporte-pro/deprecated?logo=github&logoColor=white)](https://github.com/laurent-laporte-pro/deprecated/releases/latest) [![PyPI](https://img.shields.io/pypi/v/deprecated?logo=pypi&logoColor=white)](https://pypi.org/project/Deprecated/) [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/laurent-laporte-pro/deprecated/python-package.yml?logo=github&logoColor=white)](https://github.com/laurent-laporte-pro/deprecated/actions/workflows/python-package.yml) [![Coveralls branch](https://img.shields.io/coverallsCoverage/github/laurent-laporte-pro/deprecated?logo=coveralls&logoColor=white)](https://coveralls.io/github/laurent-laporte-pro/deprecated?branch=master) [![Read the Docs (version)](https://img.shields.io/readthedocs/deprecated/latest?logo=readthedocs&logoColor=white)](https://deprecated.readthedocs.io/en/latest/?badge=latest) Removing a function from a library breaks the code of its users. Deprecating it first gives them time to migrate: the Deprecated library marks classes, functions, methods and parameters as deprecated, emits a warning that points at the calling code, and can document the deprecation in the docstring. ## Installation ```shell pip install Deprecated ``` Deprecated requires Python 3.12+ and [wrapt](https://pypi.org/project/wrapt/). ## Usage Decorate a deprecated function, method or class with `@deprecated`: ```python from deprecated import deprecated @deprecated(reason="use new_function", version="2.1.0") def some_old_function(x, y): return x + y some_old_function(1, 2) ``` Each call emits a warning located at the calling line: ```text example.py:9: DeprecationWarning: Call to deprecated function (or staticmethod) some_old_function. (use new_function) -- Deprecated since version 2.1.0. ``` `@deprecated` can also be used without arguments, and accepts other options: `category` (e.g. `FutureWarning` instead of `DeprecationWarning`), `action` (a local warning filter, e.g. `"error"`) and `extra_stacklevel` (for wrappers of deprecated functions). To deprecate a *parameter* rather than the whole function, use `@deprecated_params`: ```python from deprecated import deprecated_params @deprecated_params("color", reason="'color' is ignored, use 'style'") def draw(shape, color=None, style=None): ... ``` Calling `draw("circle", color="red")` emits `DeprecationWarning: 'color' is ignored, use 'style'`. ## Documenting the life cycle The Deprecated library can also document the life cycle of your functions and classes: the `@deprecated`, `@versionadded` and `@versionchanged` decorators update the docstring, according to your docstring format: - `deprecated.sphinx`: reStructuredText directives (`.. deprecated:: 1.2.0`), - `deprecated.google`: Google style sections (`Deprecated:`), - `deprecated.numpy`: NumPy style sections (`Deprecated` underlined with hyphens). ```python from deprecated.google import deprecated from deprecated.google import versionadded @deprecated(reason="use another function", version="1.2.0") @versionadded(version="1.0.0") def some_old_function(x, y): """Add two numbers. Args: x: first number. y: second number. """ return x + y ``` The docstring of `some_old_function` ends with: ```text Version added: 1.0.0 Deprecated: 1.2.0: use another function ``` ## Why not `warnings.deprecated`? Since Python 3.13, [`warnings.deprecated`](https://docs.python.org/3/library/warnings.html#warnings.deprecated) ([PEP 702](https://peps.python.org/pep-0702/)) emits a runtime warning and lets the type checkers report the use of deprecated objects. Deprecated complements it with version numbers, docstring updates (Sphinx, Google, NumPy), local warning filters and deprecated parameters. Both can be combined, see the [white paper](https://deprecated.readthedocs.io/en/latest/white_paper.html). See the [documentation](https://deprecated.readthedocs.io/en/latest/) for more details. ## Authors Deprecated was started by [Marcos CARDOSO](https://github.com/vrcmarcos), building on the recipes shared in [this Stack Overflow discussion](https://stackoverflow.com/questions/2536307) by [Leandro REGUEIRO](https://stackoverflow.com/users/1336250/leandro-regueiro), [Patrizio BERTONI](https://stackoverflow.com/users/1315480/patrizio-bertoni) and [Eric WIESER](https://stackoverflow.com/users/102441/eric). [Laurent LAPORTE](https://github.com/laurent-laporte-pro) has been the main author and maintainer of the library for many years. deprecated-3.0.1/docs/000077500000000000000000000000001527107537600145605ustar00rootroot00000000000000deprecated-3.0.1/docs/source/000077500000000000000000000000001527107537600160605ustar00rootroot00000000000000deprecated-3.0.1/docs/source/_ext/000077500000000000000000000000001527107537600170175ustar00rootroot00000000000000deprecated-3.0.1/docs/source/_ext/pygments_styles.py000066400000000000000000000035761527107537600226550ustar00rootroot00000000000000"""Pygments styles of the documentation, tuned for readability. The Lovelace (light mode) and Gruvbox dark (dark mode) styles are kept as is, except for the few colors whose contrast with the background is below the WCAG AA level (4.5:1): these colors are replaced by the same hue, darkened (light mode) or lightened (dark mode). """ from pygments.style import Style from pygments.styles.gruvbox import GruvboxDarkStyle from pygments.styles.lovelace import LovelaceStyle def _replace_colors(style: type[Style], colors: dict[str, str]) -> dict: """Return the token styles of *style*, with the *colors* replaced.""" styles = {} for token, definition in style.styles.items(): for old, new in colors.items(): definition = definition.replace(old, new) styles[token] = definition return styles class ReadableLovelaceStyle(LovelaceStyle): """Lovelace, on the light gray of Furo, with darker colors (4.5:1 or more).""" name = "readable-lovelace" # Light gray background (Furo's secondary background) to set the code blocks apart # from the white page. background_color = "#f8f9fb" styles = _replace_colors( LovelaceStyle, { "#888888": "#6e6e6e", # comments, punctuation "#289870": "#217c5b", # namespaces (imported modules) "#709030": "#5d7728", # string escapes, entities "#908828": "#766f21", # exceptions (e.g. DeprecationWarning), global variables "#b85820": "#af541e", # docstrings, constants }, ) class ReadableGruvboxDarkStyle(GruvboxDarkStyle): """Gruvbox dark, with lighter reds and grays (4.5:1 or more on #282828).""" name = "readable-gruvbox-dark" styles = _replace_colors( GruvboxDarkStyle, { "#fb4934": "#fb5f4d", # keywords, decorators "#928374": "#9e9184", # comments }, ) deprecated-3.0.1/docs/source/_static/000077500000000000000000000000001527107537600175065ustar00rootroot00000000000000deprecated-3.0.1/docs/source/_static/logo.svg000066400000000000000000000704321527107537600211750ustar00rootroot00000000000000 Deprecated: Python @deprecated decorator v3.0.1 deprecated-3.0.1/docs/source/_static/social-preview.svg000066400000000000000000000712231527107537600231650ustar00rootroot00000000000000 Deprecated: Python @deprecated decorator v3.0.0 deprecated-3.0.1/docs/source/api.rst000066400000000000000000000011651527107537600173660ustar00rootroot00000000000000.. This page is the only one written in reStructuredText: autodoc generates reStructuredText from the docstrings, and a Markdown (MyST) page would parse it in isolated documents, losing the hyperlink targets defined in the docstrings. .. _api: API === This part of the documentation covers all the interfaces of the Deprecated Library. .. automodule:: deprecated :members: .. automodule:: deprecated.classic :members: .. automodule:: deprecated.params :members: .. automodule:: deprecated.sphinx :members: .. automodule:: deprecated.google :members: .. automodule:: deprecated.numpy :members: deprecated-3.0.1/docs/source/changelog.md000066400000000000000000000002371527107537600203330ustar00rootroot00000000000000```{include} ../../CHANGELOG.md ``` ```{include} ../../CHANGELOG-1.3.md ``` ```{include} ../../CHANGELOG-1.2.md ``` ```{include} ../../CHANGELOG-1.1.md ``` deprecated-3.0.1/docs/source/conf.py000066400000000000000000000120571527107537600173640ustar00rootroot00000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- # # Deprecated Library Documentation build configuration file, created by # sphinx-quickstart on Wed Jul 19 22:23:11 2017. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # # import os # import sys # sys.path.insert(0, os.path.abspath('.')) import importlib.metadata import os import sys # Local extensions and Pygments styles of the documentation. sys.path.insert(0, os.path.abspath('_ext')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.intersphinx', 'sphinx.ext.coverage', 'sphinx.ext.githubpages', 'myst_parser', ] # -- MyST (Markdown) configuration ----------------------------------------- # The pages are written in Markdown (MyST); the docstrings remain in reStructuredText. # https://myst-parser.readthedocs.io/en/latest/configuration.html # `alert`: GitHub alerts (`> [!NOTE]`) used in the root files (CHANGELOG, CONTRIBUTING...). # `smartquotes`: typographic quotes and dashes, like docutils does for reStructuredText. myst_enable_extensions = ['alert', 'colon_fence', 'deflist', 'fieldlist', 'smartquotes'] myst_heading_anchors = 3 # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # The pages are written in Markdown, except `api.rst` (autodoc, see the comment in this file). source_suffix = {'.md': 'markdown', '.rst': 'restructuredtext'} # The master toctree document. master_doc = 'index' # General information about the project. project = 'Deprecated' copyright = '2017, Laurent LAPORTE' author = 'Laurent LAPORTE' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The full version, including alpha/beta/rc tags. release = importlib.metadata.version("Deprecated") # The short X.Y version. version = release.rpartition('.')[0] # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = 'en' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path exclude_patterns = [] # The Pygments (syntax highlighting) styles of the light and dark modes (the dark one is a # Furo option): Lovelace and Gruvbox dark, tuned for contrast (see `_ext/pygments_styles.py`). pygments_style = 'pygments_styles.ReadableLovelaceStyle' pygments_dark_style = 'pygments_styles.ReadableGruvboxDarkStyle' # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_theme = 'furo' # Theme options are theme-specific and customize the look and feel of a theme # further. Furo provides a light, a dark and an automatic (system preference) mode. # https://pradyunsg.me/furo/customisation/ # # The brand colors are those of the red "BEST BEFORE" band of the logo; the dark mode # uses a lighter red to keep the contrast with the dark background. html_theme_options = { 'light_css_variables': { 'color-brand-primary': '#b3261e', 'color-brand-content': '#b3261e', }, 'dark_css_variables': { 'color-brand-primary': '#f47067', 'color-brand-content': '#f47067', }, } # The title shown at the top of the sidebar (default: " documentation"). html_title = f'{project} {release}' # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = { 'python': ('https://docs.python.org/3/', None), 'wrapt': ('https://wrapt.readthedocs.io/en/latest/', None), } deprecated-3.0.1/docs/source/contributing.md000066400000000000000000000000751527107537600211130ustar00rootroot00000000000000```{include} ../../CONTRIBUTING.md :relative-docs: docs/ ``` deprecated-3.0.1/docs/source/google/000077500000000000000000000000001527107537600173345ustar00rootroot00000000000000deprecated-3.0.1/docs/source/google/google_demo.py000066400000000000000000000015341527107537600221710ustar00rootroot00000000000000from deprecated.google import deprecated from deprecated.google import versionadded from deprecated.google import versionchanged @deprecated( reason=""" This is deprecated, really. So you need to use another function. But I don\'t know which one. - The first, - The second. Just guess! """, version="0.3.0", ) @versionchanged( reason="Well, I add a new feature in this function. " "It is very useful as you can see in the example below, so try it. " "This is a very very very very very long sentence.", version="0.2.0", ) @versionadded(reason="Here is my new function.", version="0.1.0") def successor(n): """ Calculate the successor of a number. Args: n: a number Returns: number + 1 """ return n + 1 if __name__ == "__main__": print(successor.__doc__) deprecated-3.0.1/docs/source/google_deco.md000066400000000000000000000055711527107537600206600ustar00rootroot00000000000000(google_deco)= # The "Google" decorators ## Overview If you write your docstrings with the [Google Python Style Guide] format, the "Sphinx" decorators (see {ref}`sphinx_deco`) are not the best fit: they append reStructuredText directives to your docstrings, whereas a Google docstring is organized in *sections*, like `Args:`, `Returns:` or `Raises:`. The "Google" decorators have the same function as the "Sphinx" decorators, but they add *sections* to your functions or classes documentation (inside the [docstring]): - {func}`~deprecated.google.deprecated`: insert a `Deprecated:` section in docstring, and emit a warning on each call. - {func}`~deprecated.google.versionadded`: insert a `Version added:` section in docstring, don't emit warning. - {func}`~deprecated.google.versionchanged`: insert a `Version changed:` section in docstring, don't emit warning. Each item of these sections has the format `version: reason`, like the items of the `Raises:` section. The *reason* is optional, and long lines are wrapped (see the *line_length* parameter). ## Using the "Google" decorators The decorators can be combined to reflect the life cycle of a function: ```{literalinclude} google/google_demo.py ``` The docstring of the *successor()* function looks like this: ```text Calculate the successor of a number. Args: n: a number Returns: number + 1 Version added: 0.1.0: Here is my new function. Version changed: 0.2.0: Well, I add a new feature in this function. It is very useful as you can see in the example below, so try it. This is a very very very very very long sentence. Deprecated: 0.3.0: This is deprecated, really. So you need to use another function. But I don't know which one. - The first, - The second. Just guess! ``` The sections are added at the end of the docstring. If a section already exists (for instance when the `@versionchanged` decorator is used several times, or when the section is already written in the docstring), the new item is appended to this section. :::{note} Decorators must be written in reverse order: recent first, older last. ::: ## Building the documentation The [Napoleon] extension of Sphinx parses the Google docstrings, but it doesn't know the life cycle sections: declare them as custom sections in your {file}`conf.py`: ```python extensions = ["sphinx.ext.autodoc", "sphinx.ext.napoleon"] napoleon_custom_sections = ["Version added", "Version changed", "Deprecated"] ``` As for the "Sphinx" decorators, your modules must be imported during the build: the Deprecated decorators must be interpreted (see {ref}`sphinx_deco`). [docstring]: https://docs.python.org/3/glossary.html#term-docstring [google python style guide]: https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings [napoleon]: https://www.sphinx-doc.org/en/master/usage/extensions/napoleon.html deprecated-3.0.1/docs/source/index.md000066400000000000000000000020631527107537600175120ustar00rootroot00000000000000# Welcome to Deprecated ```{image} _static/logo.svg :alt: 'Deprecated: a price tag reading "@deprecated, best before: next major"' ``` Welcome to Deprecated’s Documentation. This documentation is divided into different parts. I recommend that you get started with {ref}`installation` and then head over to the {ref}`tutorial`. If you'd rather dive into the internals of the Deprecated Library, check out the {ref}`api` documentation. ## User's Guide This part of the documentation, which is mostly prose, begins with some background information about Deprecated, then focuses on step-by-step instructions for using Deprecated. ```{toctree} :maxdepth: 2 installation introduction tutorial sphinx_deco google_deco numpy_deco white_paper ``` ## API Reference If you are looking for information on a specific function, class or method, this part of the documentation is for you. ```{toctree} :maxdepth: 2 api ``` ## Additional Notes Legal information and changelog are here for the interested. ```{toctree} :maxdepth: 2 changelog license contributing release ``` deprecated-3.0.1/docs/source/installation.md000066400000000000000000000077601527107537600211150ustar00rootroot00000000000000(installation)= # Installation ## Python Version Our project supports Python 3.12 and newer versions. We recommend using the latest version of Python 3 whenever possible. :::{versionchanged} 3.0.0 Support for Python 2.7 and Python 3 versions older than 3.12 has been dropped. Use Deprecated 1.3.x on these versions. ::: ## Dependencies This library uses the [Wrapt] library as a basis to construct function wrappers and decorator functions. The table below shows the compatibility matrix between Python versions and the `wrapt` versions that have been tested to date. Recent versions are listed first. ```{list-table} Compatibility matrix (tested versions) :header-rows: 1 :widths: 25 9 9 9 * - Python / wrapt - 2.x - 1.17 - 1.16 * - py3.14 - ✓ - ✓ - ✓ * - py3.13 - ✓ - ✓ - ✓ * - py3.12 - ✓ - ✓ - ✓ ``` Legend: ✓ = tested and compatible. ### Development dependencies The project is managed with [uv]: the development dependencies are declared in the `dev` dependency group of the {file}`pyproject.toml` file, and locked in the {file}`uv.lock` file. They are installed with `uv sync`. - [pytest] is a framework which makes it easy to write small tests, yet scales to support complex functional testing for applications and libraries… - [pytest-cov] is a [pytest] plugin used to produce coverage reports. - [Ruff] is used to lint and format the source code. - [mypy] is used to type-check the source code. The quality checks and the test matrix are run with [Hatch] (installed with `uv tool install hatch`): - `hatch check code` lints the code with Ruff, - `hatch check fmt` verifies the formatting with Ruff, - `hatch check types` type-checks the code with mypy, - `hatch test --all` runs the test suite on all supported Python and wrapt versions. The version is defined in {file}`src/deprecated/__init__.py` and updated with `hatch version` (see {ref}`release`), and [sphinx] (with the [MyST] Markdown parser) is used to build the documentation: its dependencies are declared in the `docs` dependency group (`uv sync --group docs`, or `make docs`). ## Virtual environments Use a virtual environment to manage the dependencies for your project, both in development and in production. What problem does a virtual environment solve? The more Python projects you have, the more likely it is that you need to work with different versions of Python libraries, or even Python itself. Newer versions of libraries for one project can break compatibility in another project. Virtual environments are independent groups of Python libraries, one for each project. Packages installed for one project will not affect other projects or the operating system's packages. Python 3 comes bundled with the {mod}`venv` module to create virtual environments. (install-create-env)= ### Create an environment Create a project folder and a {file}`venv` folder within: ```sh mkdir myproject cd myproject python3 -m venv venv ``` On Windows: ```bat py -3 -m venv venv ``` ### Activate the environment Before you work on your project, activate the corresponding environment: ```sh . venv/bin/activate ``` On Windows: ```bat venv\Scripts\activate ``` Your shell prompt will change to show the name of the activated environment. ## Install Deprecated Within the activated environment, use the following command to install Deprecated: ```sh pip install Deprecated ``` ### Living on the edge If you want to work with the latest Deprecated code before it's released, install or update the code from the develop branch: ```sh pip install -U https://github.com/laurent-laporte-pro/deprecated/archive/develop.tar.gz ``` [hatch]: https://hatch.pypa.io/latest/ [mypy]: https://mypy.readthedocs.io/en/stable/ [pytest]: https://docs.pytest.org/en/latest/ [pytest-cov]: http://pytest-cov.readthedocs.io/en/latest/ [ruff]: https://docs.astral.sh/ruff/ [sphinx]: http://www.sphinx-doc.org/en/stable/index.html [uv]: https://docs.astral.sh/uv/ [wrapt]: http://wrapt.readthedocs.io/en/latest/ [myst]: https://myst-parser.readthedocs.io/ deprecated-3.0.1/docs/source/introduction.md000066400000000000000000000112771527107537600211330ustar00rootroot00000000000000# Introduction ## What "Deprecated" Means A function or class is deprecated when it has been superseded: you should no longer use it, since it may cease to exist in the future. As a module, a class or function evolves, its API (Application Programming Interface) inevitably changes: functions are renamed for consistency, new and better methods are added, and attributes change. But such changes introduce a problem. You need to keep the old API around until developers make the transition to the new one, but you don't want them to continue programming to the old API. The ability to deprecate a class or a function solves the problem. Since Python 3.13, the Python Standard Library provides the {func}`warnings.deprecated` decorator ([PEP 702](https://peps.python.org/pep-0702/)), which emits a runtime warning and lets the type checkers report the use of deprecated objects. The Python [Deprecated Library] complements it: deprecation messages built from a reason and a version, docstring updates (Sphinx, Google or NumPy formats), local warning filters and deprecated parameters. See the {ref}`white_paper` for a comparison and for a way to combine both. ## When to Deprecate When you design an API, carefully consider whether it supersedes an old API. If it does, and you wish to encourage developers (users of the API) to migrate to the new API, then deprecate the old API. Valid reasons to deprecate an API include: - It is insecure, buggy, or highly inefficient; - It is going away in a future release; - It encourages bad coding practices. Deprecation is a reasonable choice in all these cases because it preserves "backward compatibility" while encouraging developers to change to the new API. Also, the deprecation comments help developers decide when to move to the new API, and so should briefly mention the technical reasons for deprecation. ## How to Deprecate The Python Deprecated Library provides a `@deprecated` decorator to deprecate a class, method or function. Using the decorator causes the Python interpreter to emit a warning at runtime, when a class instance is constructed, or a function is called. The warning is emitted using the [Python warning control]. Warning messages are normally written to `sys.stderr`, but their disposition can be changed flexibly, from ignoring all warnings to turning them into exceptions. You are strongly recommended to use the `@deprecated` decorator with appropriate comments explaining how to use the new API. This ensures developers will have a workable migration path from the old API to the new API. **Example:** ```python from deprecated import deprecated @deprecated(version='1.2.0', reason="You should use another function") def some_old_function(x, y): return x + y class SomeClass: @deprecated(version='1.3.0', reason="This method is deprecated") def some_old_method(self, x, y): return x + y some_old_function(12, 34) obj = SomeClass() obj.some_old_method(5, 8) ``` When you run this Python script, you will get something like this in the console: ```bash $ pip install Deprecated $ python hello.py hello.py:15: DeprecationWarning: Call to deprecated function (or staticmethod) some_old_function. (You should use another function) -- Deprecated since version 1.2.0. some_old_function(12, 34) hello.py:17: DeprecationWarning: Call to deprecated method some_old_method. (This method is deprecated) -- Deprecated since version 1.3.0. obj.some_old_method(5, 8) ``` ## Documenting the Life Cycle The deprecation must also be documented: the Deprecated Library provides decorators which update the [docstring] of your classes and functions, according to your docstring format: | Docstring format | Module | Inserted text | Documentation | |------------------|--------------------------|--------------------------------------------------------------------------|--------------------| | reStructuredText | {mod}`deprecated.sphinx` | `.. deprecated::`, `.. versionadded::`, `.. versionchanged::` directives | {ref}`sphinx_deco` | | Google | {mod}`deprecated.google` | `Deprecated:`, `Version added:`, `Version changed:` sections | {ref}`google_deco` | | NumPy | {mod}`deprecated.numpy` | `Deprecated`, `Version added`, `Version changed` sections | {ref}`numpy_deco` | Each module provides the `@deprecated` decorator (which also emits the deprecation warning), and the `@versionadded` and `@versionchanged` decorators (which only update the docstring). [deprecated library]: https://pypi.org/project/Deprecated/ [docstring]: https://docs.python.org/3/glossary.html#term-docstring [python warning control]: https://docs.python.org/3/library/warnings.html deprecated-3.0.1/docs/source/license.md000066400000000000000000000000551527107537600200240ustar00rootroot00000000000000# License ```{include} ../../LICENSE.md ``` deprecated-3.0.1/docs/source/numpy_deco.md000066400000000000000000000062701527107537600205510ustar00rootroot00000000000000(numpy_deco)= # The "NumPy" decorators ## Overview If you write your docstrings with the [numpydoc] format, the "Sphinx" decorators (see {ref}`sphinx_deco`) are not the best fit: they append reStructuredText directives at the end of your docstrings, whereas a NumPy docstring is organized in *sections* (headers underlined with hyphens, like `Parameters` or `Returns`), and a directive appended after the last section is considered as part of this section. The "NumPy" decorators have the same function as the "Sphinx" decorators, but they add *sections* to your functions or classes documentation (inside the [docstring]): - {func}`~deprecated.numpy.deprecated`: insert a `Deprecated` section in docstring, and emit a warning on each call. - {func}`~deprecated.numpy.versionadded`: insert a `Version added` section in docstring, don't emit warning. - {func}`~deprecated.numpy.versionchanged`: insert a `Version changed` section in docstring, don't emit warning. Each item of these sections is made of the version, followed by the indented reason message, like the items of the `Raises` section. The *reason* is optional, and long lines are wrapped (see the *line_length* parameter). ## Using the "NumPy" decorators The decorators can be combined to reflect the life cycle of a function: ```{literalinclude} numpydoc/numpy_demo.py ``` The docstring of the *successor()* function looks like this: ```text Calculate the successor of a number. Parameters ---------- n : int A number. Returns ------- int The number + 1. Version added ------------- 0.1.0 Here is my new function. Version changed --------------- 0.2.0 Well, I add a new feature in this function. It is very useful as you can see in the example below, so try it. This is a very very very very very long sentence. Deprecated ---------- 0.3.0 This is deprecated, really. So you need to use another function. But I don't know which one. - The first, - The second. Just guess! ``` The sections are added at the end of the docstring. If a section already exists (for instance when the `@versionchanged` decorator is used several times, or when the section is already written in the docstring), the new item is appended to this section. :::{note} Decorators must be written in reverse order: recent first, older last. ::: ## Building the documentation The `Deprecated` section is understood by [Griffe], the docstring parser of [mkdocstrings]. The [Napoleon] extension of Sphinx parses the NumPy docstrings, but it doesn't know the life cycle sections: declare them as custom sections in your {file}`conf.py`: ```python extensions = ["sphinx.ext.autodoc", "sphinx.ext.napoleon"] napoleon_custom_sections = ["Version added", "Version changed", "Deprecated"] ``` As for the "Sphinx" decorators, your modules must be imported during the build: the Deprecated decorators must be interpreted (see {ref}`sphinx_deco`). [docstring]: https://docs.python.org/3/glossary.html#term-docstring [griffe]: https://mkdocstrings.github.io/griffe/ [mkdocstrings]: https://mkdocstrings.github.io/ [napoleon]: https://www.sphinx-doc.org/en/master/usage/extensions/napoleon.html [numpydoc]: https://numpydoc.readthedocs.io/en/latest/format.html deprecated-3.0.1/docs/source/numpydoc/000077500000000000000000000000001527107537600177165ustar00rootroot00000000000000deprecated-3.0.1/docs/source/numpydoc/numpy_demo.py000066400000000000000000000016171527107537600224510ustar00rootroot00000000000000from deprecated.numpy import deprecated from deprecated.numpy import versionadded from deprecated.numpy import versionchanged @deprecated( reason=""" This is deprecated, really. So you need to use another function. But I don\'t know which one. - The first, - The second. Just guess! """, version="0.3.0", ) @versionchanged( reason="Well, I add a new feature in this function. " "It is very useful as you can see in the example below, so try it. " "This is a very very very very very long sentence.", version="0.2.0", ) @versionadded(reason="Here is my new function.", version="0.1.0") def successor(n): """ Calculate the successor of a number. Parameters ---------- n : int A number. Returns ------- int The number + 1. """ return n + 1 if __name__ == "__main__": print(successor.__doc__) deprecated-3.0.1/docs/source/release.md000066400000000000000000000166251527107537600200340ustar00rootroot00000000000000(release)= # Release process This page explains how to prepare, document and publish a new release of Deprecated. The project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html), and its changelog follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## Overview - The version is defined **only** in {file}`src/deprecated/__init__.py` (`__version__`). Hatch reads it to build the distributions (`[tool.hatch.version]` in {file}`pyproject.toml`), and it is updated with `hatch version `. The Sphinx documentation reads it from the installed distribution metadata. - The `develop` branch receives the pull requests; the `master` branch contains the releases. - A release is prepared on a `release/X.Y.Z` branch (or `hotfix/X.Y.Z` for a patch of the latest release), merged into `master` with a pull request, then merged back into `develop`. - The git tag `vX.Y.Z` is **not** created locally: it is created by GitHub when the release is published (see {ref}`release-publish`). ```{list-table} Which part of the version to bump? :header-rows: 1 :widths: 15 85 * - Bump - When * - `major` - Incompatible changes: removed or changed public API, dropped Python version... * - `minor` - New backward-compatible features, new deprecations. * - `patch` - Backward-compatible bug fixes only. ``` ## 1. Prepare the release Start from an up-to-date `develop` branch (or `master` for a hotfix): ```sh git switch develop git pull git switch -c release/X.Y.Z # or: git switch -c hotfix/X.Y.Z master ``` Bump the version: ```sh hatch version # show the current version, e.g.: 3.0.0 hatch version minor # updates src/deprecated/__init__.py, e.g.: 3.0.0 => 3.1.0 ``` :::{note} `hatch version` refuses to lower the version: to fix a wrong bump, edit `__version__` in {file}`src/deprecated/__init__.py` (or discard the change with git). ::: The `make bump-major`, `make bump-minor` and `make bump-patch` targets are shortcuts for these commands; `make version` shows the current version. Then run `uv sync --reinstall-package deprecated`: the documentation reads the version from the installed package metadata, which is not refreshed by `hatch version`. Update the files which quote the version and cannot read it from the package metadata: - {file}`python-deprecated.spec`: the `Version:` field (Packit updates the Fedora packages from the release, but the spec file of this repository must stay consistent); - {file}`docs/source/_static/logo.svg`: the `vX.Y.Z` text (element `id="deprecated-version"`) of the logo, shown in the README and on the home page of the documentation. The text is right-aligned and fits up to 10 characters (e.g. `v10.10.100`); - {file}`docs/source/_static/social-preview.svg`: the same `vX.Y.Z` text in the social preview image of the GitHub repository (a 2:1 variant of the logo, see {ref}`release-after`). Add a new section at the top of {file}`CHANGELOG.md` for the new version, marked as unreleased: ```markdown ## vX.Y.Z (unreleased) Minor release: ### Added - ... ``` Commit these changes: ```sh git add src/deprecated/__init__.py python-deprecated.spec CHANGELOG.md \ docs/source/_static/logo.svg docs/source/_static/social-preview.svg git commit -m "chore: prepare release X.Y.Z" ``` ## 2. Update the changelog from the pull requests List the pull requests merged since the previous release `vA.B.C`, for instance with the [GitHub CLI](https://cli.github.com/) (use the date of the previous release): ```sh gh pr list --repo laurent-laporte-pro/deprecated --base develop --state merged \ --search "merged:>=YYYY-MM-DD" --limit 200 \ --json number,title,author --template \ '{{range .}}- {{.title}} (#{{.number}}, @{{.author.login}}){{"\n"}}{{end}}' ``` or with git, from the merge commits: ```sh git log --merges --first-parent --oneline vA.B.C..develop ``` The *compare* view of GitHub (`https://github.com/laurent-laporte-pro/deprecated/compare/vA.B.C...develop`) gives the same information. For each user-visible change, add an entry in the right category of the new section (`Added`, `Changed`, `Deprecated`, `Removed`, `Fixed`, `Security`, `Documentation` or `Other`), and reference the pull request and its author: ```markdown ### Fixed - Fix the stack level of the warnings of nested deprecated classes. See PR #123 (username). ``` Guidelines: - write for the users of the library: describe the effect of the change, not its implementation; - group the purely internal changes (CI, refactoring, tooling) in `Other`; - write the changelog in plain Markdown, so that GitHub renders it too: standard links, no MyST roles, and GitHub alerts (`> [!WARNING]`) for the admonitions; - flag the breaking changes with a `> [!WARNING]` alert at the top of the section; - add the `.. versionadded::`, `.. versionchanged::` or `.. deprecated::` directives to the docstrings of the changed features, with the new version number. ## 3. Finalize the release Replace `(unreleased)` by the release date (ISO 8601 format) in {file}`CHANGELOG.md`: ```markdown ## vX.Y.Z (YYYY-MM-DD) ``` Run the checks and build the documentation and the distributions: ```sh make check # uv lock --check + hatch check code/fmt/types make test-all # all Python and wrapt versions make docs-check # warnings are errors, like the CI make build # dist/deprecated-X.Y.Z.tar.gz and dist/deprecated-X.Y.Z-py3-none-any.whl ``` Commit, push the branch and open a pull request to `master`: ```sh git commit -am "chore: release X.Y.Z" git push -u origin release/X.Y.Z ``` The CI must be green before merging. Name the pull request `vX.Y.Z`. (release-publish)= ## 4. Publish the release Once the pull request is merged into `master`: 1. On GitHub, open *Releases* > *Draft a new release*. 2. In *Choose a tag*, type `vX.Y.Z` and select *Create new tag: vX.Y.Z on publish*; the target branch is `master`. 3. Use `vX.Y.Z` as the title, and copy the changelog section of the release in the description (the *Generate release notes* button lists the merged pull requests, useful to cross-check). 4. Click *Publish release*: GitHub creates the tag, and Packit proposes the update of the Fedora packages. Then publish the distributions to PyPI, from the `master` branch (build them again from the tagged commit if needed): ```sh git switch master git pull make clean build uv publish --index testpypi # optional: check the result on https://test.pypi.org/ uv publish # requires a PyPI API token (UV_PUBLISH_TOKEN) ``` Finally, merge `master` back into `develop`, so that `develop` contains the release: ```sh git switch develop git pull git merge --no-ff master git push ``` (release-after)= ## After the release - Check the [PyPI page](https://pypi.org/project/Deprecated/) and the [documentation](https://deprecated.readthedocs.io/en/latest/) of the new version. - Update the social preview image of the GitHub repository, which shows the version: export {file}`docs/source/_static/social-preview.svg` to a 1280×640 PNG with transparent corners, for instance with `rsvg-convert` (package `librsvg2-bin` on Debian/Ubuntu, `librsvg` on Homebrew): ```sh rsvg-convert -w 1280 -h 640 docs/source/_static/social-preview.svg -o dist/social-preview.png ``` then upload it in the repository *Settings* > *General* > *Social preview* > *Edit* > *Upload an image…* (GitHub accepts up to 1 MB). - Close the GitHub milestone of the release, if any. deprecated-3.0.1/docs/source/sphinx/000077500000000000000000000000001527107537600173715ustar00rootroot00000000000000deprecated-3.0.1/docs/source/sphinx/calc_mean.py000066400000000000000000000005201527107537600216420ustar00rootroot00000000000000def mean(values): """ Compute the arithmetic mean (“average”) of values. :type values: list[float] :param values: List of floats :return: Mean of values. .. deprecated:: 2.5.0 Since Python 3.4, you can use the standard function :func:`statistics.mean`. """ return sum(values) / len(values) deprecated-3.0.1/docs/source/sphinx/calc_mean_deco.py000066400000000000000000000006171527107537600226430ustar00rootroot00000000000000from deprecated.sphinx import deprecated @deprecated( reason="""Since Python 3.4, you can use the standard function :func:`statistics.mean`.""", version="2.5.0", ) def mean(values): """ Compute the arithmetic mean (“average”) of values. :type values: list[float] :param values: List of floats :return: Mean of values. """ return sum(values) / len(values) deprecated-3.0.1/docs/source/sphinx/sphinx_demo.py000066400000000000000000000014221527107537600222570ustar00rootroot00000000000000from deprecated.sphinx import deprecated from deprecated.sphinx import versionadded from deprecated.sphinx import versionchanged @deprecated( reason=""" This is deprecated, really. So you need to use another function. But I don\'t know which one. - The first, - The second. Just guess! """, version="0.3.0", ) @versionchanged( reason="Well, I add a new feature in this function. " "It is very useful as you can see in the example below, so try it. " "This is a very very very very very long sentence.", version="0.2.0", ) @versionadded(reason="Here is my new function.", version="0.1.0") def successor(n): """ Calculate the successor of a number. :param n: a number :return: number + 1 """ return n + 1 deprecated-3.0.1/docs/source/sphinx/use_calc_mean_deco.py000066400000000000000000000000651527107537600235140ustar00rootroot00000000000000from calc_mean_deco import mean print(mean.__doc__) deprecated-3.0.1/docs/source/sphinx_deco.md000066400000000000000000000133121527107537600207050ustar00rootroot00000000000000(sphinx_deco)= # The "Sphinx" decorators ## Overview Developers use the Deprecated library to decorate deprecated functions or classes. This is very practical, but you know that this library does more: you can also document your source code! How? It's very simple: instead of using the "classic" decorator, you can use one of the "Sphinx" decorators. The "Sphinx" decorators have the same function as the "classic" decorator but also allow you to add [Sphinx directives] in your functions or classes documentation (inside the [docstring]). :::{tip} If your docstrings are written with the Google or NumPy format, use the {ref}`google_deco` or the {ref}`numpy_deco` instead: they add native sections to your docstrings (like `Deprecated:` or `Version added`) rather than reStructuredText directives. ::: ## What is a Sphinx directive? [Sphinx] is a tool that makes it easy to create intelligent and beautiful documentation. This tool uses the [reStructuredText] (or [Markdown]) syntax to generate the documentation in different formats, the most common being HTML. Developers generally use this syntax to document the source code of their applications. [Sphinx] offers several directives to introduce a text block with a predefined role. Among all the directives, the ones that interest us are those related to the functions (or classes) life cycle, namely: `versionadded`, `versionchanged` and `deprecated`. In the following example, the *mean()* function can be documented as follows: ```{literalinclude} sphinx/calc_mean.py ``` Therefore, the "Sphinx" decorators allow you to add a Sphinx directive to your functions or classes documentation. In the case of the `deprecated` directive, it obviously allows you to emit a {exc}`DeprecationWarning` warning. ## Using the "Sphinx" decorators The previous example can be written using a "Sphinx" decorator: ```{literalinclude} sphinx/calc_mean_deco.py ``` You can see the generated documentation with this simple call: ```{literalinclude} sphinx/use_calc_mean_deco.py ``` The documentation of the *mean()* function looks like this: ```rst Compute the arithmetic mean (“average”) of values. :type values: list[float] :param values: List of floats :return: Mean of values. .. deprecated:: 2.5.0 Since Python 3.4, you can use the standard function :func:`statistics.mean`. ``` ## More elaborate example The Deprecated library offers you 3 decorators: - {func}`~deprecated.sphinx.deprecated`: insert a `deprecated` directive in docstring, and emit a warning on each call. - {func}`~deprecated.sphinx.versionadded`: insert a `versionadded` directive in docstring, don't emit warning. - {func}`~deprecated.sphinx.versionchanged`: insert a `versionchanged` directive in docstring, don't emit warning. The decorators can be combined to reflect the life cycle of a function: - When it is added in your API, with the `@versionadded` decorator, - When it has an important change, with the `@versionchanged` decorator, - When it is deprecated, with the `@deprecated` decorator. The example below illustrates this life cycle: ```{literalinclude} sphinx/sphinx_demo.py ``` To see the result, you can use the builtin function {func}`help` to display a formatted help message of the *successor()* function. It is something like this: ```text Help on function successor in module __main__: successor(n) Calculate the successor of a number. :param n: a number :return: number + 1 .. versionadded:: 0.1.0 Here is my new function. .. versionchanged:: 0.2.0 Well, I add a new feature in this function. It is very useful as you can see in the example below, so try it. This is a very very very very very long sentence. .. deprecated:: 0.3.0 This is deprecated, really. So you need to use another function. But I don't know which one. - The first, - The second. Just guess! ``` :::{note} Decorators must be written in reverse order: recent first, older last. ::: ## Building the documentation The easiest way to build your API documentation is to use the [autodoc] plugin. The directives like `automodule`, `autoclass`, `autofunction` scan your source code and generate the documentation from your docstrings. Usually, the first thing that we need to do is indicate where the Python package that contains your source code is in relation to the `conf.py` file. But here, that will not work! The reason is that your modules must be imported during build: the Deprecated decorators must be interpreted. So, to build the API documentation of your project with [Sphinx] you need to set up a virtual environment, and install Sphinx, external themes and/or plugins and also your project. Nowadays, this is the right way to do it. For instance, you can configure a documentation environment with [Hatch] in your {file}`pyproject.toml` file, and build the documentation with `hatch run docs:build`: ```toml [tool.hatch.envs.docs] dependencies = ["sphinx"] [tool.hatch.envs.docs.scripts] build = "sphinx-build -b html -d dist/docs/doctrees docs/source/ dist/docs/html" ``` :::{hint} You can see a sample implementation of Sphinx directives in the demo project [Deprecated-Demo.Sphinx](https://github.com/tantale/deprecated-demo.sphinx). ::: [autodoc]: https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html [docstring]: https://docs.python.org/3/glossary.html#term-docstring [hatch]: https://hatch.pypa.io/latest/ [markdown]: https://www.sphinx-doc.org/en/master/usage/markdown.html [restructuredtext]: https://www.sphinx-doc.org/en/master/usage/restructuredtext/index.html [sphinx]: https://www.sphinx-doc.org/en/master/usage/restructuredtext/directives.html [sphinx directives]: https://www.sphinx-doc.org/en/master/usage/restructuredtext/directives.html deprecated-3.0.1/docs/source/tutorial.md000066400000000000000000000263721527107537600202570ustar00rootroot00000000000000(tutorial)= # Tutorial In this tutorial, we will use the Deprecated Library to mark pieces of code as deprecated. We will also see what happens when a user calls deprecated code. ## Deprecated function First, we have this little library composed of a single module: `liberty.py`: ```{literalinclude} tutorial/v0/liberty.py ``` You decided to write a more powerful function called `better_print()` which will become a replacement of `print_value()`. And you decided that the latter function is deprecated. To mark the `print_value()` as deprecated, you can use the {func}`~deprecated.deprecated` decorator: ```{literalinclude} tutorial/v1/liberty.py ``` If the user tries to use the deprecated functions, he will have a warning for each call: ```{literalinclude} tutorial/v1/using_liberty.py ``` ```sh $ python using_liberty.py using_liberty.py:3: DeprecationWarning: Call to deprecated function (or staticmethod) print_value. liberty.print_value("hello") 'hello' using_liberty.py:4: DeprecationWarning: Call to deprecated function (or staticmethod) print_value. liberty.print_value("hello again") 'hello again' 'Hi Tom!' ``` As you can see, the deprecation warning is displayed like a stack trace. You have the source code path, the line number and the called function. This is very useful for debugging. But, this doesn't help the developer to choose an alternative: which function could he use instead? To help the developer, you can add a "reason" message. For instance: ```{literalinclude} tutorial/v2/liberty.py ``` When the user calls the deprecated functions, he will have a more useful message: ```sh $ python using_liberty.py using_liberty.py:3: DeprecationWarning: Call to deprecated function (or staticmethod) print_value. (This function is rotten, use 'better_print' instead) liberty.print_value("hello") 'hello' using_liberty.py:4: DeprecationWarning: Call to deprecated function (or staticmethod) print_value. (This function is rotten, use 'better_print' instead) liberty.print_value("hello again") 'hello again' 'Hi Tom!' ``` ## Deprecated method Decorating a deprecated method works like decorating a function. ```{literalinclude} tutorial/v3/liberty.py ``` When the user calls the deprecated methods, like this: ```{literalinclude} tutorial/v3/using_liberty.py ``` He will have: ```sh $ python using_liberty.py using_liberty.py:4: DeprecationWarning: Call to deprecated method print_value. (This method is rotten, use 'better_print' instead) obj.print_value() 'Greeting' using_liberty.py:5: DeprecationWarning: Call to deprecated method print_value. (This method is rotten, use 'better_print' instead) obj.print_value() 'Greeting' 'Greeting' ``` :::{note} The call is done to the same object, so we have 3 times 'Greeting'. ::: ## Deprecated class You can also decide that a class is deprecated. For instance: ```{literalinclude} tutorial/v4/liberty.py ``` When the user uses the deprecated class like this: ```{literalinclude} tutorial/v4/using_liberty.py ``` He will have a warning at object instantiation. Once the object is initialised, no more warnings are emitted. ```sh $ python using_liberty.py using_liberty.py:3: DeprecationWarning: Call to deprecated class Liberty. (This class is not perfect) obj = liberty.Liberty("Salutation") 'Salutation' 'Salutation' ``` If a deprecated class is used, then a warning message is emitted during class instantiation. In other words, deprecating a class is the same as deprecating its `__new__` method. As a reminder, the magic method `__new__` will be called when instance is being created. Using this method you can customize the instance creation. The {func}`~deprecated.deprecated` decorator patches the `__new__` method in order to emit the warning message before instance creation. ## Deprecated parameters It is also possible to mark one or more parameters of a function as deprecated using the {func}`deprecated.params.deprecated_params` decorator. Example: ```python from deprecated.params import deprecated_params class V2DeprecationWarning(DeprecationWarning): pass @deprecated_params( { "epsilon": "epsilon is deprecated in version v2", "start": "start is removed in version v2", }, category=V2DeprecationWarning, ) @deprecated_params("epsilon", reason="epsilon is deprecated in version v1.1") def integrate(f, a, b, n=0, epsilon=0.0, start=None): epsilon = epsilon or (b - a) / n n = n or int((b - a) / epsilon) return sum((f(a + (i * epsilon)) + f(a + (i * epsilon) + epsilon)) * epsilon / 2 for i in range(n)) integrate(lambda x: x**2, 0, 2, epsilon=0.0012, start=123) ``` When the function is called, parameters marked as deprecated will emit deprecation warnings (using the provided category and message). This allows you to inform users about alternatives or the versions in which parameters were changed or removed. ```sh $ python use_deprecated_params.py use_deprecated_params.py:22: V2DeprecationWarning: epsilon is deprecated in version v2 integrate(lambda x: x**2, 0, 2, epsilon=0.0012, start=123) use_deprecated_params.py:22: V2DeprecationWarning: start is removed in version v2 integrate(lambda x: x**2, 0, 2, epsilon=0.0012, start=123) ``` ## Controlling warnings Warnings are emitted using the [Python warning control]. By default, Python installs several warning filters, which can be overridden by the [-W] command-line option, the [PYTHONWARNINGS] environment variable and calls to {func}`warnings.filterwarnings`. The warnings filter controls whether warnings are ignored, displayed, or turned into errors (raising an exception). For instance: ```{literalinclude} tutorial/warning_ctrl/filter_warnings_demo.py ``` When the user runs this script, the deprecation warnings are ignored in the main program, so no warning message is emitted: ```sh $ python filter_warnings_demo.py fun ``` ## Deprecation warning classes The {func}`deprecated.classic.deprecated`, {func}`deprecated.sphinx.deprecated`, {func}`deprecated.google.deprecated` and {func}`deprecated.numpy.deprecated` functions use the {exc}`DeprecationWarning` category but you can customize them by using your own category (or hierarchy of categories). - *category* classes which you can use (among others) are: | Class | Description | | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | {exc}`DeprecationWarning` | Base category for warnings about deprecated features when those warnings are intended for other Python developers (ignored by default, unless triggered by code in `__main__`). | | {exc}`FutureWarning` | Base category for warnings about deprecated features when those warnings are intended for end users of applications that are written in Python. | | {exc}`PendingDeprecationWarning` | Base category for warnings about features that will be deprecated in the future (ignored by default). | You can define your own deprecation warning hierarchy based on the standard deprecation classes. For instance: ```{literalinclude} tutorial/warning_ctrl/warning_classes_demo.py ``` When the user runs this script, the deprecation warnings for the 3.0 version are ignored: ```sh $ python warning_classes_demo.py warning_classes_demo.py:30: DeprecatedIn26: Call to deprecated function (or staticmethod) foo. (deprecated function) foo() foo bar ``` ## Filtering warnings locally The {func}`deprecated.classic.deprecated`, {func}`deprecated.sphinx.deprecated`, {func}`deprecated.google.deprecated` and {func}`deprecated.numpy.deprecated` functions can change the warning filtering locally (at function calls). - *action* is one of the following strings: | Value | Disposition | | ----------- | ----------------------------------------------------------------------------------------------------------------------- | | `"default"` | print the first occurrence of matching warnings for each location (module + line number) where the warning is issued | | `"error"` | turn matching warnings into exceptions | | `"ignore"` | never print matching warnings | | `"always"` | always print matching warnings | | `"module"` | print the first occurrence of matching warnings for each module where the warning is issued (regardless of line number) | | `"once"` | print only the first occurrence of matching warnings, regardless of location | You can define the *action* keyword parameter to override the filtering warnings locally. For instance: ```{literalinclude} tutorial/warning_ctrl/filter_action_demo.py ``` In this example, even if the global filter is set to "ignore", a call to the `foo()` function will raise an exception because the *action* is set to "error". ```sh $ python filter_action_demo.py Traceback (most recent call last): File "filter_action_demo.py", line 13, in foo() File "path/to/deprecated/classic.py", line 313, in wrapper_function self.warn(self.get_deprecated_msg(wrapped_, instance_)) File "path/to/deprecated/classic.py", line 259, in warn warnings.warn( DeprecationWarning: Call to deprecated function (or staticmethod) foo. (do not call it) ``` ## Modifying the deprecated code reference By default, when a deprecated function or class is called, the warning message indicates the location of the caller. The `extra_stacklevel` parameter allows customizing the stack level reference in the deprecation warning message. This parameter is particularly useful in scenarios where you have a factory or utility function that creates deprecated objects or performs deprecated operations. By specifying an `extra_stacklevel` value, you can control the stack level at which the deprecation warning is emitted, making it appear as if the calling function is the deprecated one, rather than the actual deprecated entity. For example, if you have a factory function `create_object()` that creates deprecated objects, you can use the `extra_stacklevel` parameter to emit the deprecation warning at the calling location. This provides clearer and more actionable deprecation messages, allowing developers to identify and update the code that invokes the deprecated functionality. For instance: ```{literalinclude} tutorial/warning_ctrl/extra_stacklevel_demo.py ``` Please note that the `extra_stacklevel` value should be an integer indicating the number of stack levels to skip when emitting the deprecation warning. [-w]: https://docs.python.org/3/using/cmdline.html#cmdoption-W [python warning control]: https://docs.python.org/3/library/warnings.html [pythonwarnings]: https://docs.python.org/3/using/cmdline.html#envvar-PYTHONWARNINGS deprecated-3.0.1/docs/source/tutorial/000077500000000000000000000000001527107537600177235ustar00rootroot00000000000000deprecated-3.0.1/docs/source/tutorial/v0/000077500000000000000000000000001527107537600202505ustar00rootroot00000000000000deprecated-3.0.1/docs/source/tutorial/v0/liberty.py000066400000000000000000000002521527107537600222730ustar00rootroot00000000000000"""Liberty library is free""" import pprint def print_value(value): """ Print the value :param value: The value to print """ pprint.pprint(value) deprecated-3.0.1/docs/source/tutorial/v1/000077500000000000000000000000001527107537600202515ustar00rootroot00000000000000deprecated-3.0.1/docs/source/tutorial/v1/liberty.py000066400000000000000000000007611527107537600223010ustar00rootroot00000000000000"""Liberty library is free""" import pprint from deprecated import deprecated @deprecated def print_value(value): """ Print the value :param value: The value to print """ pprint.pprint(value) def better_print(value, printer=None): """ Print the value using a *printer*. :param value: The value to print :param printer: Callable used to print the value, by default: :func:`pprint.pprint` """ printer = printer or pprint.pprint printer(value) deprecated-3.0.1/docs/source/tutorial/v1/using_liberty.py000066400000000000000000000001601527107537600234770ustar00rootroot00000000000000import liberty liberty.print_value("hello") liberty.print_value("hello again") liberty.better_print("Hi Tom!") deprecated-3.0.1/docs/source/tutorial/v2/000077500000000000000000000000001527107537600202525ustar00rootroot00000000000000deprecated-3.0.1/docs/source/tutorial/v2/liberty.py000066400000000000000000000010501527107537600222720ustar00rootroot00000000000000"""Liberty library is free""" import pprint from deprecated import deprecated @deprecated("This function is rotten, use 'better_print' instead") def print_value(value): """ Print the value :param value: The value to print """ pprint.pprint(value) def better_print(value, printer=None): """ Print the value using a *printer*. :param value: The value to print :param printer: Callable used to print the value, by default: :func:`pprint.pprint` """ printer = printer or pprint.pprint printer(value) deprecated-3.0.1/docs/source/tutorial/v2/using_liberty.py000066400000000000000000000001601527107537600235000ustar00rootroot00000000000000import liberty liberty.print_value("hello") liberty.print_value("hello again") liberty.better_print("Hi Tom!") deprecated-3.0.1/docs/source/tutorial/v3/000077500000000000000000000000001527107537600202535ustar00rootroot00000000000000deprecated-3.0.1/docs/source/tutorial/v3/liberty.py000066400000000000000000000011161527107537600222760ustar00rootroot00000000000000"""Liberty library is free""" import pprint from deprecated import deprecated class Liberty: def __init__(self, value): self.value = value @deprecated("This method is rotten, use 'better_print' instead") def print_value(self): """Print the value""" pprint.pprint(self.value) def better_print(self, printer=None): """ Print the value using a *printer*. :param printer: Callable used to print the value, by default: :func:`pprint.pprint` """ printer = printer or pprint.pprint printer(self.value) deprecated-3.0.1/docs/source/tutorial/v3/using_liberty.py000066400000000000000000000001511527107537600235010ustar00rootroot00000000000000import liberty obj = liberty.Liberty("Greeting") obj.print_value() obj.print_value() obj.better_print() deprecated-3.0.1/docs/source/tutorial/v4/000077500000000000000000000000001527107537600202545ustar00rootroot00000000000000deprecated-3.0.1/docs/source/tutorial/v4/liberty.py000066400000000000000000000004401527107537600222760ustar00rootroot00000000000000"""Liberty library is free""" import pprint from deprecated import deprecated @deprecated("This class is not perfect") class Liberty: def __init__(self, value): self.value = value def print_value(self): """Print the value""" pprint.pprint(self.value) deprecated-3.0.1/docs/source/tutorial/v4/using_liberty.py000066400000000000000000000001301527107537600234770ustar00rootroot00000000000000import liberty obj = liberty.Liberty("Salutation") obj.print_value() obj.print_value() deprecated-3.0.1/docs/source/tutorial/warning_ctrl/000077500000000000000000000000001527107537600224145ustar00rootroot00000000000000deprecated-3.0.1/docs/source/tutorial/warning_ctrl/extra_stacklevel_demo.py000066400000000000000000000007531527107537600273370ustar00rootroot00000000000000import warnings from deprecated import deprecated @deprecated(version="1.0", extra_stacklevel=1) class MyObject: def __init__(self, name): self.name = name def __str__(self): return f"object: {self.name}" def create_object(name): return MyObject(name) if __name__ == "__main__": warnings.filterwarnings("default", category=DeprecationWarning) # warn here: print(create_object("orange")) # and also here: print(create_object("banane")) deprecated-3.0.1/docs/source/tutorial/warning_ctrl/filter_action_demo.py000066400000000000000000000003211527107537600266100ustar00rootroot00000000000000import warnings from deprecated import deprecated @deprecated(reason="do not call it", action="error") def foo(): print("foo") if __name__ == "__main__": warnings.simplefilter("ignore") foo() deprecated-3.0.1/docs/source/tutorial/warning_ctrl/filter_warnings_demo.py000066400000000000000000000003641527107537600271720ustar00rootroot00000000000000import warnings from deprecated import deprecated @deprecated(version="1.2.1", reason="deprecated function") def fun(): print("fun") if __name__ == "__main__": warnings.simplefilter("ignore", category=DeprecationWarning) fun() deprecated-3.0.1/docs/source/tutorial/warning_ctrl/warning_classes_demo.py000066400000000000000000000011131527107537600271500ustar00rootroot00000000000000import warnings from deprecated import deprecated class MyDeprecationWarning(DeprecationWarning): """My DeprecationWarning""" class DeprecatedIn26(MyDeprecationWarning): """deprecated in 2.6""" class DeprecatedIn30(MyDeprecationWarning): """deprecated in 3.0""" @deprecated(category=DeprecatedIn26, reason="deprecated function") def foo(): print("foo") @deprecated(category=DeprecatedIn30, reason="deprecated function") def bar(): print("bar") if __name__ == "__main__": warnings.filterwarnings("ignore", category=DeprecatedIn30) foo() bar() deprecated-3.0.1/docs/source/white_paper.md000066400000000000000000000364731527107537600207260ustar00rootroot00000000000000(white_paper)= # White Paper This white paper describes the state of the art of deprecation in Python (2026): the building blocks provided by the language, the deprecation marker of the type system (PEP 702), the policies of the Python Standard Library and of major Open Source projects, and the resulting best practices. It ends with the place of the Deprecated Library in this landscape. ## What is a good deprecation? A deprecation announces that a feature (a function, a class, a method, a parameter, a module attribute…) will be removed or changed in a backward incompatible way. It gives the users of a library the time to migrate their code. A good deprecation answers four questions: - **What** is deprecated? - **Since when** (in which version)? - **What should be used instead?** - **When** will it be removed? And it reaches the users through several channels: 1. a **runtime warning**, emitted when the deprecated feature is used; 2. a **static diagnostic**, reported by type checkers and IDEs before the code even runs; 3. the **documentation** (docstring, API reference, changelog, migration guide); 4. a **schedule**, which is eventually honored: the feature is really removed. ## The building blocks of the language ### Warning categories: who sees what? Python emits deprecation warnings with {func}`warnings.warn`. The warning category determines who sees the warning with the default warning filters ([PEP 565], Python 3.7+): ```{list-table} :header-rows: 1 :widths: 30 30 40 * - Category - Shown by default? - Audience * - {exc}`DeprecationWarning` - Only when triggered by code in `__main__`, and by test runners like pytest - Developers using the API (libraries, applications under development) * - {exc}`PendingDeprecationWarning` - No - Early notice for developers: the feature will be deprecated later * - {exc}`FutureWarning` - Yes - End users of applications: the behavior will change ``` Custom subclasses are common to allow a precise filtering, for instance: `RemovedInDjango70Warning` (Django), `Pandas4Warning` (pandas), `MatplotlibDeprecationWarning` (Matplotlib) or `SADeprecationWarning` (SQLAlchemy). ### The stack level A deprecation warning must point to the **caller** of the deprecated feature, not to the library itself: this is the role of the *stacklevel* argument of {func}`warnings.warn`. Getting it right is harder than it seems (decorators, wrappers, inheritance…): pandas even computes it dynamically with an internal `find_stack_level()` helper. Since Python 3.12, the *skip_file_prefixes* argument makes this robust: the frames of the given files are skipped, so the warning always refers to the first frame outside of the library: ```python import os import warnings _LIB_DIR = os.path.dirname(__file__) + os.sep def old_function(): warnings.warn( "old_function() is deprecated since 2.0 and will be removed in 3.0, " "use new_function() instead", DeprecationWarning, stacklevel=2, skip_file_prefixes=(_LIB_DIR,), ) ``` ### Deprecating a module attribute A module attribute (a constant, an alias, a function moved elsewhere…) can be deprecated with a module-level `__getattr__` ([PEP 562]), which is only called for the missing names: ```python # mylib/__init__.py import warnings from mylib.core import new_function _DEPRECATED_ALIASES = {"old_function": "new_function"} def __getattr__(name: str) -> object: if new_name := _DEPRECATED_ALIASES.get(name): warnings.warn( f"{name} is deprecated, use {new_name} instead", DeprecationWarning, stacklevel=2, ) return globals()[new_name] raise AttributeError(f"module {__name__!r} has no attribute {name!r}") ``` Since `__getattr__` is only called for missing names, the deprecated attribute must not be defined in the module namespace: only its access through `__getattr__` emits the warning. ## Deprecation in the type system: PEP 702 [PEP 702] (Python 3.13) adds the {func}`warnings.deprecated` decorator, also available for older Python versions in [typing_extensions](https://typing-extensions.readthedocs.io/): ```python from warnings import deprecated # or: from typing_extensions import deprecated @deprecated("Use new_api() instead") def old_api() -> int: return 1 @deprecated("Use NewClass instead", category=FutureWarning) class OldClass: pass ``` This decorator has two effects: - **At runtime**, it emits a warning when the decorated function is called or the class is instantiated (or subclassed). The *category* (default: {exc}`DeprecationWarning`) and the *stacklevel* can be customized; with `category=None`, no runtime warning is emitted. The message is stored in the `__deprecated__` attribute, for runtime introspection. - **Statically**, type checkers report every use of the deprecated object: ```text $ mypy --enable-error-code deprecated example.py example.py:9: error: function example.old_api is deprecated: Use new_api() instead [deprecated] ``` - mypy reports deprecations with the `deprecated` error code, which is disabled by default; `--report-deprecated-as-note` turns them into notes, and `--deprecated-calls-exclude` hides them for given functions, classes or packages. - Pyright and Pylance support PEP 702 too (`reportDeprecated`); the IDEs display deprecated objects with a strikethrough. The decorator can also be applied to **one overload** of an overloaded function: this is the typing way to deprecate a *signature*, for instance a parameter or a type of argument: ```python from typing import overload from warnings import deprecated @overload def area(width: float, height: float) -> float: ... @overload @deprecated("Passing a (width, height) tuple is deprecated, pass two arguments instead") def area(width: tuple[float, float]) -> float: ... def area(width, height=None): if height is None: width, height = width return width * height ``` Here, the type checkers only report the calls matching the deprecated overload. Note that the runtime implementation still has to emit the warning itself for the deprecated case, since the overloads don't exist at runtime. PEP 702 has limits: only the decorated classes, functions and overloads are known to the type checkers. Module attributes, parameters of non-overloaded functions or behavior changes are still only visible at runtime and in the documentation. ## The policy of the Python Standard Library The backward compatibility policy of CPython is defined by [PEP 387]: - a deprecation lasts **at least two years** (two releases), and it is preferred to wait **five years** before removal; - the deprecations are listed in the documentation, with the Python version of the removal (see [Deprecations](https://docs.python.org/3/deprecations/index.html)): for instance, `datetime.datetime.utcnow()` is deprecated since Python 3.12, in favor of `datetime.datetime.now(datetime.UTC)`; - a **soft deprecation** can be used for an API which should no longer be used to write new code, but which remains safe to use in existing code: it is documented and tested, but not developed any further, and its removal is not scheduled. For instance, the {mod}`optparse` and {mod}`getopt` modules are soft deprecated in favor of {mod}`argparse`. Internally, CPython uses a `warnings._deprecated(name, remove=(3, 14))` helper: it formats the message with the removal version, and it **raises an error** once the removal version is reached, so that a forgotten deprecation breaks the build of the next release instead of lasting forever. For instance, the AST node classes `ast.Num`, `ast.Str`… are deprecated since Python 3.8, emit a warning with this helper since Python 3.12 (`remove=(3, 14)`), and were removed in Python 3.14. ## Practices of major Open Source projects ```{list-table} :header-rows: 1 :widths: 18 82 * - Project - Practice * - [NumPy] - NEP 23: deprecation warnings for at least two releases (one year) before removal. The message gives the version of the deprecation and of the removal. `VisibleDeprecationWarning` is used when end users must see the warning (likely a bug in the user code). NumPy 2.0 removed many deprecated aliases at once. * - [pandas] - Three-stage policy (PDEP-17, pandas 3.0): a {exc}`DeprecationWarning` first, a {exc}`FutureWarning` in the last minor release before the next major one (for broader visibility), then removal in the major release. Dedicated classes like `pandas.errors.Pandas4Warning` name the version of the removal. * - [Django] - A class per removal version (`RemovedInDjango70Warning`…), with the aliases `RemovedInNextVersionWarning` and `RemovedAfterNextVersionWarning`, so that projects can filter the warnings without updating their configuration at each release. A public [deprecation timeline](https://docs.djangoproject.com/en/dev/internals/deprecation/) lists the removals of each version. * - [SQLAlchemy] - A migration mode for the 2.0 major release: in SQLAlchemy 1.4, the `RemovedIn20Warning` warnings are only emitted when the `SQLALCHEMY_WARN_20` environment variable is set. An application running its tests without these warnings (turned into errors) is ready for SQLAlchemy 2.0. * - [Matplotlib] - A rich internal API (`matplotlib._api`): `deprecated(since, alternative=..., removal=...)` for functions, classes and properties, and dedicated decorators for signature changes: `rename_parameter`, `delete_parameter`, `make_keyword_only`. * - [scikit-learn] - Renamed or removed public objects and parameters are still supported for two releases, with the `sklearn.utils.deprecated` decorator, which also updates the docstring. * - [Pydantic] - Since Pydantic 2.7, `Field(deprecated=...)` accepts a message or a {func}`warnings.deprecated` instance: a warning is emitted when the field is accessed, and the generated JSON Schema contains `"deprecated": true`. ``` Common threads: a documented and scheduled policy, messages which give the versions and the alternative, dedicated warning classes for filtering, and tools to deprecate *parameters*, not only whole functions. ## Best practices 1. **Write a complete message**: what is deprecated, since which version, what to use instead, and when it will be removed. 2. **Choose the category for the audience**: {exc}`DeprecationWarning` for developers, {exc}`FutureWarning` for end users, {exc}`PendingDeprecationWarning` for an early notice. A dedicated subclass helps the users to filter the warnings of your library. 3. **Point to the caller**: check the *stacklevel* with tests, or use *skip_file_prefixes*. 4. **Make it visible to the type checkers** with {func}`warnings.deprecated` (or `typing_extensions.deprecated`), including on overloads to deprecate a signature. 5. **Document it**: `deprecated`, `versionchanged` and `versionadded` directives in the docstrings, an entry in the changelog, and a migration guide for large changes. 6. **Test it**: check that the warning is emitted (`pytest.deprecated_call()` or `pytest.warns()`), and run the test suite with the deprecation warnings turned into errors (`-W error::DeprecationWarning`, or the `filterwarnings` option of pytest), to detect the use of deprecated features of your own dependencies early. 7. **Schedule and honor the removal**: keep a deprecation timeline, and remove the deprecated features in the announced version. A test (or a helper like `warnings._deprecated`) can fail when the removal version is reached. 8. **Prefer a soft deprecation** for a stable API which is only discouraged: document it, but don't warn and don't schedule its removal. ## Where does the Deprecated Library fit? The Deprecated Library predates PEP 702 and is complementary to {func}`warnings.deprecated`: ```{list-table} :header-rows: 1 :widths: 40 30 30 * - Feature - {func}`warnings.deprecated` - Deprecated Library * - Runtime warning for functions, methods and classes - Yes - Yes * - Detection by type checkers and IDEs - Yes - No * - Message built from a *reason* and a *version* - No (free message) - Yes * - Docstring update (Sphinx directives, Google or NumPy sections) - No - Yes ({mod}`deprecated.sphinx`, {mod}`deprecated.google`, {mod}`deprecated.numpy`) * - Local warning filter (*action*) and *extra_stacklevel* - No (only *stacklevel*) - Yes * - Deprecation of parameters - Only through overloads (static only) - Yes, at runtime ({func}`~deprecated.params.deprecated_params`) * - Python versions - 3.13+ (older versions: `typing_extensions`) - 3.12+ ``` The type checkers only recognize {func}`warnings.deprecated` and `typing_extensions.deprecated`: the `@deprecated` decorators of this library are not reported statically. To get the best of both worlds, you can combine them: {func}`warnings.deprecated` with `category=None` for the static detection only, and the Deprecated Library for the runtime warning and the documentation: ```python import warnings from deprecated.sphinx import deprecated @warnings.deprecated("Use new_api() instead", category=None) # type checkers and IDEs only @deprecated(reason="Use :func:`new_api` instead", version="2.0") # runtime warning + docstring def old_api() -> int: """Old API.""" return 1 ``` With this combination, calling `old_api()` emits a single runtime warning (from the Deprecated Library, pointing to the caller), the docstring contains the `.. deprecated:: 2.0` directive, and the type checkers report every use of `old_api`. ## References - [PEP 387] – Backwards Compatibility Policy - [PEP 565] – Show DeprecationWarning in `__main__` - [PEP 562] – Module `__getattr__` and `__dir__` - [PEP 702] – Marking deprecations using the type system - [warnings — Warning control](https://docs.python.org/3/library/warnings.html) - [mypy: the `deprecated` error code](https://mypy.readthedocs.io/en/stable/error_code_list2.html) - [pytest: how to capture warnings](https://docs.pytest.org/en/stable/how-to/capture-warnings.html) - [NEP 23 – Backwards compatibility and deprecation policy](https://numpy.org/neps/nep-0023-backwards-compatibility.html) - [PDEP-17 – Backwards compatibility and deprecation policy](https://pandas.pydata.org/pdeps/0017-backwards-compatibility-and-deprecation-policy.html) - [Django deprecation timeline](https://docs.djangoproject.com/en/dev/internals/deprecation/) - [SQLAlchemy 2.0 migration guide](https://docs.sqlalchemy.org/en/20/changelog/migration_20.html) - [Matplotlib API guidelines](https://matplotlib.org/devdocs/devel/api_changes.html) - [scikit-learn `deprecated`](https://scikit-learn.org/stable/modules/generated/sklearn.utils.deprecated.html) - [Pydantic fields: deprecated fields](https://docs.pydantic.dev/latest/concepts/fields/) [pep 387]: https://peps.python.org/pep-0387/ [pep 562]: https://peps.python.org/pep-0562/ [pep 565]: https://peps.python.org/pep-0565/ [pep 702]: https://peps.python.org/pep-0702/ [numpy]: https://numpy.org/neps/nep-0023-backwards-compatibility.html [pandas]: https://pandas.pydata.org/pdeps/0017-backwards-compatibility-and-deprecation-policy.html [django]: https://docs.djangoproject.com/en/dev/internals/deprecation/ [sqlalchemy]: https://docs.sqlalchemy.org/en/20/changelog/migration_20.html [matplotlib]: https://matplotlib.org/devdocs/devel/api_changes.html [scikit-learn]: https://scikit-learn.org/stable/modules/generated/sklearn.utils.deprecated.html [pydantic]: https://docs.pydantic.dev/latest/concepts/fields/ deprecated-3.0.1/pyproject.toml000066400000000000000000000160761527107537600165560ustar00rootroot00000000000000[build-system] requires = ["hatchling>=1.27"] build-backend = "hatchling.build" [project] name = "Deprecated" dynamic = ["version"] description = "Python @deprecated decorator to deprecate old python classes, functions or methods." readme = "README.md" license = "MIT" license-files = ["LICENSE.md"] authors = [{ name = "Laurent LAPORTE", email = "laurent.laporte.pro@gmail.com" }] maintainers = [{ name = "Laurent LAPORTE", email = "laurent.laporte.pro@gmail.com" }] keywords = ["deprecate", "deprecated", "deprecation", "warning", "warn", "decorator"] requires-python = ">=3.12" dependencies = [ # wrapt 1.16 is the first release supporting Python 3.12. "wrapt >= 1.16, < 3", ] classifiers = [ "Development Status :: 5 - Production/Stable", "Environment :: Web Environment", "Intended Audience :: Developers", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", "Topic :: Software Development :: Libraries :: Python Modules", "Typing :: Typed", ] [project.urls] Homepage = "https://github.com/laurent-laporte-pro/deprecated" Documentation = "https://deprecated.readthedocs.io/en/latest/" Source = "https://github.com/laurent-laporte-pro/deprecated" "Bug Tracker" = "https://github.com/laurent-laporte-pro/deprecated/issues" Changelog = "https://github.com/laurent-laporte-pro/deprecated/blob/master/CHANGELOG.md" [dependency-groups] dev = [ "coverage[toml]==7.16.2", "mypy == 2.3.1", "packaging == 26.3", "pytest == 9.1.1", "pytest-cov == 7.1.0", "ruff == 0.16.9", ] # Documentation (Sphinx + MyST): `uv sync --group docs`, also used by ReadTheDocs. docs = [ "furo ~= 2025.12", "myst-parser ~= 5.1", "sphinx ~= 9.1", ] # Live preview of the documentation (auto-rebuild and browser reload): `make docs-live`. docs-live = [ { include-group = "docs" }, "sphinx-autobuild ~= 2025.8", ] # -- uv ----------------------------------------------------------------------- [tool.uv] default-groups = ["dev"] [[tool.uv.index]] name = "testpypi" url = "https://test.pypi.org/simple/" publish-url = "https://test.pypi.org/legacy/" explicit = true # -- Hatch: version ------------------------------------------------------------- [tool.hatch.version] # Single source of the version: use `hatch version ` # (see docs/source/release.md). path = "src/deprecated/__init__.py" # -- Hatch: quality checks ------------------------------------------------------ # # - `hatch check code`: static analysis (Ruff lint rules configured in `[tool.ruff.lint]`). # - `hatch check fmt`: formatting (Ruff formatter configured in `[tool.ruff.format]`). # - `hatch check types`: static typing (mypy configured in `[tool.mypy]`). # # The scripts are overridden so that the project's Ruff configuration is used as-is # (Hatch would otherwise extend it with its own default rule set), and so that the # tool versions are the ones pinned in the `dev` dependency group (see `uv.lock`). [tool.hatch.envs.hatch-check-code] python = "3.12" dependencies = ["ruff == 0.16.9"] [tool.hatch.envs.hatch-check-code.scripts] lint-check = "ruff check {args:.}" lint-fix = "ruff check --fix {args:.}" [tool.hatch.envs.hatch-check-fmt] python = "3.12" dependencies = ["ruff == 0.16.9"] [tool.hatch.envs.hatch-check-fmt.scripts] format-check = "ruff format --check --diff {args:.}" format-fix = "ruff format {args:.}" [tool.hatch.envs.hatch-check-types] python = "3.12" dependencies = ["mypy == 2.3.1", "packaging == 26.3", "pytest == 9.1.1"] [tool.hatch.envs.hatch-check-types.scripts] check = "mypy {args}" # -- Hatch: test matrix (Python x wrapt compatibility) -------------------------- [tool.hatch.envs.hatch-test] extra-dependencies = ["packaging == 26.3"] [[tool.hatch.envs.hatch-test.matrix]] python = ["3.12", "3.13", "3.14"] wrapt = ["1.16", "1.17", "2.x"] [tool.hatch.envs.hatch-test.overrides] matrix.wrapt.extra-dependencies = [ { value = "wrapt ~= 1.16.0", if = ["1.16"] }, { value = "wrapt ~= 1.17.0", if = ["1.17"] }, { value = "wrapt >= 2, < 3", if = ["2.x"] }, ] # -- Ruff ----------------------------------------------------------------------- [tool.ruff] line-length = 100 target-version = "py312" extend-exclude = ["docs/source/conf.py"] [tool.ruff.lint] select = [ "E", # pycodestyle errors "W", # pycodestyle warnings "F", # Pyflakes "I", # isort "N", # pep8-naming "UP", # pyupgrade (Python 2 / legacy constructs) "B", # flake8-bugbear "C4", # flake8-comprehensions "SIM", # flake8-simplify "PT", # flake8-pytest-style "PGH", # pygrep-hooks "RUF", # Ruff-specific rules "ANN", # flake8-annotations "TID", # flake8-tidy-imports "T20", # flake8-print ] ignore = [ "SIM108", # if/else blocks are kept when each branch carries its own explanation ] [tool.ruff.lint.per-file-ignores] # Tests don't need full annotations, and some of them print to a stream on purpose. "tests/**" = ["ANN", "T20"] # Expected docstrings contain long lines on purpose (``line_length=0`` disables wrapping). "tests/test_sphinx_adapter.py" = ["E501"] # Documentation examples are meant to be run as scripts and print their result. "docs/**" = ["ANN", "T20"] [tool.ruff.lint.flake8-annotations] # `*args: Any, **kwargs: Any` is the only honest annotation for pass-through wrappers. allow-star-arg-any = true [tool.ruff.lint.isort] force-single-line = true [tool.ruff.lint.flake8-tidy-imports.banned-api] "__future__.annotations".msg = "Python 3.12+ typing is used natively: don't postpone annotations." [tool.ruff.format] quote-style = "double" docstring-code-format = true # The code examples of the documentation are not reformatted: the tutorial quotes their # output, including the line numbers of the warnings. exclude = ["*.md"] # -- mypy ----------------------------------------------------------------------- [tool.mypy] python_version = "3.12" files = ["src", "tests"] mypy_path = "src" strict = true warn_unreachable = true enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"] [[tool.mypy.overrides]] # The test suite exercises the decorators dynamically: only type-check the bodies. module = ["tests.*"] disallow_untyped_defs = false disallow_incomplete_defs = false disallow_untyped_decorators = false disallow_any_generics = false # Untyped test helpers, re-defined fixtures classes and ad hoc containers are fine here. disable_error_code = ["no-untyped-call", "no-redef", "var-annotated"] # -- pytest / coverage ---------------------------------------------------------- [tool.pytest.ini_options] minversion = "8.0" testpaths = ["tests"] python_files = ["test*.py"] addopts = ["-ra", "--strict-markers", "--strict-config"] [tool.coverage.run] source = ["deprecated"] branch = true [tool.coverage.report] # The CI fails if the total coverage drops below this threshold (currently 99%). fail_under = 95 show_missing = true exclude_also = ["if TYPE_CHECKING:", "@overload"] deprecated-3.0.1/python-deprecated.spec000066400000000000000000000022131527107537600201210ustar00rootroot00000000000000%global srcname Deprecated %global pkgname deprecated Name: python-%{pkgname} Version: 3.0.1 Release: 1%{?dist} Summary: Python decorator to deprecate old python classes, functions or methods License: MIT URL: https://github.com/laurent-laporte-pro/%{pkgname} Source0: %{pypi_source %{pkgname}} BuildArch: noarch %description Python @deprecated decorator to deprecate old python classes, functions or methods. %package -n python3-%{pkgname} Summary: %{summary} BuildRequires: python3-devel %description -n python3-%{pkgname} Python @deprecated decorator to deprecate old python classes, functions or methods. %prep %autosetup -n %{pkgname}-%{version} %generate_buildrequires %pyproject_buildrequires %build %pyproject_wheel %install %pyproject_install %pyproject_save_files %{pkgname} %check %pyproject_check_import %files -n python3-%{pkgname} -f %{pyproject_files} %license LICENSE.md %doc README.md %changelog * Fri Jul 26 2019 Petr Hracek - 1.2.6-2 - Fix python3_sitelib issue * Fri Jul 26 2019 Petr Hracek - 1.2.6-1 - Initial package deprecated-3.0.1/src/000077500000000000000000000000001527107537600144175ustar00rootroot00000000000000deprecated-3.0.1/src/deprecated/000077500000000000000000000000001527107537600165175ustar00rootroot00000000000000deprecated-3.0.1/src/deprecated/__init__.py000066400000000000000000000007371527107537600206370ustar00rootroot00000000000000""" Deprecated Library ================== Python ``@deprecated`` decorator to deprecate old python classes, functions or methods. """ #: Single source of the version, updated with `hatch version `. __version__ = "3.0.1" __author__ = "Laurent LAPORTE " __credits__ = "(c) Laurent LAPORTE" from deprecated.classic import deprecated from deprecated.params import deprecated_params __all__ = ["deprecated", "deprecated_params"] deprecated-3.0.1/src/deprecated/classic.py000066400000000000000000000411671527107537600205230ustar00rootroot00000000000000""" Classic deprecation warning =========================== Classic ``@deprecated`` decorator to deprecate old python classes, functions or methods. .. _The Warnings Filter: https://docs.python.org/3/library/warnings.html#the-warnings-filter """ import functools import importlib import inspect import os import pickle import warnings from collections.abc import Callable from typing import Any from typing import Literal from typing import SupportsIndex from typing import cast from typing import overload import wrapt #: Frames of these packages are skipped when computing the location of a warning, #: so the warning always refers to the user code, whatever the wrapt implementation #: (C extension or pure Python) and the number of nested deprecated wrappers. SKIP_FILE_PREFIXES: tuple[str, ...] = tuple( os.path.dirname(module_file) + os.sep for module_file in (__file__, wrapt.__file__) ) #: Warning filter action, see: `The Warnings Filter`_ in the Python documentation. type WarningAction = Literal["default", "error", "ignore", "always", "module", "once"] #: Object which can be decorated: a class, a function or a method. type Deprecatable = Callable[..., Any] def _resolve_wrapper(module_name: str, qualname: str) -> object: """ Find a deprecated routine by its qualified name in its module (used to unpickle it). The attributes are read with :func:`inspect.getattr_static`, so that the wrapper stored in a class is returned as is, instead of a new wrapper bound at each access. :param module_name: Name of the module which defines the routine. :param qualname: Qualified name of the routine in its module. :return: The wrapper of the deprecated routine. """ obj: object = importlib.import_module(module_name) for name in qualname.split("."): obj = inspect.getattr_static(obj, name) # A wrapt proxy of a static/class method passes `isinstance`: check the real type. if isinstance(obj, (staticmethod, classmethod)) and type(obj) in ( staticmethod, classmethod, ): obj = obj.__func__ return obj # The wrapt classes are only generic in the type stubs (they can't be subscripted at runtime). class _DeprecatedBoundFunctionWrapper(wrapt.BoundFunctionWrapper): # type: ignore[type-arg] """ Bound wrapper of a deprecated method, which can be pickled like a bound method. wrapt proxies refuse to be pickled: a deprecated method bound to an instance (or a class method bound to its class) is pickled by reference, as ``getattr(instance, name)``, like a regular bound method. A method accessed from its class (not bound) is pickled as its parent wrapper. """ def __reduce_ex__(self, protocol: SupportsIndex) -> tuple[Any, ...]: instance = self._self_instance if instance is None: return cast(tuple[Any, ...], self._self_parent.__reduce_ex__(protocol)) return getattr, (instance, self.__name__) def __reduce__(self) -> tuple[Any, ...]: return self.__reduce_ex__(pickle.DEFAULT_PROTOCOL) class _DeprecatedFunctionWrapper(wrapt.FunctionWrapper): # type: ignore[type-arg] """ Wrapper of a deprecated routine, which can be pickled like the routine itself. wrapt proxies refuse to be pickled, so a deprecated function could not be sent to a child process by :mod:`multiprocessing` (the default *spawn* start method on Windows and macOS). The wrapper is pickled by reference, like a regular function: it is found again by its qualified name in its module when it is unpickled. """ __bound_function_wrapper__ = _DeprecatedBoundFunctionWrapper def __reduce_ex__(self, protocol: SupportsIndex) -> tuple[Any, ...]: module_name = str(self.__module__) qualname = str(self.__qualname__) # Like a regular function, fail early if the routine can't be found by its name # (a local function, or a wrapper which is not stored under its own name). try: found = _resolve_wrapper(module_name, qualname) except (ImportError, AttributeError) as exc: msg = f"Can't pickle {self!r}: it's not found as {module_name}.{qualname}" raise pickle.PicklingError(msg) from exc if found is not self: msg = f"Can't pickle {self!r}: it's not the same object as {module_name}.{qualname}" raise pickle.PicklingError(msg) return _resolve_wrapper, (module_name, qualname) def __reduce__(self) -> tuple[Any, ...]: return self.__reduce_ex__(pickle.DEFAULT_PROTOCOL) class ClassicAdapter(wrapt.AdapterFactory): """ Classic adapter -- *for advanced usage only* This adapter is used to get the deprecation message according to the wrapped object type: class, function, standard method, static method, or class method. This is the base class of the :class:`~deprecated.sphinx.SphinxAdapter` class which is used to update the wrapped object docstring. You can also inherit this class to change the deprecation message. In the following example, we change the message into "The ... is deprecated.": .. code-block:: python import inspect from deprecated.classic import ClassicAdapter from deprecated.classic import Deprecatable from deprecated.classic import deprecated class MyClassicAdapter(ClassicAdapter): def get_deprecated_msg(self, wrapped: Deprecatable, instance: object | None) -> str: if instance is None: if inspect.isclass(wrapped): fmt = "The class {name} is deprecated." else: fmt = "The function {name} is deprecated." else: if inspect.isclass(instance): fmt = "The class method {name} is deprecated." else: fmt = "The method {name} is deprecated." if self.reason: fmt += " ({reason})" if self.version: fmt += " -- Deprecated since version {version}." return fmt.format( name=wrapped.__name__, reason=self.reason or "", version=self.version or "" ) Then, you can use your ``MyClassicAdapter`` class like this in your source code: .. code-block:: python @deprecated(reason="use another function", adapter_cls=MyClassicAdapter) def some_old_function(x, y): return x + y """ def __init__( self, reason: str | None = "", version: str | None = "", action: WarningAction | Literal[""] | None = None, category: type[Warning] = DeprecationWarning, extra_stacklevel: int = 0, ) -> None: """ Construct a wrapper adapter. :type reason: str | None :param reason: Reason message which documents the deprecation in your library (can be omitted). :type version: str :param version: Version of your project which deprecates this feature. If you follow the `Semantic Versioning `_, the version number has the format "MAJOR.MINOR.PATCH". :type action: Literal["default", "error", "ignore", "always", "module", "once"] :param action: A warning filter used to activate or not the deprecation warning. Can be one of "error", "ignore", "always", "default", "module", or "once". If ``None`` or empty, the global filtering mechanism is used. See: `The Warnings Filter`_ in the Python documentation. :type category: Type[Warning] :param category: The warning category to use for the deprecation warning. By default, the category class is :class:`~DeprecationWarning`, you can inherit this class to define your own deprecation warning category. :type extra_stacklevel: int :param extra_stacklevel: Number of additional stack levels to consider instrumentation rather than user code. With the default value of 0, the warning refers to where the class was instantiated or the function was called. .. versionchanged:: 1.2.15 Add the *extra_stacklevel* parameter. """ self.reason = reason or "" self.version = version or "" self.action = action self.category = category self.extra_stacklevel = extra_stacklevel super().__init__() def get_deprecated_msg(self, wrapped: Deprecatable, instance: object | None) -> str: """ Get the deprecation warning message for the user. :param wrapped: Wrapped class or function. :param instance: The object to which the wrapped function was bound when it was called. :return: The warning message. """ if instance is None: if inspect.isclass(wrapped): fmt = "Call to deprecated class {name}." else: fmt = "Call to deprecated function (or staticmethod) {name}." else: if inspect.isclass(instance): fmt = "Call to deprecated class method {name}." else: fmt = "Call to deprecated method {name}." if self.reason: fmt += " ({reason})" if self.version: fmt += " -- Deprecated since version {version}." # Only classes and routines are decorated: they always have a `__name__`. name = wrapped.__name__ # ty: ignore[unresolved-attribute] return fmt.format(name=name, reason=self.reason or "", version=self.version or "") def warn(self, msg: str) -> None: """ Emit the deprecation warning, using the *action* filter if any. The warning refers to the first frame outside of this library (see :data:`SKIP_FILE_PREFIXES`), plus *extra_stacklevel* frames. :param msg: The warning message. .. versionadded:: 3.0.0 """ stacklevel = 2 + self.extra_stacklevel if self.action: with warnings.catch_warnings(): warnings.simplefilter(self.action, self.category) warnings.warn( msg, category=self.category, stacklevel=stacklevel, skip_file_prefixes=SKIP_FILE_PREFIXES, ) else: warnings.warn( msg, category=self.category, stacklevel=stacklevel, skip_file_prefixes=SKIP_FILE_PREFIXES, ) def __call__[T: Deprecatable](self, wrapped: T) -> T: """ Decorate your class or function. :param wrapped: Wrapped class or function. :return: the decorated class or function. .. versionchanged:: 1.2.4 Don't pass arguments to :meth:`object.__new__` (other than *cls*). .. versionchanged:: 1.2.8 The warning filter is not set if the *action* parameter is ``None`` or empty. .. versionchanged:: 3.0.0 A deprecated function or method can be pickled (by reference, like a regular function), so it can be used with :mod:`multiprocessing`. .. versionchanged:: 3.0.1 Preserve the constructor signature when decorating a class. """ if inspect.isclass(wrapped): old_new1 = wrapped.__new__ @functools.wraps(old_new1) def wrapped_cls(cls: type, *args: Any, **kwargs: Any) -> object: self.warn(self.get_deprecated_msg(wrapped, None)) if old_new1 is object.__new__: return old_new1(cls) # actually, we don't know the real signature of *old_new1* return old_new1(cls, *args, **kwargs) try: signature = inspect.signature(wrapped) except (TypeError, ValueError): pass # Some built-in constructors do not expose a signature. else: # Python 3.12 prioritizes the patched __new__ over __init__. # inspect.signature removes this receiver when inspecting the class. receiver_name = "cls" while receiver_name in signature.parameters: receiver_name = "_" + receiver_name receiver = inspect.Parameter(receiver_name, inspect.Parameter.POSITIONAL_ONLY) signature = signature.replace(parameters=[receiver, *signature.parameters.values()]) setattr(wrapped_cls, "__signature__", signature) # noqa: B010 # `__new__` is patched on purpose: `setattr` avoids a type checker error. setattr(wrapped, "__new__", staticmethod(wrapped_cls)) # noqa: B010 elif inspect.isroutine(wrapped): @wrapt.decorator(proxy=_DeprecatedFunctionWrapper) def wrapper_function( wrapped_: Callable[..., Any], instance_: object | None, args_: tuple[Any, ...], kwargs_: dict[str, Any], ) -> object: self.warn(self.get_deprecated_msg(wrapped_, instance_)) return wrapped_(*args_, **kwargs_) # The function wrapper is a transparent proxy of the wrapped routine. return cast(T, wrapper_function(wrapped)) else: # pragma: no cover raise TypeError(repr(type(wrapped))) return wrapped @overload def deprecated[T: Deprecatable](wrapped: T, /) -> T: ... @overload def deprecated[T: Deprecatable]( reason: str | None = "", /, *, version: str | None = "", action: WarningAction | Literal[""] | None = None, category: type[Warning] = DeprecationWarning, extra_stacklevel: int = 0, adapter_cls: type[ClassicAdapter] = ..., **kwargs: Any, ) -> Callable[[T], T]: ... def deprecated(*args: Any, **kwargs: Any) -> Any: """ This is a decorator which can be used to mark functions as deprecated. It will result in a warning being emitted when the function is used. **Classic usage:** To use this, decorate your deprecated function with **@deprecated** decorator: .. code-block:: python from deprecated import deprecated @deprecated def some_old_function(x, y): return x + y You can also decorate a class or a method: .. code-block:: python from deprecated import deprecated class SomeClass: @deprecated def some_old_method(self, x, y): return x + y @deprecated class SomeOldClass: pass You can give a *reason* message to help the developer to choose another function/class, and a *version* number to specify the starting version number of the deprecation. .. code-block:: python from deprecated import deprecated @deprecated(reason="use another function", version="1.2.0") def some_old_function(x, y): return x + y The *category* keyword argument allow you to specify the deprecation warning class of your choice. By default, :exc:`DeprecationWarning` is used, but you can choose :exc:`FutureWarning`, :exc:`PendingDeprecationWarning` or a custom subclass. .. code-block:: python from deprecated import deprecated @deprecated(category=PendingDeprecationWarning) def some_old_function(x, y): return x + y The *action* keyword argument allow you to locally change the warning filtering. *action* can be one of "error", "ignore", "always", "default", "module", or "once". If ``None``, empty or missing, the global filtering mechanism is used. See: `The Warnings Filter`_ in the Python documentation. .. code-block:: python from deprecated import deprecated @deprecated(action="error") def some_old_function(x, y): return x + y The *extra_stacklevel* keyword argument allows you to specify additional stack levels to consider instrumentation rather than user code. With the default value of 0, the warning refers to where the class was instantiated or the function was called. """ # Note: a `bytes` reason is still accepted for backward compatibility with Python 2 # (where `str` was `bytes`), but only `str` is documented and typed. if args and isinstance(args[0], (str, bytes)): kwargs["reason"] = args[0] args = args[1:] if args and not callable(args[0]): raise TypeError(repr(type(args[0]))) if args: adapter_cls = kwargs.pop("adapter_cls", ClassicAdapter) adapter = adapter_cls(**kwargs) wrapped = args[0] return adapter(wrapped) return functools.partial(deprecated, **kwargs) deprecated-3.0.1/src/deprecated/google.py000066400000000000000000000322511527107537600203500ustar00rootroot00000000000000""" Google docstring integration ============================ We usually need to document the life-cycle of functions and classes: when they are created, modified or deprecated. The `Google Python Style Guide`_ organizes a docstring in sections: a section starts with a header line ending with a colon (like ``Args:`` or ``Returns:``) followed by an indented block. The purpose of this module is to define decorators which add the following sections to the docstring of your functions and classes: - ``Version added:``: to document the version of the project which added the described feature to the library, - ``Version changed:``: to document changes of a feature, - ``Deprecated:``: to document a deprecated feature. Each item of these sections has the format ``version: reason``, like the items of the ``Raises:`` section. If the section already exists in the docstring (for instance when the ``@versionchanged`` decorator is used several times), the item is appended to it. Of course, the ``@deprecated`` decorator will emit a deprecation warning when the function/method is called or the class is constructed. .. note:: `Napoleon`_ doesn't know these sections: to render them with Sphinx, declare them with ``napoleon_custom_sections = ["Version added", "Version changed", "Deprecated"]`` in your :file:`conf.py`. .. _Google Python Style Guide: https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings .. _Napoleon: https://www.sphinx-doc.org/en/master/usage/extensions/napoleon.html .. versionadded:: 3.0.0 """ import textwrap from collections.abc import Callable from typing import Any from typing import Literal from deprecated.classic import ClassicAdapter from deprecated.classic import Deprecatable from deprecated.classic import WarningAction from deprecated.classic import deprecated as _classic_deprecated #: Life-cycle section inserted in the docstring. type GoogleSection = Literal["versionadded", "versionchanged", "deprecated"] #: Header of each life-cycle section (without the trailing colon). SECTION_HEADERS: dict[GoogleSection, str] = { "versionadded": "Version added", "versionchanged": "Version changed", "deprecated": "Deprecated", } #: Indentation of the section items, and of the continuation lines of an item. _INDENT = " " * 4 class GoogleAdapter(ClassicAdapter): """ Google docstring adapter -- *for advanced usage only* This adapter overrides the :class:`~deprecated.classic.ClassicAdapter` in order to add a life-cycle section to the function/class docstring, using the `Google Python Style Guide`_ format. - The section can be one of "versionadded", "versionchanged" or "deprecated". - The section item starts with the version number, followed by the reason message if not empty. - The item is appended to the section if it already exists, otherwise the section is added at the end of the docstring. """ def __init__( self, section: GoogleSection, reason: str | None = "", version: str | None = "", action: WarningAction | Literal[""] | None = None, category: type[Warning] = DeprecationWarning, extra_stacklevel: int = 0, line_length: int = 70, ) -> None: """ Construct a wrapper adapter. :type section: str :param section: Life-cycle section: can be one of "versionadded", "versionchanged" or "deprecated". :type reason: str | None :param reason: Reason message which documents the deprecation in your library (can be omitted). :type version: str :param version: Version of your project which deprecates this feature. If you follow the `Semantic Versioning `_, the version number has the format "MAJOR.MINOR.PATCH". :type action: Literal["default", "error", "ignore", "always", "module", "once"] :param action: A warning filter used to activate or not the deprecation warning. Can be one of "error", "ignore", "always", "default", "module", or "once". If ``None`` or empty, the global filtering mechanism is used. See: `The Warnings Filter`_ in the Python documentation. :type category: Type[Warning] :param category: The warning category to use for the deprecation warning. By default, the category class is :class:`~DeprecationWarning`, you can inherit this class to define your own deprecation warning category. :type extra_stacklevel: int :param extra_stacklevel: Number of additional stack levels to consider instrumentation rather than user code. With the default value of 0, the warning refers to where the class was instantiated or the function was called. :type line_length: int :param line_length: Max line length of the section item (including its indentation). If non-zero, a long text is wrapped in several lines. """ if not version: raise ValueError("'version' argument is required in Google docstring sections") if section not in SECTION_HEADERS: raise ValueError(f"invalid Google docstring section: {section!r}") self.section = section self.line_length = line_length super().__init__( reason=reason, version=version, action=action, category=category, extra_stacklevel=extra_stacklevel, ) def get_section_item(self) -> list[str]: """ Build the lines of the section item: ``version: reason``. The first line is indented once, the continuation lines are indented twice. :return: the lines of the section item, without line ending. """ initial_indent = f"{_INDENT}{self.version}: " subsequent_indent = _INDENT * 2 width = self.line_length if self.line_length > len(subsequent_indent) else 2**16 reason = textwrap.dedent(self.reason).strip() if not reason: return [f"{_INDENT}{self.version}"] item_lines: list[str] = [] for paragraph in reason.splitlines(): if not paragraph: item_lines.append("") continue item_lines.extend( textwrap.fill( paragraph, width=width, initial_indent=subsequent_indent if item_lines else initial_indent, subsequent_indent=subsequent_indent, ).splitlines() ) return item_lines def __call__[T: Deprecatable](self, wrapped: T) -> T: """ Add the Google docstring section to your class or function. :param wrapped: Wrapped class or function. :return: the decorated class or function. """ wrapped.__doc__ = add_section_item( wrapped.__doc__, SECTION_HEADERS[self.section], self.get_section_item(), ) if self.section in {"versionadded", "versionchanged"}: return wrapped return super().__call__(wrapped) def add_section_item(docstring: str | None, header: str, item_lines: list[str]) -> str: """ Add an item to a section of a Google docstring. The docstring is normalized first: the lines following the summary line are dedented, so that the section headers are not indented. - If the section already exists, the item is appended to the end of the section. - Otherwise, the section is added to the end of the docstring, separated from the previous text by an empty line. :param docstring: The docstring to update (can be ``None`` or empty). :param header: The section header (without the trailing colon). :param item_lines: The lines of the section item, indented, without line ending. :return: the updated docstring. """ # -- get the docstring, normalize the trailing newlines # keep a consistent behaviour if the docstring starts with newline # or directly on the first one lines = (docstring or "").splitlines(True) or [""] docstring = lines[0] + (textwrap.dedent("".join(lines[1:])) if len(lines) > 1 else "") doc_lines = docstring.rstrip().splitlines() # -- search the section, at the first level (not indented) header_line = f"{header}:" start = next((i for i, line in enumerate(doc_lines) if line.rstrip() == header_line), None) if start is None: # An empty line must separate the original docstring and the new section. if any(line.strip() for line in doc_lines): doc_lines.append("") doc_lines.append(header_line) doc_lines.extend(item_lines) else: # The section ends before the next line which is not indented (or at the end). end = next( ( i for i in range(start + 1, len(doc_lines)) if doc_lines[i].strip() and not doc_lines[i][0].isspace() ), len(doc_lines), ) while end > start + 1 and not doc_lines[end - 1].strip(): end -= 1 doc_lines[end:end] = item_lines return "".join(f"{line}\n" for line in doc_lines) def versionadded( reason: str | None = "", version: str | None = "", line_length: int = 70 ) -> GoogleAdapter: """ This decorator can be used to insert a "Version added" section in your function/class docstring in order to document the version of the project which adds this new functionality in your library. :param str reason: Reason message which documents the addition in your library (can be omitted). :param str version: Version of your project which adds this feature. If you follow the `Semantic Versioning `_, the version number has the format "MAJOR.MINOR.PATCH", and, in the case of a new functionality, the "PATCH" component should be "0". :type line_length: int :param line_length: Max line length of the section item. If non-zero, a long text is wrapped in several lines. :return: the decorated function. """ return GoogleAdapter( "versionadded", reason=reason, version=version, line_length=line_length, ) def versionchanged( reason: str | None = "", version: str | None = "", line_length: int = 70 ) -> GoogleAdapter: """ This decorator can be used to insert a "Version changed" section in your function/class docstring in order to document the version of the project which modifies this functionality in your library. :param str reason: Reason message which documents the modification in your library (can be omitted). :param str version: Version of your project which modifies this feature. If you follow the `Semantic Versioning `_, the version number has the format "MAJOR.MINOR.PATCH". :type line_length: int :param line_length: Max line length of the section item. If non-zero, a long text is wrapped in several lines. :return: the decorated function. """ return GoogleAdapter( "versionchanged", reason=reason, version=version, line_length=line_length, ) def deprecated[T: Deprecatable]( reason: str | None = "", version: str | None = "", line_length: int = 70, **kwargs: Any, ) -> Callable[[T], T]: """ This decorator can be used to insert a "Deprecated" section in your function/class docstring in order to document the version of the project which deprecates this functionality in your library. :param str reason: Reason message which documents the deprecation in your library (can be omitted). :param str version: Version of your project which deprecates this feature. If you follow the `Semantic Versioning `_, the version number has the format "MAJOR.MINOR.PATCH". :type line_length: int :param line_length: Max line length of the section item. If non-zero, a long text is wrapped in several lines. Keyword arguments can be: - "action": A warning filter used to activate or not the deprecation warning. Can be one of "error", "ignore", "always", "default", "module", or "once". If ``None``, empty or missing, the global filtering mechanism is used. - "category": The warning category to use for the deprecation warning. By default, the category class is :class:`~DeprecationWarning`, you can inherit this class to define your own deprecation warning category. - "extra_stacklevel": Number of additional stack levels to consider instrumentation rather than user code. With the default value of 0, the warning refers to where the class was instantiated or the function was called. :return: a decorator used to deprecate a function. """ section = kwargs.pop("section", "deprecated") adapter_cls = kwargs.pop("adapter_cls", GoogleAdapter) kwargs["reason"] = reason kwargs["version"] = version kwargs["line_length"] = line_length return _classic_deprecated(section=section, adapter_cls=adapter_cls, **kwargs) deprecated-3.0.1/src/deprecated/numpy.py000066400000000000000000000326301527107537600202450ustar00rootroot00000000000000""" NumPy docstring integration =========================== We usually need to document the life-cycle of functions and classes: when they are created, modified or deprecated. The `numpydoc docstring guide`_ organizes a docstring in sections: a section starts with a header underlined with hyphens (like ``Parameters`` or ``Returns``). The purpose of this module is to define decorators which add the following sections to the docstring of your functions and classes: - ``Version added``: to document the version of the project which added the described feature to the library, - ``Version changed``: to document changes of a feature, - ``Deprecated``: to document a deprecated feature. Each item of these sections is made of the version, followed by the indented reason message, like the items of the ``Raises`` section. If the section already exists in the docstring (for instance when the ``@versionchanged`` decorator is used several times), the item is appended to it. Of course, the ``@deprecated`` decorator will emit a deprecation warning when the function/method is called or the class is constructed. .. note:: The ``Deprecated`` section is understood by `Griffe`_ (used by *mkdocstrings*). `Napoleon`_ doesn't know these sections: to render them with Sphinx, declare them with ``napoleon_custom_sections = ["Version added", "Version changed", "Deprecated"]`` in your :file:`conf.py`. .. _numpydoc docstring guide: https://numpydoc.readthedocs.io/en/latest/format.html .. _Griffe: https://mkdocstrings.github.io/griffe/ .. _Napoleon: https://www.sphinx-doc.org/en/master/usage/extensions/napoleon.html .. versionadded:: 3.0.0 """ import re import textwrap from collections.abc import Callable from typing import Any from typing import Literal from deprecated.classic import ClassicAdapter from deprecated.classic import Deprecatable from deprecated.classic import WarningAction from deprecated.classic import deprecated as _classic_deprecated #: Life-cycle section inserted in the docstring. type NumpySection = Literal["versionadded", "versionchanged", "deprecated"] #: Header of each life-cycle section. SECTION_HEADERS: dict[NumpySection, str] = { "versionadded": "Version added", "versionchanged": "Version changed", "deprecated": "Deprecated", } #: Indentation of the reason message in a section item. _INDENT = " " * 4 #: Underline of a section header. _UNDERLINE_REGEX = re.compile(r"^-{3,}\s*$") class NumpyAdapter(ClassicAdapter): """ NumPy docstring adapter -- *for advanced usage only* This adapter overrides the :class:`~deprecated.classic.ClassicAdapter` in order to add a life-cycle section to the function/class docstring, using the `numpydoc docstring guide`_ format. - The section can be one of "versionadded", "versionchanged" or "deprecated". - The section item starts with the version number, followed by the indented reason message if not empty. - The item is appended to the section if it already exists, otherwise the section is added at the end of the docstring. """ def __init__( self, section: NumpySection, reason: str | None = "", version: str | None = "", action: WarningAction | Literal[""] | None = None, category: type[Warning] = DeprecationWarning, extra_stacklevel: int = 0, line_length: int = 70, ) -> None: """ Construct a wrapper adapter. :type section: str :param section: Life-cycle section: can be one of "versionadded", "versionchanged" or "deprecated". :type reason: str | None :param reason: Reason message which documents the deprecation in your library (can be omitted). :type version: str :param version: Version of your project which deprecates this feature. If you follow the `Semantic Versioning `_, the version number has the format "MAJOR.MINOR.PATCH". :type action: Literal["default", "error", "ignore", "always", "module", "once"] :param action: A warning filter used to activate or not the deprecation warning. Can be one of "error", "ignore", "always", "default", "module", or "once". If ``None`` or empty, the global filtering mechanism is used. See: `The Warnings Filter`_ in the Python documentation. :type category: Type[Warning] :param category: The warning category to use for the deprecation warning. By default, the category class is :class:`~DeprecationWarning`, you can inherit this class to define your own deprecation warning category. :type extra_stacklevel: int :param extra_stacklevel: Number of additional stack levels to consider instrumentation rather than user code. With the default value of 0, the warning refers to where the class was instantiated or the function was called. :type line_length: int :param line_length: Max line length of the reason message (including its indentation). If non-zero, a long text is wrapped in several lines. """ if not version: raise ValueError("'version' argument is required in NumPy docstring sections") if section not in SECTION_HEADERS: raise ValueError(f"invalid NumPy docstring section: {section!r}") self.section = section self.line_length = line_length super().__init__( reason=reason, version=version, action=action, category=category, extra_stacklevel=extra_stacklevel, ) def get_section_item(self) -> list[str]: """ Build the lines of the section item: the version, followed by the indented reason message. :return: the lines of the section item, without line ending. """ item_lines = [self.version] width = self.line_length if self.line_length > len(_INDENT) else 2**16 reason = textwrap.dedent(self.reason).strip() for paragraph in reason.splitlines(): if paragraph: item_lines.extend( textwrap.fill( paragraph, width=width, initial_indent=_INDENT, subsequent_indent=_INDENT, ).splitlines() ) else: item_lines.append("") return item_lines def __call__[T: Deprecatable](self, wrapped: T) -> T: """ Add the NumPy docstring section to your class or function. :param wrapped: Wrapped class or function. :return: the decorated class or function. """ wrapped.__doc__ = add_section_item( wrapped.__doc__, SECTION_HEADERS[self.section], self.get_section_item(), ) if self.section in {"versionadded", "versionchanged"}: return wrapped return super().__call__(wrapped) def _is_section_header(doc_lines: list[str], index: int) -> bool: """Check if the line at *index* is a section header: a text underlined with hyphens.""" return ( index + 1 < len(doc_lines) and bool(doc_lines[index].strip()) and not doc_lines[index][0].isspace() and _UNDERLINE_REGEX.match(doc_lines[index + 1]) is not None ) def add_section_item(docstring: str | None, header: str, item_lines: list[str]) -> str: """ Add an item to a section of a NumPy docstring. The docstring is normalized first: the lines following the summary line are dedented, so that the section headers are not indented. - If the section already exists, the item is appended to the end of the section. - Otherwise, the section is added to the end of the docstring, separated from the previous text by an empty line. :param docstring: The docstring to update (can be ``None`` or empty). :param header: The section header. :param item_lines: The lines of the section item, without line ending. :return: the updated docstring. """ # -- get the docstring, normalize the trailing newlines # keep a consistent behaviour if the docstring starts with newline # or directly on the first one lines = (docstring or "").splitlines(True) or [""] docstring = lines[0] + (textwrap.dedent("".join(lines[1:])) if len(lines) > 1 else "") doc_lines = docstring.rstrip().splitlines() # -- search the section, at the first level (not indented) start = next( ( i for i, line in enumerate(doc_lines) if line.rstrip() == header and _is_section_header(doc_lines, i) ), None, ) if start is None: # An empty line must separate the original docstring and the new section. if any(line.strip() for line in doc_lines): doc_lines.append("") doc_lines.append(header) doc_lines.append("-" * len(header)) doc_lines.extend(item_lines) else: # The section ends before the next section header (or at the end). end = next( (i for i in range(start + 2, len(doc_lines)) if _is_section_header(doc_lines, i)), len(doc_lines), ) while end > start + 2 and not doc_lines[end - 1].strip(): end -= 1 doc_lines[end:end] = item_lines return "".join(f"{line}\n" for line in doc_lines) def versionadded( reason: str | None = "", version: str | None = "", line_length: int = 70 ) -> NumpyAdapter: """ This decorator can be used to insert a "Version added" section in your function/class docstring in order to document the version of the project which adds this new functionality in your library. :param str reason: Reason message which documents the addition in your library (can be omitted). :param str version: Version of your project which adds this feature. If you follow the `Semantic Versioning `_, the version number has the format "MAJOR.MINOR.PATCH", and, in the case of a new functionality, the "PATCH" component should be "0". :type line_length: int :param line_length: Max line length of the reason message. If non-zero, a long text is wrapped in several lines. :return: the decorated function. """ return NumpyAdapter( "versionadded", reason=reason, version=version, line_length=line_length, ) def versionchanged( reason: str | None = "", version: str | None = "", line_length: int = 70 ) -> NumpyAdapter: """ This decorator can be used to insert a "Version changed" section in your function/class docstring in order to document the version of the project which modifies this functionality in your library. :param str reason: Reason message which documents the modification in your library (can be omitted). :param str version: Version of your project which modifies this feature. If you follow the `Semantic Versioning `_, the version number has the format "MAJOR.MINOR.PATCH". :type line_length: int :param line_length: Max line length of the reason message. If non-zero, a long text is wrapped in several lines. :return: the decorated function. """ return NumpyAdapter( "versionchanged", reason=reason, version=version, line_length=line_length, ) def deprecated[T: Deprecatable]( reason: str | None = "", version: str | None = "", line_length: int = 70, **kwargs: Any, ) -> Callable[[T], T]: """ This decorator can be used to insert a "Deprecated" section in your function/class docstring in order to document the version of the project which deprecates this functionality in your library. :param str reason: Reason message which documents the deprecation in your library (can be omitted). :param str version: Version of your project which deprecates this feature. If you follow the `Semantic Versioning `_, the version number has the format "MAJOR.MINOR.PATCH". :type line_length: int :param line_length: Max line length of the reason message. If non-zero, a long text is wrapped in several lines. Keyword arguments can be: - "action": A warning filter used to activate or not the deprecation warning. Can be one of "error", "ignore", "always", "default", "module", or "once". If ``None``, empty or missing, the global filtering mechanism is used. - "category": The warning category to use for the deprecation warning. By default, the category class is :class:`~DeprecationWarning`, you can inherit this class to define your own deprecation warning category. - "extra_stacklevel": Number of additional stack levels to consider instrumentation rather than user code. With the default value of 0, the warning refers to where the class was instantiated or the function was called. :return: a decorator used to deprecate a function. """ section = kwargs.pop("section", "deprecated") adapter_cls = kwargs.pop("adapter_cls", NumpyAdapter) kwargs["reason"] = reason kwargs["version"] = version kwargs["line_length"] = line_length return _classic_deprecated(section=section, adapter_cls=adapter_cls, **kwargs) deprecated-3.0.1/src/deprecated/params.py000066400000000000000000000110531527107537600203540ustar00rootroot00000000000000""" Parameters deprecation ====================== .. _Tantale's Blog: https://tantale.github.io/ .. _Deprecated Parameters: https://tantale.github.io/articles/deprecated_params/ This module introduces a :class:`deprecated_params` decorator to specify that one (or more) parameter(s) are deprecated: when the user executes a function with a deprecated parameter, they will see a warning message in the console. The decorator is customizable, the user can specify the deprecated parameter names and associate to each of them a message providing the reason of the deprecation. As with the :func:`~deprecated.classic.deprecated` decorator, the user can specify the warning message category (a subclass of :class:`Warning`). The complete study concerning the implementation of this decorator is available on the `Tantale's blog`_, on the `Deprecated Parameters`_ page. """ import dataclasses import functools import inspect import warnings from collections.abc import Callable from typing import Any from deprecated.classic import SKIP_FILE_PREFIXES #: Attribute used to mark the wrappers created by :class:`DeprecatedParams`. _STACK_ATTR = "__deprecated_params__" @dataclasses.dataclass(frozen=True) class _DecoratorStack: """Stacked :class:`DeprecatedParams` decorators (outermost first) of a function.""" wrapper: Callable[..., Any] decorators: tuple["DeprecatedParams", ...] target: Callable[..., Any] def _get_stack(func: Callable[..., Any]) -> _DecoratorStack | None: # The attribute may have been copied by another decorator (with `functools.wraps`): # only the wrapper created by `DeprecatedParams` can be merged. stack = getattr(func, _STACK_ATTR, None) if isinstance(stack, _DecoratorStack) and stack.wrapper is func: return stack return None class DeprecatedParams: """ Decorator used to decorate a function which at least one of the parameters is deprecated. .. versionchanged:: 3.0.0 When several ``@deprecated_params`` decorators are stacked, only one warning is emitted for each deprecated parameter: the one of the outermost decorator. """ def __init__( self, param: str | dict[str, str], reason: str | None = "", category: type[Warning] = DeprecationWarning, ) -> None: self.messages: dict[str, str] = {} self.category = category self.populate_messages(param, reason=reason) def populate_messages(self, param: str | dict[str, str], reason: str | None = "") -> None: if isinstance(param, dict): self.messages.update(param) elif isinstance(param, str): self.messages[param] = reason or f"'{param}' parameter is deprecated" else: raise TypeError(param) def check_params( self, signature: inspect.Signature, *args: object, **kwargs: object, ) -> list[str]: binding = signature.bind(*args, **kwargs) bound = {**binding.arguments, **binding.kwargs} return [param for param in bound if param in self.messages] def warn_messages(self, messages: list[str]) -> None: # The warnings refer to the first frame outside of this library. for message in messages: warnings.warn( message, category=self.category, stacklevel=2, skip_file_prefixes=SKIP_FILE_PREFIXES, ) def __call__[**P, R](self, f: Callable[P, R]) -> Callable[P, R]: # Merge with an inner `@deprecated_params` decorator, if any, # to emit a single warning for each deprecated parameter. inner = _get_stack(f) decorators = (self, *inner.decorators) if inner else (self,) target: Callable[P, R] = inner.target if inner else f signature = inspect.signature(target) @functools.wraps(f) def wrapper(*args: P.args, **kwargs: P.kwargs) -> R: warned: set[str] = set() for decorator in decorators: params = [ param for param in decorator.check_params(signature, *args, **kwargs) if param not in warned ] warned.update(params) decorator.warn_messages([decorator.messages[param] for param in params]) return target(*args, **kwargs) setattr(wrapper, _STACK_ATTR, _DecoratorStack(wrapper, decorators, target)) return wrapper #: Decorator used to decorate a function which at least one #: of the parameters is deprecated. deprecated_params = DeprecatedParams deprecated-3.0.1/src/deprecated/py.typed000066400000000000000000000000001527107537600202040ustar00rootroot00000000000000deprecated-3.0.1/src/deprecated/sphinx.py000066400000000000000000000272341527107537600204120ustar00rootroot00000000000000""" Sphinx directive integration ============================ We usually need to document the life-cycle of functions and classes: when they are created, modified or deprecated. To do that, `Sphinx `_ has a set of `Paragraph-level markups `_: - ``versionadded``: to document the version of the project which added the described feature to the library, - ``versionchanged``: to document changes of a feature, - ``deprecated``: to document a deprecated feature. The purpose of this module is to define decorators which add these Sphinx directives to the docstring of your functions and classes. Of course, the ``@deprecated`` decorator will emit a deprecation warning when the function/method is called or the class is constructed. """ import re import textwrap from collections.abc import Callable from typing import Any from typing import Literal from deprecated.classic import ClassicAdapter from deprecated.classic import Deprecatable from deprecated.classic import WarningAction from deprecated.classic import deprecated as _classic_deprecated #: Sphinx paragraph-level markup inserted in the docstring. type SphinxDirective = Literal["versionadded", "versionchanged", "deprecated"] class SphinxAdapter(ClassicAdapter): """ Sphinx adapter -- *for advanced usage only* This adapter overrides the :class:`~deprecated.classic.ClassicAdapter` in order to add the Sphinx directives to the end of the function/class docstring. Such a directive is a `Paragraph-level markup `_ - The directive can be one of "versionadded", "versionchanged" or "deprecated". - The version number is added if provided. - The reason message is obviously added in the directive block if not empty. """ def __init__( self, directive: SphinxDirective, reason: str | None = "", version: str | None = "", action: WarningAction | Literal[""] | None = None, category: type[Warning] = DeprecationWarning, extra_stacklevel: int = 0, line_length: int = 70, ) -> None: """ Construct a wrapper adapter. :type directive: str :param directive: Sphinx directive: can be one of "versionadded", "versionchanged" or "deprecated". :type reason: str | None :param reason: Reason message which documents the deprecation in your library (can be omitted). :type version: str :param version: Version of your project which deprecates this feature. If you follow the `Semantic Versioning `_, the version number has the format "MAJOR.MINOR.PATCH". :type action: Literal["default", "error", "ignore", "always", "module", "once"] :param action: A warning filter used to activate or not the deprecation warning. Can be one of "error", "ignore", "always", "default", "module", or "once". If ``None`` or empty, the global filtering mechanism is used. See: `The Warnings Filter`_ in the Python documentation. :type category: Type[Warning] :param category: The warning category to use for the deprecation warning. By default, the category class is :class:`~DeprecationWarning`, you can inherit this class to define your own deprecation warning category. :type extra_stacklevel: int :param extra_stacklevel: Number of additional stack levels to consider instrumentation rather than user code. With the default value of 0, the warning refers to where the class was instantiated or the function was called. :type line_length: int :param line_length: Max line length of the directive text. If non-zero, a long text is wrapped in several lines. .. versionchanged:: 1.2.15 Add the *extra_stacklevel* parameter. """ if not version: # https://github.com/laurent-laporte-pro/deprecated/issues/40 raise ValueError("'version' argument is required in Sphinx directives") self.directive = directive self.line_length = line_length super().__init__( reason=reason, version=version, action=action, category=category, extra_stacklevel=extra_stacklevel, ) def __call__[T: Deprecatable](self, wrapped: T) -> T: """ Add the Sphinx directive to your class or function. :param wrapped: Wrapped class or function. :return: the decorated class or function. """ # -- build the directive division fmt = ".. {directive}:: {version}" if self.version else ".. {directive}::" div_lines = [fmt.format(directive=self.directive, version=self.version)] width = self.line_length - 3 if self.line_length > 3 else 2**16 reason = textwrap.dedent(self.reason).strip() for paragraph in reason.splitlines(): if paragraph: div_lines.extend( textwrap.fill( paragraph, width=width, initial_indent=" ", subsequent_indent=" ", ).splitlines() ) else: div_lines.append("") # -- get the docstring, normalize the trailing newlines # keep a consistent behaviour if the docstring starts with newline # or directly on the first one docstring = wrapped.__doc__ or "" lines = docstring.splitlines(True) or [""] docstring = textwrap.dedent("".join(lines[1:])) if len(lines) > 1 else "" docstring = lines[0] + docstring if docstring: # An empty line must separate the original docstring and the directive. docstring = re.sub(r"\n+$", "", docstring, flags=re.DOTALL) + "\n\n" else: # Avoid "Explicit markup ends without a blank line" # when the decorated function has no docstring docstring = "\n" # -- append the directive division to the docstring docstring += "".join(f"{line}\n" for line in div_lines) wrapped.__doc__ = docstring if self.directive in {"versionadded", "versionchanged"}: return wrapped return super().__call__(wrapped) def get_deprecated_msg(self, wrapped: Deprecatable, instance: object | None) -> str: """ Get the deprecation warning message (without Sphinx cross-referencing syntax) for the user. :param wrapped: Wrapped class or function. :param instance: The object to which the wrapped function was bound when it was called. :return: The warning message. .. versionadded:: 1.2.12 Strip Sphinx cross-referencing syntax from warning message. """ msg = super().get_deprecated_msg(wrapped, instance) # Strip Sphinx cross-reference syntax (like ":function:", ":py:func:" and ":py:meth:") # Possible values are ":role:`foo`", ":domain:role:`foo`" # where ``role`` and ``domain`` should match "[a-zA-Z]+" msg = re.sub(r"(?: : [a-zA-Z]+ )? : [a-zA-Z]+ : (`[^`]*`)", r"\1", msg, flags=re.X) return msg def versionadded( reason: str | None = "", version: str | None = "", line_length: int = 70 ) -> SphinxAdapter: """ This decorator can be used to insert a "versionadded" directive in your function/class docstring in order to document the version of the project which adds this new functionality in your library. :param str reason: Reason message which documents the addition in your library (can be omitted). :param str version: Version of your project which adds this feature. If you follow the `Semantic Versioning `_, the version number has the format "MAJOR.MINOR.PATCH", and, in the case of a new functionality, the "PATCH" component should be "0". :type line_length: int :param line_length: Max line length of the directive text. If non-zero, a long text is wrapped in several lines. :return: the decorated function. """ adapter = SphinxAdapter( "versionadded", reason=reason, version=version, line_length=line_length, ) return adapter def versionchanged( reason: str | None = "", version: str | None = "", line_length: int = 70 ) -> SphinxAdapter: """ This decorator can be used to insert a "versionchanged" directive in your function/class docstring in order to document the version of the project which modifies this functionality in your library. :param str reason: Reason message which documents the modification in your library (can be omitted). :param str version: Version of your project which modifies this feature. If you follow the `Semantic Versioning `_, the version number has the format "MAJOR.MINOR.PATCH". :type line_length: int :param line_length: Max line length of the directive text. If non-zero, a long text is wrapped in several lines. :return: the decorated function. """ adapter = SphinxAdapter( "versionchanged", reason=reason, version=version, line_length=line_length, ) return adapter def deprecated[T: Deprecatable]( reason: str | None = "", version: str | None = "", line_length: int = 70, **kwargs: Any, ) -> Callable[[T], T]: """ This decorator can be used to insert a "deprecated" directive in your function/class docstring in order to document the version of the project which deprecates this functionality in your library. :param str reason: Reason message which documents the deprecation in your library (can be omitted). :param str version: Version of your project which deprecates this feature. If you follow the `Semantic Versioning `_, the version number has the format "MAJOR.MINOR.PATCH". :type line_length: int :param line_length: Max line length of the directive text. If non-zero, a long text is wrapped in several lines. Keyword arguments can be: - "action": A warning filter used to activate or not the deprecation warning. Can be one of "error", "ignore", "always", "default", "module", or "once". If ``None``, empty or missing, the global filtering mechanism is used. - "category": The warning category to use for the deprecation warning. By default, the category class is :class:`~DeprecationWarning`, you can inherit this class to define your own deprecation warning category. - "extra_stacklevel": Number of additional stack levels to consider instrumentation rather than user code. With the default value of 0, the warning refers to where the class was instantiated or the function was called. :return: a decorator used to deprecate a function. .. versionchanged:: 1.2.13 Change the signature of the decorator to reflect the valid use cases. .. versionchanged:: 1.2.15 Add the *extra_stacklevel* parameter. """ directive = kwargs.pop("directive", "deprecated") adapter_cls = kwargs.pop("adapter_cls", SphinxAdapter) kwargs["reason"] = reason kwargs["version"] = version kwargs["line_length"] = line_length return _classic_deprecated(directive=directive, adapter_cls=adapter_cls, **kwargs) deprecated-3.0.1/tests/000077500000000000000000000000001527107537600147725ustar00rootroot00000000000000deprecated-3.0.1/tests/__init__.py000066400000000000000000000000001527107537600170710ustar00rootroot00000000000000deprecated-3.0.1/tests/deprecated_params/000077500000000000000000000000001527107537600204355ustar00rootroot00000000000000deprecated-3.0.1/tests/deprecated_params/__init__.py000066400000000000000000000000001527107537600225340ustar00rootroot00000000000000deprecated-3.0.1/tests/deprecated_params/test_demo_area.py000066400000000000000000000034421527107537600237650ustar00rootroot00000000000000""" This example shows how to implement a function that accepts two positional arguments or two keyword arguments. A warning message should be emitted if `x` and `y` are used instead of `width` and `height`. """ import warnings import pytest from deprecated.params import deprecated_params @deprecated_params( { "x": "use `width` instead or `x`", "y": "use `height` instead or `y`", }, ) def area(*args, **kwargs): def _area_impl(width, height): return width * height if args: # positional arguments (no checking) return _area_impl(*args) elif set(kwargs) == {"width", "height"}: # nominal case: no warning return _area_impl(kwargs["width"], kwargs["height"]) elif set(kwargs) == {"x", "y"}: # old case: deprecation warning return _area_impl(kwargs["x"], kwargs["y"]) else: raise TypeError("invalid arguments") @pytest.mark.parametrize( ("args", "kwargs", "expected"), [ pytest.param((4, 6), {}, [], id="positional arguments: no warning"), pytest.param((), {"width": 3, "height": 6}, [], id="correct keyword arguments"), pytest.param( (), {"x": 2, "y": 7}, ["use `width` instead or `x`", "use `height` instead or `y`"], id="wrong keyword arguments", ), pytest.param( (), {"x": 2, "height": 7}, [], id="invalid arguments is raised", marks=pytest.mark.xfail(raises=TypeError, strict=True), ), ], ) def test_area(args, kwargs, expected): with warnings.catch_warnings(record=True) as warns: warnings.simplefilter("always") area(*args, **kwargs) actual = [str(warn.message) for warn in warns] assert actual == expected deprecated-3.0.1/tests/deprecated_params/test_demo_paragraph.py000066400000000000000000000026161527107537600250240ustar00rootroot00000000000000""" This example shows a function with a keyword-only parameter. A warning message should be emitted if `color` is used (as a positional or keyword parameter). """ import warnings import xml.sax.saxutils import pytest from deprecated.params import deprecated_params @deprecated_params("color", reason="you should use 'styles' instead of 'color'") def paragraph(text, *, color=None, styles=None): """Create a styled HTML paragraphe.""" styles = styles or {} if color: styles["color"] = color html_styles = " ".join(f"{k}: {v};" for k, v in styles.items()) html_text = xml.sax.saxutils.escape(text) return f'

{html_text}

' @pytest.mark.parametrize( ("args", "kwargs", "expected"), [ pytest.param(("Hello",), {}, [], id="'color' not used: no warnings"), pytest.param( ("Hello",), {"styles": {"color": "blue"}}, [], id="regular usage: no warnings" ), pytest.param( ("Hello",), {"color": "blue"}, ["you should use 'styles' instead of 'color'"], id="'color' used in keyword-argument", ), ], ) def test_paragraph(args, kwargs, expected): with warnings.catch_warnings(record=True) as warns: warnings.simplefilter("always") paragraph(*args, **kwargs) actual = [str(warn.message) for warn in warns] assert actual == expected deprecated-3.0.1/tests/deprecated_params/test_demo_pow2.py000066400000000000000000000021151527107537600237400ustar00rootroot00000000000000""" This example shows a function with an unused optional parameter. A warning message should be emitted if `z` is used (as a positional or keyword parameter). """ import warnings import pytest from deprecated.params import deprecated_params @deprecated_params("z") def pow2(x, y, z=None, /): return x**y @pytest.mark.parametrize( ("args", "kwargs", "expected"), [ pytest.param((5, 6), {}, [], id="'z' not used: no warnings"), pytest.param( (5, 6, 8), {}, ["'z' parameter is deprecated"], id="'z' used in positional params", ), pytest.param( (5, 6), {"z": 8}, [], id="'z' is positional only: keyword usage is rejected", marks=pytest.mark.xfail(raises=TypeError, strict=True), ), ], ) def test_pow2(args, kwargs, expected): with warnings.catch_warnings(record=True) as warns: warnings.simplefilter("always") pow2(*args, **kwargs) actual = [str(warn.message) for warn in warns] assert actual == expected deprecated-3.0.1/tests/deprecated_params/test_demo_versions.py000066400000000000000000000062561527107537600247330ustar00rootroot00000000000000""" This example shows a function with an unused optional parameter. A warning message should be emitted if `z` is used (as a positional or keyword parameter). """ import functools import warnings from deprecated.params import deprecated_params class V2DeprecationWarning(DeprecationWarning): pass # noinspection PyUnusedLocal @deprecated_params( { "epsilon": "epsilon is deprecated in version v2", "start": "start is removed in version v2", }, category=V2DeprecationWarning, ) @deprecated_params("epsilon", reason="epsilon is deprecated in version v1.1") def integrate(f, a, b, n=0, epsilon=0.0, start=None): epsilon = epsilon or (b - a) / n n = n or int((b - a) / epsilon) return sum( (f(a + (i * epsilon)) + f(a + (i * epsilon) + epsilon)) * epsilon / 2 for i in range(n) ) def test_only_one_warning_for_each_parameter(): """ This unit test checks that only one warning message is emitted for each deprecated parameter: the message of the outermost decorator wins (here, the `epsilon` message of version v2). """ with warnings.catch_warnings(record=True) as warns: warnings.simplefilter("always") integrate(lambda x: x**2, 0, 2, epsilon=0.0012, start=123) actual = [{"message": str(w.message), "category": w.category} for w in warns] assert actual == [ {"category": V2DeprecationWarning, "message": "epsilon is deprecated in version v2"}, {"category": V2DeprecationWarning, "message": "start is removed in version v2"}, ] # The warnings refer to the caller, not to the library. assert {w.filename for w in warns} == {__file__} def test_inner_decorator_messages_are_kept(): # A parameter only deprecated by the inner decorator is still reported. with warnings.catch_warnings(record=True) as warns: warnings.simplefilter("always") integrate(lambda x: x**2, 0, 2, n=10) assert warns == [] @deprecated_params("b", reason="outer b", category=V2DeprecationWarning) @deprecated_params({"a": "inner a", "b": "inner b"}) def combine(a=None, b=None, c=None): return a, b, c def test_stacked_decorators_are_merged(): with warnings.catch_warnings(record=True) as warns: warnings.simplefilter("always") assert combine(a=1, b=2, c=3) == (1, 2, 3) assert [(str(w.message), w.category) for w in warns] == [ ("outer b", V2DeprecationWarning), ("inner a", DeprecationWarning), ] assert combine.__name__ == "combine" def test_other_decorator_between_deprecated_params_is_called(): calls = [] def spy(func): @functools.wraps(func) # copies the marker attribute of the inner wrapper def spy_wrapper(*args, **kwargs): calls.append(kwargs) return func(*args, **kwargs) return spy_wrapper @deprecated_params("b", reason="outer b") @spy @deprecated_params("a", reason="inner a") def foo(a=None, b=None): return a, b with warnings.catch_warnings(record=True) as warns: warnings.simplefilter("always") assert foo(a=1, b=2) == (1, 2) assert calls == [{"a": 1, "b": 2}] assert [str(w.message) for w in warns] == ["outer b", "inner a"] deprecated-3.0.1/tests/test.py000066400000000000000000000015401527107537600163230ustar00rootroot00000000000000import importlib.metadata from packaging.version import Version import deprecated def test_deprecated_has_docstring(): # The deprecated package must have a docstring assert deprecated.__doc__ is not None assert "Deprecated Library" in deprecated.__doc__ def test_deprecated_has_version(): # The deprecated package must have a valid (PEP 440) version number: # `packaging.version.Version` raises `InvalidVersion` otherwise. assert deprecated.__version__ is not None version = Version(deprecated.__version__) assert str(version) == deprecated.__version__ def test_deprecated_version_matches_distribution_metadata(): # The version is read by Hatch from `src/deprecated/__init__.py`: # the installed distribution metadata must be consistent. assert importlib.metadata.version("Deprecated") == deprecated.__version__ deprecated-3.0.1/tests/test_deprecated.py000066400000000000000000000237131527107537600205110ustar00rootroot00000000000000import inspect import sys import warnings from typing import Any from typing import cast import pytest import deprecated.classic class MyDeprecationWarning(DeprecationWarning): pass class WrongStackLevelWarning(DeprecationWarning): pass _PARAMS = [ None, ((), {}), (("Good reason",), {}), ((), {"reason": "Good reason"}), ((), {"version": "1.2.3"}), ((), {"action": "once"}), ((), {"category": MyDeprecationWarning}), ((), {"extra_stacklevel": 1, "category": WrongStackLevelWarning}), ] @pytest.fixture(scope="module", params=_PARAMS) def classic_deprecated_function(request): if request.param is None: @deprecated.classic.deprecated def foo1(): pass return foo1 else: args, kwargs = request.param @deprecated.classic.deprecated(*args, **kwargs) def foo1(): pass return foo1 @pytest.fixture(scope="module", params=_PARAMS) def classic_deprecated_class(request): if request.param is None: @deprecated.classic.deprecated class Foo2: pass return Foo2 else: args, kwargs = request.param @deprecated.classic.deprecated(*args, **kwargs) class Foo2: pass return Foo2 @pytest.fixture(scope="module", params=_PARAMS) def classic_deprecated_method(request): if request.param is None: class Foo3: @deprecated.classic.deprecated def foo3(self): pass return Foo3 else: args, kwargs = request.param class Foo3: @deprecated.classic.deprecated(*args, **kwargs) def foo3(self): pass return Foo3 @pytest.fixture(scope="module", params=_PARAMS) def classic_deprecated_static_method(request): if request.param is None: class Foo4: @staticmethod @deprecated.classic.deprecated def foo4(): pass return Foo4.foo4 else: args, kwargs = request.param class Foo4: @staticmethod @deprecated.classic.deprecated(*args, **kwargs) def foo4(): pass return Foo4.foo4 @pytest.fixture(scope="module", params=_PARAMS) def classic_deprecated_class_method(request): if request.param is None: class Foo5: @classmethod @deprecated.classic.deprecated def foo5(cls): pass return Foo5 else: args, kwargs = request.param class Foo5: @classmethod @deprecated.classic.deprecated(*args, **kwargs) def foo5(cls): pass return Foo5 # noinspection PyShadowingNames def test_classic_deprecated_function__warns(classic_deprecated_function): with warnings.catch_warnings(record=True) as warns: warnings.simplefilter("always") classic_deprecated_function() assert len(warns) == 1 warn = warns[0] assert issubclass(warn.category, DeprecationWarning) assert "deprecated function (or staticmethod)" in str(warn.message) assert warn.filename == __file__ or warn.category is WrongStackLevelWarning, ( "Incorrect warning stackLevel" ) # noinspection PyShadowingNames def test_classic_deprecated_class__warns(classic_deprecated_class): with warnings.catch_warnings(record=True) as warns: warnings.simplefilter("always") classic_deprecated_class() assert len(warns) == 1 warn = warns[0] assert issubclass(warn.category, DeprecationWarning) assert "deprecated class" in str(warn.message) assert warn.filename == __file__ or warn.category is WrongStackLevelWarning, ( "Incorrect warning stackLevel" ) # noinspection PyShadowingNames def test_classic_deprecated_method__warns(classic_deprecated_method): with warnings.catch_warnings(record=True) as warns: warnings.simplefilter("always") obj = classic_deprecated_method() obj.foo3() assert len(warns) == 1 warn = warns[0] assert issubclass(warn.category, DeprecationWarning) assert "deprecated method" in str(warn.message) assert warn.filename == __file__ or warn.category is WrongStackLevelWarning, ( "Incorrect warning stackLevel" ) # noinspection PyShadowingNames def test_classic_deprecated_static_method__warns(classic_deprecated_static_method): with warnings.catch_warnings(record=True) as warns: warnings.simplefilter("always") classic_deprecated_static_method() assert len(warns) == 1 warn = warns[0] assert issubclass(warn.category, DeprecationWarning) assert "deprecated function (or staticmethod)" in str(warn.message) assert warn.filename == __file__ or warn.category is WrongStackLevelWarning, ( "Incorrect warning stackLevel" ) # noinspection PyShadowingNames def test_classic_deprecated_class_method__warns(classic_deprecated_class_method): with warnings.catch_warnings(record=True) as warns: warnings.simplefilter("always") cls = classic_deprecated_class_method() cls.foo5() assert len(warns) == 1 warn = warns[0] assert issubclass(warn.category, DeprecationWarning) if sys.version_info < (3, 13): assert "deprecated class method" in str(warn.message) else: assert "deprecated function (or staticmethod)" in str(warn.message) assert warn.filename == __file__ or warn.category is WrongStackLevelWarning, ( "Incorrect warning stackLevel" ) def test_should_raise_type_error(): with pytest.raises(TypeError): # A non-callable argument (deliberately ill-typed) is rejected at runtime. deprecated.classic.deprecated(cast(Any, 5)) def test_warning_msg_has_reason(): reason = "Good reason" @deprecated.classic.deprecated(reason=reason) def foo(): pass with warnings.catch_warnings(record=True) as warns: foo() warn = warns[0] assert reason in str(warn.message) def test_warning_msg_has_version(): version = "1.2.3" @deprecated.classic.deprecated(version=version) def foo(): pass with warnings.catch_warnings(record=True) as warns: foo() warn = warns[0] assert version in str(warn.message) def test_warning_is_ignored(): @deprecated.classic.deprecated(action="ignore") def foo(): pass with warnings.catch_warnings(record=True) as warns: foo() assert len(warns) == 0 def test_specific_warning_cls_is_used(): @deprecated.classic.deprecated(category=MyDeprecationWarning) def foo(): pass with warnings.catch_warnings(record=True) as warns: foo() warn = warns[0] assert issubclass(warn.category, MyDeprecationWarning) def test_respect_global_filter(): @deprecated.classic.deprecated(version="1.2.1", reason="deprecated function") def fun(): print("fun") warnings.simplefilter("once", category=DeprecationWarning) with warnings.catch_warnings(record=True) as warns: fun() fun() assert len(warns) == 1 def test_default_stacklevel(): """ The objective of this unit test is to ensure that the triggered warning message, when invoking the 'use_foo' function, correctly indicates the line where the deprecated 'foo' function is called. """ @deprecated.classic.deprecated def foo(): pass def use_foo(): foo() with warnings.catch_warnings(record=True) as warns: warnings.simplefilter("always") use_foo() # Check that the warning path matches the module path warn = warns[0] assert warn.filename == __file__ # Check that the line number points to the first line inside 'use_foo' use_foo_lineno = inspect.getsourcelines(use_foo)[1] assert warn.lineno == use_foo_lineno + 1 def test_extra_stacklevel(): """ The unit test utilizes an 'extra_stacklevel' of 1 to ensure that the warning message accurately identifies the caller of the deprecated function. It verifies that when the 'use_foo' function is called, the warning message correctly indicates the line where the call to 'use_foo' is made. """ @deprecated.classic.deprecated(extra_stacklevel=1) def foo(): pass def use_foo(): foo() def demo(): use_foo() with warnings.catch_warnings(record=True) as warns: warnings.simplefilter("always") demo() # Check that the warning path matches the module path warn = warns[0] assert warn.filename == __file__ # Check that the line number points to the first line inside 'demo' demo_lineno = inspect.getsourcelines(demo)[1] assert warn.lineno == demo_lineno + 1 def test_nested_deprecated_classes_refer_to_the_caller(): """ When a deprecated class inherits from a deprecated class, both warnings must refer to the line where the subclass is instantiated (not to the library). """ @deprecated.classic.deprecated(reason="base") class Base: pass @deprecated.classic.deprecated(reason="child") class Child(Base): pass def use_child(): return Child() with warnings.catch_warnings(record=True) as warns: warnings.simplefilter("always") use_child() use_child_lineno = inspect.getsourcelines(use_child)[1] assert [(w.filename, w.lineno) for w in warns] == [(__file__, use_child_lineno + 1)] * 2 def test_extra_stacklevel_with_nested_deprecated_classes(): @deprecated.classic.deprecated(reason="base", extra_stacklevel=1) class Base: pass @deprecated.classic.deprecated(reason="child", extra_stacklevel=1) class Child(Base): pass def factory(): return Child() def demo(): factory() with warnings.catch_warnings(record=True) as warns: warnings.simplefilter("always") demo() demo_lineno = inspect.getsourcelines(demo)[1] assert [(w.filename, w.lineno) for w in warns] == [(__file__, demo_lineno + 1)] * 2 deprecated-3.0.1/tests/test_deprecated_class.py000066400000000000000000000165071527107537600217010ustar00rootroot00000000000000import inspect import io import warnings import pytest import deprecated.classic import deprecated.google import deprecated.numpy import deprecated.sphinx @pytest.mark.parametrize( "decorator", [ deprecated.classic.deprecated, deprecated.sphinx.deprecated, deprecated.google.deprecated, deprecated.numpy.deprecated, ], ) @pytest.mark.parametrize("custom_new", [False, True]) def test_deprecated_class_preserves_signature(decorator, custom_new): class WithInit: def __init__(self, value: int = 1, *, label: str = "default"): self.value = value self.label = label class WithNew: value: int label: str def __new__(cls, value: int = 1, *, label: str = "default"): obj = super().__new__(cls) obj.value = value obj.label = label return obj original = WithNew if custom_new else WithInit signature = inspect.signature(original) decorated = decorator(reason="Use a replacement", version="1.0")(original) assert decorated is original assert inspect.signature(decorated) == signature with pytest.warns(DeprecationWarning, match="Use a replacement"): instance = decorated(2, label="custom") assert (instance.value, instance.label) == (2, "custom") @pytest.mark.parametrize("builtin", [False, True]) def test_deprecated_class_preserves_empty_and_builtin_signatures(builtin): class Empty: pass class List(list[int]): pass original = List if builtin else Empty signature = inspect.signature(original) decorated = deprecated.classic.deprecated(original) assert inspect.signature(decorated) == signature with pytest.warns(DeprecationWarning, match="Call to deprecated class"): assert isinstance(decorated(), original) def test_deprecated_class_preserves_constructor_parameter_named_cls(): class Target: def __init__(self, cls: int = 1): self.value = cls signature = inspect.signature(Target) decorated = deprecated.classic.deprecated(Target) assert inspect.signature(decorated) == signature with pytest.warns(DeprecationWarning, match="Call to deprecated class Target"): assert decorated(2).value == 2 def test_deprecated_class_with_unavailable_signature(): class Target(dict[str, int]): pass with pytest.raises(ValueError, match="signature"): inspect.signature(Target) decorated = deprecated.classic.deprecated(Target) with pytest.warns(DeprecationWarning, match="Call to deprecated class Target"): assert decorated(value=2) == {"value": 2} def test_deprecated_class_preserves_subclass_signature(): @deprecated.classic.deprecated class Base: def __init__(self, base_value: int = 1): self.base_value = base_value class Subclass(Base): def __init__(self, text: str, *, flag: bool = False): self.text = text self.flag = flag assert str(inspect.signature(Subclass)) == "(text: str, *, flag: bool = False)" with pytest.warns(DeprecationWarning, match="Call to deprecated class Base"): instance = Subclass("value", flag=True) assert (instance.text, instance.flag) == ("value", True) def test_simple_class_deprecation(): # stream is used to store the deprecation message for testing stream = io.StringIO() # To deprecate a class, it is better to emit a message when ``__new__`` is called. # The simplest way is to override the ``__new__``method. class MyBaseClass: def __new__(cls, *args, **kwargs): print("I am deprecated!", file=stream) return super().__new__(cls, *args, **kwargs) # Of course, the subclass will be deprecated too class MySubClass(MyBaseClass): pass obj = MySubClass() assert isinstance(obj, MyBaseClass) assert inspect.isclass(MyBaseClass) assert stream.getvalue().strip() == "I am deprecated!" def test_class_deprecation_using_wrapper(): # stream is used to store the deprecation message for testing stream = io.StringIO() class MyBaseClass: pass # To deprecated the class, we use a wrapper function which emits # the deprecation message and calls ``__new__```. original_new = MyBaseClass.__new__ def wrapped_new(unused, *args, **kwargs): print("I am deprecated!", file=stream) return original_new(*args, **kwargs) # Like ``__new__``, this wrapper is a class method. # It is used to patch the original ``__new__``method. setattr(MyBaseClass, "__new__", classmethod(wrapped_new)) # noqa: B010 class MySubClass(MyBaseClass): pass obj = MySubClass() assert isinstance(obj, MyBaseClass) assert inspect.isclass(MyBaseClass) assert stream.getvalue().strip() == "I am deprecated!" def test_class_deprecation_using_a_simple_decorator(): # stream is used to store the deprecation message for testing stream = io.StringIO() # To deprecated the class, we use a simple decorator # which patches the original ``__new__`` method. def simple_decorator(wrapped_cls): old_new = wrapped_cls.__new__ def wrapped_new(unused, *args, **kwargs): print("I am deprecated!", file=stream) return old_new(*args, **kwargs) wrapped_cls.__new__ = classmethod(wrapped_new) return wrapped_cls @simple_decorator class MyBaseClass: pass class MySubClass(MyBaseClass): pass obj = MySubClass() assert isinstance(obj, MyBaseClass) assert inspect.isclass(MyBaseClass) assert stream.getvalue().strip() == "I am deprecated!" def test_class_deprecation_using_deprecated_decorator(): @deprecated.classic.deprecated class MyBaseClass: pass class MySubClass(MyBaseClass): pass with warnings.catch_warnings(record=True) as warns: warnings.simplefilter("always") obj = MySubClass() assert len(warns) == 1 assert isinstance(obj, MyBaseClass) assert inspect.isclass(MyBaseClass) assert issubclass(MySubClass, MyBaseClass) def test_class_respect_global_filter(): @deprecated.classic.deprecated class MyBaseClass: pass with warnings.catch_warnings(record=True) as warns: warnings.simplefilter("once") MyBaseClass() MyBaseClass() assert len(warns) == 1 def test_subclass_deprecation_using_deprecated_decorator(): @deprecated.classic.deprecated class MyBaseClass: pass @deprecated.classic.deprecated class MySubClass(MyBaseClass): pass with warnings.catch_warnings(record=True) as warns: warnings.simplefilter("always") obj = MySubClass() assert len(warns) == 2 assert isinstance(obj, MyBaseClass) assert inspect.isclass(MyBaseClass) assert issubclass(MySubClass, MyBaseClass) def test_simple_class_deprecation_with_args(): @deprecated.classic.deprecated("kwargs class") class MyClass: def __init__(self, arg): super().__init__() self.args = arg with pytest.warns(DeprecationWarning, match="kwargs class"): MyClass(5) with warnings.catch_warnings(record=True) as warns: warnings.simplefilter("always") obj = MyClass(5) assert len(warns) == 1 assert isinstance(obj, MyClass) assert inspect.isclass(MyClass) deprecated-3.0.1/tests/test_deprecated_metaclass.py000066400000000000000000000045631527107537600225470ustar00rootroot00000000000000import warnings from typing import ClassVar import deprecated.classic def test_with_init(): @deprecated.classic.deprecated class MyClass: def __init__(self, a, b=5): self.a = a self.b = b with warnings.catch_warnings(record=True) as warns: warnings.simplefilter("always") obj = MyClass("five") assert len(warns) == 1 assert obj.a == "five" assert obj.b == 5 def test_with_new(): @deprecated.classic.deprecated class MyClass: c: float def __new__(cls, a, b=5): obj = super().__new__(cls) obj.c = 3.14 return obj def __init__(self, a, b=5): self.a = a self.b = b with warnings.catch_warnings(record=True) as warns: warnings.simplefilter("always") obj = MyClass("five") assert len(warns) == 1 assert obj.a == "five" assert obj.b == 5 assert obj.c == 3.14 def test_with_metaclass(): class Meta(type): def __call__(cls, *args, **kwargs): obj = super().__call__(*args, **kwargs) obj.c = 3.14 return obj @deprecated.classic.deprecated class MyClass(metaclass=Meta): c: float def __init__(self, a, b=5): self.a = a self.b = b with warnings.catch_warnings(record=True) as warns: warnings.simplefilter("always") obj = MyClass("five") assert len(warns) == 1 assert obj.a == "five" assert obj.b == 5 assert obj.c == 3.14 def test_with_singleton_metaclass(): class Singleton(type): _instances: ClassVar[dict[type, object]] = {} def __call__(cls, *args, **kwargs): if cls not in cls._instances: cls._instances[cls] = super().__call__(*args, **kwargs) return cls._instances[cls] @deprecated.classic.deprecated class MyClass(metaclass=Singleton): def __init__(self, a, b=5): self.a = a self.b = b with warnings.catch_warnings(record=True) as warns: warnings.simplefilter("always") obj1 = MyClass("five") obj2 = MyClass("six", b=6) # __new__ is called only once: # the instance is constructed only once, # so we have only one warning. assert len(warns) == 1 assert obj1.a == "five" assert obj1.b == 5 assert obj2 is obj1 deprecated-3.0.1/tests/test_google.py000066400000000000000000000172431527107537600176660ustar00rootroot00000000000000import sys import textwrap import warnings import pytest import deprecated.google from deprecated.google import GoogleAdapter from deprecated.google import add_section_item class MyDeprecationWarning(DeprecationWarning): pass @pytest.fixture( scope="module", params=[ None, """This function adds *x* and *y*.""", """ This function adds *x* and *y*. Args: x: number *x* y: number *y* Returns: sum = *x* + *y* """, """This function adds *x* and *y*. Args: x: number *x* y: number *y* Returns: sum = *x* + *y* """, ], ids=["no_docstring", "short_docstring", "D213_long_docstring", "D212_long_docstring"], ) def docstring(request): return request.param @pytest.fixture(scope="module", params=["versionadded", "versionchanged", "deprecated"]) def section(request): return request.param HEADERS = { "versionadded": "Version added", "versionchanged": "Version changed", "deprecated": "Deprecated", } # noinspection PyShadowingNames @pytest.mark.parametrize( ("reason", "version", "expected"), [ ("A good reason", "1.2.0", "{header}:\n {version}: {reason}\n"), (None, "1.2.0", "{header}:\n {version}\n"), ], ids=["reason&version", "version"], ) def test_has_google_docstring(docstring, section, reason, version, expected): def foo(x, y): return x + y foo.__doc__ = docstring decorator_factory = getattr(deprecated.google, section) foo = decorator_factory(reason=reason, version=version)(foo) expected = expected.format(header=HEADERS[section], version=version, reason=reason) assert foo.__doc__ is not None current = textwrap.dedent(foo.__doc__) assert current.endswith(expected) current = current[: -len(expected)] if docstring: # An empty line must separate the original docstring and the section. assert current.endswith("\n\n") assert not current.endswith("\n\n\n") else: assert current == "" with warnings.catch_warnings(record=True) as warns: warnings.simplefilter("always") foo(1, 2) if section in {"versionadded", "versionchanged"}: assert len(warns) == 0 else: assert len(warns) == 1 assert issubclass(warns[0].category, DeprecationWarning) # noinspection PyShadowingNames def test_cls_has_google_docstring(docstring, section): class Foo: pass Foo.__doc__ = docstring decorator_factory = getattr(deprecated.google, section) decorated_cls = decorator_factory(reason="A good reason", version="1.2.0")(Foo) expected = f"{HEADERS[section]}:\n 1.2.0: A good reason\n" assert decorated_cls.__doc__ is not None assert textwrap.dedent(decorated_cls.__doc__).endswith(expected) with warnings.catch_warnings(record=True) as warns: warnings.simplefilter("always") decorated_cls() if section in {"versionadded", "versionchanged"}: assert len(warns) == 0 else: assert len(warns) == 1 assert str(warns[0].message) == ( "Call to deprecated class Foo. (A good reason) -- Deprecated since version 1.2.0." ) @pytest.mark.parametrize( ("line_length", "expected"), [ ( 50, textwrap.dedent( """ Description of foo Returns: nothing {header}: 1.2.3: foo has changed in this version bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar """ ), ), ( 0, textwrap.dedent( """ Description of foo Returns: nothing {header}: 1.2.3: foo has changed in this version bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar """ # noqa: E501 ), ), ], ids=["wrapped", "long"], ) def test_google_adapter(section, line_length, expected): reason = "\n".join(["foo has changed in this version", "", "bar " * 23, ""]) adapter = GoogleAdapter(section, reason=reason, version="1.2.3", line_length=line_length) def foo(): """ Description of foo Returns: nothing """ wrapped = adapter(foo) assert wrapped.__doc__ == expected.format(header=HEADERS[section]) def test_stacked_decorators(): @deprecated.google.deprecated(reason="Use bar() instead.", version="0.3.0") @deprecated.google.versionchanged(reason="Add the *y* parameter.", version="0.2.0") @deprecated.google.versionchanged(version="0.1.5") @deprecated.google.versionadded(reason="Here is my new function.", version="0.1.0") def foo(x, y=0): """Add *x* and *y*. Args: x: number *x* y: number *y* """ return x + y assert foo.__doc__ == textwrap.dedent( """\ Add *x* and *y*. Args: x: number *x* y: number *y* Version added: 0.1.0: Here is my new function. Version changed: 0.1.5 0.2.0: Add the *y* parameter. Deprecated: 0.3.0: Use bar() instead. """ ) def test_add_item_to_existing_section(): docstring = """Summary. Version changed: 1.0.0: First change. Returns: nothing """ assert add_section_item(docstring, "Version changed", [" 1.1.0: Second change."]) == ( textwrap.dedent( """\ Summary. Version changed: 1.0.0: First change. 1.1.0: Second change. Returns: nothing """ ) ) def test_method_and_classmethod(): class Foo: @deprecated.google.deprecated(reason="Use bar()", version="1.0.0") def foo(self): """Do foo.""" @classmethod @deprecated.google.deprecated(version="1.0.0") def baz(cls): """Do baz.""" assert Foo.foo.__doc__ == "Do foo.\n\nDeprecated:\n 1.0.0: Use bar()\n" with warnings.catch_warnings(record=True) as warns: warnings.simplefilter("always") Foo().foo() Foo.baz() if sys.version_info < (3, 13): baz_kind = "class method" else: # Since Python 3.13, ``classmethod`` no longer wraps the descriptor of the function. baz_kind = "function (or staticmethod)" assert [str(w.message) for w in warns] == [ "Call to deprecated method foo. (Use bar()) -- Deprecated since version 1.0.0.", f"Call to deprecated {baz_kind} baz. -- Deprecated since version 1.0.0.", ] def test_deprecated_keyword_arguments(): @deprecated.google.deprecated( version="1.0.0", action="error", category=MyDeprecationWarning, line_length=0 ) def foo(): pass with pytest.raises(MyDeprecationWarning): foo() @pytest.mark.parametrize("version", [None, ""]) def test_version_is_required(section, version): def foo(): pass decorator_factory = getattr(deprecated.google, section) with pytest.raises(ValueError, match="'version' argument is required"): decorator_factory(reason="A good reason", version=version)(foo) def test_invalid_section(): with pytest.raises(ValueError, match="invalid Google docstring section"): GoogleAdapter("todo", version="1.0.0") # type: ignore[arg-type] deprecated-3.0.1/tests/test_numpy.py000066400000000000000000000212051527107537600175530ustar00rootroot00000000000000import sys import textwrap import warnings import pytest import deprecated.numpy from deprecated.numpy import NumpyAdapter from deprecated.numpy import add_section_item class MyDeprecationWarning(DeprecationWarning): pass @pytest.fixture( scope="module", params=[ None, """This function adds *x* and *y*.""", """ This function adds *x* and *y*. Parameters ---------- x : int number *x* y : int number *y* Returns ------- int sum = *x* + *y* """, """This function adds *x* and *y*. Parameters ---------- x : int number *x* y : int number *y* Returns ------- int sum = *x* + *y* """, ], ids=["no_docstring", "short_docstring", "D213_long_docstring", "D212_long_docstring"], ) def docstring(request): return request.param @pytest.fixture(scope="module", params=["versionadded", "versionchanged", "deprecated"]) def section(request): return request.param HEADERS = { "versionadded": "Version added", "versionchanged": "Version changed", "deprecated": "Deprecated", } def underlined(header): return f"{header}\n{'-' * len(header)}" # noinspection PyShadowingNames @pytest.mark.parametrize( ("reason", "version", "expected"), [ ("A good reason", "1.2.0", "{header}\n{version}\n {reason}\n"), (None, "1.2.0", "{header}\n{version}\n"), ], ids=["reason&version", "version"], ) def test_has_numpy_docstring(docstring, section, reason, version, expected): def foo(x, y): return x + y foo.__doc__ = docstring decorator_factory = getattr(deprecated.numpy, section) foo = decorator_factory(reason=reason, version=version)(foo) expected = expected.format(header=underlined(HEADERS[section]), version=version, reason=reason) assert foo.__doc__ is not None current = textwrap.dedent(foo.__doc__) assert current.endswith(expected) current = current[: -len(expected)] if docstring: # An empty line must separate the original docstring and the section. assert current.endswith("\n\n") assert not current.endswith("\n\n\n") else: assert current == "" with warnings.catch_warnings(record=True) as warns: warnings.simplefilter("always") foo(1, 2) if section in {"versionadded", "versionchanged"}: assert len(warns) == 0 else: assert len(warns) == 1 assert issubclass(warns[0].category, DeprecationWarning) # noinspection PyShadowingNames def test_cls_has_numpy_docstring(docstring, section): class Foo: pass Foo.__doc__ = docstring decorator_factory = getattr(deprecated.numpy, section) decorated_cls = decorator_factory(reason="A good reason", version="1.2.0")(Foo) expected = f"{underlined(HEADERS[section])}\n1.2.0\n A good reason\n" assert decorated_cls.__doc__ is not None assert textwrap.dedent(decorated_cls.__doc__).endswith(expected) with warnings.catch_warnings(record=True) as warns: warnings.simplefilter("always") decorated_cls() if section in {"versionadded", "versionchanged"}: assert len(warns) == 0 else: assert len(warns) == 1 assert str(warns[0].message) == ( "Call to deprecated class Foo. (A good reason) -- Deprecated since version 1.2.0." ) @pytest.mark.parametrize( ("line_length", "expected"), [ ( 50, textwrap.dedent( """ Description of foo Returns ------- None nothing {header} 1.2.3 foo has changed in this version bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar """ ), ), ( 0, textwrap.dedent( """ Description of foo Returns ------- None nothing {header} 1.2.3 foo has changed in this version bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar """ # noqa: E501 ), ), ], ids=["wrapped", "long"], ) def test_numpy_adapter(section, line_length, expected): reason = "\n".join(["foo has changed in this version", "", "bar " * 23, ""]) adapter = NumpyAdapter(section, reason=reason, version="1.2.3", line_length=line_length) def foo(): """ Description of foo Returns ------- None nothing """ wrapped = adapter(foo) assert wrapped.__doc__ == expected.format(header=underlined(HEADERS[section])) def test_stacked_decorators(): @deprecated.numpy.deprecated(reason="Use bar() instead.", version="0.3.0") @deprecated.numpy.versionchanged(reason="Add the *y* parameter.", version="0.2.0") @deprecated.numpy.versionchanged(version="0.1.5") @deprecated.numpy.versionadded(reason="Here is my new function.", version="0.1.0") def foo(x, y=0): """Add *x* and *y*. Parameters ---------- x : int number *x* y : int number *y* """ return x + y assert foo.__doc__ == textwrap.dedent( """\ Add *x* and *y*. Parameters ---------- x : int number *x* y : int number *y* Version added ------------- 0.1.0 Here is my new function. Version changed --------------- 0.1.5 0.2.0 Add the *y* parameter. Deprecated ---------- 0.3.0 Use bar() instead. """ ) def test_add_item_to_existing_section(): docstring = """Summary. Version changed --------------- 1.0.0 First change. Returns ------- None nothing """ assert add_section_item(docstring, "Version changed", ["1.1.0", " Second change."]) == ( textwrap.dedent( """\ Summary. Version changed --------------- 1.0.0 First change. 1.1.0 Second change. Returns ------- None nothing """ ) ) def test_header_without_underline_is_not_a_section(): docstring = """Summary. Deprecated is a word in this paragraph. """ assert add_section_item(docstring, "Deprecated", ["1.0.0"]) == ( "Summary.\n\nDeprecated\nis a word in this paragraph.\n\nDeprecated\n----------\n1.0.0\n" ) def test_method_and_classmethod(): class Foo: @deprecated.numpy.deprecated(reason="Use bar()", version="1.0.0") def foo(self): """Do foo.""" @classmethod @deprecated.numpy.deprecated(version="1.0.0") def baz(cls): """Do baz.""" assert Foo.foo.__doc__ == "Do foo.\n\nDeprecated\n----------\n1.0.0\n Use bar()\n" with warnings.catch_warnings(record=True) as warns: warnings.simplefilter("always") Foo().foo() Foo.baz() if sys.version_info < (3, 13): baz_kind = "class method" else: # Since Python 3.13, ``classmethod`` no longer wraps the descriptor of the function. baz_kind = "function (or staticmethod)" assert [str(w.message) for w in warns] == [ "Call to deprecated method foo. (Use bar()) -- Deprecated since version 1.0.0.", f"Call to deprecated {baz_kind} baz. -- Deprecated since version 1.0.0.", ] def test_deprecated_keyword_arguments(): @deprecated.numpy.deprecated( version="1.0.0", action="error", category=MyDeprecationWarning, line_length=0 ) def foo(): pass with pytest.raises(MyDeprecationWarning): foo() @pytest.mark.parametrize("version", [None, ""]) def test_version_is_required(section, version): def foo(): pass decorator_factory = getattr(deprecated.numpy, section) with pytest.raises(ValueError, match="'version' argument is required"): decorator_factory(reason="A good reason", version=version)(foo) def test_invalid_section(): with pytest.raises(ValueError, match="invalid NumPy docstring section"): NumpyAdapter("todo", version="1.0.0") # type: ignore[arg-type] deprecated-3.0.1/tests/test_pickle.py000066400000000000000000000113421527107537600176530ustar00rootroot00000000000000""" Pickling of deprecated routines, and use with :mod:`multiprocessing` (issue #16). The deprecated routines must be defined at module level, so that they can be pickled by reference and found again when they are unpickled (in the same or in a child process). """ import multiprocessing import pickle import warnings import pytest import deprecated.classic import deprecated.google import deprecated.numpy import deprecated.sphinx from deprecated.params import deprecated_params @deprecated.classic.deprecated(reason="use new_function") def old_function(x): return x * 2 @deprecated.sphinx.deprecated(version="1.2.3", reason="use new_function") def old_sphinx_function(x): return x * 3 @deprecated.google.deprecated(version="1.2.3", reason="use new_function") def old_google_function(x): return x * 4 @deprecated.numpy.deprecated(version="1.2.3", reason="use new_function") def old_numpy_function(x): return x * 5 @deprecated_params("y", reason="y is ignored") def function_with_deprecated_params(x, y=None): return x class Foo: def __init__(self, value=1): self.value = value @deprecated.classic.deprecated def old_method(self, x): return self.value + x @classmethod @deprecated.classic.deprecated def old_class_method(cls, x): return x + 10 @staticmethod @deprecated.classic.deprecated def old_static_method(x): return x + 30 @deprecated.classic.deprecated @staticmethod def old_static_method_inner(x): return x + 40 def _round_trip(obj): for protocol in range(pickle.HIGHEST_PROTOCOL + 1): clone = pickle.loads(pickle.dumps(obj, protocol=protocol)) return clone @pytest.mark.parametrize( ("func", "expected"), [ (old_function, 2), (old_sphinx_function, 3), (old_google_function, 4), (old_numpy_function, 5), ], ids=["classic", "sphinx", "google", "numpy"], ) def test_pickle_deprecated_function(func, expected): clone = _round_trip(func) # Pickled by reference, like a regular function. assert clone is func with pytest.warns(DeprecationWarning, match="deprecated"): assert clone(1) == expected def test_pickle_function_with_deprecated_params(): clone = _round_trip(function_with_deprecated_params) assert clone is function_with_deprecated_params with pytest.warns(DeprecationWarning, match="y is ignored"): assert clone(1, y=2) == 1 @pytest.mark.parametrize( ("name", "expected"), [ ("old_class_method", 11), ("old_static_method", 31), ("old_static_method_inner", 41), ], ) def test_pickle_deprecated_class_level_method(name, expected): for owner in (Foo, Foo(5)): clone = _round_trip(getattr(owner, name)) with pytest.warns(DeprecationWarning, match="deprecated"): assert clone(1) == expected def test_pickle_deprecated_unbound_method(): clone = _round_trip(Foo.old_method) # wrapt creates a new (unbound) wrapper at each access: compare the wrapped functions. assert clone.__wrapped__ is Foo.old_method.__wrapped__ # type: ignore[attr-defined] with pytest.warns(DeprecationWarning, match="deprecated"): assert clone(Foo(5), 1) == 6 def test_pickle_deprecated_bound_method(): clone = _round_trip(Foo(5).old_method) # The instance is pickled with the bound method. assert clone.__self__.value == 5 with pytest.warns(DeprecationWarning, match="deprecated"): assert clone(1) == 6 def test_pickle_local_deprecated_function(): # Like a regular local function, a local deprecated function can't be pickled by reference. @deprecated.classic.deprecated def local_function(): pass with pytest.raises(pickle.PicklingError): pickle.dumps(local_function) def _call_old_function(x): with warnings.catch_warnings(record=True) as caught: warnings.simplefilter("always") result = old_function(x) return result, [str(w.message) for w in caught] def test_deprecated_function_with_spawn_pool(): ctx = multiprocessing.get_context("spawn") with ctx.Pool(1) as pool: # The deprecated function itself is sent to the child process... assert pool.map(old_function, [1, 2]) == [2, 4] # ... and it still emits the deprecation warning there. result, messages = pool.apply(_call_old_function, (21,)) assert result == 42 assert messages == [ "Call to deprecated function (or staticmethod) old_function. (use new_function)" ] def test_deprecated_function_as_spawn_process_target(): ctx = multiprocessing.get_context("spawn") process = ctx.Process(target=old_function, args=(1,)) process.start() process.join(timeout=60) assert process.exitcode == 0 deprecated-3.0.1/tests/test_python3_behaviour.py000066400000000000000000000121451527107537600220560ustar00rootroot00000000000000""" Non-regression tests for behaviours which differ between Python 2 and Python 3. These tests secure the migration to Python 3.12+: text/bytes handling, dictionary ordering (formerly ``collections.OrderedDict``), keyword-only/variadic parameters and the static typing of the public decorators. """ import warnings from collections.abc import Callable from typing import Any from typing import assert_type from typing import cast import pytest import deprecated.classic import deprecated.sphinx from deprecated.params import deprecated_params def _record_messages(func: Callable[..., object], *args: object, **kwargs: object) -> list[str]: with warnings.catch_warnings(record=True) as warns: warnings.simplefilter("always") func(*args, **kwargs) return [str(warn.message) for warn in warns] class TestTextAndBytes: def test_warning_message_is_str(self): @deprecated.classic.deprecated("Good reason", version="1.2.3") def foo(): pass with warnings.catch_warnings(record=True) as warns: warnings.simplefilter("always") foo() message = warns[0].message assert isinstance(message, DeprecationWarning) assert isinstance(message.args[0], str) def test_non_ascii_reason_is_preserved(self): reason = "Utilisez « bar() » à la place — 使用 bar" @deprecated.classic.deprecated(reason=reason) def foo(): pass assert _record_messages(foo) == [ f"Call to deprecated function (or staticmethod) foo. ({reason})" ] def test_non_ascii_reason_in_sphinx_docstring(self): reason = "Utilisez « bar() » à la place" @deprecated.sphinx.deprecated(reason=reason, version="1.0") def foo(): """Fonction dépréciée.""" assert foo.__doc__ == f"Fonction dépréciée.\n\n.. deprecated:: 1.0\n {reason}\n" def test_bytes_positional_reason_is_still_accepted(self): # With Python 2, ``str`` was ``bytes``: a byte string reason was accepted. # This (undocumented) behaviour is kept: the reason is formatted with ``str.format``. decorator = deprecated.classic.deprecated(cast(Any, b"old reason")) @decorator def foo(): pass assert _record_messages(foo) == [ "Call to deprecated function (or staticmethod) foo. (b'old reason')" ] class TestDeprecatedParams: def test_messages_follow_the_parameters_order(self): # ``collections.OrderedDict`` was replaced by a plain ``dict`` (ordered since Python 3.7). @deprecated_params({"b": "b is deprecated", "a": "a is deprecated"}) def foo(a=None, b=None): pass assert _record_messages(foo, a=1, b=2) == ["a is deprecated", "b is deprecated"] assert _record_messages(foo, b=2, a=1) == ["a is deprecated", "b is deprecated"] def test_variadic_keyword_parameters_are_checked(self): @deprecated_params("old") def foo(**kwargs): return kwargs assert _record_messages(foo, old=1, new=2) == ["'old' parameter is deprecated"] def test_keyword_only_parameter(self): @deprecated_params("old", reason="use 'new'") def foo(*, old=None, new=None): return old or new assert _record_messages(foo, new=1) == [] assert _record_messages(foo, old=1) == ["use 'new'"] def test_wrapper_preserves_metadata_and_result(self): @deprecated_params("z") def add(x: int, y: int, z: int = 0) -> int: """Add two numbers.""" return x + y + z with warnings.catch_warnings(): warnings.simplefilter("ignore") assert add(1, 2, z=3) == 6 assert add.__name__ == "add" assert add.__doc__ == "Add two numbers." @pytest.mark.parametrize("param", [b"z", 5, ["z"]]) def test_invalid_param_type_raises_type_error(self, param): with pytest.raises(TypeError): deprecated_params(param) class TestStaticTyping: """These assertions are checked by mypy (``hatch check types``) and at runtime.""" def test_classic_decorator_preserves_the_signature(self): @deprecated.classic.deprecated def foo(x: int) -> str: return str(x) @deprecated.classic.deprecated(reason="use bar", version="1.0") def bar(x: int) -> str: return str(x) with warnings.catch_warnings(): warnings.simplefilter("ignore") # `assert_type` fails if the decorator leaks `Any`. assert assert_type(foo(1), str) == "1" assert assert_type(bar(1), str) == "1" def test_class_decorator_preserves_the_class(self): @deprecated.classic.deprecated(reason="use Bar") class Foo: pass with warnings.catch_warnings(): warnings.simplefilter("ignore") assert isinstance(assert_type(Foo(), Foo), Foo) def test_deprecated_params_preserves_the_signature(self): @deprecated_params("y") def foo(x: int, y: int = 0) -> int: return x + y assert assert_type(foo(1), int) == 1 deprecated-3.0.1/tests/test_sphinx.py000066400000000000000000000267771527107537600177370ustar00rootroot00000000000000import re import sys import textwrap import warnings from typing import Any from typing import cast import pytest import deprecated.sphinx @pytest.fixture( scope="module", params=[ None, """This function adds *x* and *y*.""", """ This function adds *x* and *y*. :param x: number *x* :param y: number *y* :return: sum = *x* + *y* """, """This function adds *x* and *y*. :param x: number *x* :param y: number *y* :return: sum = *x* + *y* """, ], ids=["no_docstring", "short_docstring", "D213_long_docstring", "D212_long_docstring"], ) def docstring(request): return request.param @pytest.fixture(scope="module", params=["versionadded", "versionchanged", "deprecated"]) def directive(request): return request.param # noinspection PyShadowingNames @pytest.mark.parametrize( ("reason", "version", "expected"), [ ( "A good reason", "1.2.0", textwrap.dedent( """\ .. {directive}:: {version} {reason} """ ), ), ( None, "1.2.0", textwrap.dedent( """\ .. {directive}:: {version} """ ), ), ], ids=["reason&version", "version"], ) def test_has_sphinx_docstring(docstring, directive, reason, version, expected): # The function: def foo(x, y): return x + y # with docstring: foo.__doc__ = docstring # is decorated with: decorator_factory = getattr(deprecated.sphinx, directive) decorator = decorator_factory(reason=reason, version=version) foo = decorator(foo) # The function must contains this Sphinx docstring: expected = expected.format(directive=directive, version=version, reason=reason) assert foo.__doc__ is not None current = textwrap.dedent(foo.__doc__) assert current.endswith(expected) current = current.replace(expected, "") if docstring: # An empty line must separate the original docstring and the directive. assert re.search("\n[ ]*\n$", current, flags=re.DOTALL) else: # Avoid "Explicit markup ends without a blank line" # when the decorated function has no docstring assert current == "\n" with warnings.catch_warnings(record=True) as warns: foo(1, 2) if directive in {"versionadded", "versionchanged"}: # don't emit DeprecationWarning assert len(warns) == 0 else: # emit DeprecationWarning assert len(warns) == 1 # noinspection PyShadowingNames @pytest.mark.parametrize( ("reason", "version", "expected"), [ ( "A good reason", "1.2.0", textwrap.dedent( """\ .. {directive}:: {version} {reason} """ ), ), ( None, "1.2.0", textwrap.dedent( """\ .. {directive}:: {version} """ ), ), ], ids=["reason&version", "version"], ) def test_cls_has_sphinx_docstring(docstring, directive, reason, version, expected): # The class: class Foo: pass # with docstring: Foo.__doc__ = docstring # is decorated with: decorator_factory = getattr(deprecated.sphinx, directive) decorator = decorator_factory(reason=reason, version=version) decorated_cls = decorator(Foo) # The class must contain this Sphinx docstring: expected = expected.format(directive=directive, version=version, reason=reason) assert decorated_cls.__doc__ is not None current = textwrap.dedent(decorated_cls.__doc__) assert current.endswith(expected) current = current.replace(expected, "") if docstring: # An empty line must separate the original docstring and the directive. assert re.search("\n[ ]*\n$", current, flags=re.DOTALL) else: # Avoid "Explicit markup ends without a blank line" # when the decorated function has no docstring assert current == "\n" with warnings.catch_warnings(record=True) as warns: decorated_cls() if directive in {"versionadded", "versionchanged"}: # don't emit DeprecationWarning assert len(warns) == 0 else: # emit DeprecationWarning assert len(warns) == 1 class MyDeprecationWarning(DeprecationWarning): pass _PARAMS = [ {"version": "1.2.3"}, {"version": "1.2.3", "reason": "Good reason"}, {"version": "1.2.3", "action": "once"}, {"version": "1.2.3", "category": MyDeprecationWarning}, ] @pytest.fixture(scope="module", params=_PARAMS) def sphinx_deprecated_function(request): kwargs = request.param @deprecated.sphinx.deprecated(**kwargs) def foo1(): pass return foo1 @pytest.fixture(scope="module", params=_PARAMS) def sphinx_deprecated_class(request): kwargs = request.param @deprecated.sphinx.deprecated(**kwargs) class Foo2: pass return Foo2 @pytest.fixture(scope="module", params=_PARAMS) def sphinx_deprecated_method(request): kwargs = request.param class Foo3: @deprecated.sphinx.deprecated(**kwargs) def foo3(self): pass return Foo3 @pytest.fixture(scope="module", params=_PARAMS) def sphinx_deprecated_static_method(request): kwargs = request.param class Foo4: @staticmethod @deprecated.sphinx.deprecated(**kwargs) def foo4(): pass return Foo4.foo4 @pytest.fixture(scope="module", params=_PARAMS) def sphinx_deprecated_class_method(request): kwargs = request.param class Foo5: @classmethod @deprecated.sphinx.deprecated(**kwargs) def foo5(cls): pass return Foo5 # noinspection PyShadowingNames def test_sphinx_deprecated_function__warns(sphinx_deprecated_function): with warnings.catch_warnings(record=True) as warns: warnings.simplefilter("always") sphinx_deprecated_function() assert len(warns) == 1 warn = warns[0] assert issubclass(warn.category, DeprecationWarning) assert "deprecated function (or staticmethod)" in str(warn.message) # noinspection PyShadowingNames def test_sphinx_deprecated_class__warns(sphinx_deprecated_class): with warnings.catch_warnings(record=True) as warns: warnings.simplefilter("always") sphinx_deprecated_class() assert len(warns) == 1 warn = warns[0] assert issubclass(warn.category, DeprecationWarning) assert "deprecated class" in str(warn.message) # noinspection PyShadowingNames def test_sphinx_deprecated_method__warns(sphinx_deprecated_method): with warnings.catch_warnings(record=True) as warns: warnings.simplefilter("always") obj = sphinx_deprecated_method() obj.foo3() assert len(warns) == 1 warn = warns[0] assert issubclass(warn.category, DeprecationWarning) assert "deprecated method" in str(warn.message) # noinspection PyShadowingNames def test_sphinx_deprecated_static_method__warns(sphinx_deprecated_static_method): with warnings.catch_warnings(record=True) as warns: warnings.simplefilter("always") sphinx_deprecated_static_method() assert len(warns) == 1 warn = warns[0] assert issubclass(warn.category, DeprecationWarning) assert "deprecated function (or staticmethod)" in str(warn.message) # noinspection PyShadowingNames def test_sphinx_deprecated_class_method__warns(sphinx_deprecated_class_method): with warnings.catch_warnings(record=True) as warns: warnings.simplefilter("always") cls = sphinx_deprecated_class_method() cls.foo5() assert len(warns) == 1 warn = warns[0] assert issubclass(warn.category, DeprecationWarning) if sys.version_info < (3, 13): assert "deprecated class method" in str(warn.message) else: assert "deprecated function (or staticmethod)" in str(warn.message) def test_should_raise_type_error(): with pytest.raises(TypeError): @deprecated.sphinx.deprecated(version="4.5.6", reason=cast(Any, 5)) def foo(): pass def test_warning_msg_has_reason(): reason = "Good reason" @deprecated.sphinx.deprecated(version="4.5.6", reason=reason) def foo(): pass with warnings.catch_warnings(record=True) as warns: foo() warn = warns[0] assert reason in str(warn.message) def test_warning_msg_has_version(): version = "1.2.3" @deprecated.sphinx.deprecated(version=version) def foo(): pass with warnings.catch_warnings(record=True) as warns: foo() warn = warns[0] assert version in str(warn.message) def test_warning_is_ignored(): @deprecated.sphinx.deprecated(version="4.5.6", action="ignore") def foo(): pass with warnings.catch_warnings(record=True) as warns: foo() assert len(warns) == 0 def test_specific_warning_cls_is_used(): @deprecated.sphinx.deprecated(version="4.5.6", category=MyDeprecationWarning) def foo(): pass with warnings.catch_warnings(record=True) as warns: foo() warn = warns[0] assert issubclass(warn.category, MyDeprecationWarning) def test_can_catch_warnings(): with warnings.catch_warnings(record=True) as warns: warnings.simplefilter("always") warnings.warn("A message in a bottle", category=DeprecationWarning, stacklevel=2) assert len(warns) == 1 @pytest.mark.parametrize( ("reason", "expected"), [ ("Use :function:`bar` instead", "Use `bar` instead"), ("Use :py:func:`bar` instead", "Use `bar` instead"), ], ) def test_sphinx_syntax_trimming(reason, expected): @deprecated.sphinx.deprecated(version="4.5.6", reason=reason) def foo(): pass with warnings.catch_warnings(record=True) as warns: foo() warn = warns[0] assert expected in str(warn.message) # noinspection SpellCheckingInspection @pytest.mark.parametrize( ("reason", "expected"), [ # classic examples using the default domain (Python) ("Use :func:`bar` instead", "Use `bar` instead"), ("Use :function:`bar` instead", "Use `bar` instead"), ("Use :class:`Baz` instead", "Use `Baz` instead"), ("Use :exc:`Baz` instead", "Use `Baz` instead"), ("Use :exception:`Baz` instead", "Use `Baz` instead"), ("Use :meth:`Baz.bar` instead", "Use `Baz.bar` instead"), ("Use :method:`Baz.bar` instead", "Use `Baz.bar` instead"), # other examples using a domain : ("Use :py:func:`bar` instead", "Use `bar` instead"), ("Use :cpp:func:`bar` instead", "Use `bar` instead"), ("Use :js:func:`bar` instead", "Use `bar` instead"), # the reference can have special characters: ("Use :func:`~pkg.mod.bar` instead", "Use `~pkg.mod.bar` instead"), # edge cases: ("Use :r:`` instead", "Use `` instead"), ("Use :d:r:`` instead", "Use `` instead"), ("Use :r:`foo` instead", "Use `foo` instead"), ("Use :d:r:`foo` instead", "Use `foo` instead"), ("Use r:`bad` instead", "Use r:`bad` instead"), ("Use ::`bad` instead", "Use ::`bad` instead"), ("Use :::`bad` instead", "Use :::`bad` instead"), ], ) def test_get_deprecated_msg(reason, expected): adapter = deprecated.sphinx.SphinxAdapter("deprecated", reason=reason, version="1") actual = adapter.get_deprecated_msg(lambda: None, None) assert expected in actual deprecated-3.0.1/tests/test_sphinx_adapter.py000066400000000000000000000073241527107537600214220ustar00rootroot00000000000000import textwrap import pytest from deprecated.sphinx import SphinxAdapter from deprecated.sphinx import deprecated from deprecated.sphinx import versionadded from deprecated.sphinx import versionchanged @pytest.mark.parametrize( ("line_length", "expected"), [ ( 50, textwrap.dedent( """ Description of foo :return: nothing .. {directive}:: 1.2.3 foo has changed in this version bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar """ ), ), ( 0, textwrap.dedent( """ Description of foo :return: nothing .. {directive}:: 1.2.3 foo has changed in this version bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar """ ), ), ], ids=["wrapped", "long"], ) @pytest.mark.parametrize("directive", ["versionchanged", "versionadded", "deprecated"]) def test_sphinx_adapter(directive, line_length, expected): lines = [ "foo has changed in this version", "", # newline "bar " * 23, # long line "", # trailing newline ] reason = "\n".join(lines) adapter = SphinxAdapter(directive, reason=reason, version="1.2.3", line_length=line_length) def foo(): """ Description of foo :return: nothing """ wrapped = adapter.__call__(foo) expected = expected.format(directive=directive) assert wrapped.__doc__ == expected @pytest.mark.parametrize("directive", ["versionchanged", "versionadded", "deprecated"]) def test_sphinx_adapter__empty_docstring(directive): lines = [ "foo has changed in this version", "", # newline "bar " * 23, # long line "", # trailing newline ] reason = "\n".join(lines) adapter = SphinxAdapter(directive, reason=reason, version="1.2.3", line_length=50) def foo(): pass wrapped = adapter.__call__(foo) expected = textwrap.dedent( """ .. {directive}:: 1.2.3 foo has changed in this version bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar """ ) expected = expected.format(directive=directive) assert wrapped.__doc__ == expected @pytest.mark.parametrize( ("decorator_factory", "directive"), [ (versionadded, "versionadded"), (versionchanged, "versionchanged"), (deprecated, "deprecated"), ], ) def test_decorator_accept_line_length(decorator_factory, directive): reason = "bar " * 30 decorator = decorator_factory(reason=reason, version="1.2.3", line_length=50) def foo(): pass foo = decorator(foo) expected = textwrap.dedent( """ .. {directive}:: 1.2.3 bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar """ ) expected = expected.format(directive=directive) assert foo.__doc__ == expected @pytest.mark.parametrize("version", ["", None]) @pytest.mark.parametrize("directive", ["versionchanged", "versionadded", "deprecated"]) def test_sphinx_adapter__version_is_required(directive, version): # https://github.com/laurent-laporte-pro/deprecated/issues/40 with pytest.raises(ValueError, match="'version' argument is required"): SphinxAdapter(directive, reason="some reason", version=version) deprecated-3.0.1/tests/test_sphinx_class.py000066400000000000000000000077051527107537600211120ustar00rootroot00000000000000import inspect import io import warnings import deprecated.sphinx def test_class_deprecation_using_a_simple_decorator(): # stream is used to store the deprecation message for testing stream = io.StringIO() # To deprecated the class, we use a simple decorator # which patches the original ``__new__`` method. def simple_decorator(wrapped_cls): old_new = wrapped_cls.__new__ def wrapped_new(unused, *args, **kwargs): print("I am deprecated!", file=stream) return old_new(*args, **kwargs) wrapped_cls.__new__ = classmethod(wrapped_new) return wrapped_cls @simple_decorator class MyBaseClass: pass class MySubClass(MyBaseClass): pass obj = MySubClass() assert isinstance(obj, MyBaseClass) assert inspect.isclass(MyBaseClass) assert stream.getvalue().strip() == "I am deprecated!" def test_class_deprecation_using_deprecated_decorator(): @deprecated.sphinx.deprecated(version="7.8.9") class MyBaseClass: pass class MySubClass(MyBaseClass): pass with warnings.catch_warnings(record=True) as warns: warnings.simplefilter("always") obj = MySubClass() assert len(warns) == 1 assert isinstance(obj, MyBaseClass) assert inspect.isclass(MyBaseClass) assert issubclass(MySubClass, MyBaseClass) def test_subclass_deprecation_using_deprecated_decorator(): @deprecated.sphinx.deprecated(version="7.8.9") class MyBaseClass: pass @deprecated.sphinx.deprecated(version="7.8.9") class MySubClass(MyBaseClass): pass with warnings.catch_warnings(record=True) as warns: warnings.simplefilter("always") obj = MySubClass() assert len(warns) == 2 assert isinstance(obj, MyBaseClass) assert inspect.isclass(MyBaseClass) assert issubclass(MySubClass, MyBaseClass) def test_isinstance_versionadded(): # https://github.com/laurent-laporte-pro/deprecated/issues/48 @deprecated.sphinx.versionadded(version="X.Y", reason="some reason") class VersionAddedCls: pass @deprecated.sphinx.versionadded(version="X.Y", reason="some reason") class VersionAddedChildCls(VersionAddedCls): pass instance = VersionAddedChildCls() assert isinstance(instance, VersionAddedChildCls) assert isinstance(instance, VersionAddedCls) def test_isinstance_versionchanged(): @deprecated.sphinx.versionchanged(version="X.Y", reason="some reason") class VersionChangedCls: pass @deprecated.sphinx.versionchanged(version="X.Y", reason="some reason") class VersionChangedChildCls(VersionChangedCls): pass instance = VersionChangedChildCls() assert isinstance(instance, VersionChangedChildCls) assert isinstance(instance, VersionChangedCls) def test_isinstance_deprecated(): @deprecated.sphinx.deprecated(version="X.Y", reason="some reason") class DeprecatedCls: pass @deprecated.sphinx.deprecated(version="Y.Z", reason="some reason") class DeprecatedChildCls(DeprecatedCls): pass with warnings.catch_warnings(record=True) as warns: warnings.simplefilter("always") instance = DeprecatedChildCls() assert isinstance(instance, DeprecatedChildCls) assert isinstance(instance, DeprecatedCls) # Both warnings refer to the user code, not to the library. assert [str(w.message) for w in warns] == [ "Call to deprecated class DeprecatedChildCls. (some reason)" " -- Deprecated since version Y.Z.", "Call to deprecated class DeprecatedCls. (some reason) -- Deprecated since version X.Y.", ] assert [w.filename for w in warns] == [__file__, __file__] def test_isinstance_versionadded_versionchanged(): @deprecated.sphinx.versionadded(version="X.Y") @deprecated.sphinx.versionchanged(version="X.Y.Z") class AddedChangedCls: pass instance = AddedChangedCls() assert isinstance(instance, AddedChangedCls) deprecated-3.0.1/tests/test_sphinx_metaclass.py000066400000000000000000000057561527107537600217650ustar00rootroot00000000000000import warnings from typing import ClassVar import deprecated.sphinx def test_with_init(): @deprecated.sphinx.deprecated(version="1.2.3") class MyClass: def __init__(self, a, b=5): self.a = a self.b = b with warnings.catch_warnings(record=True) as warns: warnings.simplefilter("always") obj = MyClass("five") assert len(warns) == 1 assert obj.a == "five" assert obj.b == 5 def test_with_new(): @deprecated.sphinx.deprecated(version="1.2.3") class MyClass: c: float def __new__(cls, a, b=5): obj = super().__new__(cls) obj.c = 3.14 return obj def __init__(self, a, b=5): self.a = a self.b = b with warnings.catch_warnings(record=True) as warns: warnings.simplefilter("always") obj = MyClass("five") assert len(warns) == 1 assert obj.a == "five" assert obj.b == 5 assert obj.c == 3.14 def test_with_metaclass(): class Meta(type): def __call__(cls, *args, **kwargs): obj = super().__call__(*args, **kwargs) obj.c = 3.14 return obj @deprecated.sphinx.deprecated(version="1.2.3") class MyClass(metaclass=Meta): c: float def __init__(self, a, b=5): self.a = a self.b = b with warnings.catch_warnings(record=True) as warns: warnings.simplefilter("always") obj = MyClass("five") assert len(warns) == 1 assert obj.a == "five" assert obj.b == 5 assert obj.c == 3.14 def test_with_singleton_metaclass(): class Singleton(type): _instances: ClassVar[dict[type, object]] = {} def __call__(cls, *args, **kwargs): if cls not in cls._instances: cls._instances[cls] = super().__call__(*args, **kwargs) return cls._instances[cls] @deprecated.sphinx.deprecated(version="1.2.3") class MyClass(metaclass=Singleton): def __init__(self, a, b=5): self.a = a self.b = b with warnings.catch_warnings(record=True) as warns: warnings.simplefilter("always") obj1 = MyClass("five") obj2 = MyClass("six", b=6) # __new__ is called only once: # the instance is constructed only once, # so we have only one warning. assert len(warns) == 1 assert obj1.a == "five" assert obj1.b == 5 assert obj2 is obj1 def test_docstring_with_metaclass(): class Meta(type): pass @deprecated.sphinx.deprecated(version="1.2.3", reason="use another class") class MyClass(metaclass=Meta): """My class.""" assert MyClass.__doc__ == "My class.\n\n.. deprecated:: 1.2.3\n use another class\n" with warnings.catch_warnings(record=True) as warns: warnings.simplefilter("always") MyClass() assert [str(w.message) for w in warns] == [ "Call to deprecated class MyClass. (use another class) -- Deprecated since version 1.2.3." ] deprecated-3.0.1/uv.lock000066400000000000000000010336771527107537600151550ustar00rootroot00000000000000version = 1 revision = 3 requires-python = ">=3.12" resolution-markers = [ "python_full_version >= '3.15'", "python_full_version < '3.15'", ] [[package]] name = "accessible-pygments" version = "0.0.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pygments" }, ] sdist = { url = "https://files.pythonhosted.org/packages/bc/c1/bbac6a50d02774f91572938964c582fff4270eee73ab822a4aeea4d8b11b/accessible_pygments-0.0.5.tar.gz", hash = "sha256:40918d3e6a2b619ad424cb91e556bd3bd8865443d9f22f1dcdf79e33c8046872", size = 1377899, upload-time = "2024-05-10T11:23:10.216Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/8d/3f/95338030883d8c8b91223b4e21744b04d11b161a3ef117295d8241f50ab4/accessible_pygments-0.0.5-py3-none-any.whl", hash = "sha256:88ae3211e68a1d0b011504b2ffc1691feafce124b845bd072ab6f9f66f34d4b7", size = 1395903, upload-time = "2024-05-10T11:23:08.421Z" }, ] [[package]] name = "alabaster" version = "1.0.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/a6/f8/d9c74d0daf3f742840fd818d69cfae176fa332022fd44e3469487d5a9420/alabaster-1.0.0.tar.gz", hash = "sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e", size = 24210, upload-time = "2024-07-26T18:15:03.762Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/7e/b3/6b4067be973ae96ba0d615946e314c5ae35f9f993eca561b356540bb0c2b/alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b", size = 13929, upload-time = "2024-07-26T18:15:02.05Z" }, ] [[package]] name = "anyio" version = "4.15.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "idna" }, { name = "typing-extensions", marker = "python_full_version < '3.15'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a9/d2/f4d173e22df740bc37b1db102b386ba719b66e95b0f0d751f556b387e6d2/anyio-4.15.1.tar.gz", hash = "sha256:9f28306018cbd6d329e64a36d58256edff76dd996fe423bc957326e578b82a94", size = 276966, upload-time = "2026-09-05T10:42:39.44Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/12/b8/4bd346e22b28902df4d651910f5242c28d84e4a5c2435ca5c3f797ed7e2e/anyio-4.15.1-py3-none-any.whl", hash = "sha256:6152fdbbf9a77fdec97731721bebf7c4c44f7c29b424b0065826173efc7ed101", size = 132079, upload-time = "2026-09-05T10:42:37.923Z" }, ] [[package]] name = "ast-serialize" version = "0.11.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/54/1e/4f6082cdd6e5a29093513e9a3eabc5ed1c5331a9a84386b2fece80a00a48/ast_serialize-0.11.2.tar.gz", hash = "sha256:976a5bd75845d22f4b52905ddf53ab669ef1b14dba7735f5512841a2ef2b5450", size = 954387, upload-time = "2026-09-13T18:48:55.673Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/a3/2e/beec3364eef4b01793a676d8cd16e9014c42044a5505000ceae3955e33fa/ast_serialize-0.11.2-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:f6a8dfc5ab204a706f6e5d39c6f77c18c27ef084fa2081803a64a9160ce89277", size = 897089, upload-time = "2026-09-13T18:47:22.69Z" }, { url = "https://files.pythonhosted.org/packages/6f/d7/ef56443df2891c6ba2c4019c2cb3dcaf97c9948da6d963068e04e8dac6ea/ast_serialize-0.11.2-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:cb073bfa15742699d408ac50f60878383b5665ae1791d1b6799ea6f08633cd77", size = 1235218, upload-time = "2026-09-13T18:47:24.541Z" }, { url = "https://files.pythonhosted.org/packages/42/8d/cff58d17ba1d0272ff0b7ab5d3bdfcf8f47317eb0f47c001d394bffebf95/ast_serialize-0.11.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1d6ad94edbe93bf1dabc06c9f37d55b898fdabc456aa6d7ced5e23c14f795f32", size = 1216399, upload-time = "2026-09-13T18:47:26.202Z" }, { url = "https://files.pythonhosted.org/packages/de/d2/a1da7675af5f42335c36e4da6d86ef4fd7168cead18de81df0a2d6faeb1a/ast_serialize-0.11.2-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40b2801cf2221bd922d9f69d2f0ebc373c3db47207315d525b2d87fa161a2af4", size = 1282064, upload-time = "2026-09-13T18:47:27.787Z" }, { url = "https://files.pythonhosted.org/packages/97/89/5a400a13b2c9c0152ebb5ad45408a3fe5e4e60e325d3ac4e5cf6e915a0cc/ast_serialize-0.11.2-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fd666cebd6ab3b3c0fd348a6202c26e18a401ee34293c3804d3472266bc146f6", size = 1285864, upload-time = "2026-09-13T18:47:29.667Z" }, { url = "https://files.pythonhosted.org/packages/02/b8/80a381c70fd49f0316fb0383c4f9e4c13e81b010b64889bd45898ce8f5f4/ast_serialize-0.11.2-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0d01f61352c96370febf6c0dbd488dee9183a731fb2702170da9163ae317cded", size = 1554755, upload-time = "2026-09-13T18:47:31.257Z" }, { url = "https://files.pythonhosted.org/packages/90/97/dcaa34a32d2db789221c125b3eb10feb5089715fe53d9874d627afc26231/ast_serialize-0.11.2-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a0fd40c668b0fa19b8fdb61d9e63d547e2e19cfbfe053a51ef0b6c37070298a8", size = 1301807, upload-time = "2026-09-13T18:47:32.714Z" }, { url = "https://files.pythonhosted.org/packages/5c/9a/84a22420cb312642d7d31547c644d09a3d101418c6d6b9ef2ec30735cf11/ast_serialize-0.11.2-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efa819d7c14c8e4153dcd84671826331538be7cbe460383fc6386f5eea5bd234", size = 1301941, upload-time = "2026-09-13T18:47:34.418Z" }, { url = "https://files.pythonhosted.org/packages/20/8a/aa5f3dcf1aed9678c25982f40d366004e3c0cac47bc0c240f6b837dcbb1f/ast_serialize-0.11.2-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:a9ffa8a197a721f07a352d0be6185f5b3e6f9aaebfdb66169ed652108531ae3b", size = 1307910, upload-time = "2026-09-13T18:47:36.253Z" }, { url = "https://files.pythonhosted.org/packages/78/79/91a5102797fe3dc992171382d8579bcb33cbd1424b864ad3117ac43fb3fe/ast_serialize-0.11.2-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:00119a8fb8c1dc0f1fab023f4d8071fa49e3b0208ee54d589fd463c16ab0124e", size = 1356258, upload-time = "2026-09-13T18:47:37.984Z" }, { url = "https://files.pythonhosted.org/packages/51/52/54eeef9918e187ced417c4363eecea66975314cd5b9c91759eef7f7b714b/ast_serialize-0.11.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0de02520c11391a026e62987a9aa2c3c2ff01545155059ddf0c4bdf2c5ecbe9f", size = 1459057, upload-time = "2026-09-13T18:47:39.891Z" }, { url = "https://files.pythonhosted.org/packages/e4/cb/fd84b52b15d42f2423319cffd1fb7f1e9df5d5198e69ab0b449c450254cf/ast_serialize-0.11.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:b4e4558956b6a0fb35e18fba58f7d1810b1f2c0e6b52352572cd5dfb6b4ef33a", size = 1562447, upload-time = "2026-09-13T18:47:41.727Z" }, { url = "https://files.pythonhosted.org/packages/be/92/9fb34f2e64b84a63cca92fb86bd0847b995a63b67477f44c20502fb60352/ast_serialize-0.11.2-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:6061a54f39e82a9f2cbcb9c268fc441890e4818a6636473caa4f4063254e0750", size = 1556423, upload-time = "2026-09-13T18:47:43.357Z" }, { url = "https://files.pythonhosted.org/packages/75/0f/c43c44449e7ebc4e83ebd48750088fb06234622faa2d62d2a6dc8970d2a3/ast_serialize-0.11.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:85fbb01e83967a126d71f679f2b9528ef0912cb0854aa1a4657314c34e255b57", size = 1687156, upload-time = "2026-09-13T18:47:44.995Z" }, { url = "https://files.pythonhosted.org/packages/2b/a7/9e520f4a79b639da9ee20c1e747c3d739329e902fc55ac38065f25419f56/ast_serialize-0.11.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:7aaaffc32905159774a107d3cf33dad59bd41b7a0d1bc9885532186753ee7439", size = 1481008, upload-time = "2026-09-13T18:47:46.602Z" }, { url = "https://files.pythonhosted.org/packages/48/a8/bdd3989f19de09cffcd8179c131f6741a5a8619705fc75b09541ff61530b/ast_serialize-0.11.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:08eda88a0f290a36c38cab33df8bf7e35eb95bc802ca5beb2c8fcda471a7d10c", size = 1501597, upload-time = "2026-09-13T18:47:48.265Z" }, { url = "https://files.pythonhosted.org/packages/a5/8a/ca2dce2950875a4ef1d7c298196f803b0adcdb7c15ed0cecc71d84bccd70/ast_serialize-0.11.2-cp314-cp314t-win32.whl", hash = "sha256:76cc294246e60a914326b4ca88c6a5ea89c064906614aaf1537ce82f09e9449f", size = 1119503, upload-time = "2026-09-13T18:47:49.896Z" }, { url = "https://files.pythonhosted.org/packages/5a/12/3f38e3613d07c46f9f81c5b1352748c6552397cc52825502e2c6ae44c6ea/ast_serialize-0.11.2-cp314-cp314t-win_amd64.whl", hash = "sha256:43b51e6ebe6549bf21416c3c78ee886147b80875a87cc6f69e303dde0d75be0b", size = 1156828, upload-time = "2026-09-13T18:47:51.454Z" }, { url = "https://files.pythonhosted.org/packages/22/90/f89a4f67428a261daafdb69a0d0132c27933268702d1ba47e0b61c51aff1/ast_serialize-0.11.2-cp314-cp314t-win_arm64.whl", hash = "sha256:8df32ad4ff7843734a6c2f067ee974f6d3109ee5a2c3e1a9d2f79347bd282a9a", size = 1128298, upload-time = "2026-09-13T18:47:53.008Z" }, { url = "https://files.pythonhosted.org/packages/0b/55/a1962188abf0e62d84d55892bb044347e434711763b9a1d4ad867a70c1be/ast_serialize-0.11.2-cp315-abi3.abi3t-macosx_10_12_x86_64.whl", hash = "sha256:ab924ba260efd7509492f272d4e236d24564033f20c005d7c63a107c6a76fc85", size = 1235457, upload-time = "2026-09-13T18:47:54.554Z" }, { url = "https://files.pythonhosted.org/packages/2a/ad/439c2959150718446af76fbe2f4000f35eba9869ef8564f3d9a3d0b1c370/ast_serialize-0.11.2-cp315-abi3.abi3t-macosx_11_0_arm64.whl", hash = "sha256:a586be418eb70a9f1396cea29ddac8f4b9bf277fb73ea2340db31e218bc00f32", size = 1215705, upload-time = "2026-09-13T18:47:56.178Z" }, { url = "https://files.pythonhosted.org/packages/6d/d8/2c6542fc3e7c56a0a25d8d12d034d5a2d2e1900e292567b1c1dca8e83124/ast_serialize-0.11.2-cp315-abi3.abi3t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8532f20916fa3189d4d785ef2a62d93c4d651ec9c5bffda66d2fc36898351f34", size = 1282530, upload-time = "2026-09-13T18:47:57.619Z" }, { url = "https://files.pythonhosted.org/packages/fa/ad/6f6755cd0842db46c3b10b1e4735f14aad78d71dea4753eb46933101711b/ast_serialize-0.11.2-cp315-abi3.abi3t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ee732ae167e686d1d3c00f98d7d82b23138304694f0441b14d7ddf9c0f8a921c", size = 1287792, upload-time = "2026-09-13T18:47:59.227Z" }, { url = "https://files.pythonhosted.org/packages/03/40/5da672f5dd23fb7dc0c884c97711e56a3540f2fe3c4355a81f8beb385911/ast_serialize-0.11.2-cp315-abi3.abi3t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:75a1c7f46b9c19fc0ae01ca6fd076301628faa2ed7a8edbd55c6353c483946a3", size = 1557971, upload-time = "2026-09-13T18:48:00.96Z" }, { url = "https://files.pythonhosted.org/packages/df/c7/2bb25684f697801eb72866fdb94ed5edbff3867ce878b0e542a4a5b9dab9/ast_serialize-0.11.2-cp315-abi3.abi3t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2fdf31a0bb85ea2575cc91669f005e6647d2efed491231c4dc1497bc9a5b3aa6", size = 1303230, upload-time = "2026-09-13T18:48:02.337Z" }, { url = "https://files.pythonhosted.org/packages/d8/85/754681846f26e0ff1da729b1ffe3171e93c22f0aa6ec3cea5b14e3703846/ast_serialize-0.11.2-cp315-abi3.abi3t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b78e6fdef3b06c86ed263e1962fee5a7b9d2d158e738b212d13b2c605ee12f5", size = 1302271, upload-time = "2026-09-13T18:48:03.915Z" }, { url = "https://files.pythonhosted.org/packages/fb/dc/f5521d8cb44b69095c3982ae3658a12c403e0efa19e51aeb9c8a79dff60c/ast_serialize-0.11.2-cp315-abi3.abi3t-manylinux_2_31_riscv64.whl", hash = "sha256:8d62a47714c8bc432b9fabcc29989c815c5da17327d35151f2fd0d85c2a7a5ff", size = 1309529, upload-time = "2026-09-13T18:48:05.562Z" }, { url = "https://files.pythonhosted.org/packages/73/0d/649182c7fd7c4f782279bed514de2dd67e48a5afecb605a098d64fdc01fd/ast_serialize-0.11.2-cp315-abi3.abi3t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8a5ffa70e76191dcf240d3c43e20c93b3bfd26f54d89148c762d57837f5bcd2c", size = 1356869, upload-time = "2026-09-13T18:48:07.534Z" }, { url = "https://files.pythonhosted.org/packages/bf/cc/aff4d84c16afa742d13a75384127c7d24594dc8c304f0558a15924fd51af/ast_serialize-0.11.2-cp315-abi3.abi3t-musllinux_1_2_aarch64.whl", hash = "sha256:bfbe47a3a7c368f28836e78b2440a3643ac0ec4c67d9fe53588e1448f0a3d35d", size = 1460006, upload-time = "2026-09-13T18:48:09.162Z" }, { url = "https://files.pythonhosted.org/packages/94/a7/891cbec2e5e0d7159196159d3ff0646622f3120ff4576c839ac2dd56c719/ast_serialize-0.11.2-cp315-abi3.abi3t-musllinux_1_2_armv7l.whl", hash = "sha256:7f1823275b246f9c7d373be6879e4eec09686948895d4ad083f4b27fd7e4da70", size = 1562935, upload-time = "2026-09-13T18:48:10.978Z" }, { url = "https://files.pythonhosted.org/packages/45/c4/2c8c4498340ea9aff87a9fd408309aa25d56dd51d7bbddfdb46a3c31424a/ast_serialize-0.11.2-cp315-abi3.abi3t-musllinux_1_2_i686.whl", hash = "sha256:57c0f5cb0021a5beb1e5e4d6e840ae2f23a28909703ef4d256a144cc1ad3d437", size = 1557109, upload-time = "2026-09-13T18:48:12.616Z" }, { url = "https://files.pythonhosted.org/packages/0d/8b/c5d4e5226fa18885fe17f949aee3ab1aeb8389c384d946ec1b7c9489cc94/ast_serialize-0.11.2-cp315-abi3.abi3t-musllinux_1_2_ppc64le.whl", hash = "sha256:cd320a5c4f1f2742af97eea22954f776379175c5ef2504801e9a155f2ff9a4d7", size = 1691603, upload-time = "2026-09-13T18:48:14.293Z" }, { url = "https://files.pythonhosted.org/packages/73/d6/1d2ca472586f9e3416a289a22f36eeb6dd6f47d77b1a4aba358405babbc7/ast_serialize-0.11.2-cp315-abi3.abi3t-musllinux_1_2_riscv64.whl", hash = "sha256:13b13afe32e845c86a573497729e1b7ddeb26c572c78bf50ece51da23b8fad5e", size = 1483053, upload-time = "2026-09-13T18:48:15.789Z" }, { url = "https://files.pythonhosted.org/packages/0e/16/d3703a7c1e3c76b144ac9349a54d3926d0749918a8dc13a66cede208b8ec/ast_serialize-0.11.2-cp315-abi3.abi3t-musllinux_1_2_x86_64.whl", hash = "sha256:9d80a81ec84660422579bdb8e789f656a794b48c7a1ae1261f6bd8bc1897d17d", size = 1502499, upload-time = "2026-09-13T18:48:17.405Z" }, { url = "https://files.pythonhosted.org/packages/2b/e4/d974e55c2e247ef26ed1df01c74940583db9a5b3a8bcaad5732c6e2047fb/ast_serialize-0.11.2-cp315-abi3.abi3t-win32.whl", hash = "sha256:af8c003ce721b0099dd55cef4ba733500fc3054ea0cc8565d8957aaf7cccdeb4", size = 1119739, upload-time = "2026-09-13T18:48:19.005Z" }, { url = "https://files.pythonhosted.org/packages/0d/00/d229443488e095054d5e0c0cc20689a2633b899d735849ff1b2c8e4f0cbf/ast_serialize-0.11.2-cp315-abi3.abi3t-win_amd64.whl", hash = "sha256:554d117cb916d8032d85007c654d179efbbfd446174c048062778136a922944f", size = 1158602, upload-time = "2026-09-13T18:48:20.524Z" }, { url = "https://files.pythonhosted.org/packages/11/75/389fc1a6cfa0c4b2ce522f47d8401329d8bb11732e516d46465960fef1d9/ast_serialize-0.11.2-cp315-abi3.abi3t-win_arm64.whl", hash = "sha256:d60515335750d431e462af6e722bb55720a5e7827192777bddfd9c4376065a4d", size = 1128842, upload-time = "2026-09-13T18:48:22.052Z" }, { url = "https://files.pythonhosted.org/packages/45/7d/c4f36898f19c728d091cdfdf960c9488e8d82bbe2e49ba13c05f43907a5d/ast_serialize-0.11.2-cp315-cp315-pyemscripten_2026_5_wasm32.whl", hash = "sha256:89499a439955931281986e97ca4dd3c064bf0d2e0027c0017344eb86667733a1", size = 897204, upload-time = "2026-09-13T18:48:23.695Z" }, { url = "https://files.pythonhosted.org/packages/b1/54/f67120006fc73a55b6d057d4662d061fbb4eceafce3047c76ca8b382eb11/ast_serialize-0.11.2-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:daadf1c3e0224621607ffe16f1379e4bd372271ed2e1db8a67878f0bab3ef7e4", size = 1240734, upload-time = "2026-09-13T18:48:25.287Z" }, { url = "https://files.pythonhosted.org/packages/9a/7e/8f2ab68bddbe58a66fbbaad87beeae3e7d7edddb17263d1fc423936cf34d/ast_serialize-0.11.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:1844ed9a487fb3de7325c52ddb33f2918b66b65cd54d3f8d83d23785ffe99fa4", size = 1228053, upload-time = "2026-09-13T18:48:26.788Z" }, { url = "https://files.pythonhosted.org/packages/d2/1b/8a69ab68f4c1603819f0481d756abdd8caf27cec7f1d77caa71007ebe997/ast_serialize-0.11.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b17869f4ba261a5fa468a753328a548f4dbaf74b4eadae9e28aff66df7f1425b", size = 1292542, upload-time = "2026-09-13T18:48:28.295Z" }, { url = "https://files.pythonhosted.org/packages/d1/ce/872f2e00f0467c289e483f0a34543463347243a2d0632748d89fcee5e0dc/ast_serialize-0.11.2-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:feb16d9c2a720e0120c58dd5d6e7b3c7c86b43249b60a3bc212bcb8fa031e2dd", size = 1294791, upload-time = "2026-09-13T18:48:29.969Z" }, { url = "https://files.pythonhosted.org/packages/3a/82/36277c12af861c64b375c316135d8feffe3f400568463a8d2b2de4c2c4fb/ast_serialize-0.11.2-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f3109fe4805384effc8d0f8e41fbf875aa8f389af91b4348c1cfb60ea6e4cb82", size = 1567583, upload-time = "2026-09-13T18:48:31.85Z" }, { url = "https://files.pythonhosted.org/packages/b0/d7/ec643df91cea8bcbcb4e8011d6a8b08e5119b84f9554879f3e3c786d29d1/ast_serialize-0.11.2-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abdb3e49ba053c3486ac1263bee9f16cc9a4a8abd9f8c90bfc21e3669f3ad9d1", size = 1312878, upload-time = "2026-09-13T18:48:33.495Z" }, { url = "https://files.pythonhosted.org/packages/04/6f/4c992cd7841ba589fefb14ddc9aff2f6db7f2a615d4074f9ad04115b5ce0/ast_serialize-0.11.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a7004ba572f09be34342ccb98dcd4bad5707d3d81adc8cb4c3f685d2a2c51bbc", size = 1312642, upload-time = "2026-09-13T18:48:35.294Z" }, { url = "https://files.pythonhosted.org/packages/d5/e3/22aaa209c231a83cfea004fd67dee7a7a54da3f169c6c460b14b96887385/ast_serialize-0.11.2-cp39-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:59c25f47524efa052971b860e128b1add0c94ede7dd16b2962952c85c3582365", size = 1319776, upload-time = "2026-09-13T18:48:36.866Z" }, { url = "https://files.pythonhosted.org/packages/c1/f7/d4685fb54d10108ce44d3bc893ef670854d61645d47ed96d73524db90c23/ast_serialize-0.11.2-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f3a367e0e05ed2d1b747ceb07aa728a8c204cc008b589127e9bd4f40053d7575", size = 1365324, upload-time = "2026-09-13T18:48:38.412Z" }, { url = "https://files.pythonhosted.org/packages/42/3a/250643ffad02bda520c50a9a5f02a5d43259a06f34ce393c91761d134d7e/ast_serialize-0.11.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:00bbf1f6669f813b48925b759f7ae4591067d456d443924055cab386e7e0a719", size = 1467653, upload-time = "2026-09-13T18:48:40.348Z" }, { url = "https://files.pythonhosted.org/packages/c7/da/af66a646b9b7f8fdec95ce83fc7b1fe538b06864bc79bd554ac4fae2e6ea/ast_serialize-0.11.2-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:ec1c20f89c3e0d83576e3c06f79375ce936266591fe0d5fd969914af3185cbaa", size = 1571914, upload-time = "2026-09-13T18:48:41.968Z" }, { url = "https://files.pythonhosted.org/packages/34/82/77a9714564b9e8800087a8afec41527c65c39e49282baae2ac847b9c1c6a/ast_serialize-0.11.2-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:c58bb119b73657fdc5569692f316e1e25ca114bd62f7782eb527c6be438ba3a9", size = 1569862, upload-time = "2026-09-13T18:48:43.701Z" }, { url = "https://files.pythonhosted.org/packages/65/06/fa77b52f46b9bd6dcd8ff2b880e3781f8c1a316bb1342bc3de92907c6f96/ast_serialize-0.11.2-cp39-abi3-musllinux_1_2_ppc64le.whl", hash = "sha256:f739e0b601be7300c5697a2573d9200bd1db74b34ab111ef9537b9d5dcd7f106", size = 1699020, upload-time = "2026-09-13T18:48:45.261Z" }, { url = "https://files.pythonhosted.org/packages/e1/09/239c83153c7e0798e5867d6909cb06f53dccfef02f6999c8e2e21ecb98c3/ast_serialize-0.11.2-cp39-abi3-musllinux_1_2_riscv64.whl", hash = "sha256:cae5addfbb54cc1d47fe947ef9138e9d83849ed1cbc72b819cf36d96a2315b07", size = 1492869, upload-time = "2026-09-13T18:48:46.922Z" }, { url = "https://files.pythonhosted.org/packages/2f/eb/6108fb9a43fc7ab5529856e38e33c6e3e064fbfe375fdcbb208c7cd5438d/ast_serialize-0.11.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2fa3be25f7f5351b1b39c9f8a52779b2dbf21199efbae564b4746422e8edca4e", size = 1511621, upload-time = "2026-09-13T18:48:48.667Z" }, { url = "https://files.pythonhosted.org/packages/8a/82/60367e58ef346a41ebc90d3f28593c1b8f5c2cb5314c7b2bbd98910ee131/ast_serialize-0.11.2-cp39-abi3-win32.whl", hash = "sha256:d70556a2f9230a44c99a655774cde823f056efc34466eabfb4085f0cb1ea9f99", size = 1125873, upload-time = "2026-09-13T18:48:50.661Z" }, { url = "https://files.pythonhosted.org/packages/23/bf/b419c3205ce1143ba7c69baef4f0ba43c14d8712113bf34f9e0d27d609be/ast_serialize-0.11.2-cp39-abi3-win_amd64.whl", hash = "sha256:b9065dd23131a23b41f5bab3bf4e9b3c350a3fe8e36e8200eded9b729fcea484", size = 1165434, upload-time = "2026-09-13T18:48:52.169Z" }, { url = "https://files.pythonhosted.org/packages/91/a7/c8bbb2173f7a7131b3b2412035b2d814ab5ef2ce9799bd06f07c451640e4/ast_serialize-0.11.2-cp39-abi3-win_arm64.whl", hash = "sha256:dab599cbdcb7b45b18c41fad746645580b3a24357082b7f0e8921cd373804f27", size = 1136031, upload-time = "2026-09-13T18:48:54.04Z" }, ] [[package]] name = "babel" version = "2.18.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/7d/b2/51899539b6ceeeb420d40ed3cd4b7a40519404f9baf3d4ac99dc413a834b/babel-2.18.0.tar.gz", hash = "sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d", size = 9959554, upload-time = "2026-02-01T12:30:56.078Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35", size = 10196845, upload-time = "2026-02-01T12:30:53.445Z" }, ] [[package]] name = "beautifulsoup4" version = "4.15.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "soupsieve" }, { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/43/65/318323f98dbee45d42dff61d8f047181bc6f2268a9068cfad035a46be5af/beautifulsoup4-4.15.0.tar.gz", hash = "sha256:288e3ca7d54b06f2ac191970bc275c1939cb46d450b255bf6718b04aa37ab4f7", size = 632571, upload-time = "2026-06-07T16:44:20.453Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/88/c6/92fcd42f1ba33e1184263f25bfabf3d27c383410470f169e4b8163bf9c17/beautifulsoup4-4.15.0-py3-none-any.whl", hash = "sha256:d6f88de62e1d4e38ecb1077eb9724cd0eff29d2a08ca16a401e9b9e93f117cf9", size = 109924, upload-time = "2026-06-07T16:44:21.566Z" }, ] [[package]] name = "certifi" version = "2026.7.22" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/a3/c2/24167ea9858356b47a87a50d39908bfdb72ceeefe0041586e704e5376b3a/certifi-2026.7.22.tar.gz", hash = "sha256:741e2c3b351ddf169a738da9f2c048608ff7f2c5cc02f1ebc6b118bb090d5d55", size = 138112, upload-time = "2026-07-22T03:35:12.644Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/0b/a7/71ac2cff56fec219ed242bb11b8efb69fcc4bec75db06fb7bfe35de520e6/certifi-2026.7.22-py3-none-any.whl", hash = "sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775", size = 136983, upload-time = "2026-07-22T03:35:11.276Z" }, ] [[package]] name = "charset-normalizer" version = "3.5.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/e5/3f/143b048436775b0f76ac3eec145c019e8173ccc2885c8f20319b996d5e83/charset_normalizer-3.5.1.tar.gz", hash = "sha256:6117b84ea48435e5356dc737f5121485c30920ba43375fa7b434fd753df0eac3", size = 171764, upload-time = "2026-08-15T08:20:44.807Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/30/27/78873dc8b6a56357517b74b6bb9568b80450e7bb4f6ef7e3fa9d22aa0bd7/charset_normalizer-3.5.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:5b6d1386bf0096d26d3a863dc0a487a5b4eb9aa93cf5ba69683d29dde6b9d60f", size = 344456, upload-time = "2026-08-15T08:17:10.072Z" }, { url = "https://files.pythonhosted.org/packages/9a/4c/be49ada26b1f0232d57aa89bbebf997a5cc2332a5616b6eca26ff680044d/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4582c27e8c889d64811987b5967fbd3ae0c823fe1fd933b543d55ac20bb475fa", size = 238530, upload-time = "2026-08-15T08:17:11.563Z" }, { url = "https://files.pythonhosted.org/packages/76/84/6f1290fa07ae6978d3960caa3eb1b8019bf9284ab7c2297b00c099ef4250/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:1d1c7a53a6c2103925cdd6d7229f8c567379f211c869793df679f2e9f738c369", size = 230200, upload-time = "2026-08-15T08:17:12.919Z" }, { url = "https://files.pythonhosted.org/packages/e7/a0/47b18adeed31c8f16ba9700f32c1b18594cfa09f47eb672a488c273c22bf/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e6621fb2a4988d6e53eedc455e5903e2679f3967b8acb3d639f1b63c14a2e893", size = 262222, upload-time = "2026-08-15T08:17:14.571Z" }, { url = "https://files.pythonhosted.org/packages/38/fe/341861ac118dae06f3ec0eb487488af52128f2ef2faf0b11003944d22259/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7c0c10730342b0c9b35dd1d619beb8214e520bd96a1f870f452680b238aab3e0", size = 258951, upload-time = "2026-08-15T08:17:16.158Z" }, { url = "https://files.pythonhosted.org/packages/6f/89/bb5108dc6c3651dca963f2b0a3ba19bbcb370c94e1b6d3e0e844a58e6dca/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b9af956078716df40d985fb0dfeb2c2120c5ca92ba4ff4b388acfd01cdc14d08", size = 248801, upload-time = "2026-08-15T08:17:17.683Z" }, { url = "https://files.pythonhosted.org/packages/b1/ba/ef83ae3aca816393decfa3530976f38a79812d707b80b580ac33b83f9877/charset_normalizer-3.5.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f9f8405c2c758532c74fed975dbee57be1f31a6e865c031870c79a6ed3212ada", size = 244070, upload-time = "2026-08-15T08:17:19.191Z" }, { url = "https://files.pythonhosted.org/packages/f6/0b/c5292a2462d69b7378ea89793bbb5b2b6fcf6f7dd6d1667f9619094ad553/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:96fef3e886d6a9874b14f27fc193fbdc69d5d8035783d86aa4e1cea594e695f9", size = 240110, upload-time = "2026-08-15T08:17:20.547Z" }, { url = "https://files.pythonhosted.org/packages/46/22/111e5be3b740d5c2a5bfcedb3d237b6591e5c2e82ae9d6ffcb121fe0909c/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5d8531a6569d025f68e2321e7638fb7978f23db58e5f69f56913837aae03816e", size = 232836, upload-time = "2026-08-15T08:17:21.895Z" }, { url = "https://files.pythonhosted.org/packages/f9/d2/d2aad6fe0dbb44b194bf3becb60f5a0ac48446ade999a47fe7bb41eb09a7/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:aae2ee51122d3ae968a3837d97dc24a0aeebb0dea23694422cd172bd30017cd6", size = 262712, upload-time = "2026-08-15T08:17:23.727Z" }, { url = "https://files.pythonhosted.org/packages/35/5a/337e4663a5eae6de99db940ee8066d4145caafb61327db62deda15313cce/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:7235dc28fc6dd9d832ac7c7bce95367dedb85929f17368a0c2bee1e080b9acbf", size = 242977, upload-time = "2026-08-15T08:17:25.157Z" }, { url = "https://files.pythonhosted.org/packages/ca/85/f82f8a92e31c7519410e2e1afdc630f28ec47490ce2c09a11c1a43cbb459/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4abdc5f9ad448c1ecbfae2974b820535d6bc6e7eef63babbab3d81cf46968c71", size = 260207, upload-time = "2026-08-15T08:17:26.602Z" }, { url = "https://files.pythonhosted.org/packages/b7/52/643d11ffd60e9ac2fd1fb87e167a19285b9eefeff4a40e63c87cbfbeab36/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ba501e667c17d8411f98e67a022d9604ef179aff0e459b7e292c796837c13573", size = 250562, upload-time = "2026-08-15T08:17:27.971Z" }, { url = "https://files.pythonhosted.org/packages/62/16/46556278c2168d12df9da7fede5dc6fc70e60301b26a82bbeec238c9cfe3/charset_normalizer-3.5.1-cp312-cp312-win32.whl", hash = "sha256:cfa1c0cc3a8f9f53f1243a5a99ac36fd003880199383b37672e86ddda9cb07e2", size = 178507, upload-time = "2026-08-15T08:17:29.277Z" }, { url = "https://files.pythonhosted.org/packages/9d/7a/4c6c298171e6b3e745633180ff59350fc0ca0db1ffd28df1e369e0579f71/charset_normalizer-3.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:3617ac3cfd8b9888f145ad89dd6e692285834b0201c6074a5eeaad3fd4d668c2", size = 200551, upload-time = "2026-08-15T08:17:30.668Z" }, { url = "https://files.pythonhosted.org/packages/cd/d7/eb95a042f0dd22e304b0b6472b154f3546a1a039a9ee89ccb2a7f61591fc/charset_normalizer-3.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:88e85ab89cb822c1e635f51d6d32e488f94e002e70e2f492bdb8b945543f345a", size = 180700, upload-time = "2026-08-15T08:17:32.028Z" }, { url = "https://files.pythonhosted.org/packages/bc/61/2cb6ad133dbbb449fa2d37ccae973232f4827e799af258d15e589a3d1e9e/charset_normalizer-3.5.1-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:4f298bdadb8f0b9e5672877f647d1be9373ef5320c9e2f049795e26cad28b6a9", size = 211584, upload-time = "2026-08-15T08:17:33.597Z" }, { url = "https://files.pythonhosted.org/packages/18/57/a305c968be1ca13f3dd1b32f445877e97addf55d80b65c7cb35fac82b777/charset_normalizer-3.5.1-cp313-cp313-android_24_x86_64.whl", hash = "sha256:88ca277405c2d3b71c4e1c2ee0e7966e807bcba86a69d11e19ba199d18ae4491", size = 223359, upload-time = "2026-08-15T08:17:35.022Z" }, { url = "https://files.pythonhosted.org/packages/09/0a/d3646670292ce8d8f8cc11ac067d44885e697a5591f57a9221128da5e7b3/charset_normalizer-3.5.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:9362dd90aa7dab48c0054a21187791ccf05473f7dba5d92b8033ae62164675e7", size = 194464, upload-time = "2026-08-15T08:17:36.452Z" }, { url = "https://files.pythonhosted.org/packages/de/93/d51ec556e01042fed6f993ea859311bc7917b466684182fbbceb6ca24762/charset_normalizer-3.5.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:977cdbd483a9cff38179bea4fd754289a6f2195c7abd414aba85410b3e66cc5e", size = 197676, upload-time = "2026-08-15T08:17:37.819Z" }, { url = "https://files.pythonhosted.org/packages/a4/a0/562247944386f7d4ef94467e84876600cc1e0f1b93239aaa9213d2bc3cbd/charset_normalizer-3.5.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e90251c0c7bdd54a100a0dce3c07b7e637278c93af29dbf78ebb89a58c4bac7d", size = 340473, upload-time = "2026-08-15T08:17:39.303Z" }, { url = "https://files.pythonhosted.org/packages/31/e7/1d994be1b93d41e9502b8b0460eaa88a1dd8df335df415db87d6c3e91ab2/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94d78ecec2605a8d0398b0f365d5f12a63248438516f5dac536a5eff7337df4a", size = 240156, upload-time = "2026-08-15T08:17:40.66Z" }, { url = "https://files.pythonhosted.org/packages/09/53/27923ce5cc6cbccb832037b27dca98882d9c53e9b69e866bbbef4aae7fc8/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d59b75732e9b6f27388e10c14b0259cc5f2e48c78627d185e6a177b58ad3cffe", size = 228246, upload-time = "2026-08-15T08:17:42.003Z" }, { url = "https://files.pythonhosted.org/packages/ce/48/5a97e84d63af1d55c07439cb80e56d99a8efb4295700eb4e18c0d1615d2c/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0d929fc574b4d6fd9e7c0f5c2ede8716a41911923aa7fa5fce38e0818aa4a1ac", size = 263660, upload-time = "2026-08-15T08:17:43.627Z" }, { url = "https://files.pythonhosted.org/packages/7a/c2/071575791dcc88316c0a9a65ce38897a82e4cfe4a325f0f7fe1b1ac47bcf/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:394fea06235c8543390050ed5f529187074b029fb027213f6c46ac11ab5d950e", size = 260354, upload-time = "2026-08-15T08:17:45.094Z" }, { url = "https://files.pythonhosted.org/packages/fb/af/63240b0c0248c075c2535a1f1bd992821d8251b9f173abc13329661d09e4/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62b55f6722735a6c472f88361cde6640608773d9443cebdbb51abf436a1fcdd3", size = 250638, upload-time = "2026-08-15T08:17:46.496Z" }, { url = "https://files.pythonhosted.org/packages/4d/66/70dfad64f15be09c15ccfee81330a7e515895dbe296dd23114e9a231268a/charset_normalizer-3.5.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fa48b1b63d639f9483e0633e092f5851e2348c352f1f9bb6c8182f87884ef876", size = 244583, upload-time = "2026-08-15T08:17:47.963Z" }, { url = "https://files.pythonhosted.org/packages/c0/24/ef36367d38b9ddd4bccbf72888c342e8de1f5ae506fa0b2dcf970e2732a1/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c71fb0d56c920c269cd3e2e3fe7c610e3f1fdb21a6ce60efa6430ff63676cea6", size = 242038, upload-time = "2026-08-15T08:17:49.481Z" }, { url = "https://files.pythonhosted.org/packages/db/ab/55e683ba0fff2e43adafc10daa3001eac90fdaa419a97227d5a7067eedde/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:485a0d363cafefcd2538a73c7c838daa2035f09b2c9f9b5e3133f80c6aeb84c2", size = 233677, upload-time = "2026-08-15T08:17:50.845Z" }, { url = "https://files.pythonhosted.org/packages/bd/67/0f40eaf8d1b6e7cf15e82382a2965efaca787fc1c2794b7021d37aaf5036/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5c0ea61a470e070686aa30892fed79e297d2c8d0ab46b8bcdf027d38c51da591", size = 264491, upload-time = "2026-08-15T08:17:52.61Z" }, { url = "https://files.pythonhosted.org/packages/5c/64/12b4c2a11ee8df4fcc518c78b0d93e3a92bd3d5253d1617ce74ff0e8c7ef/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:90b7481fb62fbe172c558bc6fd1c4c98d82004a54a7551f20e11ac9bf0b8708c", size = 245196, upload-time = "2026-08-15T08:17:54.023Z" }, { url = "https://files.pythonhosted.org/packages/37/2e/651d910af6d0fba325eee1cda37ec5443462ed25360e666c144166eb6091/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:35fe081843b35aad20ffeccec3eeffbe637b15d14f3fb22cc1b59cd8ec17e93c", size = 261660, upload-time = "2026-08-15T08:17:55.491Z" }, { url = "https://files.pythonhosted.org/packages/90/c6/b09e05e6db7f64338e0dc067c79577b1138da86c1e38369096851d96be88/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fd0350afdc3aabd5576f60ea109228bd5538139713c7b094c5cd27c73a98bc6f", size = 252618, upload-time = "2026-08-15T08:17:57.025Z" }, { url = "https://files.pythonhosted.org/packages/76/4e/362d4f9fdcdf5556fb2aa3ce7d4a58ebce03ed1ff03aa1d9aca8d02f13f3/charset_normalizer-3.5.1-cp313-cp313-pyemscripten_2025_0_wasm32.whl", hash = "sha256:9d9a0dc7cbe9bec24c3f767c9122c41fe5a1bc43f47cd099d00d393e09769de4", size = 140362, upload-time = "2026-08-15T08:17:58.425Z" }, { url = "https://files.pythonhosted.org/packages/b4/d4/703be739b26acce318bd29eb3b25b7209e1b1f527f9eae3d1f1f01fdde2b/charset_normalizer-3.5.1-cp313-cp313-win32.whl", hash = "sha256:d63600d620ad0064c3a748b950ac5ea38a80190e5498532efefa4b7b3f1da1f3", size = 177755, upload-time = "2026-08-15T08:18:00.037Z" }, { url = "https://files.pythonhosted.org/packages/8a/33/56d97ade41c8db611e727168c52ae46c9224c362ec28d4b65d7e9869e8da/charset_normalizer-3.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:aea996a6aba25260827c9ea511d1addfde2da9eb686ac961838509086188b7e6", size = 199295, upload-time = "2026-08-15T08:18:01.506Z" }, { url = "https://files.pythonhosted.org/packages/5b/75/5b20dd1e6573a01a08158fe104104fa2c8abf941745596954185726cd46c/charset_normalizer-3.5.1-cp313-cp313-win_arm64.whl", hash = "sha256:fd0a274c0e5f9a21565cd9d3dd749b61f96b7aa1e20a93aa1ba4029518f2e5c0", size = 179856, upload-time = "2026-08-15T08:18:02.929Z" }, { url = "https://files.pythonhosted.org/packages/29/cd/2b812ce5e888f1ce69a5350281e58aab07ae64a958ecae8912f30865718e/charset_normalizer-3.5.1-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:774d157f112367ff4abd29019f38f023c24e00e56edc7829c20e358a5a913ad8", size = 212318, upload-time = "2026-08-15T08:18:04.403Z" }, { url = "https://files.pythonhosted.org/packages/9e/4a/a6ee107430768a5334e6d63f31f148a04a1a491ef161a1ac9415a73f2fa8/charset_normalizer-3.5.1-cp314-cp314-android_24_x86_64.whl", hash = "sha256:26422d45fd13551cf564c58932f7d72b4f58b93b0fcf18c35ba6be12b46bb102", size = 224897, upload-time = "2026-08-15T08:18:05.997Z" }, { url = "https://files.pythonhosted.org/packages/c3/d9/35ae3f64f29d0179c35c3baefe575904df2913dde519129c7f75995a2b1d/charset_normalizer-3.5.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:09a7bba9f739468c8e78c36a75c33768e53cb1959fc638f510454c14683f00d5", size = 194848, upload-time = "2026-08-15T08:18:07.397Z" }, { url = "https://files.pythonhosted.org/packages/74/76/f2fc7380f056cc273a53af37f50d08ad54b2c59f61078f31432edcf1c2bd/charset_normalizer-3.5.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:4c9548dc78002099910abaebc0a72ac58b7d30931869e0351c09b507dff4ece3", size = 198163, upload-time = "2026-08-15T08:18:08.989Z" }, { url = "https://files.pythonhosted.org/packages/e9/40/095ce62fa078483cccc1fa2b36e6bc9580b85422a20ee9f925341c50e44f/charset_normalizer-3.5.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c428c6c31eb5f4277d7f8eccaf767fbd548ddd5ce3c8b4f4cbbfab3d96b5904c", size = 341823, upload-time = "2026-08-15T08:18:10.458Z" }, { url = "https://files.pythonhosted.org/packages/f1/5a/0e58b1c04a1596e0256f407274a92d5fb2ee21324409d1fab1da48a65b5b/charset_normalizer-3.5.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2f06b7eae9dbe77fe1d644ca244dad508de8d302870a43f3c559b521270938a0", size = 242458, upload-time = "2026-08-15T08:18:11.989Z" }, { url = "https://files.pythonhosted.org/packages/22/95/b4618ce912e6db0b1aae89ba788e38e8a7eba0f3025cc66e8c0699f977b2/charset_normalizer-3.5.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6b7430cf5728e68f6c462254009a6ef4086e1bea43cf2f57aa9c55fb4f50ff96", size = 226717, upload-time = "2026-08-15T08:18:13.401Z" }, { url = "https://files.pythonhosted.org/packages/8a/76/c681192bbda3d55356db5dadd64381d5202b37c6b598fcda5282e88b5d3d/charset_normalizer-3.5.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab743e9bc90c1f73552ec33e10e3331315acd2c397b36065b591b0181de533cc", size = 266111, upload-time = "2026-08-15T08:18:14.961Z" }, { url = "https://files.pythonhosted.org/packages/88/be/55127bfca72c0cff6c022488d140d7c5b04c771e3b72e9bdb4836d54979d/charset_normalizer-3.5.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f6f7deae3feb4edfa2efaf7c574fe88cbf055038a6abdb40188e4fff66d5699f", size = 263128, upload-time = "2026-08-15T08:18:16.515Z" }, { url = "https://files.pythonhosted.org/packages/e0/91/39c3af510b0aa32bbda03374259200f28430febfd1bf5e511fe765282ce5/charset_normalizer-3.5.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15f024313246a4ed976c60f440bb8d257815513a681d212ff74fd46f7d715a90", size = 251240, upload-time = "2026-08-15T08:18:18.127Z" }, { url = "https://files.pythonhosted.org/packages/1c/a5/cbe418bbc6ecdfc3e05a0116002897c4b403a5e838d697e64c78e9f0190d/charset_normalizer-3.5.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:823f82903d189af463d7df250ef1f7f696f3cee08cc8d91deb565e8d425f6506", size = 245282, upload-time = "2026-08-15T08:18:19.625Z" }, { url = "https://files.pythonhosted.org/packages/cc/a4/689bb42e8e7cd492f3cb64907c6bc00ad247ec9a3628cd3f8eed126e8ae1/charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:01e93745f7f219b703b60ba7afead36cfc4242782be5af484673fc500df12da5", size = 244597, upload-time = "2026-08-15T08:18:21.121Z" }, { url = "https://files.pythonhosted.org/packages/c1/ce/9962938e179cf9f699d3f1e7b3114b5d7642dee6a893745229f9dd04f274/charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:329fc3ccb63ad22d867d84c2adea759a64079a37ba4a343433b02c7a2816871e", size = 231376, upload-time = "2026-08-15T08:18:22.57Z" }, { url = "https://files.pythonhosted.org/packages/85/54/46000450ada53bd9eac5429a2c8c54cd2d9b39c0c255f229aea9af0948a5/charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:bb57753e36e4855b8ca375069482250a6246372331a3e4f3407eaebb007443f5", size = 266715, upload-time = "2026-08-15T08:18:24.235Z" }, { url = "https://files.pythonhosted.org/packages/3d/bb/618749d70f792b44252a777bf89bfb86823b9bbc1ea13fe8ce759b07f38a/charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:fce8cbd4997efeb450bd298b54f755dcdff18d496f7a5ddbb4867c6d7c88fdc3", size = 245848, upload-time = "2026-08-15T08:18:25.726Z" }, { url = "https://files.pythonhosted.org/packages/7e/3f/ffb64458527c7668031d5eb095d978de561958dc9f5b53f8e488a533e603/charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:6c9cdde8becb25a7fde49924511aa2644d6f8081cc8df8e9452724303348d8e3", size = 264521, upload-time = "2026-08-15T08:18:27.193Z" }, { url = "https://files.pythonhosted.org/packages/4f/ab/74a55fd803916a35ac461daf002708191aac19b546b80dc8cabfedc63d98/charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9ac4444d8d4fd4c4bd08bf451ed3167aa9e7ec6cdb41b648794f1d1103652e36", size = 253054, upload-time = "2026-08-15T08:18:28.568Z" }, { url = "https://files.pythonhosted.org/packages/a0/2a/6a9034b7d3c60b17499afb482df5878bf9fa20b50cc3887d5ef017a833db/charset_normalizer-3.5.1-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:f03ac127268b43ef4fe9e6ab6794a6794b49485a0cc0c1db79876d2f33f75bc7", size = 140580, upload-time = "2026-08-15T08:18:30.214Z" }, { url = "https://files.pythonhosted.org/packages/f3/46/1d362e1a00d035d66b9869e1281eee115907f7e390a16a07824ab5737360/charset_normalizer-3.5.1-cp314-cp314-win32.whl", hash = "sha256:1f5883d77fd409a261abb5dc8ccbe335720d798b1de4abb3b1d47ccbbc76b53b", size = 180325, upload-time = "2026-08-15T08:18:31.877Z" }, { url = "https://files.pythonhosted.org/packages/7a/7c/4938c329b6a9d446f6a59aa2092ff7118f274209b5ed0e26893d1d30a63c/charset_normalizer-3.5.1-cp314-cp314-win_amd64.whl", hash = "sha256:c658c50ac0c98cd755a2dd50b7977d3bca7df401dcc47fbdfa87db53ef7d4e8b", size = 204175, upload-time = "2026-08-15T08:18:33.466Z" }, { url = "https://files.pythonhosted.org/packages/ac/33/eeb384dbd8dec570661354592f4f2e1b2fcc92585624d146a000caf53841/charset_normalizer-3.5.1-cp314-cp314-win_arm64.whl", hash = "sha256:4bea7f8ebe90bbd7f0e4a2de42ca6924ba23e3e76418c408ff82f1d46fabd687", size = 184123, upload-time = "2026-08-15T08:18:34.913Z" }, { url = "https://files.pythonhosted.org/packages/1c/6c/c73fa9d5a85f6ab05395de61c5f6984e0a9ff40bb5ff888d46dff02526c6/charset_normalizer-3.5.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:fbc597639158fd7c14d55e808718848319540f51b0e6746e3eefa59723a4a348", size = 381682, upload-time = "2026-08-15T08:18:36.349Z" }, { url = "https://files.pythonhosted.org/packages/30/c7/63565f860921457feba93bae6c86fb7746deb4cffeed2f375cb845318146/charset_normalizer-3.5.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e71c909f353863b2b89c83de2ebed71ea6d0df8a6ef65a128193c5e650766bef", size = 240826, upload-time = "2026-08-15T08:18:37.887Z" }, { url = "https://files.pythonhosted.org/packages/06/ae/7ae8807410dfa33f8e6f1715740adeaafa8a816cc4cb33508f54b1f7c896/charset_normalizer-3.5.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7ac76cf9afd34929d76eb7fcb63be476a4853d8a96f0dcf2d0db68a0cbdf9885", size = 227861, upload-time = "2026-08-15T08:18:39.315Z" }, { url = "https://files.pythonhosted.org/packages/e9/a3/887c1642f0da26000b0e0652d91071113c0e72cea33952e225cf589f49a9/charset_normalizer-3.5.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a3a370082ce34d0612f421e15fe011c53bb1feff21a26d06ad4fb244dab5a375", size = 260758, upload-time = "2026-08-15T08:18:40.88Z" }, { url = "https://files.pythonhosted.org/packages/3e/11/e6f5b9a3d0e55b0ef7505cd3765cdd48f22db89994c947b316f52f801fd8/charset_normalizer-3.5.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:256dd4d85d9e4dc595e2bc983c980e73f62ddeb3165c58b4c3dfe78c5c8548c1", size = 259950, upload-time = "2026-08-15T08:18:42.351Z" }, { url = "https://files.pythonhosted.org/packages/1b/ee/e4e10a94d51cd1ee638aa7e00b65399e6b2a4e8376ab6d2eac9f95586671/charset_normalizer-3.5.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:58d4aa13a59c969dbfdf9e6a9560e242cbfd9e8a8f50c2747714df1a423adf65", size = 249329, upload-time = "2026-08-15T08:18:43.914Z" }, { url = "https://files.pythonhosted.org/packages/c4/25/d5f4198819e6059735a84e8d0bfb72dc33976da67b97adcd3fb5a5e07ec6/charset_normalizer-3.5.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0c6dfb5ca6723eeed15aa8e564a014d69fcb8812f94eef11fe3631e0508199f5", size = 243137, upload-time = "2026-08-15T08:18:45.368Z" }, { url = "https://files.pythonhosted.org/packages/a5/e9/e925ca7569cf9fb9701fd82503fee73eea5268fdb856bdd64947092d3daa/charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c010f5581d9c612804cc59fcf7b524b707fbcb72828551237ab545bb5c7034af", size = 242820, upload-time = "2026-08-15T08:18:46.842Z" }, { url = "https://files.pythonhosted.org/packages/34/17/672c251a888ed2aebcdd2fe830ad0104e25ff83c43f5c4f9c15e9fc6853c/charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:52ec005752a56ae79547a05c0139ca2501a0c866390b6115008456b9f0e7cde1", size = 230504, upload-time = "2026-08-15T08:18:48.353Z" }, { url = "https://files.pythonhosted.org/packages/3f/fc/f6a85abebd42ce4da2f1db0aa56cc6a0df1995e318b3875d14401b8381d1/charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:2bced4061f000f7187254a02ad3433ae17eaf991747ceea2f478422590a5bba9", size = 263087, upload-time = "2026-08-15T08:18:49.859Z" }, { url = "https://files.pythonhosted.org/packages/98/66/7c42677e739ba66746b297e2046918d793078094dc239e1e72768cffccc6/charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:9eea3ab2597a5e65fe65296e2d6a84570845a6b55532d90333d740d48bbc850a", size = 243269, upload-time = "2026-08-15T08:18:51.601Z" }, { url = "https://files.pythonhosted.org/packages/de/d8/a50b79237f417af10f8c2a501ce8d1ca87829a22e69117891ca4ba20a69e/charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:496846868fea80e479324862fa877f02411f2fd0f83b79ccee2607aa68b2a032", size = 258766, upload-time = "2026-08-15T08:18:53.23Z" }, { url = "https://files.pythonhosted.org/packages/2e/1d/0fc91aeaeb3c83b748f532399ce67cf84604b48297405d740000f7a9e786/charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:85d5855daafc240cc045c026d7a15fd198a09b0fc8ff6f5ecbb5297b509cb11e", size = 250814, upload-time = "2026-08-15T08:18:54.768Z" }, { url = "https://files.pythonhosted.org/packages/ae/10/3d8c777cf9024615295aa1b808324ad5b4a77855869c00824bad74ffaf8a/charset_normalizer-3.5.1-cp314-cp314t-win32.whl", hash = "sha256:58d3e12c88e0950bca850ae1f7c256055c097639c2edb9eb123af9807d8b15e4", size = 191074, upload-time = "2026-08-15T08:18:56.305Z" }, { url = "https://files.pythonhosted.org/packages/4d/81/ae557d3c44d1a1d688696d60563413a0866a91b7ebc50f20df838be3d8c8/charset_normalizer-3.5.1-cp314-cp314t-win_amd64.whl", hash = "sha256:acaf604462bf330b0d07e7a07c1d6e4adac79e5fb13e9c5140590542cafacc00", size = 216476, upload-time = "2026-08-15T08:18:57.889Z" }, { url = "https://files.pythonhosted.org/packages/27/e9/61c01fb8b804692569c036b3fc50495814502dcf13a60649c6055390b02c/charset_normalizer-3.5.1-cp314-cp314t-win_arm64.whl", hash = "sha256:fdb8a068947befafba9952162645dc2fecaeb400e64584829ed5e9b2fbe21a7f", size = 194115, upload-time = "2026-08-15T08:18:59.418Z" }, { url = "https://files.pythonhosted.org/packages/4a/4e/8544831ef59d8f27ce92c80871380fdacc8076a8a56ed62f82e54f991333/charset_normalizer-3.5.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:9085f87b0e38a2b92b8923059b4e8789fe40d9279712d15dcc670048d77079af", size = 342048, upload-time = "2026-08-15T08:19:01.054Z" }, { url = "https://files.pythonhosted.org/packages/7f/a6/e3b46852424246065355644f4fb6dbccc0239a42a2eee27ecfc8957f0bcd/charset_normalizer-3.5.1-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2679de311c7946dde5d3b6f44941844133ff5c7cb86099c0061ab1e8901c20a8", size = 242997, upload-time = "2026-08-15T08:19:02.492Z" }, { url = "https://files.pythonhosted.org/packages/03/3b/0cc9a26777334ab2f2e3089b948bbf4e4fe72ea70b897715ef6415043ec8/charset_normalizer-3.5.1-cp315-cp315-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:baf3775a2635e5a11fbd5e4e64ee69c7e86875d224a5c72aca4c141064589a90", size = 237014, upload-time = "2026-08-15T08:19:03.943Z" }, { url = "https://files.pythonhosted.org/packages/8c/c2/027335f0aa337a2a2e121bac1ad88c4f02ba6053ea0926802784f3db11af/charset_normalizer-3.5.1-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ac8c94b6539074e0f40899301273ac8402b9b3e01c7b7ba269ff30340aaaf20", size = 266174, upload-time = "2026-08-15T08:19:05.598Z" }, { url = "https://files.pythonhosted.org/packages/86/d3/e367787febe4e74769dec0f406f2c3c8d1b955fce5aee1fd0f94e8367a45/charset_normalizer-3.5.1-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8fe532b3c966d1fb794e0698e4589d0444017ae77fc0b31edea13c0e35bcc449", size = 263361, upload-time = "2026-08-15T08:19:07.251Z" }, { url = "https://files.pythonhosted.org/packages/af/3d/391b193eb9f3e84b02f9314088c386debdc0debee843535aaea2e2c6715d/charset_normalizer-3.5.1-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5c84bec0ab5ae0c64bfe73a7d2adcb5ce73b467523fc27fd6a28ab2aa6cbe35a", size = 252143, upload-time = "2026-08-15T08:19:08.816Z" }, { url = "https://files.pythonhosted.org/packages/2e/57/de221f1745a90d418199761967e2776bfe2c275a1194220985e8c1d37833/charset_normalizer-3.5.1-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:854066be00447fa8de2ccbbe893e2ffc4b123ef16d897af794c1e18bd4a714b0", size = 252086, upload-time = "2026-08-15T08:19:10.255Z" }, { url = "https://files.pythonhosted.org/packages/c8/e3/d119f86a01f9331e8186175f24873b1d74a7ee9e2e4b4d68f9947dae5afd/charset_normalizer-3.5.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:21b82d8082f6f5e7f456ef0bd16323d08de1266efbfeb476e64b2a91d1471a4e", size = 245231, upload-time = "2026-08-15T08:19:11.807Z" }, { url = "https://files.pythonhosted.org/packages/26/de/d8e48c135ae480879539cdb179c8d3b50c7879497d75dd899b5763b69cee/charset_normalizer-3.5.1-cp315-cp315-musllinux_1_2_armv7l.whl", hash = "sha256:838648accb3a7fd9803fd45c87bce8509648eb0c11bc34e216141300977244f2", size = 241546, upload-time = "2026-08-15T08:19:13.416Z" }, { url = "https://files.pythonhosted.org/packages/67/c4/217755fd1abc50d326c252922cd642002758095a81ff45010337b8b3ef65/charset_normalizer-3.5.1-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:195ce897c6153c0700078142cf8efe3e6454ca4cf4357499e4078dfd83396626", size = 267033, upload-time = "2026-08-15T08:19:14.981Z" }, { url = "https://files.pythonhosted.org/packages/b8/d7/34d8e404e358d2adcc5a228c2134643af00104c8fb0bf525f3688d756f05/charset_normalizer-3.5.1-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:978eab16f55b4ab2c2a745be9a0a840bf8f09a7f227d9c76eb30214d078865a5", size = 252045, upload-time = "2026-08-15T08:19:16.618Z" }, { url = "https://files.pythonhosted.org/packages/5e/fa/40414471acf0aa0692ca77305aa00e434fcd8288f0941c93c30e9a5f8f2f/charset_normalizer-3.5.1-cp315-cp315-musllinux_1_2_s390x.whl", hash = "sha256:cc0329df4caaceb950d2f580b5ac716a377f7059624a0bafaeaf8a218c6ed774", size = 264866, upload-time = "2026-08-15T08:19:18.101Z" }, { url = "https://files.pythonhosted.org/packages/32/90/fcc850bae791abd2e0c041847f13e270aa08692a79f3e00de6d2dce1cb50/charset_normalizer-3.5.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:687c9ca3035544b113bea2055e180af96fb63c0c476e22a9180f51925186e7b7", size = 253932, upload-time = "2026-08-15T08:19:19.734Z" }, { url = "https://files.pythonhosted.org/packages/af/af/53afe99068b3c10b4cbae592a52ef72a7c92c0188440e83ee3a078fd8f75/charset_normalizer-3.5.1-cp315-cp315-win32.whl", hash = "sha256:706bfd38730a5ac7a365793269a00f4e988178cec121391f4248d84ad8c972e9", size = 180320, upload-time = "2026-08-15T08:19:21.37Z" }, { url = "https://files.pythonhosted.org/packages/c9/bc/f46a132041b29e4a8779ed712d3df1bf112e94ca8de58b66d7ec2c0cf8b9/charset_normalizer-3.5.1-cp315-cp315-win_amd64.whl", hash = "sha256:92caef967d287a407085d61176fce4012b1dd62daed4eb6d5ceb26d3d2538712", size = 204174, upload-time = "2026-08-15T08:19:23.088Z" }, { url = "https://files.pythonhosted.org/packages/a1/5d/9ed554480eda8e447b673648628fdc29574d23dbad01fe11837adedd1cae/charset_normalizer-3.5.1-cp315-cp315-win_arm64.whl", hash = "sha256:5fc45d653ea8c9a20479167e11d4a0f8cb2fa3470737ab6f9c827532313187b7", size = 184126, upload-time = "2026-08-15T08:19:24.471Z" }, { url = "https://files.pythonhosted.org/packages/3b/32/9b8929bf384061ee1fe5d9c27c6f9776d3d824039ad4e14c88ec00c7808e/charset_normalizer-3.5.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:59171c6e45bf07d0d5cab3b0bf81d945035530f6873398b3b531c31184d46663", size = 381441, upload-time = "2026-08-15T08:19:26.038Z" }, { url = "https://files.pythonhosted.org/packages/96/10/e9aa7923d3ddac652c99a1c5f7be494e737e151566a44abe018daf757f2c/charset_normalizer-3.5.1-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9dbdd9205662134957cf0c324f639bdc5031c0ca056e2369e238db75187c0f11", size = 241742, upload-time = "2026-08-15T08:19:27.532Z" }, { url = "https://files.pythonhosted.org/packages/28/53/a2d249ebddf47b889a100c0bdcb61a2f9dbb8bc24ef325cc062e4f476877/charset_normalizer-3.5.1-cp315-cp315t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e4b018dc5a0eee4676e38fe84a47a427816c590b93b55d9025274ec4d6ffc2dc", size = 235298, upload-time = "2026-08-15T08:19:29.274Z" }, { url = "https://files.pythonhosted.org/packages/7d/07/469f78af590f7d5cd48e20d8dbfa3d66deeff9ba37768c04d886b5afd45c/charset_normalizer-3.5.1-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ced3fdd71aaa83ce593746c2edb42b7a59cb4c19c8b5c407781c72e493aae55a", size = 262500, upload-time = "2026-08-15T08:19:30.955Z" }, { url = "https://files.pythonhosted.org/packages/55/66/3bb56a47f7dcba014055b1a1d33c6f08bbe9c1e74dba154cfa25f90ae885/charset_normalizer-3.5.1-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:19a3dd5aa73cef1c99687c4fc57db016a9c17104ae1185da88ba566a5d3bebe4", size = 258888, upload-time = "2026-08-15T08:19:32.458Z" }, { url = "https://files.pythonhosted.org/packages/ff/c1/2adc2800903fb013210349313b710a5376856578d9e33e6b9a1d8b36714a/charset_normalizer-3.5.1-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cc5d36d96478aa9c60654bd932525bf32964c62a7281eafdf16d85003a8d6004", size = 250243, upload-time = "2026-08-15T08:19:33.94Z" }, { url = "https://files.pythonhosted.org/packages/95/b5/a18d0dd1157ab655cc2cb14a545f4a4784bbad70ab3502412e36097502d9/charset_normalizer-3.5.1-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:04368edf83514385ffc3e1cfd4546e595f4f1272dd23ba437a93a9cc3741d47b", size = 249871, upload-time = "2026-08-15T08:19:35.413Z" }, { url = "https://files.pythonhosted.org/packages/ad/c3/525f508cd1e58d0450ac55ed40ac75bc3a97482c59def5278456a5fbf03c/charset_normalizer-3.5.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:9b5db6052055d34d41230fb78d7c439c23dc536a9896f6cb039e8dd92cfc1263", size = 243580, upload-time = "2026-08-15T08:19:36.886Z" }, { url = "https://files.pythonhosted.org/packages/7c/c1/49a91fe7e97c8140094ca5c64161ab623a70d9f636bf834eace14048acb5/charset_normalizer-3.5.1-cp315-cp315t-musllinux_1_2_armv7l.whl", hash = "sha256:252d099029bcbea642f2a06c4ed5046bdf8b5a8150b64afa5e027e88b106e5ee", size = 239807, upload-time = "2026-08-15T08:19:38.392Z" }, { url = "https://files.pythonhosted.org/packages/d3/58/56a48c296601274c4689b864a8e2dfb209b81dfcb39472753ce95eea662b/charset_normalizer-3.5.1-cp315-cp315t-musllinux_1_2_ppc64le.whl", hash = "sha256:6199d5606e2bbf2b096cf64d03f8b6790c91081d5ac866b8e7bb6422738cc60c", size = 264083, upload-time = "2026-08-15T08:19:39.856Z" }, { url = "https://files.pythonhosted.org/packages/10/4c/dc48409274a1817ff349711d26c62aa0c597df865d4d69ef79160c859193/charset_normalizer-3.5.1-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:77efcff2b23071c349402ac1066667a3d011f62398d81408c9b88ad991747c9e", size = 250317, upload-time = "2026-08-15T08:19:41.53Z" }, { url = "https://files.pythonhosted.org/packages/81/58/d325912115caec62d6bdd77bbab5e0b7da5d234a9f20affdffcbcb530d0b/charset_normalizer-3.5.1-cp315-cp315t-musllinux_1_2_s390x.whl", hash = "sha256:a5cbd90ecf0fc62e64726917ad083b73001f0563657a87ec3c0b504e277dc90d", size = 258173, upload-time = "2026-08-15T08:19:43.07Z" }, { url = "https://files.pythonhosted.org/packages/34/f7/b13b1ccae2c8ec63980d13be1890eb73f8aeabbfce02a24aabc0908788f5/charset_normalizer-3.5.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:4d26f14f041e83dd8edfd61f4cd4fa7285d31798b5bf1f28e70c367ba6c41d61", size = 251960, upload-time = "2026-08-15T08:19:44.587Z" }, { url = "https://files.pythonhosted.org/packages/1e/25/ed3f9919c5aef8cc818be1f972f565f7610d7b2076b8ebb98839516ffc3c/charset_normalizer-3.5.1-cp315-cp315t-win32.whl", hash = "sha256:ac13b004224fb341e1e25a1ed5e19d32f57cdb2a403e01f003b46f051a550f6f", size = 191186, upload-time = "2026-08-15T08:19:46.293Z" }, { url = "https://files.pythonhosted.org/packages/69/d5/43c2b3e9d8267092b913eb8b0603f0f71993c395632886bd37a7223f96cf/charset_normalizer-3.5.1-cp315-cp315t-win_amd64.whl", hash = "sha256:35aea775dc2bd5f54cd84a1cd2696cc3207c479cb9cf0bd346f0d343e4300ddb", size = 215947, upload-time = "2026-08-15T08:19:47.853Z" }, { url = "https://files.pythonhosted.org/packages/a8/76/9aad3e9c8865e5e0efa9a7f6f81c37a67635a985145ecd44528a81e088ee/charset_normalizer-3.5.1-cp315-cp315t-win_arm64.whl", hash = "sha256:fb78f6e7fcd8ad785d28cd577168bc1aaee827b25bb8755638f694794ea98f0a", size = 193909, upload-time = "2026-08-15T08:19:49.383Z" }, { url = "https://files.pythonhosted.org/packages/5b/97/fb4e82231aba271ffd775a1b4993b0defc4e3059f286ae41d9433409fe85/charset_normalizer-3.5.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:41876ee62a3dddf48ff1121ad8f0798032aa03f2fd35f21f34a4cab14f18d8d2", size = 331467, upload-time = "2026-08-15T08:19:50.959Z" }, { url = "https://files.pythonhosted.org/packages/9f/2f/fe3f187327aac18e2d54e9d2b08e15d27bf9b642d9e51c219f130fc34d1a/charset_normalizer-3.5.1-cp37-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a6dac12ff6b846103483683f60c5f8fee205121adc58ffd87e90a90a3af69e99", size = 253057, upload-time = "2026-08-15T08:19:52.654Z" }, { url = "https://files.pythonhosted.org/packages/d7/c7/9e48cee5c161fe24da823b61bf381921d77cb994a0a4de148e95018c1984/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cee5dd7c6fb5dd52a0fe2a740f9bc6e3593f5f8b1788bde49de02086f30182b2", size = 240930, upload-time = "2026-08-15T08:19:54.163Z" }, { url = "https://files.pythonhosted.org/packages/49/e0/716601f3cc69be7b198951150c75ead1ece33c3c8036ff6ffa46029659a0/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:343fb4f2821043bd87095f7b08a1a181febc8e36ac64212143bbfd0a0e1bc235", size = 230822, upload-time = "2026-08-15T08:19:55.807Z" }, { url = "https://files.pythonhosted.org/packages/d3/05/71bfc5caa0abcc45aea1f6a4d50ac68e59605ddc7666fe8494f4cd229665/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ae4a097991662cd4fff0ddc74e0fe7874f82e00042fa0ea00855645ed0c79598", size = 260037, upload-time = "2026-08-15T08:19:57.312Z" }, { url = "https://files.pythonhosted.org/packages/c3/92/de7e32ed05341e7a9c4c877c318418197b7f2d66a3b68d561bf2ac57ca3e/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4b599739b93b2cbeded49645ae3c8d1405c29ddfbceac1545c87a3f9580a9e96", size = 255097, upload-time = "2026-08-15T08:19:59.056Z" }, { url = "https://files.pythonhosted.org/packages/f5/7b/ade0a122600319dfa0b1000ab0f9731c94a817904cf3c5de408c73a4ede7/charset_normalizer-3.5.1-cp37-abi3-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b39b69b347e5e47a3b5b8cfc005c68c1ba347474e3960236c4944a8ecd174962", size = 250166, upload-time = "2026-08-15T08:20:00.612Z" }, { url = "https://files.pythonhosted.org/packages/75/9c/019fbb9f4834491a160951349b1a3714439376f66e5f7cf18b4f18f0c7aa/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:a2028475ba855475b8b4d3cfeb4994269c967aea8b9892dfba907f4263a863a3", size = 241821, upload-time = "2026-08-15T08:20:02.321Z" }, { url = "https://files.pythonhosted.org/packages/2b/b8/11d4840bfc99330cc7fbcc2681ee5a044553a6e77655508d8f9b2bff7b34/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:36047af20e17097c3bb9476c2b7655f2f7aa51322c0ba58c07695bedf755a950", size = 232529, upload-time = "2026-08-15T08:20:04.008Z" }, { url = "https://files.pythonhosted.org/packages/18/96/2b3a21492d9f65171ac75d872f5018260013d00bfa0ff70ec9f179148cbd/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_ppc64le.whl", hash = "sha256:4c4fb141a727957c93edfe5c32a26ceb6b5f6461d67146e2d39f51e16170bea8", size = 260348, upload-time = "2026-08-15T08:20:05.877Z" }, { url = "https://files.pythonhosted.org/packages/d6/aa/a69a2028e8bd052476c245460ab19d7de595de084dd968f2d75cd50c3e25/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_riscv64.whl", hash = "sha256:2f293479cce755c75f1697e87c409b7ae4c555c7dfecb6e988ad13abba943031", size = 247234, upload-time = "2026-08-15T08:20:07.487Z" }, { url = "https://files.pythonhosted.org/packages/35/8a/3d130aeabcaf3d2466af76b7b141c08d9e89c9016ab4b7cdd0f7dc2d1c62/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_s390x.whl", hash = "sha256:3588e376b3ea2eea84976f67273d679f229e24c66dce7b82ae45aef04ff6e072", size = 256917, upload-time = "2026-08-15T08:20:09.142Z" }, { url = "https://files.pythonhosted.org/packages/80/c2/a7379b840292d0c1ab9fbd17d1f3967aa81794dc95bc74be8999d7fedcf7/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e199fb99720074809a7720f1c0b4d919eea8b87e88713e0f8f602f7bef543d9d", size = 254846, upload-time = "2026-08-15T08:20:10.727Z" }, { url = "https://files.pythonhosted.org/packages/01/65/d43b714731bb2f40d4053dfa00ecfc1c5a301f8e3316c5db3a09af59fe94/charset_normalizer-3.5.1-cp37-abi3-win32.whl", hash = "sha256:dd732602a7009217f658d5863d12d79d373a4de0eebc111094bcdd3bb8e0a6cc", size = 174216, upload-time = "2026-08-15T08:20:12.334Z" }, { url = "https://files.pythonhosted.org/packages/35/4f/b911ed898b26a09789eba9c9200c999aff6c61b4bafaf4838e56d1a1e1a3/charset_normalizer-3.5.1-cp37-abi3-win_amd64.whl", hash = "sha256:70055ff39b97c99e7ae40ea3e393fb62aa2e44dbd9b29f8d14f42fb0025c3959", size = 199764, upload-time = "2026-08-15T08:20:13.908Z" }, { url = "https://files.pythonhosted.org/packages/f0/a7/920baf467bfd9bf689f3b318340f37aee4572a71f162bd8db51da55ba4fa/charset_normalizer-3.5.1-cp37-abi3-win_arm64.whl", hash = "sha256:87e4f41d375c0b9be2fb5251aee4b8a689169e134535aed81bf085c3b647451e", size = 287318, upload-time = "2026-08-15T08:20:15.551Z" }, { url = "https://files.pythonhosted.org/packages/cc/61/d01fc49b8dea277640b55a9e15960dbca9fdc8c9fde18e572d39c59f4019/charset_normalizer-3.5.1-py3-none-any.whl", hash = "sha256:6df0ec430f9a831772c23ca5a224cba36517a58a84bb32c32bb59a9fa67c47f6", size = 68658, upload-time = "2026-08-15T08:20:43.306Z" }, ] [[package]] name = "click" version = "8.5.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/c7/0e/7fa0ef50764b67090eca4114772a2abf8b6148198475e54c660b97caeee6/click-8.5.0.tar.gz", hash = "sha256:ba0d2089de75ea0310e2dde03160e6ca10009947fb95a182f9b54021bb272e34", size = 382235, upload-time = "2026-08-26T13:33:14.56Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/58/50/6c0d534c5f134586a8e1ba4e330569e32f057e33372ae556463212fb4cd3/click-8.5.0-py3-none-any.whl", hash = "sha256:255bc9599cf7748b4b1a446ccc735421bd08a2ae529a8b88597d3de5664ee360", size = 125251, upload-time = "2026-08-26T13:33:12.928Z" }, ] [[package]] name = "colorama" version = "0.4.6" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] [[package]] name = "coverage" version = "7.16.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/2f/55/d1eaf3e73781174340a00dc1ba2aee8a65f82fadb18e2797b192b6b3925b/coverage-7.16.2.tar.gz", hash = "sha256:ca64d9f1f384f151b9511bec01126072acd2f313439f8ed015a22d8790aab6fa", size = 971999, upload-time = "2026-09-27T12:29:01.118Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/5e/2c/f8296c63c5d542f3d21aed685e56b7031a419037d155bb3382fc0940d249/coverage-7.16.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:218d742afca2b5ad5ca759e93eddedfbcc6eadf8322f080dcefc40b7bd4e2d48", size = 223969, upload-time = "2026-09-27T12:26:16.753Z" }, { url = "https://files.pythonhosted.org/packages/90/23/6f3dcb1423a0d43216e402ea1746e4a7c7c44f38896b97dd573790f56a40/coverage-7.16.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a9a638be322a8d76a41cdb17781c7f82aaee6a66493d8ffb7e2c09ee22423d99", size = 224328, upload-time = "2026-09-27T12:26:18.15Z" }, { url = "https://files.pythonhosted.org/packages/ac/7d/8f3b6dc920e3fc6732f7678785a2091db439f186afbec30dbf2214d9b1f7/coverage-7.16.2-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:724bd0f1e81856b35e59fc98cf7b4e544a3cb662e4e0864dca73d4326ee9d808", size = 255832, upload-time = "2026-09-27T12:26:19.799Z" }, { url = "https://files.pythonhosted.org/packages/d1/36/6c45f15be4eca4ac1062c6a55a323286494c99726a7e58951fe85967ac08/coverage-7.16.2-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5375ebd99038021b35e99dc88255022912c06565d316212f4a576e4b08d30f5d", size = 258571, upload-time = "2026-09-27T12:26:21.199Z" }, { url = "https://files.pythonhosted.org/packages/34/fb/b54cbeba3ad89082c2e441278681859e538322cc34b84b2af7ebff00080f/coverage-7.16.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a076277ca9f5750cc230f0f578ebd2620cec60255b25707361699fef6fb465c", size = 259680, upload-time = "2026-09-27T12:26:22.822Z" }, { url = "https://files.pythonhosted.org/packages/6e/a2/0dc65ec3d61930e1e4c2e371763b15eb4290896eb343a12d5d3091308116/coverage-7.16.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:58d4a54c6ea672afef66d49be922a2c69826c5ae1a42a9cd94f0c9c2bacdf800", size = 261945, upload-time = "2026-09-27T12:26:24.336Z" }, { url = "https://files.pythonhosted.org/packages/d6/93/5fad7a61f2c14e08e98946fc31c1c7ffc1195061bf3fdc351db3be77a863/coverage-7.16.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0dcbcfcc059117284c603ff8cb61a65872512882f84a8cf0339241f7f7c2f148", size = 256191, upload-time = "2026-09-27T12:26:25.89Z" }, { url = "https://files.pythonhosted.org/packages/2d/47/74e5de9227b939ece9f64e729645ddc4296bea10dbfa98721c1333c8be2e/coverage-7.16.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:afdf43b72ef3876c1fe66423b91466e37877c9e81e8cec70542b7e8525b9d1b7", size = 257610, upload-time = "2026-09-27T12:26:27.35Z" }, { url = "https://files.pythonhosted.org/packages/13/fe/2cf28d40b43645d1b72388fe3ee7f7c747533a6a9557bb8c24a7ae74fe1a/coverage-7.16.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9acc7f7ec4a1b5f89bd929fde5b8a714f6fafdc6cc18725413d510aa082b47ad", size = 255757, upload-time = "2026-09-27T12:26:28.949Z" }, { url = "https://files.pythonhosted.org/packages/d7/3d/7c149fd99fc8bbc39c80db5e688d1d39fd040be2ecb78b8335a51a55b9c0/coverage-7.16.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:80d3f7b48d43ee8fc5e8707a8adb43d743a5a1a85256c25a24f9d6d0e2238fa6", size = 259822, upload-time = "2026-09-27T12:26:30.515Z" }, { url = "https://files.pythonhosted.org/packages/e6/3f/b283fce09d5995e227bd8e513358dd7471bedc0f78abc85a925ebdb0a2f6/coverage-7.16.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:126d1af8804d7224421fe991ff65d3ce649081560df7a98b1a5ffff07f9923bd", size = 255325, upload-time = "2026-09-27T12:26:32.037Z" }, { url = "https://files.pythonhosted.org/packages/bf/91/f3325edf0c4223fb1fe1532b8dbef2a1d2f729459a9a7d1a44d073bae534/coverage-7.16.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c19cd6d025c1673f22afcd22c7df8a662d779e05d8e3fa6820c22afb895b0206", size = 257191, upload-time = "2026-09-27T12:26:33.525Z" }, { url = "https://files.pythonhosted.org/packages/c4/89/21eb5e83ecf2eed523c4eb3d65ae513cd082c8fd1b6deb34c4cb6c332f97/coverage-7.16.2-cp312-cp312-win32.whl", hash = "sha256:152877cdc8a07264882cfcd503ba56a3ef6cba56a70e8c70f6eb8ffd7384789a", size = 226034, upload-time = "2026-09-27T12:26:35.021Z" }, { url = "https://files.pythonhosted.org/packages/db/de/e3ad6d864c0833624b4f1f9b53f9e58e116c945e5e965c3f1e172c5e84cd/coverage-7.16.2-cp312-cp312-win_amd64.whl", hash = "sha256:e6c52d3307824ff93b39efd99e4185d557db40bd841452abfb32e5d9151ca162", size = 226569, upload-time = "2026-09-27T12:26:36.604Z" }, { url = "https://files.pythonhosted.org/packages/3e/c1/bccc58ebe5489cc70628f635c1932fd371f5d7da850dbcf960f95f4c4afc/coverage-7.16.2-cp312-cp312-win_arm64.whl", hash = "sha256:a678c0b6b22086ec2427359d22e37445d4a792f5fdbbc744112c7dade65cad02", size = 226372, upload-time = "2026-09-27T12:26:38.406Z" }, { url = "https://files.pythonhosted.org/packages/f0/f6/8eb4f220ef24f84fb27d852d4f9bf83e0c73ec1a4a08dd9a87e3f4529739/coverage-7.16.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1a37c6e478cf687e1aa30a593d19c92c02fad9d122b51ab73f51b8dc7a0c0fc9", size = 223993, upload-time = "2026-09-27T12:26:40.164Z" }, { url = "https://files.pythonhosted.org/packages/40/23/d4bbaf0c154e0b0c2b5264890dbf6ef098dcb50ec8f2469be9490d191660/coverage-7.16.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0993d0e90858c03943d3cb152e068a20dd4707924deec84dd2230261baae3b1b", size = 224368, upload-time = "2026-09-27T12:26:41.762Z" }, { url = "https://files.pythonhosted.org/packages/7f/48/fc1e88fd571ec5cb38150b7f89f7696ca1bdf9920e01432febb69774cc85/coverage-7.16.2-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bb2fc905bbf4e6b7f40806ea79e31515abf6349594cdf0adf27c4215f0463204", size = 255358, upload-time = "2026-09-27T12:26:43.442Z" }, { url = "https://files.pythonhosted.org/packages/1d/56/6785397d07c29c8e70fbb9a07e97d062b43c21ffc5f12385917847f09f63/coverage-7.16.2-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4358b9c8c0125b460407f3017c6cce8156e904b32772c5630d27112f52bdbfe5", size = 257955, upload-time = "2026-09-27T12:26:45.725Z" }, { url = "https://files.pythonhosted.org/packages/27/3b/c8cdd07721e5f99abd81cea970d971997f99bf158c0b85f51bd284179c8b/coverage-7.16.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f15254427c9b33eedac4f198eaf9e356eb4f6214551afb43da6194a2c088ad7", size = 259183, upload-time = "2026-09-27T12:26:47.208Z" }, { url = "https://files.pythonhosted.org/packages/9b/11/606b192fe43d32574ec6238549d48de588fdcc18485682a5ec0a8ac357f2/coverage-7.16.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9a75a4704ff640e46170042eec1f984385a121227c505d5a16ad8e495f452541", size = 261325, upload-time = "2026-09-27T12:26:49.084Z" }, { url = "https://files.pythonhosted.org/packages/67/90/eea481f8b0305ceeb33f081a5f47e298391dbd1b589de0c4b3b3aa50d3f2/coverage-7.16.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:14253fc7bb15749b849795a06f5d3b6d8bc3fb8a4b5ddc341faf7a89dce205fc", size = 255531, upload-time = "2026-09-27T12:26:50.509Z" }, { url = "https://files.pythonhosted.org/packages/6b/be/dedbf9aea1457b120c27ac10b8fc2a357f37fa2b54c3e7286d42980a0a2a/coverage-7.16.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:921415102a90637fcc2e3f169f61dad7699ecf690e8639fc21b813acbedc0967", size = 257329, upload-time = "2026-09-27T12:26:52.005Z" }, { url = "https://files.pythonhosted.org/packages/fa/cb/b25c19d5bb2bd0f2e4e27fe8e2ffcae80c7a91ae181c0dc749ed60e9b1a4/coverage-7.16.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cce2bc991293f15cc4084ca116827b5900c5f34e1a54dfe83f10ab5c43162eb7", size = 255294, upload-time = "2026-09-27T12:26:53.634Z" }, { url = "https://files.pythonhosted.org/packages/5f/a2/892c5c5f4ad44b7b2ca009aee705191f3f268f15052244f2f9e3539b2e35/coverage-7.16.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:e1fa594c887365b69745f25a416806e61085dd07b94c9eae68a6e20730629b23", size = 259444, upload-time = "2026-09-27T12:26:55.243Z" }, { url = "https://files.pythonhosted.org/packages/ed/99/a562537deba0a3e370182ae71c149be796c39d8087365f17a09188f27145/coverage-7.16.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:11e597173af1dc33d5f8a7332ada544199269a223af1ee1770ddd5e245ad0fe8", size = 255111, upload-time = "2026-09-27T12:26:56.851Z" }, { url = "https://files.pythonhosted.org/packages/2d/20/854ec68641a9b3362ff068a32dfa41637299761617ef253791dbade6fc76/coverage-7.16.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3e7f99698ba3a7d13988bdd984b7ebf13af4dbe2166dc8502eef90d77603b0a4", size = 256882, upload-time = "2026-09-27T12:26:58.41Z" }, { url = "https://files.pythonhosted.org/packages/db/0d/748e4518b0ac0f9ff2687c248a6e5f8c0737306e709372632a2556f84443/coverage-7.16.2-cp313-cp313-win32.whl", hash = "sha256:f80bd9f9633eafc73d0a913ba2645c96ba58bba1befc30590f7c0fbfde59d865", size = 226045, upload-time = "2026-09-27T12:26:59.983Z" }, { url = "https://files.pythonhosted.org/packages/31/fa/6e46edba66a183fe4d99d4bb52c173287e9b8dddabe0888d24cb8210e580/coverage-7.16.2-cp313-cp313-win_amd64.whl", hash = "sha256:8be099e979fc42559328a21828281b4578304191ae46ed4e80a407048a82eee6", size = 226581, upload-time = "2026-09-27T12:27:01.494Z" }, { url = "https://files.pythonhosted.org/packages/1b/d9/9ef6845367600b336ff75d000444a0d32497d6972c833141bd39356abf68/coverage-7.16.2-cp313-cp313-win_arm64.whl", hash = "sha256:28ff850182a67d117990fa2ce5ea1032836d8c9630dae867e8bdd3bff4533b79", size = 226409, upload-time = "2026-09-27T12:27:03.116Z" }, { url = "https://files.pythonhosted.org/packages/59/4c/577fc0803dab4155dcf808faffbdd7b159256781c0874a8586e17b81b149/coverage-7.16.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:4ee546b9e4872ffa194bf07ac87bfa1202ebb824d0795dc1ef22f175545ca90a", size = 224138, upload-time = "2026-09-27T12:27:05.141Z" }, { url = "https://files.pythonhosted.org/packages/75/9e/e3785ba3ecba2bd11efc74bfe2801ca4b78c4480b15a375648d809a59da3/coverage-7.16.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a2fac6895eb299a2e52d7bbb8fb3903502b9da8d3f5309ceb16ec40c646b58ee", size = 224445, upload-time = "2026-09-27T12:27:06.805Z" }, { url = "https://files.pythonhosted.org/packages/f0/d0/963ff22d3fd27117da3b8cc442f5bdc91196f783321e1a8ff0ec43476772/coverage-7.16.2-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:57ff3783f99d75a1e81dd56a9737eb5665e6736a5d93258ba596b6dcad8fd05b", size = 256112, upload-time = "2026-09-27T12:27:08.43Z" }, { url = "https://files.pythonhosted.org/packages/a8/d4/a306940c81c6ae759e82fff27d20b7fdc6896e422b821f51313cce212b6c/coverage-7.16.2-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:35f37886699cb9abd29958247d718628d5bc6f39e623dff66a09e546c42a7e03", size = 258727, upload-time = "2026-09-27T12:27:09.927Z" }, { url = "https://files.pythonhosted.org/packages/b9/a3/d3d99d93b02517087aa05bc0cf2d04d372956b849e5443e059079901429b/coverage-7.16.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0fd7a86fdda7cb6d616d178654bd0ad6bc0f3f33c2e478aa598500a1a9e34eda", size = 259909, upload-time = "2026-09-27T12:27:11.55Z" }, { url = "https://files.pythonhosted.org/packages/08/44/39dd599181726758dd185ae4dc0c0ab3aeabf7ca70e68e145060feeaaa16/coverage-7.16.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ac0f3b379c94acc2f7dce5f5f0b24d44fa1cc6a509717ef83dfee07450c2117c", size = 262481, upload-time = "2026-09-27T12:27:13.17Z" }, { url = "https://files.pythonhosted.org/packages/99/e8/91ee43f6ded411460c359d7e1aebde4d6fd8f00a2e5394182d9d212eb23c/coverage-7.16.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7d0732c83746bc24123c581a85d9dd96b70ddb538c9076020aa1a041790361e9", size = 256012, upload-time = "2026-09-27T12:27:14.91Z" }, { url = "https://files.pythonhosted.org/packages/11/8c/e9499ddc33197bd7eabcb1118ca81756fc874457b324e2b479a4804b2ad2/coverage-7.16.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7b451c68218c150f616bc9649783ec8de76a59792c759b43aa0c9c0466a465e4", size = 257898, upload-time = "2026-09-27T12:27:16.588Z" }, { url = "https://files.pythonhosted.org/packages/5f/6e/c081cb5991a0afba99f9c4ad6c74a5fce9513a38ddc64e3e6680c6fed9af/coverage-7.16.2-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a56ac4fa5a75c7e182e8f62600cfb4aff43c5ed7356a034f3557659c3bec1d90", size = 255938, upload-time = "2026-09-27T12:27:18.19Z" }, { url = "https://files.pythonhosted.org/packages/b2/42/1c3d819e8f9b6eb01c2fe90874d67a8882adb9507e0bbb09361ed131ea89/coverage-7.16.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:4cc4f73aa3fabc36e32046d6cd2971405948d8a903636508a3d3b2f9128b3a95", size = 260368, upload-time = "2026-09-27T12:27:19.903Z" }, { url = "https://files.pythonhosted.org/packages/19/4f/d70eac07901fd587b6ab05e659b52afe13959992aa5113bf6cce059cc572/coverage-7.16.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:723dcdab91357159b722935b500ee8abc0a66c8c432e1e9fabf4cc7598952de8", size = 255620, upload-time = "2026-09-27T12:27:21.621Z" }, { url = "https://files.pythonhosted.org/packages/34/5e/6d87af88317d3d9a9b18a9ca1bc1673eb516917f296e579d0d4a55cb3490/coverage-7.16.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5397e21a90dde0e9c6896b77ded8f0be26b66f8b22b33aed41f6043ed95d55e6", size = 257521, upload-time = "2026-09-27T12:27:23.358Z" }, { url = "https://files.pythonhosted.org/packages/79/bb/90c2641170d2fa1a6757b3f8450ba2740197317b0ddd749e9604b914e886/coverage-7.16.2-cp314-cp314-win32.whl", hash = "sha256:848893e1d361448c113dc2f0913503522a6f7be231d0e38333d2a22d9698a011", size = 226248, upload-time = "2026-09-27T12:27:25.153Z" }, { url = "https://files.pythonhosted.org/packages/30/08/d8d0478bb02c8eb0ae20a496fc80c40fcf4d3450bd184300d682ba2d28a6/coverage-7.16.2-cp314-cp314-win_amd64.whl", hash = "sha256:5a27b731c171e43dc8b5f32b76a5051dde2ec9b9366c87028f08a7088ebc2c7b", size = 226732, upload-time = "2026-09-27T12:27:26.907Z" }, { url = "https://files.pythonhosted.org/packages/32/3f/0001da22155b0a8ce063ec0f7e64ecbe17b373f306e7a74435f6d6accb72/coverage-7.16.2-cp314-cp314-win_arm64.whl", hash = "sha256:1c569a9fd25505f1cd6bea90588818f90373ce90e2632e2cacf19ddbd6e14fdb", size = 226645, upload-time = "2026-09-27T12:27:28.588Z" }, { url = "https://files.pythonhosted.org/packages/d7/85/6d8813aff9b8b8586691a9d33c43c5604f7227622574da7cdc3d91a86861/coverage-7.16.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d93db87adb6b1c1b408dce4763314b55d76a9f589e96783a84ac9e7689e48bdf", size = 224871, upload-time = "2026-09-27T12:27:30.32Z" }, { url = "https://files.pythonhosted.org/packages/5c/70/444f3a4981ac2cda40fdcf4cc9b56a4e1a33c222abeb33e51ed3e3eb2a6b/coverage-7.16.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:aa62c85046473959c13ba9edca9dc90a77d5c1095b1ba313556314d77fe5b036", size = 225123, upload-time = "2026-09-27T12:27:32.33Z" }, { url = "https://files.pythonhosted.org/packages/d0/c1/980681cd7b33eb66ac835044116ef0a92e11fcc7bdd866cc89d10b1130b9/coverage-7.16.2-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:db76506aa5416081f3e8974ae0f7965c58ada0bb0ef7339ac86099588dbb20d3", size = 265049, upload-time = "2026-09-27T12:27:34.085Z" }, { url = "https://files.pythonhosted.org/packages/b2/e3/87679875c33bb2191f0f05544a1cc9adcc940fe0c35443a10f2df753dde5/coverage-7.16.2-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a0f2285329dac10ab08f79cb11f5692c497018e6c7c511f95e6fd63a70b8f831", size = 267717, upload-time = "2026-09-27T12:27:36.025Z" }, { url = "https://files.pythonhosted.org/packages/76/64/5d372776d6eb523d4e93bafba2253f96984e3b18261c4cc56a50863c6d0d/coverage-7.16.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:382d3346d56b0eec1b793d53a4c88799c8053f516aa3a8d7c44315696954bacf", size = 270047, upload-time = "2026-09-27T12:27:37.96Z" }, { url = "https://files.pythonhosted.org/packages/be/c1/44082ff0cbf9f97d0043f57970a71204097ec7ba606361a9fd2065393669/coverage-7.16.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:648352b94507179d82637292e7ae8802508d95f78e2f00a705a50b6c48011681", size = 271360, upload-time = "2026-09-27T12:27:39.766Z" }, { url = "https://files.pythonhosted.org/packages/b8/17/9a215efe25b5e0ecc87c89dbe525c4a87d14d87c8c0c7316ef140a5f6f3e/coverage-7.16.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fb2bde05838fffae1a1bf75e5d411a6cac3e4e9bb97e6640fed8cd47888b33f0", size = 264736, upload-time = "2026-09-27T12:27:42.072Z" }, { url = "https://files.pythonhosted.org/packages/a2/da/7f0a31af8e448107d4d32844bd684757f51ea907bc0c68c8fd537b2123ff/coverage-7.16.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6a75180829efb8ae62b4aded25be6ddca1c888d138d2d82e21d93bfbd88f41cb", size = 267600, upload-time = "2026-09-27T12:27:43.85Z" }, { url = "https://files.pythonhosted.org/packages/dd/a4/3bfecbd3366b775bacdcb3330394d356cf384b5d8f5b2146ac4b14b252b5/coverage-7.16.2-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:99704f73721e23859112072d522076e11c31744fc96b5652e5dd2018aa4359f7", size = 264767, upload-time = "2026-09-27T12:27:45.768Z" }, { url = "https://files.pythonhosted.org/packages/b8/3f/5d62163732d87e4a0c4710a0eab30f0fd6a2d480112abe2029f014fe8c9d/coverage-7.16.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:29309ccc86b7f33df7db12813c299f215bbbc470ed6292d0bedd63ffae1ebf64", size = 269026, upload-time = "2026-09-27T12:27:47.787Z" }, { url = "https://files.pythonhosted.org/packages/49/4d/8e4579f225426535085a9be371cc75e3b026d058d679b80affbdfb4c3ef0/coverage-7.16.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:30c1b65d529e46569899fadca59e4a87c1faf2886923f1307ba61e654d4f3c20", size = 264147, upload-time = "2026-09-27T12:27:49.681Z" }, { url = "https://files.pythonhosted.org/packages/d1/36/ef1f77e2c3f7bb03c2b13b9a2006f88700fdd75535ef158d70049f425c1c/coverage-7.16.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dcf4bc2aab4e16b1c4c0c2005918f23a7dd5d7821ddae82caed9e3342dc2fcce", size = 266376, upload-time = "2026-09-27T12:27:51.551Z" }, { url = "https://files.pythonhosted.org/packages/be/79/0cb2bf4428830dec971c718c2c841a039c084415c99e67281f5a72841aab/coverage-7.16.2-cp314-cp314t-win32.whl", hash = "sha256:a9cd3de0a5bfe7b0e21ee10e1a14e3d61bf52efc88217ab1d95d6ace6970bd46", size = 226585, upload-time = "2026-09-27T12:27:53.945Z" }, { url = "https://files.pythonhosted.org/packages/3c/f9/da17121c16667fd84998e972200ae226a41540f6ea4795776c6d99e8976f/coverage-7.16.2-cp314-cp314t-win_amd64.whl", hash = "sha256:611a44e5229a59d7483ce830160e1a0e85f700562c7a5651c7c63fb8f4eb528c", size = 227378, upload-time = "2026-09-27T12:27:55.778Z" }, { url = "https://files.pythonhosted.org/packages/74/89/01179c62d1b7e6e33bd5001566b02d7f778cf33d3ec1e81e94ca170c517f/coverage-7.16.2-cp314-cp314t-win_arm64.whl", hash = "sha256:22957cef43ce038641de78ba995de7568d2d6a37c6ddbf7fa0fd7d1ae2344d91", size = 227064, upload-time = "2026-09-27T12:27:57.496Z" }, { url = "https://files.pythonhosted.org/packages/4c/57/52935003c3f627ba6e5203d7179aad32448c10899663a30336aba8e81a2c/coverage-7.16.2-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:414c26dfdb96aac2d570a54e03008f001e32eb2d413705365503648c6bd361d8", size = 224140, upload-time = "2026-09-27T12:27:59.343Z" }, { url = "https://files.pythonhosted.org/packages/31/38/df472520f3e626524d7e2fc9d6da0afe7895a2f1489d36b48af8ca40bb41/coverage-7.16.2-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:00d3eb96e9988c45f50cccd1f1496571ac5c1f91386ac02c4d55516eeda19a24", size = 224449, upload-time = "2026-09-27T12:28:01.299Z" }, { url = "https://files.pythonhosted.org/packages/0c/aa/3be084d5b82e63ccdad4ed751e4acbae294673573e30481d29f8b7402eec/coverage-7.16.2-cp315-cp315-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4dbbd1155ca46e6e0b6b89d204428c56ef6a459af21333f365d135a2820e5a09", size = 256060, upload-time = "2026-09-27T12:28:03.185Z" }, { url = "https://files.pythonhosted.org/packages/de/29/48fca82a7ebf7ff7b2e35019cc9537e7f65e4d2aa1215cc5a8792c989251/coverage-7.16.2-cp315-cp315-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8fc15cc8d0d06e873c00ef18e1372d605f9aaf3de27d8c24e50782e75bc8b843", size = 259106, upload-time = "2026-09-27T12:28:05.15Z" }, { url = "https://files.pythonhosted.org/packages/06/3d/b2d5986f2dd53fe201aa1be2e4ab204fa1aed5101e67c0dbbb419b850aee/coverage-7.16.2-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c6afdd69218202bc1758c9a14b86b8cf1084f37ed2ca143e567a103772b16d1", size = 260674, upload-time = "2026-09-27T12:28:06.868Z" }, { url = "https://files.pythonhosted.org/packages/ce/7e/b50160be3506ead12e6480d14279af7f0f17627694300a2d1fd2c42d2ff5/coverage-7.16.2-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:aba5c63b7afdc749cc9eae943d5b868cba2b261a176378fa1c5a30bc8bc89982", size = 263139, upload-time = "2026-09-27T12:28:08.771Z" }, { url = "https://files.pythonhosted.org/packages/14/5e/7c805ac9a32606de1399bd7e9bd375aa2f973dc61b12680d9e6403c2e891/coverage-7.16.2-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9174f0af24e5eff248b9dbfe76ec5275a3d19d37edbc2810543f12cf97347a34", size = 256591, upload-time = "2026-09-27T12:28:10.842Z" }, { url = "https://files.pythonhosted.org/packages/ab/9e/76f1ed129a2daf658a3ea17122824cf2e3b91fea0460d8d3664fc5a61018/coverage-7.16.2-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:80e9fdb4c3d926b6ba721d4bf7435bdb869c3527ae7803290361d0ab73db13b6", size = 258708, upload-time = "2026-09-27T12:28:12.962Z" }, { url = "https://files.pythonhosted.org/packages/5a/b7/8d62e75f48b527619239a65294f842d4b7fd02a0839d43ae1de80184e2df/coverage-7.16.2-cp315-cp315-musllinux_1_2_i686.whl", hash = "sha256:7b3bce4a0d05401d70b7d0d5ca783e686bc9d30e81dbd7d980d532609bf809e4", size = 256562, upload-time = "2026-09-27T12:28:14.934Z" }, { url = "https://files.pythonhosted.org/packages/b8/8d/0a15f95c3afb78e947c52644786ba4bc9de259905687dd720d5e6fae2e76/coverage-7.16.2-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:44f21e407b278efdfc1ee5e481e00518bd1d500310a30a5fbf2bcbedfef4aaf0", size = 261077, upload-time = "2026-09-27T12:28:17.215Z" }, { url = "https://files.pythonhosted.org/packages/25/00/88389987305a47d732866c07c8a500000ab574df9505e3114ac69c8d027f/coverage-7.16.2-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:59c3926585e1cd1f2190f4b2ac9014de1bbeaf0d5d0587b0dc6b0aa90d17896a", size = 256034, upload-time = "2026-09-27T12:28:19.08Z" }, { url = "https://files.pythonhosted.org/packages/92/02/34d079d4952ad461bde037d353f9a6e037a7edc45fe0f9ee8781ff73f028/coverage-7.16.2-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:066429634299e14dd2d511e1e85f8f9cecc500781f6b41907c0dd6f1baea7e63", size = 258034, upload-time = "2026-09-27T12:28:21.242Z" }, { url = "https://files.pythonhosted.org/packages/f6/d8/3e59a62879285b464ec1b10fd824fbc1af9ce66e842cd39974f80a0becc4/coverage-7.16.2-cp315-cp315-win32.whl", hash = "sha256:893ea9cf86cb8d2546812ac93d973aaf2ee1fb45110a873b014214fd23e3725e", size = 226252, upload-time = "2026-09-27T12:28:23.102Z" }, { url = "https://files.pythonhosted.org/packages/f4/e1/128026e1b2836e9ad6b219207ba9edf1c5e0088a7869e23088aee7fbbe7a/coverage-7.16.2-cp315-cp315-win_amd64.whl", hash = "sha256:01c6908bc613b420c26c818fe948e1b97dfd041a53c98b01c63bd8321f5c9aae", size = 226728, upload-time = "2026-09-27T12:28:25.21Z" }, { url = "https://files.pythonhosted.org/packages/a8/f4/c9fa8e7cf525ca7748ac52b0ee89331d13fe09808e45c679830708782e90/coverage-7.16.2-cp315-cp315-win_arm64.whl", hash = "sha256:967d72c835d7a8cf0af99ec813a2d06e3db6df706402f1fe85b31b437645f495", size = 226648, upload-time = "2026-09-27T12:28:27.136Z" }, { url = "https://files.pythonhosted.org/packages/a2/13/e96b045447a856666f36f9c653e2a80bdaa732aaaf72412b19aa2c26a473/coverage-7.16.2-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:98d9c97f51b334b0adce7b964442a9af33c1a00c6ac856984cc5dc8d18f81c75", size = 224858, upload-time = "2026-09-27T12:28:29.169Z" }, { url = "https://files.pythonhosted.org/packages/23/90/087f6ad1bd3df059632ca3407a4e6552ed1053ee35354de0a771acf35423/coverage-7.16.2-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:3e861f1071dcc2fec1e88bef0920f6b1eaa66a143555b4f8ab79ba2b0f30ef55", size = 225139, upload-time = "2026-09-27T12:28:31.131Z" }, { url = "https://files.pythonhosted.org/packages/7e/8e/285dcef0184358044e7cbcd810a1bdc9566bc620f54702d605477155df4a/coverage-7.16.2-cp315-cp315t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:fb9d92ecfe2d5b494367c67f7446f8b75b68d8d0c8cf3bc3e6997478be25d9e2", size = 265090, upload-time = "2026-09-27T12:28:33.04Z" }, { url = "https://files.pythonhosted.org/packages/06/b2/cc83f3a6e5789a4e89059c69555bc641c2efcde568405a1c06fc702951ab/coverage-7.16.2-cp315-cp315t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:eb57acff4a74246ae513c142d4b36e18c389c3aed8661914a53f7cd0071031b2", size = 268254, upload-time = "2026-09-27T12:28:35.135Z" }, { url = "https://files.pythonhosted.org/packages/ac/41/f548c19530f5d66ac6e3c92bbcbc49da7261de3a458b9f3e54a3efb1a0b2/coverage-7.16.2-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:444889f7f66b74e4455c0a97e0e166dd41177f1dca8c0239a47cff25e05ba7e1", size = 270695, upload-time = "2026-09-27T12:28:36.959Z" }, { url = "https://files.pythonhosted.org/packages/94/61/4dc27cf82ef96434d2874110ad0cc10ea4621025705dc5049862bd3bd181/coverage-7.16.2-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a740ea6f083c6db7b926534d159508f80ba275ab35e722522de0d18d0f56e55f", size = 271853, upload-time = "2026-09-27T12:28:38.821Z" }, { url = "https://files.pythonhosted.org/packages/38/29/bf8072b1b8bd5f2de8b21460a404460b1a2b97e80a9464c78ec0271f6199/coverage-7.16.2-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8e209591f7c41ae4a9171335cf6156afda0b21de73b02f73f5aa95b2d5fbb08d", size = 265644, upload-time = "2026-09-27T12:28:40.815Z" }, { url = "https://files.pythonhosted.org/packages/7c/2f/0aecb8721be5cdeb8afd9d6d9f6b463f074e4d8d37f00f4c42442522709f/coverage-7.16.2-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:396bb16e04ce04efbb3df91456ae4e3da918e69ecdf67fb711b0a0fdf35ccce0", size = 268530, upload-time = "2026-09-27T12:28:42.725Z" }, { url = "https://files.pythonhosted.org/packages/ab/0b/92b4b7628268ee711249958e68fc0328779bd3d9a7ab4715379465aedb84/coverage-7.16.2-cp315-cp315t-musllinux_1_2_i686.whl", hash = "sha256:9cdf19874e0d247f32f03609200370343c3c7aa260b191d8c2bb251d36198283", size = 265154, upload-time = "2026-09-27T12:28:44.684Z" }, { url = "https://files.pythonhosted.org/packages/7b/d9/41c95c1ab29b3dcd357cd1227181d1c98185632aca41ce670ce671b23a43/coverage-7.16.2-cp315-cp315t-musllinux_1_2_ppc64le.whl", hash = "sha256:fd3d72233eb8b48acc94fa57d44e2d32ce8e7abed02882ccb6d855ccc4ed33ec", size = 269799, upload-time = "2026-09-27T12:28:46.672Z" }, { url = "https://files.pythonhosted.org/packages/80/07/ebeb259aa5362b033a137b86d7274ff4b109d59be8cc9913889b783bf75a/coverage-7.16.2-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:bb4ffe96aa663cee727659db5a2afeb38c95f8677b747d447b90d6d4874ea2c5", size = 265196, upload-time = "2026-09-27T12:28:48.996Z" }, { url = "https://files.pythonhosted.org/packages/b2/18/8437620f90d023680a072eee02f968055f3658bbfb7d386d0ea34cfb7f30/coverage-7.16.2-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:dba2edfb054f6d4a08df9d1637c39a5aa3865bca6617c13c86be21e45658a59c", size = 267266, upload-time = "2026-09-27T12:28:51.361Z" }, { url = "https://files.pythonhosted.org/packages/28/6c/f08e8ee4293e6434035424180bef4d45e028e8ecc006c61bf9453e74405e/coverage-7.16.2-cp315-cp315t-win32.whl", hash = "sha256:251aed777c47c77aba047096d4542889db089227655711dfc2b9c54ef0e15e35", size = 226583, upload-time = "2026-09-27T12:28:53.33Z" }, { url = "https://files.pythonhosted.org/packages/f7/fd/3f939c2847f4a72c20cff8b1ac33da78ea91a2d38d9b43336e60db719103/coverage-7.16.2-cp315-cp315t-win_amd64.whl", hash = "sha256:2aca0bdfa9e91621d5b09d815357bf63def4fc0e9cb66da67bf2cf93f3b1a6f5", size = 227374, upload-time = "2026-09-27T12:28:55.158Z" }, { url = "https://files.pythonhosted.org/packages/5a/35/b98cdc354c952402132e675a87f2cc3227fb68f959c84aaa491fbe15933d/coverage-7.16.2-cp315-cp315t-win_arm64.whl", hash = "sha256:b88841e654f09732804809e435b3e005a929ffd9998b872b7b213957b8759cb8", size = 227064, upload-time = "2026-09-27T12:28:57.075Z" }, { url = "https://files.pythonhosted.org/packages/3f/0c/7a64e1ac90541a8edf50daef0914848011fb057a5bf55284a4811e21939a/coverage-7.16.2-py3-none-any.whl", hash = "sha256:11d28e9123a9156cb405d8d27b44256c9a58fb5decc2073a8f17862057e3aa0f", size = 215754, upload-time = "2026-09-27T12:28:59.075Z" }, ] [[package]] name = "deprecated" source = { editable = "." } dependencies = [ { name = "wrapt" }, ] [package.dev-dependencies] dev = [ { name = "coverage" }, { name = "mypy" }, { name = "packaging" }, { name = "pytest" }, { name = "pytest-cov" }, { name = "ruff" }, ] docs = [ { name = "furo" }, { name = "myst-parser" }, { name = "sphinx" }, ] docs-live = [ { name = "furo" }, { name = "myst-parser" }, { name = "sphinx" }, { name = "sphinx-autobuild" }, ] [package.metadata] requires-dist = [{ name = "wrapt", specifier = ">=1.16,<3" }] [package.metadata.requires-dev] dev = [ { name = "coverage", extras = ["toml"], specifier = "==7.16.2" }, { name = "mypy", specifier = "==2.3.1" }, { name = "packaging", specifier = "==26.3" }, { name = "pytest", specifier = "==9.1.1" }, { name = "pytest-cov", specifier = "==7.1.0" }, { name = "ruff", specifier = "==0.16.9" }, ] docs = [ { name = "furo", specifier = "~=2025.12" }, { name = "myst-parser", specifier = "~=5.1" }, { name = "sphinx", specifier = "~=9.1" }, ] docs-live = [ { name = "furo", specifier = "~=2025.12" }, { name = "myst-parser", specifier = "~=5.1" }, { name = "sphinx", specifier = "~=9.1" }, { name = "sphinx-autobuild", specifier = "~=2025.8" }, ] [[package]] name = "docutils" version = "0.22.4" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/ae/b6/03bb70946330e88ffec97aefd3ea75ba575cb2e762061e0e62a213befee8/docutils-0.22.4.tar.gz", hash = "sha256:4db53b1fde9abecbb74d91230d32ab626d94f6badfc575d6db9194a49df29968", size = 2291750, upload-time = "2025-12-18T19:00:26.443Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl", hash = "sha256:d0013f540772d1420576855455d050a2180186c91c15779301ac2ccb3eeb68de", size = 633196, upload-time = "2025-12-18T19:00:18.077Z" }, ] [[package]] name = "furo" version = "2025.12.19" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "accessible-pygments" }, { name = "beautifulsoup4" }, { name = "pygments" }, { name = "sphinx" }, { name = "sphinx-basic-ng" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ec/20/5f5ad4da6a5a27c80f2ed2ee9aee3f9e36c66e56e21c00fde467b2f8f88f/furo-2025.12.19.tar.gz", hash = "sha256:188d1f942037d8b37cd3985b955839fea62baa1730087dc29d157677c857e2a7", size = 1661473, upload-time = "2025-12-19T17:34:40.889Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/f4/b2/50e9b292b5cac13e9e81272c7171301abc753a60460d21505b606e15cf21/furo-2025.12.19-py3-none-any.whl", hash = "sha256:bb0ead5309f9500130665a26bee87693c41ce4dbdff864dbfb6b0dae4673d24f", size = 339262, upload-time = "2025-12-19T17:34:38.905Z" }, ] [[package]] name = "h11" version = "0.16.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, ] [[package]] name = "idna" version = "3.20" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/f5/08/8eea9d4b8302028f3abb2c0813953f7aec26d33b7a8960ed760e65ff29fa/idna-3.20.tar.gz", hash = "sha256:a7db850025b95ded1eae8a46181a1a6c56c92c96f0e2b005d9ff8dc0210cab44", size = 216463, upload-time = "2026-09-17T14:11:04.752Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/58/a2/bb081bab032533a855d44de1d56f8e8426114ff1ba5d1f07a438a0a654f8/idna-3.20-py3-none-any.whl", hash = "sha256:ab7ae7122974553370f0bdb919e1a960b2cd1bc1ef0276416d896db81c14582c", size = 69583, upload-time = "2026-09-17T14:11:03.168Z" }, ] [[package]] name = "imagesize" version = "2.0.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/fb/5e/513ff06670c84e7b9887c1fdf61b2d42b4f574a831f2f1d2222023049d8a/imagesize-2.0.1.tar.gz", hash = "sha256:b2ba6a4dea487a7ebcd53248d3476aca449d30db12a2dde5e0c5ca9624fd77e5", size = 1883774, upload-time = "2026-08-24T12:35:19.13Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/01/f9/575c8d760eae1fc99651b7cc5efd96ad5379ca4d6b53750b0fb4fe983f34/imagesize-2.0.1-py3-none-any.whl", hash = "sha256:ea0c9a0384df69ed86a943a15cde37d0360b82491b3910dc2215e202e62b5b02", size = 14794, upload-time = "2026-08-24T12:35:12.548Z" }, ] [[package]] name = "iniconfig" version = "2.3.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, ] [[package]] name = "jinja2" version = "3.1.6" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markupsafe" }, ] sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, ] [[package]] name = "librt" version = "0.15.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/36/9b/356320fbae2ac8467e21c5e73e1389c80468e4998c62cc7d3536cc51b614/librt-0.15.0.tar.gz", hash = "sha256:4e66cbe84437497d951b799d3e1551291b6fb3d643820a7014b3655d57a59162", size = 214338, upload-time = "2026-08-07T10:49:42.663Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/ba/39/99c25030e782bdfb7a21be8c05254806a2e4bbb05c8d50c2a2130acbfa05/librt-0.15.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e87bc679f86a99aa3b26e3c78eeb821a247c9a28eae48eaafcc32c3bf4c3bb9e", size = 151021, upload-time = "2026-08-07T10:47:00.057Z" }, { url = "https://files.pythonhosted.org/packages/14/43/f4b1bd1b2888798a1409808889a25ea1ba49eaabce7d681ed27734c2df9d/librt-0.15.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:71599e011ac880e8e45d46047d714871894c7d4ab6f25626f8d4f89da21f368d", size = 155267, upload-time = "2026-08-07T10:47:01.311Z" }, { url = "https://files.pythonhosted.org/packages/0c/db/3ad9c965c72f1e1d6beeec44ec10a54e17be8ae042fbb4baade16cbadced/librt-0.15.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c802434092b769b1d613ed2e13fac15fbfce1934a74bd10283b03c0fae231cd1", size = 503136, upload-time = "2026-08-07T10:47:02.45Z" }, { url = "https://files.pythonhosted.org/packages/4b/07/5888a6d76acd62ebce66c61b74d94e9370b9c32929f111e487bb6546f8ed/librt-0.15.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:5500eeae393a184d14e1f35645962c27129d20c81afa4069e6ef826ebc2b3aaa", size = 496670, upload-time = "2026-08-07T10:47:03.675Z" }, { url = "https://files.pythonhosted.org/packages/29/39/ab57cc2f5b276156da02bb7f5a8921bada1cb1993ffec99acf811c602c23/librt-0.15.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6ecfc32dfb46fb7b565bcd6abf9412acf978775a998273d22888a6d7953730dd", size = 513688, upload-time = "2026-08-07T10:47:04.981Z" }, { url = "https://files.pythonhosted.org/packages/a7/b9/bdbb0b648b5c2befb031f4c6f3b1dd857415e8fb492a25a3c764a6681e6c/librt-0.15.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:89cc46cfd15022e35084355478c9ac809d90b1152222706ac9a7655ec21df6fa", size = 531904, upload-time = "2026-08-07T10:47:06.211Z" }, { url = "https://files.pythonhosted.org/packages/93/26/473c2e4b6c104e9e58e27ce95fc8005c8bd4fc36cae4f254371125a92db8/librt-0.15.0-cp312-cp312-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d5f51401d102c885b9ca509e62c79b1dbff286e1b9b047fde6f763780789356d", size = 524427, upload-time = "2026-08-07T10:47:07.592Z" }, { url = "https://files.pythonhosted.org/packages/26/60/03b3abb82b41714671b907bf6989b228e31e6a8af52dec82b5b0728dc250/librt-0.15.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cc30523e3f1a23fb7511cc659834a0d01a1042bb9de359bc1c131cc4ec6c9656", size = 543155, upload-time = "2026-08-07T10:47:08.866Z" }, { url = "https://files.pythonhosted.org/packages/f2/0e/9bb1f0a4affbd0a1888f4f79dc03ed2a299d9a2c26c59ab2a97dcbf11903/librt-0.15.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:59fe030d8ae4a57e3fb7756bf35a858de74e04066fc8555c53d0af979132af81", size = 546890, upload-time = "2026-08-07T10:47:10.327Z" }, { url = "https://files.pythonhosted.org/packages/dc/84/6937a280d461f7de6e031ffb02edc2b7c3c90d49d630565ce8ff27cbc5f2/librt-0.15.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5a6526a2a956bbb1e4ae3568c82e650fc99119c66bb011ea60715744955a2b4d", size = 555163, upload-time = "2026-08-07T10:47:11.798Z" }, { url = "https://files.pythonhosted.org/packages/bc/95/2a2853c1ee014bf102116e7f897a04beeaeb2461b45b79af98bdfb95f1ef/librt-0.15.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:85ea21ec6730194d67156b0e0b5430ccb1d61f8b8b907e39b37f9812b74a13f0", size = 535812, upload-time = "2026-08-07T10:47:13.279Z" }, { url = "https://files.pythonhosted.org/packages/c9/4c/cf9601c1b4c5f09280acd5d83abdb2e68527a2be8257136eb42304218622/librt-0.15.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1e47b8ba865d7ede071a91a7163073bbaeb72541f1ef8a07d512c45c7b5007f2", size = 573688, upload-time = "2026-08-07T10:47:14.727Z" }, { url = "https://files.pythonhosted.org/packages/47/6d/9ac7cbec46189a7625af4b5acbd25f10d827f4141b2002181848c8418923/librt-0.15.0-cp312-cp312-win32.whl", hash = "sha256:a5207ec414d1c4a2a7231b2086970dc036f94293cdf338190984958a013a42f1", size = 106138, upload-time = "2026-08-07T10:47:15.973Z" }, { url = "https://files.pythonhosted.org/packages/38/d0/2ae99c83be86ce23f925ac1aeeedc777e97f427c4a8d190c70d0a16e9a87/librt-0.15.0-cp312-cp312-win_amd64.whl", hash = "sha256:73b30cfa976659b3917c8f6153bdb0591c6a9ec6583599fd24a689b690622022", size = 126974, upload-time = "2026-08-07T10:47:17.049Z" }, { url = "https://files.pythonhosted.org/packages/5d/ef/dd24f9635c730b86b87587967dda7516b1845e8b17684603d31607fed598/librt-0.15.0-cp312-cp312-win_arm64.whl", hash = "sha256:a54cf9e0ef47b96af580849db5471142200568ce1e02cbf416addab551369570", size = 112292, upload-time = "2026-08-07T10:47:18.222Z" }, { url = "https://files.pythonhosted.org/packages/e7/42/467b53a601b406ccd7b97c1fd54b59cb34f9185ad5ce7e9d5c3c4e8961c8/librt-0.15.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:db13ca398005abcbe538deda87b686d9bd08b7001cf40c4c06b444960ae10a26", size = 151029, upload-time = "2026-08-07T10:47:19.312Z" }, { url = "https://files.pythonhosted.org/packages/3e/e6/36c2299b7a94b84fdd01220d8a777a71be5be0925bb0dbdf71c0a06a34d9/librt-0.15.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:aa1f1995789dca3698bc550aaceb09a51bd5df0a057ff84ff15296cd1975b801", size = 155194, upload-time = "2026-08-07T10:47:20.398Z" }, { url = "https://files.pythonhosted.org/packages/c9/b6/ed5071f9325845e670bd36012757419767fbf56af77ed483077b9e4db541/librt-0.15.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55456ea87d8df21808446d03817be2f65e20391c1c615d9187440dff28cd08dc", size = 502568, upload-time = "2026-08-07T10:47:21.652Z" }, { url = "https://files.pythonhosted.org/packages/7f/81/6450c67c3615d87704bcbc21323fafc69c799b06a044c447529f725d4b01/librt-0.15.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:5a86a5a08c2235316bdb359d5dbb6ce0abfca7fac06363103e2c5af571d92f95", size = 496153, upload-time = "2026-08-07T10:47:22.925Z" }, { url = "https://files.pythonhosted.org/packages/e1/d6/5f52b722bc75076954b3bfd49be15ea362df4d580c6fb315d0f617100d30/librt-0.15.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e56b6a368529bed262da40ce13f8fef590db0479819cca84f16a1f01ac356d0b", size = 513336, upload-time = "2026-08-07T10:47:24.213Z" }, { url = "https://files.pythonhosted.org/packages/8d/e2/c08fd1d36ce63ea5a12b85c5d37f4550b5f86a692167e41e5a74222607ae/librt-0.15.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:234d8d394721fa0d786af15ebf1f3fb7f3ed82fd1cd0cde45c2f247b5d4281d2", size = 531661, upload-time = "2026-08-07T10:47:25.507Z" }, { url = "https://files.pythonhosted.org/packages/3f/d8/d9482fcbeb177b9eb87bb3899eeb3b42be690313c652f9e146b1d0681fb2/librt-0.15.0-cp313-cp313-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d8363d7accb0286ac3a0e633f396e93800dafb8150494505daf9515bbda591f3", size = 524487, upload-time = "2026-08-07T10:47:26.79Z" }, { url = "https://files.pythonhosted.org/packages/10/cc/075171517b41f861753034fbb151b42cfc83bcc853849f24f5e66fd60ccf/librt-0.15.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0f0ee3644d951f31055ad07d77d92520e84505dd7a432cc4cd501dd70ee06785", size = 543201, upload-time = "2026-08-07T10:47:27.999Z" }, { url = "https://files.pythonhosted.org/packages/b0/03/42c2330f37eeb475b6affeedd06518f60035f323af3a839335e3fc9fef2d/librt-0.15.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2cfd1a81a648806e6a7717be4cc4d1bb392fa229752bf8444ba365e381e984d6", size = 546467, upload-time = "2026-08-07T10:47:29.396Z" }, { url = "https://files.pythonhosted.org/packages/57/1e/1ad4c5638f7e64d8560328bd25c54b409a661bdb6ff254b38ff90744288d/librt-0.15.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:a6cd22c9da0d866558e46a041f1cc0c2bbb26b61b137b2347fa834c332e1d101", size = 555139, upload-time = "2026-08-07T10:47:30.815Z" }, { url = "https://files.pythonhosted.org/packages/49/41/39fa7d15db1204cd1cbe6514680fbdc243adf754a0885061308f43afc013/librt-0.15.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:6d5225ef8801e4ea5e482fa9b5dfb891dd9ef6f6d870f1f25d449ca2c70ac218", size = 536050, upload-time = "2026-08-07T10:47:32.222Z" }, { url = "https://files.pythonhosted.org/packages/1e/88/c6dcf0dd8e26dc0c9a499a2abab8646c86dcaf9ecea9524cb46d3686331a/librt-0.15.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d28a05796b99f749bf8794f17ba9ba1612d0076b802e9cfc62c554634e9ce3b", size = 573700, upload-time = "2026-08-07T10:47:33.527Z" }, { url = "https://files.pythonhosted.org/packages/1b/9b/ab54c71a7918a7c34fa5327fb61390a77446a07a146fbfb1165250a61035/librt-0.15.0-cp313-cp313-pyemscripten_2025_0_wasm32.whl", hash = "sha256:2067ff438048cead9d223ca5675bae2a25e520a7c3e6c1498bf9c6892d22caab", size = 82194, upload-time = "2026-08-07T10:47:34.835Z" }, { url = "https://files.pythonhosted.org/packages/8d/b2/4f9a243bb892395f3becb80789ade13771701091f9f07ab8230247953ba8/librt-0.15.0-cp313-cp313-win32.whl", hash = "sha256:1cd3b721f24c206398b9e26da3c3a9c011e6e89d06f318ba8ebefc30f1003890", size = 106231, upload-time = "2026-08-07T10:47:36.251Z" }, { url = "https://files.pythonhosted.org/packages/bf/af/64aff4885a40b93132382f2c314647d722574605416504379184ef3045ea/librt-0.15.0-cp313-cp313-win_amd64.whl", hash = "sha256:f395a4a9a03ac062dbe9a9f82e0c720502e590a38feee6a757bc82e9c63afbd8", size = 126996, upload-time = "2026-08-07T10:47:37.453Z" }, { url = "https://files.pythonhosted.org/packages/27/83/335bccf6c7cb9028cb0b54aead27d9ece3f01f83bc6baa2abace5da655c1/librt-0.15.0-cp313-cp313-win_arm64.whl", hash = "sha256:0a15cb554761247d84a3ec0cbdf4078d70725384f0e4662c0fa3b26266eb60ad", size = 112188, upload-time = "2026-08-07T10:47:38.729Z" }, { url = "https://files.pythonhosted.org/packages/a8/93/949053fb462eecc4a9a5ee770a81f4b40be7b79538b245545d4aebc6b58b/librt-0.15.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f5de7feedc56337a088eb15cd9fafa9938367362221d8cc62c642b7f94821993", size = 149833, upload-time = "2026-08-07T10:47:39.86Z" }, { url = "https://files.pythonhosted.org/packages/61/ca/8281aa6cd560a3420e4497729f6b704b53be3eeaaef82d5aeadddaf7441f/librt-0.15.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:6c0eb900c0e91f4aebe680845242e614f1864edfd44106380d0752ac29522bf8", size = 154088, upload-time = "2026-08-07T10:47:41.065Z" }, { url = "https://files.pythonhosted.org/packages/dd/02/1a1662dceaba6a086360891448d5ce9a7d3555976cae59a31a39d744b9c7/librt-0.15.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e8c9a650a188e38bac005048cbe6342e81407782944d01934540ab75e417df21", size = 494215, upload-time = "2026-08-07T10:47:42.388Z" }, { url = "https://files.pythonhosted.org/packages/69/84/99211619dc656370a3740c33d2b0b6d5a3fb1e73689314f6ed477a397dc4/librt-0.15.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:92bfed8deec93df30286b9fe9e3b1dd17329cc076a192b4ee5ec223841d54953", size = 491173, upload-time = "2026-08-07T10:47:43.683Z" }, { url = "https://files.pythonhosted.org/packages/d4/aa/5448d0b05f4579b635d3899176817ebf561af0e57bacd425b5b1887264c1/librt-0.15.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ec4b19788f835711a2072f9dbe6b03b3bf32ed1f0fb30cf399bdd59d9f0c33fa", size = 505512, upload-time = "2026-08-07T10:47:45.314Z" }, { url = "https://files.pythonhosted.org/packages/95/82/01940e40b83c43a546c4a3c896cf34ca272a9690899d55914e4827b3dcce/librt-0.15.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d4c7bacb70930f3d0a56f4ecf1be474a1f0d941b01dd73b756f3c256d42cb879", size = 523073, upload-time = "2026-08-07T10:47:46.66Z" }, { url = "https://files.pythonhosted.org/packages/88/fa/759c0030f3ee371439eb26de34fc745807caf0abb878af7af4b8b7c3dd3d/librt-0.15.0-cp314-cp314-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3e79f05e4a08b4d880342673312bbc895b56df7765605796f15902eb5367d3ae", size = 515080, upload-time = "2026-08-07T10:47:48.319Z" }, { url = "https://files.pythonhosted.org/packages/0b/27/894e072228fcb159703c655da69f8cd10dbed489c36e3df7dd032a2483be/librt-0.15.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a417149c0cba4d50b61e992e5a15e69eaf96746609b461cc4ed168aeef6b79dd", size = 534164, upload-time = "2026-08-07T10:47:49.875Z" }, { url = "https://files.pythonhosted.org/packages/98/a3/0078e91c1f36f8815db17827de15650b9a3fe56c55fbf998c854b34e40d3/librt-0.15.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:da7a94d6a3411f579d72aa3e3bc5fbca7ed4549f3dbd7e5de3aa567333374285", size = 540616, upload-time = "2026-08-07T10:47:51.408Z" }, { url = "https://files.pythonhosted.org/packages/86/33/81a29b796dd52a45e9ef7974c7732926e8f10f15b8d2be505665979f896d/librt-0.15.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:856f743ae607f2c1380eccb566c0038a9fb3eabf0fc2be2704d76d9f73557239", size = 545890, upload-time = "2026-08-07T10:47:52.818Z" }, { url = "https://files.pythonhosted.org/packages/05/82/8be1baa1350e5d30cfd70ae79d0a6f4dc5862ef47f7bb2808aabc9bb86e5/librt-0.15.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:779a6e7c894737e5983e7790a9c78c4000c30e23c9aada08081bdbea53b0fa60", size = 523287, upload-time = "2026-08-07T10:47:54.165Z" }, { url = "https://files.pythonhosted.org/packages/c6/4f/d1be6a01a35c20ef734e0e44113f87d4af756a9354a89dcfbe3b4f8af5e1/librt-0.15.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:96bb17dbe8bab3c0954fbebfc69ed395599de75b6bbc35e3270a878e15d4dd65", size = 565868, upload-time = "2026-08-07T10:47:55.566Z" }, { url = "https://files.pythonhosted.org/packages/67/88/649cfa33f5825927b160610f670bdab012a64d627eddb94fa795ea4292fd/librt-0.15.0-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:7220697efaa6e5348fc3d18ee7f8563d4bfecd9872b37ffb915bfc1d08840622", size = 81619, upload-time = "2026-08-07T10:47:56.886Z" }, { url = "https://files.pythonhosted.org/packages/22/31/8e88a8d5e48fc8d1a817787fb6811dfff6499acd6c8683dd83934aa6ede0/librt-0.15.0-cp314-cp314-win32.whl", hash = "sha256:f54598964d357b1c5ab77cf5d92f21e598fe0e23cdbe9618480807f81b4eba15", size = 100138, upload-time = "2026-08-07T10:47:58.093Z" }, { url = "https://files.pythonhosted.org/packages/80/92/20fd6c4b6a1b1a564b076d55cd3d427d8428217d7638dc25a654cc4791d4/librt-0.15.0-cp314-cp314-win_amd64.whl", hash = "sha256:3ff5893a2c23d886aa9ce786de5ac6ddc74aeeaf90743682b74d920e117d2e28", size = 121258, upload-time = "2026-08-07T10:47:59.564Z" }, { url = "https://files.pythonhosted.org/packages/fc/28/6af430b44d9ebb897b865a3c363b6dcace51357be2347cc0f8f869656a86/librt-0.15.0-cp314-cp314-win_arm64.whl", hash = "sha256:3722a099730704c9a3d70c879fc0f51daec25fe5f1555672d97bc595abeafb95", size = 106467, upload-time = "2026-08-07T10:48:01.097Z" }, { url = "https://files.pythonhosted.org/packages/7e/aa/b42bb798942ced219f6d63b27e07f91237887a8d0bd0921666db79a13790/librt-0.15.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:38c0c7d4b6fc06c3324b3f9162c8391bfc4fd9dde53afe1033ce7edb48d5a714", size = 159523, upload-time = "2026-08-07T10:48:02.442Z" }, { url = "https://files.pythonhosted.org/packages/75/03/1b53cd4ef904e73b1d828a5f90143bf94a2967d7cfff0b9ccf93e12aa9b4/librt-0.15.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8b2fdd7ead3c995c37940a790690660d0ca006c302db26cc51933f6766866fc3", size = 161638, upload-time = "2026-08-07T10:48:03.725Z" }, { url = "https://files.pythonhosted.org/packages/ac/c4/9f9c9fba097d49e9e694c2b4dc331df31884645ecbc58a93b4b5fc69d2c5/librt-0.15.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2fde98cf1fc4bac144ce23c2c4c017b924ba714509ea9334977b0b27050c837d", size = 701795, upload-time = "2026-08-07T10:48:05.135Z" }, { url = "https://files.pythonhosted.org/packages/4c/05/0966840bda0380c8ae167b9043c6230202941cc90ea29c48e096964c765e/librt-0.15.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:e3b461183c5fa7681b48560f91515f53a953122fb30c71e07abc67d7ddf58c38", size = 682147, upload-time = "2026-08-07T10:48:06.555Z" }, { url = "https://files.pythonhosted.org/packages/18/af/1c47ca573c30ea47d195aec26133af522fea1104afaace028d7b32247ea8/librt-0.15.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4bbcc257e3babea20a91715c361b24554ec4e8f51aa578568afc230799fe1a19", size = 696397, upload-time = "2026-08-07T10:48:08.03Z" }, { url = "https://files.pythonhosted.org/packages/2e/0f/1aed6223d4f9f9d1171a8596ff100ea4c3f7699fea7a4ba657c3e60daa6c/librt-0.15.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b845b8d48088fad0cadc84be4b8fda63203be7e9237b71015b3925443c1f35ab", size = 722542, upload-time = "2026-08-07T10:48:09.569Z" }, { url = "https://files.pythonhosted.org/packages/c6/22/9e3a929aea456c97d69e6ef3884efea56d4807f97399471cc946baebd8af/librt-0.15.0-cp314-cp314t-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b30e600e8f337b9bd7f39b86d9fdfedc73cc46e3d0f745931a23a234220bb7e2", size = 729709, upload-time = "2026-08-07T10:48:11.129Z" }, { url = "https://files.pythonhosted.org/packages/e9/1b/c327ef6018e3a9ca0b8e7c5eddeeb331ba8f9b76c24e126d37d0f6d62faf/librt-0.15.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:64b0c8c35aa4c4ed79896359f3e0b285cbe4e610042106500da4811c322cc108", size = 752891, upload-time = "2026-08-07T10:48:12.558Z" }, { url = "https://files.pythonhosted.org/packages/d7/d1/d5f1ea02c56930087009e39db9b70660a663e76c730b27b925d786718457/librt-0.15.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:0da0d94cb802f32a0524653e7201f2cef72d5f700a5407678f5290483d4fcd08", size = 745301, upload-time = "2026-08-07T10:48:14.55Z" }, { url = "https://files.pythonhosted.org/packages/d9/3c/5f7c585d15ebb2250c73e7c0ee4e9e47be72c65d520c07ddbcdc62037674/librt-0.15.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:4a6369168d371207339b1e50d4532b06a7121586141f82599505a3f315751d47", size = 747921, upload-time = "2026-08-07T10:48:16.453Z" }, { url = "https://files.pythonhosted.org/packages/7f/52/1443a446486eba966bcbca1696b472e4f210320ec42f490a47f48fbf0fdc/librt-0.15.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:c434e072557ade9cbc642d052c89d031efe47d5c9614523619d0d74a02378e81", size = 727561, upload-time = "2026-08-07T10:48:18.089Z" }, { url = "https://files.pythonhosted.org/packages/79/91/2270a9380f11725cf83ce1925a5e32dd1dde2be9bba597f25c10a38644e7/librt-0.15.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c7eec6a42018bc1d45763b1c162d3d2bf7c3b9a1b0ed30d3e91dcba390efefcc", size = 774417, upload-time = "2026-08-07T10:48:19.611Z" }, { url = "https://files.pythonhosted.org/packages/9e/3b/f4b1548d4f5b99186737fe27aec238e9823e8d5d23bf4df007c030689dc5/librt-0.15.0-cp314-cp314t-win32.whl", hash = "sha256:6912fa5e635d74529ac7cdb1bdf6ca3af4453da8d1edbe0110ee1cb4ad407ebf", size = 104381, upload-time = "2026-08-07T10:48:21.048Z" }, { url = "https://files.pythonhosted.org/packages/80/b6/134afad262def1de04c0843c376d02135f1168af43f22e09a52bd8394727/librt-0.15.0-cp314-cp314t-win_amd64.whl", hash = "sha256:8e11699ed745931c395acd3621b07062e0f840efa6935aad87a64ed0995f0915", size = 127034, upload-time = "2026-08-07T10:48:22.561Z" }, { url = "https://files.pythonhosted.org/packages/99/5f/1b6846b20572bd699c9e9ec321a5f781845bee477df2aa2a43b28bc40119/librt-0.15.0-cp314-cp314t-win_arm64.whl", hash = "sha256:5d2a91724463bfed4f573cd7a9fdc856d2e230d0c0e5a61416a93481dccd8605", size = 110827, upload-time = "2026-08-07T10:48:23.804Z" }, { url = "https://files.pythonhosted.org/packages/c6/44/4de9f4ddadb009a55c7758eb5736d62534a7daaf27bd71bc50e64b606b06/librt-0.15.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:8443e38dcfcfdbcf5add5118c623efd788d65ac2e25756d6251a54a06a4d0aca", size = 149843, upload-time = "2026-08-07T10:48:25.148Z" }, { url = "https://files.pythonhosted.org/packages/1f/eb/5d9ab71e30119c44094e0275f38b47dd327aea0f843a080396677029d508/librt-0.15.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:6d15a29033c57490cfe2069097c6fc4049e4e65ffbb749be7dc453b7c4c68965", size = 154510, upload-time = "2026-08-07T10:48:26.485Z" }, { url = "https://files.pythonhosted.org/packages/d0/9c/8505d1b8f5e8c19587bd03f7429993b3e9ce5c06819d856bfb11d919374c/librt-0.15.0-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d2c05c729b589e734c09578bf5964be48a911765484840d017bbc84f49d4c4ad", size = 497543, upload-time = "2026-08-07T10:48:28.045Z" }, { url = "https://files.pythonhosted.org/packages/1d/9a/3a8390775cb095765aded027ac9c63e7c8ea74e731498607544c6505de0e/librt-0.15.0-cp315-cp315-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:fa60887537e1d0cd2d9982269d33a709bf54b195cd2b9364fc0a758022af5bd9", size = 480452, upload-time = "2026-08-07T10:48:29.531Z" }, { url = "https://files.pythonhosted.org/packages/e7/40/258a4a7117ee915d66de5cd9b8ade65a440993161107ce3a686f1859955c/librt-0.15.0-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d8bc24219b24c0af375718942ab75e3544b2763085f40f965be4326734ae8328", size = 507768, upload-time = "2026-08-07T10:48:31.007Z" }, { url = "https://files.pythonhosted.org/packages/6b/c6/2f4dd296c97a0b85b98894519b279408ec9dd602d4f692b1ea0e25dee670/librt-0.15.0-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:86a21a7bd3fe3a419512ef424cc1c020f6771d0b29cfddff36d1635a855e63f0", size = 525122, upload-time = "2026-08-07T10:48:32.7Z" }, { url = "https://files.pythonhosted.org/packages/49/dd/29eab42be13b2bf0ea8cb227135a45d44693e30a7e8b92871981ff56b82b/librt-0.15.0-cp315-cp315-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:dbab647e88d90b3167b91efe7091e248653688ed4337e4f90907a722c7361bb9", size = 520371, upload-time = "2026-08-07T10:48:34.294Z" }, { url = "https://files.pythonhosted.org/packages/91/ed/4bad71adeca8fe208b775c2a35417fa5a2584c8f4791daaf89a89450fea1/librt-0.15.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:d8edcf6f550e918dca779c069b9e156385c60b406f99fc7641f32c52f7193659", size = 537258, upload-time = "2026-08-07T10:48:35.88Z" }, { url = "https://files.pythonhosted.org/packages/4c/63/59dba6143fdcc7240c54458b629f3250000a61b8945890fc9efd451b19c5/librt-0.15.0-cp315-cp315-musllinux_1_2_i686.whl", hash = "sha256:8b62076030baa2d8b1501a46bf0e19c27a489aa90671c55665bff7887f7660b0", size = 527432, upload-time = "2026-08-07T10:48:37.466Z" }, { url = "https://files.pythonhosted.org/packages/ec/21/21a24c6a2327d8362580efebe77286bf47b0f4062ec5ea41766e609d3c7d/librt-0.15.0-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:d00d20d1818e82a07a0ee0aa89a98b17ed7916b92441090b683719cb20a59b6d", size = 548108, upload-time = "2026-08-07T10:48:39.384Z" }, { url = "https://files.pythonhosted.org/packages/5a/6d/fc68c89a7971418b41f9a873623ff935cb864097544c6a2f8ce491c8ef5d/librt-0.15.0-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:4e6ee93fc3cf848dcbf0cce2eca73d8e7dcd0cc2b6df3a529d57750b30a4c55c", size = 529681, upload-time = "2026-08-07T10:48:41.392Z" }, { url = "https://files.pythonhosted.org/packages/65/7e/c2d98766124400d722063a630b0fde38a9fc768705d37eecca15c47dc192/librt-0.15.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:32896a0af72508ea979e0acb4e4c04cbeeae04938167950d535c83c45597167d", size = 567736, upload-time = "2026-08-07T10:48:43.124Z" }, { url = "https://files.pythonhosted.org/packages/55/6c/f8c34a95e3a515c6e1c192b89511e7253c89a7760c6b500d57ffdb8d2dc8/librt-0.15.0-cp315-cp315-pyemscripten_2026_5_wasm32.whl", hash = "sha256:ec3ba415afaf951f6951b1dd16d3c8e4f540065fc382d7e70b823a79567ca374", size = 81673, upload-time = "2026-08-07T10:48:44.645Z" }, { url = "https://files.pythonhosted.org/packages/c9/9e/e23fa8e78679ec45728188650b39e8ff476c83b691c96f749217df3b1b7c/librt-0.15.0-cp315-cp315-win32.whl", hash = "sha256:d2813ba2503764f0450680c533d13df7cff9b49df1411062eded5f67db4195b9", size = 100081, upload-time = "2026-08-07T10:48:46.171Z" }, { url = "https://files.pythonhosted.org/packages/e1/dc/3eb4c5e297343f0620a55532cd7c8d764d3001fa2159212dadf480464827/librt-0.15.0-cp315-cp315-win_amd64.whl", hash = "sha256:b87d67e33afaf265262f2a66db578284b88ee2e6fcd224579cb5c15518677ad8", size = 121228, upload-time = "2026-08-07T10:48:47.631Z" }, { url = "https://files.pythonhosted.org/packages/97/70/43abce19f04e49762f8ec834c8fafee13cc40fd6b94a72a24e534febfcd0/librt-0.15.0-cp315-cp315-win_arm64.whl", hash = "sha256:713bd7df21170b982e729e46870f31d6b437bd1a9b4648cffb529bd3c2ec5c4b", size = 106487, upload-time = "2026-08-07T10:48:49.095Z" }, { url = "https://files.pythonhosted.org/packages/de/15/83f2deddb9368b8951ec8c9477269b5b9b8bd9bbf15e57402d0f38817dca/librt-0.15.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:3de789c82752730f94782a5ee518baf9c05edf85733aeaf73bb6e518755cdf54", size = 159448, upload-time = "2026-08-07T10:48:50.649Z" }, { url = "https://files.pythonhosted.org/packages/06/bf/043097353f9b3c73b583d07f6b8e552795463f4bfc8caf85e42eee50c26a/librt-0.15.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:e0b5deec9a8664eb722c797241970fd4aa1894d25fda36a1ddac0f7407606bd6", size = 161686, upload-time = "2026-08-07T10:48:52.174Z" }, { url = "https://files.pythonhosted.org/packages/f4/2a/8ae77f9719d42ce71cd708560a3557b38ac3c17a0383e57f87084de45bbe/librt-0.15.0-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5563302a8359bc2295bb7084d1a8ed1519df96afb30eb2aa4e0bff7b54228988", size = 710668, upload-time = "2026-08-07T10:48:53.782Z" }, { url = "https://files.pythonhosted.org/packages/61/34/c0436ea134deb9a0d6da80a396a2739a81cb31e0418f7227239e23140898/librt-0.15.0-cp315-cp315t-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:22d6263b9d39d7bbb286fa791945646e3218f1be2d693e36fb630f1d0e59cd13", size = 679396, upload-time = "2026-08-07T10:48:55.645Z" }, { url = "https://files.pythonhosted.org/packages/4a/9f/001e0d99aa9250d5cd5715a9081291a20656083459f9019cda15255329e1/librt-0.15.0-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:39ffd14646190c454f0d86e0d256b33f00a87a26ab410e619773b841d0e41416", size = 704313, upload-time = "2026-08-07T10:48:57.46Z" }, { url = "https://files.pythonhosted.org/packages/2d/53/b34fa9d0ff00f136f4d58ebb4c411ff634baed1eb412bb602a2bc8dcafcb/librt-0.15.0-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c47318cd3a61401452de11282242937e3e057c4fd3dbaf601e269d0928a06c0a", size = 729847, upload-time = "2026-08-07T10:48:59.231Z" }, { url = "https://files.pythonhosted.org/packages/86/ac/fa4d7a424665040e95baf480a6d523446057684b6758624c85338e8a23b2/librt-0.15.0-cp315-cp315t-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a56a1d4f859a82ca5b99fc4b82c9b027b15e3c455c5cd99e7d0719f27bb20b6c", size = 742736, upload-time = "2026-08-07T10:49:01.151Z" }, { url = "https://files.pythonhosted.org/packages/8a/f1/e17a9bb5de6fb8c3186ed1a7d68d21618b027ac2d3633e03d3b6109c67ae/librt-0.15.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:077471b3182db4e17c36ae91555f36a4d2c00080b267f749bcad34a478a9a302", size = 763454, upload-time = "2026-08-07T10:49:03.039Z" }, { url = "https://files.pythonhosted.org/packages/1d/ec/ecd02cd30935b931b9cdbfed6ab5a099c51b280b4e7baa274da80978ed27/librt-0.15.0-cp315-cp315t-musllinux_1_2_i686.whl", hash = "sha256:411ca4d1b905b860ceba7570dd6717a71dedaddcc4b0f77ece710aa41ee11f8d", size = 743296, upload-time = "2026-08-07T10:49:04.941Z" }, { url = "https://files.pythonhosted.org/packages/e6/b5/b3c2b8353ce820a4854f78d19321344242f89fa71c975b71132ba9bf242a/librt-0.15.0-cp315-cp315t-musllinux_1_2_ppc64le.whl", hash = "sha256:1256589e0b0adb31751d685a68bce29d73407ddf4ef05d4188f49d5dcf9566d9", size = 756217, upload-time = "2026-08-07T10:49:06.825Z" }, { url = "https://files.pythonhosted.org/packages/3c/52/6cc22542ba59146b05cca2a656f9ff8bb67e38e63d12c3b0cc183d837bf1/librt-0.15.0-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:f42b74a53e5f26a0ba0007411a7455b66c67ce4022a39cc1f56fc4efd65bcbab", size = 741934, upload-time = "2026-08-07T10:49:08.839Z" }, { url = "https://files.pythonhosted.org/packages/40/32/a04b72b1aa86e3be23b2ecff8c1aad2dcc955bd3956d6d26e7e34267e57a/librt-0.15.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:291bf73caf78b9e88d6fae9bfd693207ff7d832e2fdbe2cf8e746bc13f5f892b", size = 783763, upload-time = "2026-08-07T10:49:10.661Z" }, { url = "https://files.pythonhosted.org/packages/6c/f0/89eb11dffbe9279ff37144dec786927314502ae0b114f1449dc78c458aab/librt-0.15.0-cp315-cp315t-win32.whl", hash = "sha256:c16d15ee371643ab48dc8248a3e680ebbeca573a13af2c3dd0c985b142d77162", size = 104313, upload-time = "2026-08-07T10:49:12.305Z" }, { url = "https://files.pythonhosted.org/packages/6d/4a/1f1978c200f563beda63c36adff2d65bbecb81e365e8e69e572f5f70fbc6/librt-0.15.0-cp315-cp315t-win_amd64.whl", hash = "sha256:dbd605739f228912dc49027cb764456b9757750bdc2b6b7773164db7096c6fd1", size = 126889, upload-time = "2026-08-07T10:49:13.881Z" }, { url = "https://files.pythonhosted.org/packages/38/a6/800800bfed7b1fb10fc3f3d557785c3854e80d3f7a9800d784b176a1fc2d/librt-0.15.0-cp315-cp315t-win_arm64.whl", hash = "sha256:84d244b00604d17df3fc7736c327892d6bba66181254aa4087be807b6c342bdc", size = 110700, upload-time = "2026-08-07T10:49:15.499Z" }, ] [[package]] name = "markdown-it-py" version = "4.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mdurl" }, ] sdist = { url = "https://files.pythonhosted.org/packages/06/ff/7841249c247aa650a76b9ee4bbaeae59370dc8bfd2f6c01f3630c35eb134/markdown_it_py-4.2.0.tar.gz", hash = "sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49", size = 82454, upload-time = "2026-05-07T12:08:28.36Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl", hash = "sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a", size = 91687, upload-time = "2026-05-07T12:08:27.182Z" }, ] [[package]] name = "markupsafe" version = "3.0.3" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, ] [[package]] name = "mdit-py-plugins" version = "0.6.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markdown-it-py" }, ] sdist = { url = "https://files.pythonhosted.org/packages/59/fc/f8d0863f8862f25602c0404d75568e89fb6b4109804645e5cdfb1be5cf56/mdit_py_plugins-0.6.1.tar.gz", hash = "sha256:a2bca0f039f39dbd35fb74ae1b5f998608c437463371f0ff7f49a19a17a114d0", size = 56114, upload-time = "2026-05-13T09:03:38.91Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/a5/69/6da5581c6a7fede7dc261bf4e67d6adca4196f176b43288b55b3db395b6e/mdit_py_plugins-0.6.1-py3-none-any.whl", hash = "sha256:214c82fb2ac524472ab6a5bcab1de80f73b50443e187f401bfd77efbc7c6481d", size = 66663, upload-time = "2026-05-13T09:03:37.76Z" }, ] [[package]] name = "mdurl" version = "0.1.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, ] [[package]] name = "mypy" version = "2.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "ast-serialize" }, { name = "librt", marker = "platform_python_implementation != 'PyPy'" }, { name = "mypy-extensions" }, { name = "pathspec" }, { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/82/6a/878cc1097d4035f82bd516658d0c528d2a9955bc7b363afcbd0b07fea11b/mypy-2.3.1.tar.gz", hash = "sha256:47c1b1207258513a9d93495f69c8be9de73916186f0e52703e8c461b7a623419", size = 3992554, upload-time = "2026-08-15T03:03:38.549Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/85/da/d6effc4f808a842d91edc22535dc9e799d2ff6e91449168b7f47a0771f54/mypy-2.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a32bbbb940af990d3be0b8af321c7b6815bb1b3b48142fe7459b9cc5f58959ff", size = 14047547, upload-time = "2026-08-15T03:02:57.707Z" }, { url = "https://files.pythonhosted.org/packages/e4/e6/478229701dab76f26485fc8ff5d6f241f393da22447400bbc56f6946aebe/mypy-2.3.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff715e45b2231a8e85de1d163d1b42791e4d7aab8f5145f85fee1b710b735aff", size = 14216515, upload-time = "2026-08-15T03:01:26.496Z" }, { url = "https://files.pythonhosted.org/packages/8d/fe/7c42327a3b21e84681f691982cbfe43f334a3685f3b683b72c376476c4fa/mypy-2.3.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:858fc57d3d91fa728e33e7ad71def60fc6272694607b306cd3292db53ae39080", size = 15307789, upload-time = "2026-08-15T03:03:31.62Z" }, { url = "https://files.pythonhosted.org/packages/59/f4/7e597edbe01b5a56fa958ce541302dcaabfed979966f1dffedbea0ea0fc2/mypy-2.3.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:851833db876e7b650f93719c74b7879a08e338979c96054fdfc3bfd90a486355", size = 15548831, upload-time = "2026-08-15T03:03:15.55Z" }, { url = "https://files.pythonhosted.org/packages/a3/52/cb31e084bc0314a1e384bdd677a4b80e55af04ccac077545e2238b9d320a/mypy-2.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:4c5095a327483591c94e0c8d3ef9e50d4ab1369b541eae007c1f23bc2a41f6bb", size = 11226359, upload-time = "2026-08-15T03:03:29.002Z" }, { url = "https://files.pythonhosted.org/packages/7a/47/88fcf6217b43fa2da81a8c2611370af18141536a4f0294bbf98b457d456d/mypy-2.3.1-cp312-cp312-win_arm64.whl", hash = "sha256:bbfe022634a2a195406bd469e888d2eaf193b02ba7e607391cd7640374aaae3b", size = 10214707, upload-time = "2026-08-15T03:02:48.807Z" }, { url = "https://files.pythonhosted.org/packages/de/cf/862010ee800ca9c2bd0c4c0dacf0f092e5411824a09b8f97ad4be8fe250e/mypy-2.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:114dff494000f18bd10d5d95d84b8567b26da60279ecbe838131841df20e635d", size = 13964542, upload-time = "2026-08-15T03:02:21.43Z" }, { url = "https://files.pythonhosted.org/packages/75/5a/3f3a2107b41e3e92e617e25daaee121413b91e9784bea733131ed4fecc5d/mypy-2.3.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c8637731bb5eee3671eb2c3200827aa3564ed8a9309ecee4d1afe77e6d031bdb", size = 14168922, upload-time = "2026-08-15T03:03:00.351Z" }, { url = "https://files.pythonhosted.org/packages/8b/41/04dc4fe7e63d7820fa4eff272e95157d30cbea921388f3ab3fe77794cd0b/mypy-2.3.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c80fbc405ed8020f5ff3802dc18cf060197bcdd3fbdd6a26ef2fd34dfdd5226", size = 15244791, upload-time = "2026-08-15T03:02:31.089Z" }, { url = "https://files.pythonhosted.org/packages/96/fc/c3053b26b9054949285aa868cb6af8c10e7591541cacd79c5dcc06a1fcf9/mypy-2.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:84081f538ce27375045c02e3d7f81bd11d853400621ae245d87ce7b6c420ec74", size = 15501627, upload-time = "2026-08-15T03:03:34.128Z" }, { url = "https://files.pythonhosted.org/packages/70/4e/d77daab008bbc4e5001374d7928f4a260d28f0e6747af444fc4763f7a310/mypy-2.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:e9144ac16fde007096f9563eb2041b4433c2d705c4218edeb79e7e9d01035ee6", size = 11243961, upload-time = "2026-08-15T03:02:11.952Z" }, { url = "https://files.pythonhosted.org/packages/f0/f8/7eb68c136e4abd30569fe31ef2bfcb7eceae9952cab80017c04cd09f5d0c/mypy-2.3.1-cp313-cp313-win_arm64.whl", hash = "sha256:77ad9529e67dca28e511f5cd5671436584ce91f6d3bac159a353158187b986ac", size = 10213219, upload-time = "2026-08-15T03:02:26.361Z" }, { url = "https://files.pythonhosted.org/packages/be/c4/42a49d44aeff804edf1b19acce0b49e8bd1a9c57dee9605dd8d980aa43d7/mypy-2.3.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:192abaedf75da1bc0b1cef104927e70ec49c1ef0031cc4825c7ee10a438ed24d", size = 13986778, upload-time = "2026-08-15T03:01:33.69Z" }, { url = "https://files.pythonhosted.org/packages/45/13/9331fd2dfed7194d66c5304072894a8be3e51e9deda6863c1eceaa35a43d/mypy-2.3.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf678dffd16efcda2c15cbd30e9ecc0081388e29ea23687a88e686ed92638dc3", size = 14188467, upload-time = "2026-08-15T03:02:40.554Z" }, { url = "https://files.pythonhosted.org/packages/78/f7/f4a34edab45667c5465855dc585a20e87978ffa8aee711445b7239d120c6/mypy-2.3.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e036f06b41630f4c8a1d48f9ac6aa26acc65f8be089973f5519da643318f03f", size = 15225538, upload-time = "2026-08-15T03:03:09.761Z" }, { url = "https://files.pythonhosted.org/packages/40/05/534b3590757bd05794f73e07f6666c2a77b8597ffed795c94ce570096aa0/mypy-2.3.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:71af9c8a894e862b58e92abb08e53b05a384a1e5e5d6dc7cda59126211a53d82", size = 15480805, upload-time = "2026-08-15T03:01:41.134Z" }, { url = "https://files.pythonhosted.org/packages/55/da/bdfba852e2562f599624af5bb7d29e36b0b4f526f2b8bac85efe0dd1803d/mypy-2.3.1-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:3c80cd23d85368bdd9f37d5231dfd97d35bcbf5bf41af96ef3a9b078ad1957f9", size = 7761712, upload-time = "2026-08-15T03:02:36.008Z" }, { url = "https://files.pythonhosted.org/packages/98/31/60fc64a74cdba4f2a5d642d32317993e479163e1ac7d91b695e5d15e2264/mypy-2.3.1-cp314-cp314-win_amd64.whl", hash = "sha256:4956f34d145e145562a0a0bf367f642bbc85c04ec2baf47ae015947c3169a85d", size = 11423968, upload-time = "2026-08-15T03:02:06.931Z" }, { url = "https://files.pythonhosted.org/packages/a9/23/eb5950b24cd26ba3b78f87707a275568d633c77dae8e61c9661be6055ca6/mypy-2.3.1-cp314-cp314-win_arm64.whl", hash = "sha256:cfb12e360242d23d91f5e978d94f58ea66acf5804c4fb6f2f794a20d4cb1b595", size = 10399323, upload-time = "2026-08-15T03:02:33.671Z" }, { url = "https://files.pythonhosted.org/packages/82/c7/f80f4e46c0b9a00eb5f78a79d49dda8bdf56a5230f7257fb33e76be04da7/mypy-2.3.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e5f1c50bb05b64e2026b52867e8d21106f01313c744a2c4ecc34c90d12e8d6e2", size = 15121308, upload-time = "2026-08-15T03:01:46.053Z" }, { url = "https://files.pythonhosted.org/packages/5d/74/9b04f17c7074cc5188f02fb63a2ca1d43fedf479e84fe3091c39061a1d7f/mypy-2.3.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:667196b352f4cf304ded4c10f90cfc179263a1acfb3cdcfa984bdfd340d498bc", size = 15536590, upload-time = "2026-08-15T03:01:35.941Z" }, { url = "https://files.pythonhosted.org/packages/26/04/c837ef6208e567774e2ed1f863f8ba6ec4817b1b6dd426315e5d559b6ec9/mypy-2.3.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b9c53e395c12cad2c6d4b67d5da7c6057638a132d85c08b73646b18f802a0045", size = 16791074, upload-time = "2026-08-15T03:01:31.073Z" }, { url = "https://files.pythonhosted.org/packages/37/68/48730230afa45192d5bd429a6a2ff24a6f8dedda90fdf2b221792b54518f/mypy-2.3.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:18162b128c3f9c703cd35f5537446900b0d21a2549aa7a95d21380d2ef643fb0", size = 17069183, upload-time = "2026-08-15T03:02:28.566Z" }, { url = "https://files.pythonhosted.org/packages/1c/ea/ca23fc9c20eeda09a15c9cbcf50015d0e73f409f6ead059e42aa69a608ff/mypy-2.3.1-cp314-cp314t-win_amd64.whl", hash = "sha256:30c0477d4aab7b7f39c8397dc877f2c96b9fe5588ec379f372c56eb63d599f63", size = 12154679, upload-time = "2026-08-15T03:02:04.809Z" }, { url = "https://files.pythonhosted.org/packages/3b/67/8d982126034990869466f73b8db80dcb2234a7ac39b4dad093e047a79835/mypy-2.3.1-cp314-cp314t-win_arm64.whl", hash = "sha256:6941ab3619377bc3f32ca02876b07d27f216f5201604b664d3937ea0fdd23bb4", size = 10969159, upload-time = "2026-08-15T03:02:38.152Z" }, { url = "https://files.pythonhosted.org/packages/ee/f7/41e7f2d8117fbc7a7587286162ffe2f688984b69c46ed63cf5f2e4fc3bae/mypy-2.3.1-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:6f041a6de52c9217ca125e78ba0a335cb7fd98a1c0580978e49ab2b126f70b57", size = 13990694, upload-time = "2026-08-15T03:03:21.919Z" }, { url = "https://files.pythonhosted.org/packages/06/85/8f665811a0c8f3bf6fa1d9acd665ec2d97a2bcc453ae68dcd92340941cd6/mypy-2.3.1-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5159ae60f5dbc3a498af5ba8365505808ac8031bc63f9e00304ad545d40bdd9b", size = 14203518, upload-time = "2026-08-15T03:01:48.455Z" }, { url = "https://files.pythonhosted.org/packages/2d/82/91b866c8546b120bff83b73a439d90d2d63ef3aff113599e6b8e4d566848/mypy-2.3.1-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47a8a7a0a7f6f6e63995c0ac36fa0c07b127413fdc81f0439b7f3dccafd33561", size = 15220224, upload-time = "2026-08-15T03:01:23.577Z" }, { url = "https://files.pythonhosted.org/packages/c8/78/c226c99208ee40de7c768369fa533f933afa003dfdc606ff021450724e91/mypy-2.3.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:2329c0501293d4e1f33bc15d04d6304d65a1cdda967ee93a05c1e681a3923133", size = 15501512, upload-time = "2026-08-15T03:02:09.453Z" }, { url = "https://files.pythonhosted.org/packages/a9/e7/7cfb3f106c393979f4cc37ad6c0586044d50401e3c35b0c003e4f3ba6bc9/mypy-2.3.1-cp315-cp315-pyemscripten_2026_5_wasm32.whl", hash = "sha256:bb26deed807bdb0457cf3e3f1cd7c4a1cf9d66864eaf1b4a61e06805d4c6b1f9", size = 7761913, upload-time = "2026-08-15T03:01:55.65Z" }, { url = "https://files.pythonhosted.org/packages/99/3c/52affefa273b97939a1f474ae4a349c8718635c15b941112dfab4291b0c1/mypy-2.3.1-cp315-cp315-win_amd64.whl", hash = "sha256:375d7013876a8233b2d05be185bfa09f689696cd999ce8b1cfe6acac5c80e8a3", size = 11422533, upload-time = "2026-08-15T03:03:24.101Z" }, { url = "https://files.pythonhosted.org/packages/2a/b7/75643e70c72a5b346d8a9b1543c967ea8824df2ee3fb7ccba652c272b7bb/mypy-2.3.1-cp315-cp315-win_arm64.whl", hash = "sha256:586b3612214cceabb3c0f588c97e7d1e535393f06a60e912e994f6b3ace97523", size = 10397931, upload-time = "2026-08-15T03:02:55.265Z" }, { url = "https://files.pythonhosted.org/packages/10/ce/53be21f2d4adfcd26f63f1184a13ed797015ab463853f117e2e11e4d726f/mypy-2.3.1-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:ef0c6335cda9d807f8193d8ff6204a72bc909fa9882aacbca14f43cdb7188306", size = 15118669, upload-time = "2026-08-15T03:02:51.479Z" }, { url = "https://files.pythonhosted.org/packages/62/43/20de757cd42989d291a17fad607742c4c74e875ce5cea00e5a5225020ac1/mypy-2.3.1-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e598c8c66401d26b150872154a286e6d484cf2789c3bb28a7556806298423021", size = 15545627, upload-time = "2026-08-15T03:03:05.132Z" }, { url = "https://files.pythonhosted.org/packages/7e/fc/092bdf77ad280eaf501422f0f3b966012b528076cc13e41a774861c907d1/mypy-2.3.1-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eda22fd4efa9dcd39331d1dede9b5b8b8a7fd69af07592e778433da98610d29e", size = 16764157, upload-time = "2026-08-15T03:02:23.958Z" }, { url = "https://files.pythonhosted.org/packages/94/5c/c94c4d62d909b07f552d0d9356d7acc943825558e602a64822ffa2231536/mypy-2.3.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:2a0ba2e57847849fb0d1fcdabb32786d223095ed8bc121dfe322bcdb3d9c46bc", size = 17073258, upload-time = "2026-08-15T03:02:14.573Z" }, { url = "https://files.pythonhosted.org/packages/c0/f7/511a88b89e478053c02d22039bb8f3ce4183efe8fd7a4f0a5910a8bb0a32/mypy-2.3.1-cp315-cp315t-win_amd64.whl", hash = "sha256:3f7e865dd51f235f60a2dbcd8728a1c095f5ca28f095d48a725b84cd935735c4", size = 12135505, upload-time = "2026-08-15T03:02:16.714Z" }, { url = "https://files.pythonhosted.org/packages/71/bf/02573b56964ecb0f7c644f915f53c325ae15c3faec521c5adf11599a32df/mypy-2.3.1-cp315-cp315t-win_arm64.whl", hash = "sha256:8ad80807dc3ab8ea978b1b2b6e4a657194ace1d4ef03e0e731aff1abd517da29", size = 10962647, upload-time = "2026-08-15T03:01:43.712Z" }, { url = "https://files.pythonhosted.org/packages/8e/41/9675c7a1e78edecfba0b79e587a52594c56e189368261dc7b3a7fffb9527/mypy-2.3.1-py3-none-any.whl", hash = "sha256:6ed5c7e3419083268e5c9258bd1c1ef91af44a9e89374dbcaf37b775716e72eb", size = 2754338, upload-time = "2026-08-15T03:02:53.4Z" }, ] [[package]] name = "mypy-extensions" version = "1.1.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, ] [[package]] name = "myst-parser" version = "5.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "docutils" }, { name = "jinja2" }, { name = "markdown-it-py" }, { name = "mdit-py-plugins" }, { name = "pyyaml" }, { name = "sphinx" }, ] sdist = { url = "https://files.pythonhosted.org/packages/21/dc/603751677fff302f34396e206b610f556a59d7fe58b9a2145f54e96b48e8/myst_parser-5.1.0.tar.gz", hash = "sha256:ab69322dc6719dcc7f296479dbb70181b66df6ed315064f92dbc85c0e1bf2f02", size = 101182, upload-time = "2026-05-13T09:38:19.361Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/09/dc/f3dfb7488b770f3f67e6545085bf2abea5172e88f57b8ad25ef860ca704c/myst_parser-5.1.0-py3-none-any.whl", hash = "sha256:9c91c52b3cdb4d94a6506e4fab4e2f296c7623a0da0dcbe6de1565c3dad67a8a", size = 85817, upload-time = "2026-05-13T09:38:17.904Z" }, ] [[package]] name = "packaging" version = "26.3" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/7d/fa/3944b40b07da9ce895c0e6303a5ab7d53da063554f534556b134a54d6093/packaging-26.3.tar.gz", hash = "sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79", size = 313412, upload-time = "2026-08-04T18:15:28.737Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c", size = 129956, upload-time = "2026-08-04T18:15:27.159Z" }, ] [[package]] name = "pathspec" version = "1.1.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/5a/82/42f767fc1c1143d6fd36efb827202a2d997a375e160a71eb2888a925aac1/pathspec-1.1.1.tar.gz", hash = "sha256:17db5ecd524104a120e173814c90367a96a98d07c45b2e10c2f3919fff91bf5a", size = 135180, upload-time = "2026-04-27T01:46:08.907Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl", hash = "sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189", size = 57328, upload-time = "2026-04-27T01:46:07.06Z" }, ] [[package]] name = "pluggy" version = "1.6.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, ] [[package]] name = "pygments" version = "2.21.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/49/2e/ced460408999b33da6b31b0021b0f37d329e202d4169aeb164493778f25b/pygments-2.21.0.tar.gz", hash = "sha256:610ca751c9bc2492b38eb9a38a7fbc93edbbb2d7182edaf34e66ae493dee5c8c", size = 5005329, upload-time = "2026-08-17T08:02:48.824Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/71/46/17f022dd3e953bf20a04a028a21ec746d942f8d2af30fa0f124fa0e6a684/pygments-2.21.0-py3-none-any.whl", hash = "sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9", size = 1250147, upload-time = "2026-08-17T08:02:44.912Z" }, ] [[package]] name = "pytest" version = "9.1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, { name = "iniconfig" }, { name = "packaging" }, { name = "pluggy" }, { name = "pygments" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" }, ] [[package]] name = "pytest-cov" version = "7.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "coverage" }, { name = "pluggy" }, { name = "pytest" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b1/51/a849f96e117386044471c8ec2bd6cfebacda285da9525c9106aeb28da671/pytest_cov-7.1.0.tar.gz", hash = "sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2", size = 55592, upload-time = "2026-03-21T20:11:16.284Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl", hash = "sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678", size = 22876, upload-time = "2026-03-21T20:11:14.438Z" }, ] [[package]] name = "pyyaml" version = "6.0.3" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, ] [[package]] name = "requests" version = "2.34.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, { name = "charset-normalizer" }, { name = "idna" }, { name = "urllib3" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ac/c3/e2a2b89f2d3e2179abd6d00ebd70bff6273f37fb3e0cc209f48b39d00cbf/requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed", size = 142856, upload-time = "2026-05-14T19:25:27.735Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075, upload-time = "2026-05-14T19:25:26.443Z" }, ] [[package]] name = "roman-numerals" version = "4.1.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/ae/f9/41dc953bbeb056c17d5f7a519f50fdf010bd0553be2d630bc69d1e022703/roman_numerals-4.1.0.tar.gz", hash = "sha256:1af8b147eb1405d5839e78aeb93131690495fe9da5c91856cb33ad55a7f1e5b2", size = 9077, upload-time = "2025-12-17T18:25:34.381Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/04/54/6f679c435d28e0a568d8e8a7c0a93a09010818634c3c3907fc98d8983770/roman_numerals-4.1.0-py3-none-any.whl", hash = "sha256:647ba99caddc2cc1e55a51e4360689115551bf4476d90e8162cf8c345fe233c7", size = 7676, upload-time = "2025-12-17T18:25:33.098Z" }, ] [[package]] name = "ruff" version = "0.16.9" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/96/bf/c935ca98e73fe8ce65b87ef08a280c0c1e85295d569228c15e87d8fdfaf1/ruff-0.16.9.tar.gz", hash = "sha256:12b625c6cfba78d285d9f48eda5f053374f1e53cb10ef17342a383750db99161", size = 4948764, upload-time = "2026-09-24T20:37:49.416Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/0d/26/df51322b52ee1ada7eff2d071ea09d11d5c2d1dcc9f02594f5785c4d1635/ruff-0.16.9-py3-none-linux_armv6l.whl", hash = "sha256:95e6f022090368ab3b824c36276839c53b2adf1a3f4c09fefc33dfc400f6da96", size = 10082922, upload-time = "2026-09-24T20:37:13.045Z" }, { url = "https://files.pythonhosted.org/packages/a5/27/7bf51f5a7aa375e9f339280a303aab44ca75dc1525f1cdc5991761685b0f/ruff-0.16.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:a5f27be168556594a86d2f415db0cf43f5291917849318f873c7e2791f7a8c67", size = 10236360, upload-time = "2026-09-24T20:37:16.049Z" }, { url = "https://files.pythonhosted.org/packages/b6/63/09659283f92f02dff45809da194a70da2f688d87c55d8875c4fae3536072/ruff-0.16.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1632eb1d6197f33bd00b1acbc5b71009e89a8895c158e2d2b03a834fac964ab6", size = 9892940, upload-time = "2026-09-24T20:37:17.957Z" }, { url = "https://files.pythonhosted.org/packages/24/58/98de1b72ec172f5f8f1731236fe21585b3998bf7dfe9fcc44ae9ba626012/ruff-0.16.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b3f951b14d865d5952c89d40a5ca07e87abe24fa5453299878411e127748fb1c", size = 10032114, upload-time = "2026-09-24T20:37:19.942Z" }, { url = "https://files.pythonhosted.org/packages/c8/7d/f1e17c54ab59d4bad1dce8ee3e22a7a1d0ef4745240decacdcf3832b5bb2/ruff-0.16.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:447fc07e1573afff7cb02803462b12b6c8ece7cf10e2cd78565fa6d7a1c0bf8d", size = 9910227, upload-time = "2026-09-24T20:37:21.872Z" }, { url = "https://files.pythonhosted.org/packages/34/19/436f647a65075bbd3bab2668b3bdaa5120559b294694018cdcefabbbf30b/ruff-0.16.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8a3e039a6a40ed976c491722b60e0ae4a4aa1a86057f540ee7a37a5d19ae9120", size = 10547484, upload-time = "2026-09-24T20:37:24.229Z" }, { url = "https://files.pythonhosted.org/packages/03/59/38430a6bc2f6d8095447ac39625cf8b6e9344a47e6c26225c8ba1bff3ffb/ruff-0.16.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4684dded7db60aa57cb118fa158630f5feade4af5782903b6053484bdf9bd129", size = 11412367, upload-time = "2026-09-24T20:37:26.307Z" }, { url = "https://files.pythonhosted.org/packages/c8/bd/bbb6d7fc7f208c8b8c50dd5dc8206e4cfdb1e7a8fb852606a3adf370c880/ruff-0.16.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d29c934357e45642fda2f34c0b1f4025b4a6c01e15e4bf0016879d60078a142c", size = 10869787, upload-time = "2026-09-24T20:37:28.35Z" }, { url = "https://files.pythonhosted.org/packages/bc/b8/9c543074918061abbefc3bd139bee22de35abedb00dde0f2d27288838962/ruff-0.16.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a21713e629d3e5bdb2f5c2def1cc7f04f47fa8e1a7eb0571b4a28e1da64bc728", size = 10406494, upload-time = "2026-09-24T20:37:30.624Z" }, { url = "https://files.pythonhosted.org/packages/35/7a/5a8851bd146e7ccf8fd4b003f6c75c11f8fbdb0e60673b45097986b6bf41/ruff-0.16.9-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:7baa24ef5fc8e77aa93879e1d3f43754a01ae488e869f1ae30cf431afd4d2452", size = 10590083, upload-time = "2026-09-24T20:37:32.439Z" }, { url = "https://files.pythonhosted.org/packages/87/f0/4c3467188f23f806960b46fa76575a7cd0514c9ba90562650b71efc96980/ruff-0.16.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a41aac6230aadfaa133bdfa1614488531ffa3e0837567ae04c0da2058a9c0f9e", size = 10119151, upload-time = "2026-09-24T20:37:34.581Z" }, { url = "https://files.pythonhosted.org/packages/15/34/5a4def5adea572ce6aea0bb64f21f928ee317b80e0db747d7979b01d7261/ruff-0.16.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:c2529fb5896d49115b0e9aa8f887490b34bbe76baf879ec2264ac59406869ce7", size = 9911544, upload-time = "2026-09-24T20:37:36.796Z" }, { url = "https://files.pythonhosted.org/packages/62/5d/d15ebea7499eef9373318c0ee6ca127832927c6529731f6d48e18dce7ca9/ruff-0.16.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:41e3870277694177429b56406d65dfbdb2c2802c52b715edaf6a0b829c69d4ee", size = 10269884, upload-time = "2026-09-24T20:37:38.857Z" }, { url = "https://files.pythonhosted.org/packages/d1/56/c5d3cd119ded7a3c7aba0e961b69cb3df701c91662c98ad694467d060ce1/ruff-0.16.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:8adbe4e58af167f767d7b2ba5e83c42e878350796cf78c2f5e14ab9903a92588", size = 10749366, upload-time = "2026-09-24T20:37:41.042Z" }, { url = "https://files.pythonhosted.org/packages/ac/fe/734ec7527029ac757ecf821f143c9f3fcf69149c21f53a044a899b430f5a/ruff-0.16.9-py3-none-win32.whl", hash = "sha256:0e1dbc2073624dee6618d41d0098690a7244654af746704b64759e12b6b6b385", size = 10152355, upload-time = "2026-09-24T20:37:43.025Z" }, { url = "https://files.pythonhosted.org/packages/14/21/26e4643629b3ebb44f0a06f9c9a53058d63d989415f63a9a3c28e2ee7f22/ruff-0.16.9-py3-none-win_amd64.whl", hash = "sha256:6bd40fec8cd4c8a3d4dd589bd8ad4e6320c13c29234159bfd959a40d529d597b", size = 10592965, upload-time = "2026-09-24T20:37:44.944Z" }, { url = "https://files.pythonhosted.org/packages/51/60/5fb1a39dbb5ae314d5f59bc7348a63c1d5c20f3cd83914c4b5cb0be31d2d/ruff-0.16.9-py3-none-win_arm64.whl", hash = "sha256:ed1a252039200f57a59eebc063b54beabea67bfbaaca0eeaa7f54b5fbcda2284", size = 10458649, upload-time = "2026-09-24T20:37:46.882Z" }, ] [[package]] name = "snowballstemmer" version = "3.1.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/43/f8/0a71edf031f03c40db17503cb8ca78a69a171254e568e7db241b0ab57ea1/snowballstemmer-3.1.1.tar.gz", hash = "sha256:e07bbc54a0d798fe6010a12398422e62a8bfbba95c394fd0956ef58cb4d3e260", size = 123314, upload-time = "2026-06-03T00:56:40.194Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/4c/07/2ebca9b11fb9be7340a818d8d6f63feaebb146be2c4afbd6061701d6df6e/snowballstemmer-3.1.1-py3-none-any.whl", hash = "sha256:7e207fa178741da09cdee59d3ecec3827ad5f92b1fc5c9ff3755b639f71f5752", size = 104164, upload-time = "2026-06-03T00:56:38.614Z" }, ] [[package]] name = "soupsieve" version = "2.10" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/71/c3/1b817965ac12dc002d7c9cd7dfffdd7d4fbf9b45763ef2ffe7b86ee94670/soupsieve-2.10.tar.gz", hash = "sha256:49e9380d7d2905463583bafe285e818c7366a9ed7b3aee221c1ac79c905d8bc0", size = 124337, upload-time = "2026-09-24T02:36:35.048Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/66/87/5ed59e1d0290564e2027ed066c52b059892c4637741337fc0967183c8d4d/soupsieve-2.10-py3-none-any.whl", hash = "sha256:8596eb8967d744174820280fa62b4542a2e955bfaccca73ed8a13c6eb8e9b502", size = 38019, upload-time = "2026-09-24T02:36:33.709Z" }, ] [[package]] name = "sphinx" version = "9.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "alabaster" }, { name = "babel" }, { name = "colorama", marker = "sys_platform == 'win32'" }, { name = "docutils" }, { name = "imagesize" }, { name = "jinja2" }, { name = "packaging" }, { name = "pygments" }, { name = "requests" }, { name = "roman-numerals" }, { name = "snowballstemmer" }, { name = "sphinxcontrib-applehelp" }, { name = "sphinxcontrib-devhelp" }, { name = "sphinxcontrib-htmlhelp" }, { name = "sphinxcontrib-jsmath" }, { name = "sphinxcontrib-qthelp" }, { name = "sphinxcontrib-serializinghtml" }, ] sdist = { url = "https://files.pythonhosted.org/packages/cd/bd/f08eb0f4eed5c83f1ba2a3bd18f7745a2b1525fad70660a1c00224ec468a/sphinx-9.1.0.tar.gz", hash = "sha256:7741722357dd75f8190766926071fed3bdc211c74dd2d7d4df5404da95930ddb", size = 8718324, upload-time = "2025-12-31T15:09:27.646Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/73/f7/b1884cb3188ab181fc81fa00c266699dab600f927a964df02ec3d5d1916a/sphinx-9.1.0-py3-none-any.whl", hash = "sha256:c84fdd4e782504495fe4f2c0b3413d6c2bf388589bb352d439b2a3bb99991978", size = 3921742, upload-time = "2025-12-31T15:09:25.561Z" }, ] [[package]] name = "sphinx-autobuild" version = "2025.8.25" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama" }, { name = "sphinx" }, { name = "starlette" }, { name = "uvicorn" }, { name = "watchfiles" }, { name = "websockets" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e0/3c/a59a3a453d4133777f7ed2e83c80b7dc817d43c74b74298ca0af869662ad/sphinx_autobuild-2025.8.25.tar.gz", hash = "sha256:9cf5aab32853c8c31af572e4fecdc09c997e2b8be5a07daf2a389e270e85b213", size = 15200, upload-time = "2025-08-25T18:44:55.436Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/d7/20/56411b52f917696995f5ad27d2ea7e9492c84a043c5b49a3a3173573cd93/sphinx_autobuild-2025.8.25-py3-none-any.whl", hash = "sha256:b750ac7d5a18603e4665294323fd20f6dcc0a984117026d1986704fa68f0379a", size = 12535, upload-time = "2025-08-25T18:44:54.164Z" }, ] [[package]] name = "sphinx-basic-ng" version = "1.0.0b2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "sphinx" }, ] sdist = { url = "https://files.pythonhosted.org/packages/98/0b/a866924ded68efec7a1759587a4e478aec7559d8165fac8b2ad1c0e774d6/sphinx_basic_ng-1.0.0b2.tar.gz", hash = "sha256:9ec55a47c90c8c002b5960c57492ec3021f5193cb26cebc2dc4ea226848651c9", size = 20736, upload-time = "2023-07-08T18:40:54.166Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/3c/dd/018ce05c532a22007ac58d4f45232514cd9d6dd0ee1dc374e309db830983/sphinx_basic_ng-1.0.0b2-py3-none-any.whl", hash = "sha256:eb09aedbabfb650607e9b4b68c9d240b90b1e1be221d6ad71d61c52e29f7932b", size = 22496, upload-time = "2023-07-08T18:40:52.659Z" }, ] [[package]] name = "sphinxcontrib-applehelp" version = "2.0.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/ba/6e/b837e84a1a704953c62ef8776d45c3e8d759876b4a84fe14eba2859106fe/sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1", size = 20053, upload-time = "2024-07-29T01:09:00.465Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5", size = 119300, upload-time = "2024-07-29T01:08:58.99Z" }, ] [[package]] name = "sphinxcontrib-devhelp" version = "2.0.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/f6/d2/5beee64d3e4e747f316bae86b55943f51e82bb86ecd325883ef65741e7da/sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad", size = 12967, upload-time = "2024-07-29T01:09:23.417Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/35/7a/987e583882f985fe4d7323774889ec58049171828b58c2217e7f79cdf44e/sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2", size = 82530, upload-time = "2024-07-29T01:09:21.945Z" }, ] [[package]] name = "sphinxcontrib-htmlhelp" version = "2.1.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/43/93/983afd9aa001e5201eab16b5a444ed5b9b0a7a010541e0ddfbbfd0b2470c/sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9", size = 22617, upload-time = "2024-07-29T01:09:37.889Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8", size = 98705, upload-time = "2024-07-29T01:09:36.407Z" }, ] [[package]] name = "sphinxcontrib-jsmath" version = "1.0.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/b2/e8/9ed3830aeed71f17c026a07a5097edcf44b692850ef215b161b8ad875729/sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8", size = 5787, upload-time = "2019-01-21T16:10:16.347Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", size = 5071, upload-time = "2019-01-21T16:10:14.333Z" }, ] [[package]] name = "sphinxcontrib-qthelp" version = "2.0.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/68/bc/9104308fc285eb3e0b31b67688235db556cd5b0ef31d96f30e45f2e51cae/sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab", size = 17165, upload-time = "2024-07-29T01:09:56.435Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/27/83/859ecdd180cacc13b1f7e857abf8582a64552ea7a061057a6c716e790fce/sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb", size = 88743, upload-time = "2024-07-29T01:09:54.885Z" }, ] [[package]] name = "sphinxcontrib-serializinghtml" version = "2.0.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/3b/44/6716b257b0aa6bfd51a1b31665d1c205fb12cb5ad56de752dfa15657de2f/sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d", size = 16080, upload-time = "2024-07-29T01:10:09.332Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072, upload-time = "2024-07-29T01:10:08.203Z" }, ] [[package]] name = "starlette" version = "1.7.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/7b/2b/3850dc6bf7ef71b088962eba31dafc6cffd2f96e577ebb0bb316df96da3e/starlette-1.7.0.tar.gz", hash = "sha256:c79f74ea63cff761804fbbfb182f1e0b440c2d07b164d24700c5a1bab5d6ff5d", size = 2736246, upload-time = "2026-09-23T07:30:26.35Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/4e/d6/1ec1b290f9e0fb067899b61e1d37a30c923068bad260b216dbe37a7d2967/starlette-1.7.0-py3-none-any.whl", hash = "sha256:67f8e99895493dd2911a03f11314af6ceebeae4e704bb9f43dfc6a9db151c93e", size = 78980, upload-time = "2026-09-23T07:30:24.567Z" }, ] [[package]] name = "typing-extensions" version = "4.16.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" }, ] [[package]] name = "urllib3" version = "2.8.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/e3/05/b17359e1cefb4f909b5e40b1b90a496d987258916dbbf88e842c729f510e/urllib3-2.8.0.tar.gz", hash = "sha256:63bf2ead4c879426ebf22ef2a781eeb4aa3b4ae798a0435506f8687fd5bb9b63", size = 458972, upload-time = "2026-09-15T19:29:36.253Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/92/9d/c4e665119135114480843e7ab388fa94d8480650450e6f8e26b70d323a4c/urllib3-2.8.0-py3-none-any.whl", hash = "sha256:0cf3cae568d36aa9576b28dfb35f11328f1cb974ca7647d9475ebb86c75ac6e3", size = 135717, upload-time = "2026-09-15T19:29:34.577Z" }, ] [[package]] name = "uvicorn" version = "0.54.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, { name = "h11" }, ] sdist = { url = "https://files.pythonhosted.org/packages/da/34/30e9280707135d2cfc589dfff3cb796bd07a3aeb1a3e415ba09dd89d7bb4/uvicorn-0.54.0.tar.gz", hash = "sha256:a2e33cbfaa0306f8e6b0c13e0cb89d7d7a2da3e62b90c66e18c33d9807b28620", size = 112283, upload-time = "2026-09-25T06:52:37.601Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/38/0c/b54a4fdd7f90a3af8b02ebc9ce6712c2c208b7926a2f7bad95c33ebbe943/uvicorn-0.54.0-py3-none-any.whl", hash = "sha256:505bdb0f318731d45f1f712071fc781a8981f6847a31c902c9f5e652d4f67faf", size = 87427, upload-time = "2026-09-25T06:52:35.829Z" }, ] [[package]] name = "watchfiles" version = "1.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b3/68/e6aa0b77d217b31f8f486ec0cdfe5e00e6e38dc0be657e7d85819b9faf0a/watchfiles-1.3.0.tar.gz", hash = "sha256:99aee4a07847c06820765fd7b1b49ceac4f3f711ccb7d104655a33231de1c207", size = 108119, upload-time = "2026-09-21T09:08:55.664Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/68/fa/c0b840d5d8bafe640925408ce11095948c8945bb61be1e67d0ab629b872a/watchfiles-1.3.0-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:000b9688fc8133037a8b075c8ebf98f32844ff8964dda61e85c1db547dafc441", size = 399703, upload-time = "2026-09-21T09:08:13.518Z" }, { url = "https://files.pythonhosted.org/packages/c7/8a/894799b485fe9473ad10422a0d9668e53fb78e3a2b5cc6061159572844ad/watchfiles-1.3.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:bbc1198edfdc90fda0600f825aa94150f428dfcbf8138746f55998e0e660d64c", size = 397647, upload-time = "2026-09-21T09:08:15.16Z" }, { url = "https://files.pythonhosted.org/packages/5a/61/f277cd5ead05f5d37b98b41fbdd0c943ee0c04d49738f62e902cb291cf43/watchfiles-1.3.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4af9c464d410e8c44b58ddf1cbec7c3a02c1cb2c6c80df9c74659ba2fbda0ab5", size = 454994, upload-time = "2026-09-21T09:08:16.587Z" }, { url = "https://files.pythonhosted.org/packages/46/66/ea4c01382975e53b1ac1b0a0ea1b353416b8127fb6e04f5c1c313119aab6/watchfiles-1.3.0-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb94e40b9a0db19636b5e3fed0a9803452ecc7381df13b385272abb779ee83ca", size = 460105, upload-time = "2026-09-21T09:08:17.961Z" }, { url = "https://files.pythonhosted.org/packages/31/c3/1a501b597817096c9831c255f6270c124ce0e2faeb74124be24dcc93b86d/watchfiles-1.3.0-cp310-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7398930a2b76b9dc67a4e6b8bfc7baf30946892a86f417a3920c07bdd6febc30", size = 493761, upload-time = "2026-09-21T09:08:19.327Z" }, { url = "https://files.pythonhosted.org/packages/5a/16/77352d05e152c7786a016e9f1decdb4507e8aa5bcf3343c72142a0413d14/watchfiles-1.3.0-cp310-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:025b108f2d5cc2799cb941f79380df3e8b22bfb589c218e801767fe78d598c95", size = 577500, upload-time = "2026-09-21T09:08:20.723Z" }, { url = "https://files.pythonhosted.org/packages/95/fe/81001c55b24466fe9911135e50269fd3a29f43e446627a92adfab54553b6/watchfiles-1.3.0-cp310-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:be9ef3cd403d756a304a0a08a112163c63da7ec72c8c9d083c98818291ff8f29", size = 469209, upload-time = "2026-09-21T09:08:22.346Z" }, { url = "https://files.pythonhosted.org/packages/8a/cf/e3ad894ed5909a7c44b0707e89bc25d19a3d78b3a7aec846119443b6ae56/watchfiles-1.3.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b5768b49e426fd5b550b012c866db347cdf15c398ef98dc557b6e6b72fa74cd1", size = 458353, upload-time = "2026-09-21T09:08:23.7Z" }, { url = "https://files.pythonhosted.org/packages/7f/f5/2f106829e6e2bc91ad74614730f6df484202b9a4d20d927dbf2f08d37d44/watchfiles-1.3.0-cp310-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:d36c72fcdc08f143d87316a4c4c3b064e340da2c7383d39803950740ed418935", size = 468707, upload-time = "2026-09-21T09:08:25.167Z" }, { url = "https://files.pythonhosted.org/packages/15/37/2741ab776b978669c1f3bba1e573f1a184f727ab8b4d3a6d9c61090800ed/watchfiles-1.3.0-cp310-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:f0c9865240e065a2247f4b5248528ae3d01e0e8ae071462c250c884dc16e3ee3", size = 632510, upload-time = "2026-09-21T09:08:26.532Z" }, { url = "https://files.pythonhosted.org/packages/48/64/db4a4684275fe9b696ee5118c194705c1423b3d42181c3f8ee70194d5c73/watchfiles-1.3.0-cp310-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:380f513d26cc2e598266b88d66456e07d67ef4be8f0f435a1154d9c44b43d509", size = 661723, upload-time = "2026-09-21T09:08:28.004Z" }, { url = "https://files.pythonhosted.org/packages/52/80/95ddb24f6a2f595c031c24432ab7b8aa7d6d1ada0c6d63a552166e06e8d1/watchfiles-1.3.0-cp310-abi3-win32.whl", hash = "sha256:509d9f74d2bec5c1f4cd868ddcfe0c9601f7ea53066dfcb19d8ede742c4de661", size = 277761, upload-time = "2026-09-21T09:08:29.335Z" }, { url = "https://files.pythonhosted.org/packages/48/a1/1c4a1b3c8030cd03206232612be8a6e8799093490c220b0fcb935d02b75a/watchfiles-1.3.0-cp310-abi3-win_amd64.whl", hash = "sha256:1acabde19b67e673274e89a04d613b7fb1f122e2c4b8ec9a581e59c149264b61", size = 290643, upload-time = "2026-09-21T09:08:30.45Z" }, { url = "https://files.pythonhosted.org/packages/82/e0/d2db577c632d26d08078c4390dd870b8555c4e558df25d22270f0166c71f/watchfiles-1.3.0-cp310-abi3-win_arm64.whl", hash = "sha256:d978cc1dd7ba44f5590d7de74f7a9c8be7262d9e469c2c0efb8bbfc2574321dc", size = 285510, upload-time = "2026-09-21T09:08:31.561Z" }, { url = "https://files.pythonhosted.org/packages/4f/55/a122743607939429ab6a7272672f49303aa02bbd34c3f06676765a3861de/watchfiles-1.3.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:e0204f90677b7fb8d3a824279a71a01166bea9f6e53923b8056592d87b33b0b6", size = 395978, upload-time = "2026-09-21T09:08:32.747Z" }, { url = "https://files.pythonhosted.org/packages/3e/9d/0ee1b0ea42acfe72afa2484f0222e2386dd09992e9034a81e63cc3cdab0a/watchfiles-1.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7c1125e99f84934a92996ae343501285617373acd66f16b59fd5654b27ad8d80", size = 387997, upload-time = "2026-09-21T09:08:33.953Z" }, { url = "https://files.pythonhosted.org/packages/b7/f2/4319c6b683e8bd28465dfc53c750617a2a07f072eb2e3ab0182ccf812229/watchfiles-1.3.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a85e243c87109d9f48a2e0d10d6abdee8768cc114848b32281ec83d7f81d86ef", size = 448067, upload-time = "2026-09-21T09:08:35.104Z" }, { url = "https://files.pythonhosted.org/packages/b7/71/9f37ac192a51a24de3a2d7fe9aac1e99b2b2668300674ff7a9f85d95beb1/watchfiles-1.3.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dd538c71766c59c732e2c5cedc39323c99c11897b99f93eac773438e54af206a", size = 452881, upload-time = "2026-09-21T09:08:36.238Z" }, { url = "https://files.pythonhosted.org/packages/34/07/1224667c32a4a96876ddd642da69733cbfe6a15897db0865aec5a882cebc/watchfiles-1.3.0-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:da213822ec9082b62cadf3008e07a8c690ec7a3c89f6dc5e55a8cf07b56a1b26", size = 484802, upload-time = "2026-09-21T09:08:37.717Z" }, { url = "https://files.pythonhosted.org/packages/bd/4d/7f29cd87e1f826df86fc0418d0b8f370ca0311c29c618406dc492aad9951/watchfiles-1.3.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d4593392e87669836670f87d62fc754059eaeb0e6157af44060a46558cebe5a3", size = 567522, upload-time = "2026-09-21T09:08:38.94Z" }, { url = "https://files.pythonhosted.org/packages/e3/ac/3aa1f84abd4dbbfbae99a817dd931f77b439261c911189cad4c30fdfd470/watchfiles-1.3.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:26b81bc515a0f03f66a69a6fea86eea54c8eca4173c46dd7222569f79dd2f977", size = 461997, upload-time = "2026-09-21T09:08:40.142Z" }, { url = "https://files.pythonhosted.org/packages/82/1c/27c66764d5b0dbb70a36770a32eca217eba739c28b24e8583a035087a3d7/watchfiles-1.3.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca39661934749df580d89f3dde266f2af7c6968f4c21c72ce2dc387d299e6aa7", size = 451856, upload-time = "2026-09-21T09:08:41.305Z" }, { url = "https://files.pythonhosted.org/packages/1e/b3/a45827084d953c1d0dd3d75abf85dcac26a0f5d115ad2fe0b7e9e931573b/watchfiles-1.3.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:04c5500faa725d69a99b0f63750e80795e455bb951849e9c98a811b4324997c4", size = 459889, upload-time = "2026-09-21T09:08:42.58Z" }, { url = "https://files.pythonhosted.org/packages/7a/3d/07740d23ce2bb23419bb24055c435a9abe5fe03efdaea1ba0720b831aab6/watchfiles-1.3.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:ec3cd4bb181a7b6329134204c2277c7144ab5f62cce9c5335d6f901ee7ce8d6a", size = 624482, upload-time = "2026-09-21T09:08:44.064Z" }, { url = "https://files.pythonhosted.org/packages/91/46/9540f8123eaec86df989f26d7704cad0448d9fb7271763a4f8f9fe23617d/watchfiles-1.3.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:e5871d4a7f788a9f64fb06f5793389d29d171e5c32d37c3ebfd520b017cc7694", size = 653724, upload-time = "2026-09-21T09:08:45.257Z" }, { url = "https://files.pythonhosted.org/packages/25/9d/b9deb52f1e4528ffe44338c5ebab95c32ed79203082da661de50288e130a/watchfiles-1.3.0-cp314-cp314t-win32.whl", hash = "sha256:81c989d7267bfb7b676cd32c83bec97914e396870983cbf493e29415037bff5e", size = 270863, upload-time = "2026-09-21T09:08:46.518Z" }, { url = "https://files.pythonhosted.org/packages/59/59/8b1881afccbafda06248be4185e28034dd73c80c85e4da24f2cb0c1321eb/watchfiles-1.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d754e049006e81a9be49dc78ba7802081cee426e968cbc63ee19e164fa2d0e39", size = 286879, upload-time = "2026-09-21T09:08:47.874Z" }, { url = "https://files.pythonhosted.org/packages/46/dd/41c12ae9745d5d72f709333287679d71cbba68ad7678fed872207bba94f4/watchfiles-1.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:a5b631db08fd3032db57ec6a92d1777cd60f88803287e4e941a4858c53aab1e0", size = 276924, upload-time = "2026-09-21T09:08:48.976Z" }, ] [[package]] name = "websockets" version = "17.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/18/72/fba934cb3dff7a85d811820efffcd141ddd52b5a2a01637f64551373ff4d/websockets-17.1.tar.gz", hash = "sha256:acfea4c20bf54384883ea33b1240fc1db4f52e190823a4e2b334bc3e8bfca96a", size = 187520, upload-time = "2026-08-26T17:25:33.063Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/a6/0d/098f23c4c858e5de9459ffc554fa07d5493fbcfca7f040b5800cf1cecc35/websockets-17.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:76dd004f59115087c7b700474cb18f01325e37250032e19396c08ae41448e4b3", size = 217015, upload-time = "2026-08-26T14:55:45.194Z" }, { url = "https://files.pythonhosted.org/packages/13/86/bc1317b1a4d8c4688e2a7e564b5e004dab44c2534d7ca05de6ae9a863fca/websockets-17.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:581fa678ef46f4277cc8491312468e582f8ad609dbab907ba6096a08c6a0ff98", size = 214692, upload-time = "2026-08-26T14:55:46.366Z" }, { url = "https://files.pythonhosted.org/packages/8f/e7/df821761772beaa48c211ee0e234930b35c1473778470773823f56d3911b/websockets-17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:87f0d5e77548b0c40c8464cdb6108792e7e53f487c6400028a4ec28a8afbe5ab", size = 214959, upload-time = "2026-08-26T14:55:47.885Z" }, { url = "https://files.pythonhosted.org/packages/3e/92/c3fb72f11764812fc648bf3838d224972427b348e8b3989d9e0a9df87da3/websockets-17.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:882af300d2c6a092b93767d5de03c7bb56dfb06314140c8e872d3f48e09f7b74", size = 224278, upload-time = "2026-08-26T14:55:49.241Z" }, { url = "https://files.pythonhosted.org/packages/fb/05/9f82d090c8d2d861604147ef6dfb938a90b039f9358d5193f1df62558593/websockets-17.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0c863507ada5805517ca6dff1c524dcd42942efe6304dacf06700878398d21a6", size = 224557, upload-time = "2026-08-26T14:55:50.348Z" }, { url = "https://files.pythonhosted.org/packages/8a/50/5cbf677b865290fe36819ff00615826e7edc1df38786f770123ff39a933d/websockets-17.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d41ef69d5416fbc1d98cf96c37be6192d10fd101c3e0f8b3ddc36e09432b3c08", size = 225791, upload-time = "2026-08-26T14:55:51.75Z" }, { url = "https://files.pythonhosted.org/packages/c1/1c/eb8a032285243381b09a221ae384c972d5000453ad136add4d1595cec798/websockets-17.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5aefe78e6a3077fe22b5e64b04666a85a3eb8b934d40e8595a693adcbceb6f11", size = 228574, upload-time = "2026-08-26T14:55:52.922Z" }, { url = "https://files.pythonhosted.org/packages/69/85/413736251cb3ac04ce84cbd90e893d9a36a9698d4820b323aff3aa187e50/websockets-17.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f64e001bb7fa89b9f32cfa600bf8e9ac8ca26759d9b92ae01453ee303d9cd7b4", size = 226428, upload-time = "2026-08-26T14:55:54.263Z" }, { url = "https://files.pythonhosted.org/packages/d2/2b/a08bcc7fa1ca81a10f84ba32b6e6edd73a913f4b0c2640eed1fd626efacd/websockets-17.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:677014a073bcb1fbaa7e21144786864f16c08f856d66834f611eceb9006cbab8", size = 225184, upload-time = "2026-08-26T14:55:55.943Z" }, { url = "https://files.pythonhosted.org/packages/e5/8a/3bd2d0cf6b148c8c866d5d9fdcde30c04bfd81fdfac86813e69377eb4448/websockets-17.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0de501b7f2db11e83739ac20e2d33d46da4604b829f506c24be80e7def069391", size = 222430, upload-time = "2026-08-26T14:55:57.103Z" }, { url = "https://files.pythonhosted.org/packages/3b/c9/8e891ae342668735eabbbc669895e15195e4b45f24a4beeb58af76f414c7/websockets-17.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f62114a54117e4948a1e414e89521f7fe1e3c2f83f2a571a06a4fc6718b0900a", size = 225227, upload-time = "2026-08-26T14:55:58.375Z" }, { url = "https://files.pythonhosted.org/packages/e1/6f/c816f332dca11425e9bda7c07f7573eb5c5f8a735849d02b0d81e8ee20fa/websockets-17.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:eec113a5b41d124ef42ff56b0d74a6da3fd986400038eab9e58ee42a4024e837", size = 223831, upload-time = "2026-08-26T14:55:59.664Z" }, { url = "https://files.pythonhosted.org/packages/53/67/5e91d5308ce24fc1ec74f56536c12f4888bad45ff5ea50f3180f8c518c57/websockets-17.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5f051f8030a51815dc00e24bd2e5f1435af095c1cc111d747ac6e2a3620d7641", size = 224600, upload-time = "2026-08-26T14:56:00.873Z" }, { url = "https://files.pythonhosted.org/packages/bb/96/faa298ecf2570d35b0eb37caddf4992178d907e108ed74bfffb6bc092c29/websockets-17.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:655a8e28010f09fd6fa317e857afab3af7647f33e41dee88fa421e92086d1090", size = 225707, upload-time = "2026-08-26T14:56:02.001Z" }, { url = "https://files.pythonhosted.org/packages/0b/12/5710d2482ca5061c1eec5eb46f6313837c760d4115b1795c85b6c08be4e3/websockets-17.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:dc2b79afc074d2f3e64b26539350f697fe1b85ea1c49ea24eb588f247b053ce1", size = 223263, upload-time = "2026-08-26T14:56:03.092Z" }, { url = "https://files.pythonhosted.org/packages/27/47/0c30f4eebfd1d93fae779d268f678d48847fb98516f5200849574eee8820/websockets-17.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e4bd7eacb87d8cf3ed70d6392c770a0d92441f05d7d2a3efafb5bc171d5e3067", size = 224244, upload-time = "2026-08-26T14:56:04.321Z" }, { url = "https://files.pythonhosted.org/packages/41/33/46c256195a1255079ae23d1b1267b2e1843dc5f46a67f973cdf2a3523dff/websockets-17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ccbf3f4a9890d50b3a08ee04029fde30a03bfdeffaa19977628bf17251764e60", size = 224520, upload-time = "2026-08-26T14:56:05.521Z" }, { url = "https://files.pythonhosted.org/packages/06/9a/aef0792731df4352e5f417369b532b3325fe434765ca90c193f594ae1e67/websockets-17.1-cp312-cp312-win32.whl", hash = "sha256:7e724f843fa6a0614aece65a7c73e51d0f4412ca41dccac13c3caf98e69536bb", size = 217485, upload-time = "2026-08-26T14:56:06.715Z" }, { url = "https://files.pythonhosted.org/packages/50/23/493ecfdaf32898e5ea24dc900e33e5e317f9662d5d9ab2d44b2e111b4e1c/websockets-17.1-cp312-cp312-win_amd64.whl", hash = "sha256:617243e19a0992095956f406ee9cd3bc4ba92862d83cb1d83bb59ce574412bec", size = 217786, upload-time = "2026-08-26T14:56:08.055Z" }, { url = "https://files.pythonhosted.org/packages/97/3d/91954e2f7876f74ce1213e9b92c65a63b559cc4b942a931ebeb351cd9932/websockets-17.1-cp312-cp312-win_arm64.whl", hash = "sha256:9f4a08ff7cb68c27b18e09223cc6304e01d0f82d5a240d251266dfd2e6e44729", size = 217711, upload-time = "2026-08-26T14:56:09.267Z" }, { url = "https://files.pythonhosted.org/packages/1d/31/5f6450a7879f4f063ef08897cc385ea3ce3f1fe17f08b11e3fd959abdf27/websockets-17.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2a0162a6372110a5601cb5c9fd826635cedf69f3e110c545dd19774e040b970e", size = 217006, upload-time = "2026-08-26T14:56:10.509Z" }, { url = "https://files.pythonhosted.org/packages/d0/2a/c1b006fc861695d2aa4e35327b842015ce1d98cf8f99241829b3d6460bfc/websockets-17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:829dba1bc049779de9b332088c1a6a9858e96bd67e50b6b644a95e02b67836bc", size = 214690, upload-time = "2026-08-26T14:56:11.681Z" }, { url = "https://files.pythonhosted.org/packages/46/69/66e5b7d01445e0eeb1d4ab419c30315f2c90cf7a8a8cd4ecc47f894dba54/websockets-17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fd8f47dbf2e8adb15c847215f83436de3fdb120b51fdae0fbbdf69fd97a3ad80", size = 214947, upload-time = "2026-08-26T14:56:12.923Z" }, { url = "https://files.pythonhosted.org/packages/07/ce/033cafe2d2538562efa876b9149a2c7a0f7787870a4b1bb6e28adc9ceb6b/websockets-17.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9f4c0377a83e163a303514fdfab501dbe379bdc13e5b9312a91d112658b29dce", size = 224329, upload-time = "2026-08-26T14:56:14.212Z" }, { url = "https://files.pythonhosted.org/packages/34/c7/e1c2e8a67f6cc0aa43abe0046fb3b7a020980649e6a843751dc7ce9eb170/websockets-17.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c3241d684a76eaaef8b2dc789afde4343cd3aad55ea81e4e8ab3605b529bae51", size = 224611, upload-time = "2026-08-26T14:56:15.702Z" }, { url = "https://files.pythonhosted.org/packages/be/de/07c6d48eb3d2069709410c851e7de10ab83d752c4bd09862899627c2729b/websockets-17.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e5f5c7a893507d0e83a80b88aefd6522f7e882cd53f9722c6f23f5a020c9557c", size = 225848, upload-time = "2026-08-26T14:56:16.962Z" }, { url = "https://files.pythonhosted.org/packages/f3/dd/3c68572d20509648cc2fb6f50ccf3deeb4b87270f2c8966e99476e278ea3/websockets-17.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:00bf34b64501e3477e81fc281532ff3cbf4da26633c10b63979d5085d46602d3", size = 227290, upload-time = "2026-08-26T14:56:18.204Z" }, { url = "https://files.pythonhosted.org/packages/0a/4a/8f6651c8a22093539c9215af0c5bbf217b87b382c99d2112039b92d593c2/websockets-17.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ce0305b702b20d1e1d60a9aaace6bc89970e1753565543f310d549eab22c2435", size = 226476, upload-time = "2026-08-26T14:56:19.459Z" }, { url = "https://files.pythonhosted.org/packages/f5/be/f6fc33cea86b1127fd1297b18c107e81580ab55a73a39f9a934441ef321f/websockets-17.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29176d8b429cfa0fa443c473878d37a5c06cfd0cb36b71ba4314accc71e05906", size = 225233, upload-time = "2026-08-26T14:56:20.939Z" }, { url = "https://files.pythonhosted.org/packages/cb/83/65edaf05f7c9b1dea82f4d252fdc37706a84571646f06119a27b0a16fe19/websockets-17.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3709a1ab30b4b922027d22f68d2b61a0656a91680ac894a537624e6be7dd7f7c", size = 222488, upload-time = "2026-08-26T14:56:22.208Z" }, { url = "https://files.pythonhosted.org/packages/07/42/d1169c2f7f1f0032b0d4b0c00f0711a070cd7c735de37bfeb876bc0f9606/websockets-17.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:43bd0c1ceb924d67f5c1a5254d8361dd9d94246e6331a726064dfa2917880780", size = 225295, upload-time = "2026-08-26T14:56:23.445Z" }, { url = "https://files.pythonhosted.org/packages/a6/f4/64e2a386c3899b917c2933225c9b47887874229d159797f3bf1a11c20d51/websockets-17.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:1fce0f43e0d41422e0b2cad6561e1970df22f212f4c7e884967df7cf591b031c", size = 223891, upload-time = "2026-08-26T14:56:24.647Z" }, { url = "https://files.pythonhosted.org/packages/26/b3/dfb5c482f7e310a3432fdbb045ddfe6d34114680e89a233d4ff900a32961/websockets-17.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4031152769179ab8dcdeafc7b0e58052a49117560a28671700b47b2c7b717aad", size = 224661, upload-time = "2026-08-26T14:56:26.027Z" }, { url = "https://files.pythonhosted.org/packages/a4/cf/94865130a336029f46412adc127c4fbe380f46172b90ce251369e35c4302/websockets-17.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:a06f3b5085176763182449559e20391d7ce616a8972a9f7a33deda87ea6d4f3c", size = 225766, upload-time = "2026-08-26T14:56:27.455Z" }, { url = "https://files.pythonhosted.org/packages/96/34/eb8c658f86dfe562ed49a887a27424bfe9e618c26ea6f865b093d075d3a6/websockets-17.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:77b37cceca17291897c3c73bd30a7c7c7909593554b5da574ec852af83c1742a", size = 223323, upload-time = "2026-08-26T14:56:28.807Z" }, { url = "https://files.pythonhosted.org/packages/1b/7e/2629609652ece5ca0c7ac235927dd4511b08131e3a5d53439b798fddf002/websockets-17.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d8e83333385cac6030a5167fd18bf96cc6c58b914c308e683f05b0cf94bc8dd0", size = 224276, upload-time = "2026-08-26T14:56:29.991Z" }, { url = "https://files.pythonhosted.org/packages/a1/6b/8525737fe840b38e5f40956c198fb586a4fac1e07144d41a5b949b989cf8/websockets-17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:073c5c3f7e127041fa9d34a9e29ceefee8c3cafbd267ed2927318f425144380d", size = 224558, upload-time = "2026-08-26T14:56:31.184Z" }, { url = "https://files.pythonhosted.org/packages/74/ab/3a958c6cbcf74b118f601c20a80ac8bd5e8dfec0bcf7345116feaeefb121/websockets-17.1-cp313-cp313-win32.whl", hash = "sha256:2afb58c7ba48b329d56769f8dfd89f394efe587b65ef806bae810a484d6d3608", size = 217475, upload-time = "2026-08-26T14:56:32.431Z" }, { url = "https://files.pythonhosted.org/packages/22/36/fb521f0f2994c25509651f169efe5582dddd8713d57a0757ba87859372ef/websockets-17.1-cp313-cp313-win_amd64.whl", hash = "sha256:0340bbef6bfbe16da888b3983d666a4db4954ac3253c38f13bc7aba0c7db5a2f", size = 217784, upload-time = "2026-08-26T14:56:33.608Z" }, { url = "https://files.pythonhosted.org/packages/68/92/9b8419584681a12a7534b746dfb2737c466efe2455483e2fbf8b941a04ec/websockets-17.1-cp313-cp313-win_arm64.whl", hash = "sha256:7a72efa3bf4fa3a6669a54420a472ad056da3973d827f10e3a536da463f926c2", size = 217715, upload-time = "2026-08-26T14:56:34.865Z" }, { url = "https://files.pythonhosted.org/packages/90/0d/500cf5daea09d4669dff3a7d67159094a0bd6c4ef130381404f6edd3eb5f/websockets-17.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:0c9982938980e086da59f70d05f9418cd143401a601a0faac10fa48f7bb1cd3e", size = 217048, upload-time = "2026-08-26T14:56:36.03Z" }, { url = "https://files.pythonhosted.org/packages/97/12/5b12c6168aa269cffbfd24d177cd492b130120403a418c7e89462e27b4ac/websockets-17.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:57b39dc8541cf7ed3f639da82bf7451060483967f9e733da1f8173e4095f0642", size = 214737, upload-time = "2026-08-26T14:56:37.43Z" }, { url = "https://files.pythonhosted.org/packages/0c/36/e453e5106e4e2416f008ac222837c2f1637a063b08008afcd1088889b631/websockets-17.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:96abdecbaae746851b87c3a36cb4a661df93ca3d92f114270f79228bf1d00de6", size = 214955, upload-time = "2026-08-26T14:56:38.71Z" }, { url = "https://files.pythonhosted.org/packages/dd/30/0204bb86176db02cdfc678ce65ed808a66fab87d250ce61a8790800a60b0/websockets-17.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d9fc873e239c5abeb150bc24dbd1a7af23a9254526383ce0a077f5e20adbeb19", size = 224331, upload-time = "2026-08-26T14:56:39.924Z" }, { url = "https://files.pythonhosted.org/packages/46/c8/d8372256e00c4e3cab1115c45075d1eeedb642a3f2b42bd70c4deae03f06/websockets-17.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6f42912fa9eb4cb7c7ec9fde9b3332ba339eb8a8811981043d4029599f3d950b", size = 224685, upload-time = "2026-08-26T14:56:41.169Z" }, { url = "https://files.pythonhosted.org/packages/12/7d/650355b8f67f908ff99603351d4458d1a0b787d627950a47c38db7e25308/websockets-17.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f98bf378d7a5be047a044a1a27c987a8f355e10e3b5754617dbe756248cbc5ce", size = 225927, upload-time = "2026-08-26T14:56:42.359Z" }, { url = "https://files.pythonhosted.org/packages/34/6c/a9ffa5b903579eed76017870f055d75ecc73988d9d0c9b65a92ba0bf2a27/websockets-17.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d334d11398086bb5559606cb42d51c013ea7c061c7db701521392373d3c087f5", size = 227300, upload-time = "2026-08-26T14:56:43.538Z" }, { url = "https://files.pythonhosted.org/packages/9b/5d/4551c2269066af7481ee44605a0813770961615b5b5da3e87a8f5cb859ea/websockets-17.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5c27336b1a0ac56569493e858497870347854372395f50483725f8cdacc5a45c", size = 226533, upload-time = "2026-08-26T14:56:44.669Z" }, { url = "https://files.pythonhosted.org/packages/3c/43/237a99233e5c445759a613831b3a92e91905afc064dc3bd0ad33c35fd1e2/websockets-17.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:67258b00302a5aaf0b267771c7014b13429abd7ea17eebc4c55bd935ff101555", size = 225280, upload-time = "2026-08-26T14:56:45.83Z" }, { url = "https://files.pythonhosted.org/packages/d3/b5/e9407a91613d1d1cd932414143a1012096b26674a782fc55a0bd23217ee4/websockets-17.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:455ffeea0879d313205df1e745e5883e1feb7f31ecd26be882f5f0babd3db04f", size = 222540, upload-time = "2026-08-26T14:56:47.053Z" }, { url = "https://files.pythonhosted.org/packages/db/d2/db76628db0577b783205d9779f64d8e373416b04c62d1546be4b75dc8540/websockets-17.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f7233eaf441a345a5943a929fd4b5ea3278f11aed35a9ed0f3106b8cb3ca846a", size = 225354, upload-time = "2026-08-26T14:56:48.32Z" }, { url = "https://files.pythonhosted.org/packages/a9/4c/2174181c067b89a74ae18e2650c2ac29959f4b796afe876ab3f4d30d642c/websockets-17.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:c65da239a5ad553619804c1f9d65c1a0b3005381c6158ee14da2c7444cbd0c78", size = 223867, upload-time = "2026-08-26T14:56:49.579Z" }, { url = "https://files.pythonhosted.org/packages/df/75/274decb9a8253561b5be3261e02a6676fc8ecdf31e95b722e53d5bfb8fd2/websockets-17.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:9fa1ffa08c81a4f809cdab6129f8e55bee4650b9d6d3461019dda73aacd146b6", size = 224652, upload-time = "2026-08-26T14:56:50.885Z" }, { url = "https://files.pythonhosted.org/packages/9f/e6/49824f1fb4db7656d2f7492b1d8be16147b759d909490e32f4776843ee64/websockets-17.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:406b8107943a43ef4649b1e0cb0cdc052bbf08fe1c8905a623c4af9586e5cebb", size = 225822, upload-time = "2026-08-26T14:56:52.356Z" }, { url = "https://files.pythonhosted.org/packages/b8/6a/5dc43838c0b02a95f42c47a0de33c5ddd7767a9feeb4d0d8777ac1cfefe4/websockets-17.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:4e8ffcb486c8490a34a4cef5e4409d8da5a1cb1681e5bf7d786ce5e84aa8540d", size = 223379, upload-time = "2026-08-26T14:56:53.699Z" }, { url = "https://files.pythonhosted.org/packages/c2/62/585637cf06d6b321232f79c55dc14d65518d12cf87c94c44f5864068810e/websockets-17.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:fb88076df585b69c5761c387c0081aa87d7b9eb1b205a6535ca4777e25650d81", size = 224330, upload-time = "2026-08-26T14:56:55.184Z" }, { url = "https://files.pythonhosted.org/packages/de/68/c3b234a6a1366b6ab5bbfaa4434a1b946e1dc4e8ddd6824bfd93a8835b7f/websockets-17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5d4724255fb8398acd9e583b97eb2279cec20e0bd0f9a94bf75f6056ef9f13da", size = 224622, upload-time = "2026-08-26T14:56:56.393Z" }, { url = "https://files.pythonhosted.org/packages/6a/d4/84cf3d1376f5d8207f55f43c1c818babd6b89447f5dcd01f18a6d5526796/websockets-17.1-cp314-cp314-win32.whl", hash = "sha256:be3f0129c5654517b2abf07dcb75bb1d9479759a4ccfb569e8293579e9fc029a", size = 217036, upload-time = "2026-08-26T14:56:57.652Z" }, { url = "https://files.pythonhosted.org/packages/d0/0f/9e7ac63c5d7cb642952200814f584318e65146df008b7d375d5d9c6b2c97/websockets-17.1-cp314-cp314-win_amd64.whl", hash = "sha256:2a4dc6ef83f4559e0d05f313a375cb38f63c986096a9da99fe94fdd779d313e5", size = 217382, upload-time = "2026-08-26T14:56:59.065Z" }, { url = "https://files.pythonhosted.org/packages/54/bb/1ae6b91f7f3ac05f5c9f14a72dc2181c115ff370bcd8a7f10f02c174adfd/websockets-17.1-cp314-cp314-win_arm64.whl", hash = "sha256:46c0331c9eaaf73a559f3a9e388466be0df96eb83d40f06f1ca6ab6613b35c82", size = 217268, upload-time = "2026-08-26T14:57:00.654Z" }, { url = "https://files.pythonhosted.org/packages/b3/f0/f65644d0e0b2b90918a8c41503841cc4072a58f2bf76c09bc36e751fc0dd/websockets-17.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:d411ea5ca18ac1b12c0c94be88b60c18ca641ac43bcdfdf1c9f79d46cdbe1603", size = 217379, upload-time = "2026-08-26T14:57:02.181Z" }, { url = "https://files.pythonhosted.org/packages/ff/35/4c46d1f620ac1a30f92b6eae78ee40a772a93f568647ca7ccdc5ea283cf8/websockets-17.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:07fa3e7c30e2c577928d359b56bf872a3e0cbcc15553eaa0907c1ee86344b56f", size = 214911, upload-time = "2026-08-26T14:57:03.478Z" }, { url = "https://files.pythonhosted.org/packages/04/6e/4587e8406d7c1188e97b9cf466c081e93399380d447f885bfce81626cd37/websockets-17.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6de9acef07e3a78e9567fcd26c29011a4da8f050b13004bbf880a0fd82a6eea5", size = 215115, upload-time = "2026-08-26T14:57:04.692Z" }, { url = "https://files.pythonhosted.org/packages/ec/06/1381c8fff525041025909eb80ace32489194a00ba22a0a8d428030afcc84/websockets-17.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ea0ed9373b880115911d9d39634bccc95b8ce590c9c42e8589f5cacc3ef3cee2", size = 224696, upload-time = "2026-08-26T14:57:05.899Z" }, { url = "https://files.pythonhosted.org/packages/36/9d/9034e867dc85340be058619751742b895f722326e83100d110063461ca07/websockets-17.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:50903d335bfda026c2fa11dd9aed09d8cbee0c451e3a85122a9acb041b7dc69b", size = 224975, upload-time = "2026-08-26T14:57:07.262Z" }, { url = "https://files.pythonhosted.org/packages/40/eb/ed03aa3cae748ebf6397e5d44028f433f746bad09dc568ff754fda3a3c9b/websockets-17.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5a74531ce81af587f906ab42f194032388fcff8fc7938402e5917c9147a39441", size = 226151, upload-time = "2026-08-26T14:57:08.524Z" }, { url = "https://files.pythonhosted.org/packages/b1/c9/cc1964a096d16f3b73cb1ee5f14f277f5a3bcac07c6e8f9a1dcded99f4c8/websockets-17.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8fbf28e639544503b7d1c96452a5e5e043e4108d89b1f3fa02910603622d19db", size = 228292, upload-time = "2026-08-26T14:57:09.846Z" }, { url = "https://files.pythonhosted.org/packages/1a/26/46da6dd0363c2db2e4876fd59a40fd40c1943a82d7018d0a33afbce47d52/websockets-17.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f612dc57f00c07cf4aa2673f7cbceabd654ad2457b7e639f061b794d6e11f9fd", size = 226722, upload-time = "2026-08-26T14:57:11.118Z" }, { url = "https://files.pythonhosted.org/packages/78/98/ecd8f5e1c5d0e54c08ebc5c66852271112166db68107cb0e17ca1bf25009/websockets-17.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1c7ac77401227212dc6e849182feee50d57cf456ec6329ffd6979c94bb136c5c", size = 225451, upload-time = "2026-08-26T14:57:12.601Z" }, { url = "https://files.pythonhosted.org/packages/65/4d/da8d2760db53e17aae763738b6ba834b1fcf16813d3632f3edb6951e1ec8/websockets-17.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:32a2a68d989d6e5b74a9d5095415c51189ebae29fceb7cf2b64a1c0318a81256", size = 223003, upload-time = "2026-08-26T14:57:13.875Z" }, { url = "https://files.pythonhosted.org/packages/a4/40/ea401c141a79c5b1d0021a0dab9d0df2051c108f1620fbb39a6e7c714c3b/websockets-17.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:aec00f018d34c67500ff0438dc314b40277be4a1b983cbacbf53ccf7db63e257", size = 225704, upload-time = "2026-08-26T14:57:15.091Z" }, { url = "https://files.pythonhosted.org/packages/e1/8e/07ab3f44215d89840d5385fdcaaab1fed8caeffa67c6899e15062957c12c/websockets-17.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:0014eaff8ad5b3b43feda2279f9d34bf2eaae040720b9fbbb55944b10f40b14d", size = 224192, upload-time = "2026-08-26T14:57:16.3Z" }, { url = "https://files.pythonhosted.org/packages/58/93/ccf1af0a23e5748d4e22292a377d78d15cf294d7e707bbb11a8990ae6bd5/websockets-17.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:db9d7ee47f3ba531e278be539af39e2c7c7d28fb94897b6cd1120d63b0ef5922", size = 225082, upload-time = "2026-08-26T14:57:17.531Z" }, { url = "https://files.pythonhosted.org/packages/e2/db/e32200f99ce282e728d2929f2c429db353cf3282db7d0eba99eb32c9fec1/websockets-17.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:ff3e2ba7a9f0a110b0555452e9b5a03a34e11662544e01beea15f144b48ba7b7", size = 226101, upload-time = "2026-08-26T14:57:18.802Z" }, { url = "https://files.pythonhosted.org/packages/28/3d/e7a6e9777b29433620167c98f3caaff0d6b08b1239a273ef7f7fd1393349/websockets-17.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:6da17fc94bd270f5987b10bee113461ac36a36a98b0481ddcc98056e5a90001a", size = 223794, upload-time = "2026-08-26T14:57:20.313Z" }, { url = "https://files.pythonhosted.org/packages/48/05/ac569090726dedd6656f3ee28b0c02dfb1ba76e898dceaccc2987a237cef/websockets-17.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:e8dc3fa6d6b7ead3f9de57895f41b116a28787548e066365d9d90f7356bcaad2", size = 224567, upload-time = "2026-08-26T14:57:21.634Z" }, { url = "https://files.pythonhosted.org/packages/14/50/4ef62941111db6b31193f4fabbb65f845a5177579040cb8fe0d774d25034/websockets-17.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b65d5fe48219dc2d5e158de9e6514e75600f379cc7e37108d35f31764c155566", size = 224993, upload-time = "2026-08-26T14:57:22.86Z" }, { url = "https://files.pythonhosted.org/packages/28/42/2b95ada4ea19bf3a2072b68669ce4f4afb212690b727d31640576287fd68/websockets-17.1-cp314-cp314t-win32.whl", hash = "sha256:2cce251f3e2469b99b6802b55435bcdd07123b41870f54c87b336183af9d7e68", size = 217168, upload-time = "2026-08-26T14:57:24.466Z" }, { url = "https://files.pythonhosted.org/packages/32/0a/67d5ee08dd8060a37d612fd40a625b5376ad19ae48fe1c8ad428c278b817/websockets-17.1-cp314-cp314t-win_amd64.whl", hash = "sha256:8f6c38cdcaf98a911d7acc25577f2f9e710f3a2fc2bde1563556784320196b51", size = 217508, upload-time = "2026-08-26T14:57:25.983Z" }, { url = "https://files.pythonhosted.org/packages/76/a3/822005d0c674451d2411027b878cdc128a2b7ea5a30d337d9e279da22eba/websockets-17.1-cp314-cp314t-win_arm64.whl", hash = "sha256:d1e2f5fa2b6d01f0d85b4f223fea7ed1d504be282a02a81bd2be4817ef7a2f03", size = 217425, upload-time = "2026-08-26T14:57:27.324Z" }, { url = "https://files.pythonhosted.org/packages/de/d5/99a6c6a1eb5d5ae9f45f59a3c97f4e3b21f310eb404a547fb3e7d2fc054c/websockets-17.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:88381602e379165b66244b2ebc29f9b23ea0851fbe63ae157f91ca324f072d6f", size = 216970, upload-time = "2026-08-26T14:57:28.575Z" }, { url = "https://files.pythonhosted.org/packages/a6/0e/1e7f6e833728193958d3ed3d67b5d57c3c7cfa948abf94d4bc553257c954/websockets-17.1-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:88bc5138e53903a85c354e59df7ba73ce306f7b09724cef74dba121e60a88ce2", size = 214699, upload-time = "2026-08-26T14:57:29.862Z" }, { url = "https://files.pythonhosted.org/packages/07/00/95d39549f86e34425a0412bcbe61708dd1fc46af654e2134a6c4389102ad/websockets-17.1-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:3546ef55b3a074494106508bc6505c73825970d2d9505f7bf53882b3e88b0d1e", size = 214927, upload-time = "2026-08-26T14:57:31.148Z" }, { url = "https://files.pythonhosted.org/packages/4c/ff/b442415fc4f7f9943b0fc8e8eebaa13923ca73361e167c439ba634eecbd9/websockets-17.1-cp315-cp315-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9ae55d24241fc055f22aea3ac924559069848bd0ad4ea065fdd72d2194685fe8", size = 224373, upload-time = "2026-08-26T14:57:32.833Z" }, { url = "https://files.pythonhosted.org/packages/a8/dd/b83537aae4cf61615b9d8b2dbb235c0030ba85457a6d934798273814600f/websockets-17.1-cp315-cp315-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d7b349265fad6244013eecd99df8d83c12bf3013943e431f4fadd5bffc37db42", size = 224801, upload-time = "2026-08-26T14:57:34.041Z" }, { url = "https://files.pythonhosted.org/packages/76/83/5ab0abed58454909e8dbab45086ac68ee4556d7a8ada26735addc909b903/websockets-17.1-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc5789e5ea182b77a38881383ada5347202a6c66f4857d054e075290e80b604b", size = 225967, upload-time = "2026-08-26T14:57:35.292Z" }, { url = "https://files.pythonhosted.org/packages/4b/26/e2412f2b998a8c1dfc00c0709ff6ee0c634dd0b0b4f92bdfe9667876b71c/websockets-17.1-cp315-cp315-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:ce13c7d233239e739600a57d4a73c1192ad8259e655a4d55aa1a454242bc809d", size = 227664, upload-time = "2026-08-26T14:57:36.493Z" }, { url = "https://files.pythonhosted.org/packages/ec/25/0dd4495df3c0e02f6db705312ba85ab9b2dd42257dc23eb0da10066e4844/websockets-17.1-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1036189bd34b0bc1b10a4679321e2c7968af317efe6e8e4c1c5141c4254fb5bb", size = 226447, upload-time = "2026-08-26T14:57:37.781Z" }, { url = "https://files.pythonhosted.org/packages/be/67/6df3f63ffc48f08126ed0cd2fd2a41092967c3e364f8ec100deae90b6d77/websockets-17.1-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e78fd4b7b2c5086a38671c9c882c1e643385eccea360b5b1fda4a105e590087e", size = 225343, upload-time = "2026-08-26T14:57:39.133Z" }, { url = "https://files.pythonhosted.org/packages/b1/8d/a8479bbb09ff054907d141123d8f52fb6ae5ac39c6dbe39e6a02a8408309/websockets-17.1-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:46e7a10bf04318c7b0c0273791925ae5e1cbe4a11e34aa934d2ef27862058a80", size = 222748, upload-time = "2026-08-26T14:57:40.478Z" }, { url = "https://files.pythonhosted.org/packages/40/fb/4c3d2a3269cde3f3087916de9c3d9fc5d7196b46846d8c3a9ae59ad0a884/websockets-17.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:33e45c7ea38428e740a7f233555d71df0b875cef7fc080acebc9654475e35335", size = 225453, upload-time = "2026-08-26T14:57:41.859Z" }, { url = "https://files.pythonhosted.org/packages/7f/1c/6467b401d19408f34e1c7389c222c2c7e1dfdf08c551190269b5eabc726c/websockets-17.1-cp315-cp315-musllinux_1_2_armv7l.whl", hash = "sha256:6e63c01803be425ff062b7f7fc201a74def1d49fc94a2410dd17375df75936e9", size = 224112, upload-time = "2026-08-26T14:57:43.136Z" }, { url = "https://files.pythonhosted.org/packages/c5/5f/744e032ac80e11039a7447657ebabb46e9b5c2dbcec83be571335212932f/websockets-17.1-cp315-cp315-musllinux_1_2_i686.whl", hash = "sha256:722ec21717eec6477bce582147a28acdfe034e604239466a6a95daedb863e774", size = 224646, upload-time = "2026-08-26T14:57:44.871Z" }, { url = "https://files.pythonhosted.org/packages/9f/47/bcb9128d9afc4d0934d9192e2a24897ca2f7a63df2654904915349c6c46d/websockets-17.1-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:e74e41f0ad12ff1e8983e349daef79d37cc8280c743ce9d134d6c74c18dab5d6", size = 225797, upload-time = "2026-08-26T14:57:46.338Z" }, { url = "https://files.pythonhosted.org/packages/c7/e0/b058047b7cf565e1105b10ef6b6b24a6ebe3575678c7dc75a645334705a7/websockets-17.1-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:12fe8984a32dbfd084e0603f1a8d740c0180cb85b3174585c54a80d2455a8394", size = 223605, upload-time = "2026-08-26T14:57:48.175Z" }, { url = "https://files.pythonhosted.org/packages/b9/69/fc1555bff884de363f1bf9eebf2836dbeb29fa7e4f957debb7bbcf43abba/websockets-17.1-cp315-cp315-musllinux_1_2_s390x.whl", hash = "sha256:01dcb47deebc40b38fd4a493b9b9f4d0a704b7bec6f35e4d34085b329abce71a", size = 224508, upload-time = "2026-08-26T14:57:49.407Z" }, { url = "https://files.pythonhosted.org/packages/e7/f9/648d4e68621688b19093b06f7b497d520952e68cdea1c1b54371fe9491de/websockets-17.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:f4c45ee2512d3757b5e6c67c5a34e435143f2ecb7df3324f9fd888688c45c0f4", size = 224767, upload-time = "2026-08-26T14:57:50.799Z" }, { url = "https://files.pythonhosted.org/packages/58/93/f8342b55864f71df13eb8e9ef7dce691b87a87f04f75bb8a1385b3336e7c/websockets-17.1-cp315-cp315-win32.whl", hash = "sha256:0f4f50dfe2cc810fc4e2de979b35e83bf8bb4bccdc6fe472d93762ea7b1d5927", size = 217003, upload-time = "2026-08-26T14:57:52.122Z" }, { url = "https://files.pythonhosted.org/packages/ea/f0/7b5fdb774c245e0b6217009e2a24d2105c1a64923949f33be41aa7959302/websockets-17.1-cp315-cp315-win_amd64.whl", hash = "sha256:4af784f3e436f65b355c117c6497320f2b5cf6a559295cb1c4c7338e335d45cc", size = 217300, upload-time = "2026-08-26T14:57:53.492Z" }, { url = "https://files.pythonhosted.org/packages/76/33/1fe6ed1b5087516115ca451b2c240314b010647071f8fc3bd78a21e4dddb/websockets-17.1-cp315-cp315-win_arm64.whl", hash = "sha256:d58159af7835fde09c462394293c0d7aaf8fb4557d8f8e5699f5e722ccae013d", size = 217214, upload-time = "2026-08-26T14:57:54.88Z" }, { url = "https://files.pythonhosted.org/packages/94/ca/ed02e75996a266d76c5fcb5dd9b930db4cf2b388ca5fa3d2a72086f81568/websockets-17.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:1a5cf4e7bbe3ca499e6a289206cb4fcb7444b09919e129bd517f57d5fa192c13", size = 217282, upload-time = "2026-08-26T14:57:56.108Z" }, { url = "https://files.pythonhosted.org/packages/bd/7d/d536f5bc89ea5b52fd1c1727c59fabafee6bc41f5ce92c3bd2f83047908c/websockets-17.1-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:416b4bc8789a1865a3ff643ec4ee073a5f52402d0dbeafd27b1798d5dd6b6a51", size = 214863, upload-time = "2026-08-26T14:57:57.355Z" }, { url = "https://files.pythonhosted.org/packages/37/37/944cf17bad668e9be1247e6314f88a48b9faf7c250e383410db8b38af0b9/websockets-17.1-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:259f45358c76d3b18489e3e80636cdbe807e05ecf1b10fdf1a779106d23d0c8e", size = 215073, upload-time = "2026-08-26T14:57:58.719Z" }, { url = "https://files.pythonhosted.org/packages/74/bf/3267966cc1bbc2b8fa62fd329651b0af502df1f5d1c0eed027ff339d6aa8/websockets-17.1-cp315-cp315t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d9d01e8ede41fea4f5a847dad9d628355f74905f437a5b6856d67aa66d193800", size = 225229, upload-time = "2026-08-26T14:58:00.235Z" }, { url = "https://files.pythonhosted.org/packages/7f/d8/85ea722f483510abb39fc71aafb4465d17cf9051a275ab036874ff3c300c/websockets-17.1-cp315-cp315t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a7b35181a14cbfcae163b4de545d22abfd07d06c2c41ca69cfcd99251d6888ab", size = 225500, upload-time = "2026-08-26T14:58:01.994Z" }, { url = "https://files.pythonhosted.org/packages/50/ce/64c7d00005bd0d15ecb5c5fcb7fb2597b6b92ddd16c4fa6bbc3d2835ad63/websockets-17.1-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6a8e768a048c2220697477ce2e67e4345dc9f693d0ee6af53945b5e30227c6a7", size = 226829, upload-time = "2026-08-26T14:58:03.327Z" }, { url = "https://files.pythonhosted.org/packages/b4/dc/096c67940fb957e667ca3c542818150434eb0388c6fdc90b3a502f3c3e96/websockets-17.1-cp315-cp315t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:880069d21cc33a558dcf180924a546d1ecf8ada5be3e4e70acee87019d706a24", size = 228457, upload-time = "2026-08-26T14:58:04.78Z" }, { url = "https://files.pythonhosted.org/packages/51/fe/f2331b6b7ccc67589891da354fa46a5cb79e95f83b9fd0e734d77f1f2140/websockets-17.1-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cec1bb8f22abccc8d20f8ca63df9be41600c26c190f4b97ee86c675fd4a863a6", size = 227265, upload-time = "2026-08-26T14:58:06.102Z" }, { url = "https://files.pythonhosted.org/packages/47/a5/fb1642302f8ec77ca922203074f155a9831a5128ad75e725059a476d1227/websockets-17.1-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f3a1d577e081667dda7f8e5b4796e6e32f9713c93e2a3d930669519840a3c623", size = 226143, upload-time = "2026-08-26T14:58:07.464Z" }, { url = "https://files.pythonhosted.org/packages/d7/41/7133fcfb63f5562750b269d6a845c689dde6a2c6407286da395beea19ddd/websockets-17.1-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:dc053f9e95a76213c5eb7ed95779f7daf0d2bf0e4e03073629ebfa43a033f151", size = 223501, upload-time = "2026-08-26T14:58:08.766Z" }, { url = "https://files.pythonhosted.org/packages/64/b1/82b36bfabc79ff2d383a1fc043cee6a13f794ef4f6bf1b4810ad6988cf6f/websockets-17.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:bb0efe019480a1c93e168ce96479273aaebd672fc8c350d5eed1e507ababb1b8", size = 226330, upload-time = "2026-08-26T14:58:09.987Z" }, { url = "https://files.pythonhosted.org/packages/41/7d/5b511b9bf6e9ad331e6ff902fcbcc71c3794d10ef3b5efe80ccb8f0a7861/websockets-17.1-cp315-cp315t-musllinux_1_2_armv7l.whl", hash = "sha256:615746b12b26a3fd4077bc6fbeb277a1c192a45dd57b531d07ad9ed5c52a9a7a", size = 224980, upload-time = "2026-08-26T14:58:11.303Z" }, { url = "https://files.pythonhosted.org/packages/e0/50/aed08f25301f8eef23be903ff9319fcf35630ca2bdec9d226f7d804dd5b3/websockets-17.1-cp315-cp315t-musllinux_1_2_i686.whl", hash = "sha256:1a20136d61f9ca3a31493732762661fafc2c20e8861930214e21afc6a8a692a2", size = 225478, upload-time = "2026-08-26T14:58:12.543Z" }, { url = "https://files.pythonhosted.org/packages/3e/47/0d63d4168536b4682c9d19b7399443b1176f25dbb68878374fa716670230/websockets-17.1-cp315-cp315t-musllinux_1_2_ppc64le.whl", hash = "sha256:2786cbd273ab69c22612db8a41229ddf2c158060b17b5928884bf388d07887f3", size = 226588, upload-time = "2026-08-26T14:58:14.457Z" }, { url = "https://files.pythonhosted.org/packages/b3/dd/844bd0b6386fc81ed6a55f4b6dd26f01c6987eda205afa10175ea12b2164/websockets-17.1-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:b1c323fc3be1dc3f87f6c59458cb7d9e13dcbbf971d6c3f3e2bbaf58d3bfcdfe", size = 224336, upload-time = "2026-08-26T14:58:15.778Z" }, { url = "https://files.pythonhosted.org/packages/96/18/03709c84bc88ec4dcea68d4be4ccd07d611073dec111203a5bf45af8809d/websockets-17.1-cp315-cp315t-musllinux_1_2_s390x.whl", hash = "sha256:12c8e2b25df59755954a04dfa09c990b96691025aaf7eafd19ed6da24b09c18d", size = 225197, upload-time = "2026-08-26T14:58:17.141Z" }, { url = "https://files.pythonhosted.org/packages/27/cf/0d1c694b6466c89e875b85b32b51312c472cf6708eee91914866f5087dde/websockets-17.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:f58f58b4b29bbea2a3635e2c56eff4a3adab011fe383802a9e542e31b97085fc", size = 225493, upload-time = "2026-08-26T14:58:18.521Z" }, { url = "https://files.pythonhosted.org/packages/4e/f5/99857c3dd9676749f33e3668665a34ad6099505fb8d75eb084f49f7807a9/websockets-17.1-cp315-cp315t-win32.whl", hash = "sha256:f78a3ffb1994304db2c0c4588e4d1a518079b557054fa3bb985a6f5e50ff49a3", size = 217130, upload-time = "2026-08-26T14:58:20.037Z" }, { url = "https://files.pythonhosted.org/packages/2c/84/77599922ab441bfe61508f97dab2c71f8e114d31793993ea54011db16199/websockets-17.1-cp315-cp315t-win_amd64.whl", hash = "sha256:ad68c28a27246fed109a4409393d677b7e1388345cbbd2f5aee5c182d8506110", size = 217448, upload-time = "2026-08-26T14:58:21.382Z" }, { url = "https://files.pythonhosted.org/packages/ce/3c/8b9a225b523f06a9389be81f1b0ab07c49bec6014742e6aa359c1f920f1f/websockets-17.1-cp315-cp315t-win_arm64.whl", hash = "sha256:e552e0037230ac16e5f568de7012041344d1b18c9feed30ec2891b8eba55af81", size = 217372, upload-time = "2026-08-26T14:58:22.807Z" }, { url = "https://files.pythonhosted.org/packages/41/63/23572870e01836a98346075b9e17a8bc24a6ddd9800a3204ceee58677f3c/websockets-17.1-py3-none-any.whl", hash = "sha256:f221081107b8c48184d99f7019604486376e7ef826037e70aad6b02540732c23", size = 211134, upload-time = "2026-08-26T17:25:31.397Z" }, ] [[package]] name = "wrapt" version = "2.5.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/3e/d2/a254a26d8ceaea87e0eee2e89fcfe53ddc1858418647493bb2937549ab6f/wrapt-2.5.0.tar.gz", hash = "sha256:c48cdb6c904dca76d9915a579e4a5fab6b0c25f650c1019ce78a78effaf7a345", size = 183288, upload-time = "2026-09-27T01:41:56.874Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/2a/a6/44589f9b34160280a1fbccbcf206b18df034a1b3e9584336d3a7f039a33c/wrapt-2.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b898caea081303006decc562c7fca5126f7c96507e78dd8f1ae3285dfa50ddc7", size = 106192, upload-time = "2026-09-27T01:40:00.941Z" }, { url = "https://files.pythonhosted.org/packages/ce/2b/94db3ba2e9528400e4173cbb67a56822a4644d4c2b319feb0cc638094b36/wrapt-2.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8837fbe708cb9d8a2d32a37dee836d24a531f02560db26418e2b181986fa21cb", size = 105828, upload-time = "2026-09-27T01:40:02.494Z" }, { url = "https://files.pythonhosted.org/packages/11/0e/3ce67af67525c0068680637c0eba823ce2f0fc6eff2d1779cab656fb8c3d/wrapt-2.5.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0cabb9c17ab79b2549d1f23b36f436473ad9253ef53995a817feba26fae69d5b", size = 250177, upload-time = "2026-09-27T01:40:03.819Z" }, { url = "https://files.pythonhosted.org/packages/d3/3b/262b2c3c38aca6fa32cfea6407dbb346ce26ff788179b98137b3b3f3d8ca/wrapt-2.5.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6761765cc520ff9616fb035c02a85d1d744f7f70edd4649718fd0d09c589eacf", size = 250332, upload-time = "2026-09-27T01:40:05.15Z" }, { url = "https://files.pythonhosted.org/packages/2e/ec/d54d273a2223d1964ecd5b5954aca18e8fbf2c5d8471e60ef3cbaaed9325/wrapt-2.5.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a145a7826eddea3eb5814903f98f93756042b919bb5305544cb1331daa2705b1", size = 229600, upload-time = "2026-09-27T01:40:06.47Z" }, { url = "https://files.pythonhosted.org/packages/8e/88/33c75ac47b13b0bf77b44d96d02b1d0a2e6db66180909bacce86ef8fcd8e/wrapt-2.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6a9ee62a970075738909909bdbef3a7da9f7ae03dfca584db283547a29503b56", size = 246890, upload-time = "2026-09-27T01:40:07.838Z" }, { url = "https://files.pythonhosted.org/packages/d2/0e/c3a3a158801e5bee7a0d9aa3561a127bd80f166b02b7b520a39e45d8f9b9/wrapt-2.5.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:691671ea05684f921ffc2e935fd3f9311c1795a10fbbfa006b46269733668f66", size = 226463, upload-time = "2026-09-27T01:40:09.206Z" }, { url = "https://files.pythonhosted.org/packages/7c/2c/4c48ba51698a87e2e8299fc04856bbef391cb725f195bc93f0bc6196e1f2/wrapt-2.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e716f47c7f61e11709d3c0904213c94fc22999abf0c41461276cef886c1e8b4d", size = 238583, upload-time = "2026-09-27T01:40:10.631Z" }, { url = "https://files.pythonhosted.org/packages/33/d2/077835618ed96b131730f74301a7236efd841ec2f341afe36303389b56ef/wrapt-2.5.0-cp312-cp312-win32.whl", hash = "sha256:5421acb5c363a9bc959122a8645e3f1f42010c932dc53885b11a5ff5b5a6d730", size = 100221, upload-time = "2026-09-27T01:40:11.91Z" }, { url = "https://files.pythonhosted.org/packages/96/9f/9e56db2a3492275be809082c6f63a7a063f52b3e1812aae1eb03ec33f64c/wrapt-2.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:ab45839c912777e2738fed369636589c8b2a6d9c44ca56de0fd0814581d467c2", size = 105786, upload-time = "2026-09-27T01:40:13.208Z" }, { url = "https://files.pythonhosted.org/packages/0c/e4/e37ff75e5254564aa50e13bc4313383d02d5ec78f5bd3b9b1994dd7052d5/wrapt-2.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:ce4cab32c37ef71e69cf88f909b7febd0dd79543e5ae3650e2b874e0f3d3b975", size = 102937, upload-time = "2026-09-27T01:40:14.534Z" }, { url = "https://files.pythonhosted.org/packages/d6/4b/cc7bb5668f7ddc0e73e236e96a0c06cab8fddfca9c53538c9dffac62db6f/wrapt-2.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b312b3cc87951faaed3cfef984d768ee8bee7f935d9cc929aaa9946b0b96a98c", size = 105808, upload-time = "2026-09-27T01:40:15.826Z" }, { url = "https://files.pythonhosted.org/packages/4a/13/5d15ef0e2f42d5f084930dc4863e6e52c160c28301c8780aae170c58421c/wrapt-2.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c57ddae24cf72eb6bd18112638a987cafe6109d90f2df111e6934362cc03ac1a", size = 105931, upload-time = "2026-09-27T01:40:17.136Z" }, { url = "https://files.pythonhosted.org/packages/1e/02/c7174e78b0c38bb279b2d3c25a6bd7fb9d3b0200c3e5a8fad084e7cc3e85/wrapt-2.5.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b95a6eca3b927853529eea958310563c83140ae8451dd5dc4399c7da385dc4f3", size = 248259, upload-time = "2026-09-27T01:40:18.446Z" }, { url = "https://files.pythonhosted.org/packages/a4/f9/47ae1d7ef325c3f6c81ae3c1fb4a3fef9d98c8025ed676c0bfc1550903ce/wrapt-2.5.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6058e12e9caa33468f9a36fb88c15a4bb30a479f997b37834b83abdbf062f264", size = 248224, upload-time = "2026-09-27T01:40:19.713Z" }, { url = "https://files.pythonhosted.org/packages/38/7b/a394448bcbbaf8e5a3f856520edbbb1b92fc42061def56284c9083f3ac87/wrapt-2.5.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0d245ac03f5ae77f1eea6eb19edd9e778c2f772490c20496c2f1cd3a102ee1b6", size = 227137, upload-time = "2026-09-27T01:40:21.359Z" }, { url = "https://files.pythonhosted.org/packages/41/45/fc252bda5aa1ca01bc838d3b108778e786a2a13d0c52fd17c5f6179aa246/wrapt-2.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f4ef4935962f7029b2058a99f1a47ccbffc3be919dddb3becb6c2c48eac3d9f0", size = 244710, upload-time = "2026-09-27T01:40:22.693Z" }, { url = "https://files.pythonhosted.org/packages/0b/1c/527d1bde7371dcc2c378d88c97de03b121b486fb4cd3dbb399bc332c0676/wrapt-2.5.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f12e80c3089ebc03727d368f8205b811b5af2cd4a72b5e4cac75e901dd316e39", size = 225053, upload-time = "2026-09-27T01:40:24.345Z" }, { url = "https://files.pythonhosted.org/packages/81/8e/2b823fded8c3b815408c58633929812eacd29d824fe57548b7868c4ee422/wrapt-2.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a346408f19b6d589bf029f25f65c0b4cdeed6302ef8f40da4e5d1552d22dc037", size = 236884, upload-time = "2026-09-27T01:40:25.654Z" }, { url = "https://files.pythonhosted.org/packages/00/d7/5d185c1193b073a0bf4cbe862b5d31f81067eddc39eff30ae632f346563d/wrapt-2.5.0-cp313-cp313-win32.whl", hash = "sha256:79e68f0fd7d381b9bbd71776f602a2d5440d4d2077459128e02fd6607465422c", size = 100198, upload-time = "2026-09-27T01:40:27.111Z" }, { url = "https://files.pythonhosted.org/packages/ce/9a/51d95640e01d0ebdd04a7223755f076e4936b0c124ce99bb01a12b53e66c/wrapt-2.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:77f0a74ff6f6cf89f5b673a732d5afe1911a6e6b1c017260836fdfdf85518dc1", size = 105610, upload-time = "2026-09-27T01:40:28.465Z" }, { url = "https://files.pythonhosted.org/packages/67/52/183d5ce7c2a9391774e6a623be6ae564351545713ec9a3693528f89c8e85/wrapt-2.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:b620d7559b6b2197c5730332fab0867ecf1c8cb74d45533ebbcbcad1eacf4616", size = 102641, upload-time = "2026-09-27T01:40:30Z" }, { url = "https://files.pythonhosted.org/packages/f3/4b/0009086ab8f2d5fb32405ef49fdd11104ce40f69ae9f4cdfba8326462816/wrapt-2.5.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:65f2ee406dc592a5b22a7dc6abac13e8a3e8de4b2ecf5dc3c22937865496e4b6", size = 106182, upload-time = "2026-09-27T01:40:31.503Z" }, { url = "https://files.pythonhosted.org/packages/5d/35/8f38339a4c55a42df00296dcf6ad50598d2280049f7a6ffa525e9a1f66d1/wrapt-2.5.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d75d6366203c8d025c1a74bae0b565952187ddae79bd5c7bf10687652a56f020", size = 106123, upload-time = "2026-09-27T01:40:32.927Z" }, { url = "https://files.pythonhosted.org/packages/23/38/285b433121d73c7a447b5b82d93c91dc3330f33ab5853975f34551e0c773/wrapt-2.5.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b640460f0ffb346b192686bd6fac5589e35a6c6640501c59a9fb6e82b0dd6bd8", size = 251245, upload-time = "2026-09-27T01:40:34.309Z" }, { url = "https://files.pythonhosted.org/packages/7b/a6/3f63f4637e89484c1839a9ba3aedda5b2912e7ce12617034c6bd49752cfa/wrapt-2.5.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f17c5a3836397bf59fd57b0e5b0dc42969b1daa70d31aa361c6e13cbf138b5a", size = 250483, upload-time = "2026-09-27T01:40:35.841Z" }, { url = "https://files.pythonhosted.org/packages/e9/cd/f24ee96016da222dbb921cfb22e2beb5ca189a7b730ef49e8bb106b49449/wrapt-2.5.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4343880acd72e74233baf092285aaaf4306244e31d7601828bd2600316027df0", size = 231341, upload-time = "2026-09-27T01:40:37.295Z" }, { url = "https://files.pythonhosted.org/packages/23/eb/c9b180124271e494f615a130f966be56143e3e26e87706bc28582f94bc09/wrapt-2.5.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ea4fdc79c0045d6bb1603c109127145245cafe888588888444e1e37fbeadbac3", size = 247736, upload-time = "2026-09-27T01:40:38.653Z" }, { url = "https://files.pythonhosted.org/packages/fd/60/345b8c213389809435d1950136b09991a1af2a66b988d0cd930ecd1b9f19/wrapt-2.5.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:f42239c89430eee2d8a6dec39e34677abdbb67fff63caf2467dd6124ea4d4d58", size = 228533, upload-time = "2026-09-27T01:40:40.12Z" }, { url = "https://files.pythonhosted.org/packages/ea/15/c79f0f5827a9062c6be4fc25dc73e92fe1c014c7bfde2e61c8c0b56a91af/wrapt-2.5.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:bad63bb4dea3c58e8078a3a173259ac2df5442a437e49c632b4099c0250e803b", size = 238830, upload-time = "2026-09-27T01:40:41.505Z" }, { url = "https://files.pythonhosted.org/packages/5f/de/79a95ac238c9cae7ae7eb3a18501afc646e3ed61d8d108c725b17bbee301/wrapt-2.5.0-cp314-cp314-win32.whl", hash = "sha256:b58138d19f34e32833e62de5e910bc2a8baae43310b921d783bd39b15227c2dd", size = 100664, upload-time = "2026-09-27T01:40:42.884Z" }, { url = "https://files.pythonhosted.org/packages/f0/15/32de0f1e6a46a82c773430672562d53203406df14a6d73c93abb59b679e9/wrapt-2.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:1a3c4035d2026b87ef23dd8d165f1f8d3853ee2bd02791cfd22bd8c6226c41ce", size = 105986, upload-time = "2026-09-27T01:40:44.652Z" }, { url = "https://files.pythonhosted.org/packages/d9/2a/10a7ff69097385de15b3db7d91587a54c26f8025fcbf36a1d9e83a1e0ad1/wrapt-2.5.0-cp314-cp314-win_arm64.whl", hash = "sha256:def66258d97ebf1e4e97def12c5daa542d1cc728a3da83ed3a43933f56df6dab", size = 103718, upload-time = "2026-09-27T01:40:45.956Z" }, { url = "https://files.pythonhosted.org/packages/f7/01/963f893b1906ac6c2aecb777c36e9ab2156a4cf89fabf6125e953ec4ad52/wrapt-2.5.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:23a9d6cb6413359b76f030d6bbb75340b7669c69da245dde2919a4c93708993b", size = 109290, upload-time = "2026-09-27T01:40:47.22Z" }, { url = "https://files.pythonhosted.org/packages/cd/6c/30e04d2b1284de2eea5411850008e0411d1876bf4552dc0990c904a0a783/wrapt-2.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:21cfe343ef9c2deb865ad0d5c57822266447c88dcf6d8805dd8c363fe367f30c", size = 109597, upload-time = "2026-09-27T01:40:49Z" }, { url = "https://files.pythonhosted.org/packages/6a/34/3980fe5a899b69454f66db2991c144ecc828dbbd355ce6cd7b881056ebbc/wrapt-2.5.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0dfc38cb672af51fc29696ba9c6f05d2315f5e62c4af2564e50f07f81198a163", size = 283840, upload-time = "2026-09-27T01:40:50.406Z" }, { url = "https://files.pythonhosted.org/packages/b6/b4/b37001235fd5871b3f31941229f8fef608279353b772dab3ccb248fd8726/wrapt-2.5.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:181a45000506a6382eb337354ca7e8525690702f1ccf2eae4f23a210ff339543", size = 293622, upload-time = "2026-09-27T01:40:51.868Z" }, { url = "https://files.pythonhosted.org/packages/09/b3/9b751c6268fa2111efc7e43895105bc0f60a83896b08581009e77563f8c7/wrapt-2.5.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:58b2a87c65cbfb20917ec48ace47f71b1962c1f81dbf18a4052e3037abf72028", size = 262840, upload-time = "2026-09-27T01:40:53.297Z" }, { url = "https://files.pythonhosted.org/packages/47/7d/b7b51d601981ccc1f7b9e6023991548dec43dc9d40317597fbe0085bc876/wrapt-2.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0ea62bc142f4fa8b2e0ab058f50699ccd679ef6199c8fa3cc1c2396c7a659000", size = 288781, upload-time = "2026-09-27T01:40:54.756Z" }, { url = "https://files.pythonhosted.org/packages/d1/82/1a84f288246905d0a71d44aa1f470ff8c75df2b96ef791d2938124449cb0/wrapt-2.5.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:425349a99b8c9540399d36620c376dc26e6aca93071cd6fafa239c2f1b5d53a4", size = 260116, upload-time = "2026-09-27T01:40:56.667Z" }, { url = "https://files.pythonhosted.org/packages/5a/0e/0572224d1c4a3f0846f82614702ec3110d45c843dcc7781c5f33779e1fdc/wrapt-2.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fe09aac4837ec720af606a493e814dcc3f65631984e1b7231b589efcf9917024", size = 278485, upload-time = "2026-09-27T01:40:58.443Z" }, { url = "https://files.pythonhosted.org/packages/76/44/5a5c111f8ac6dd15f54437c2161588431d3924718a7e4de59c471cd794e9/wrapt-2.5.0-cp314-cp314t-win32.whl", hash = "sha256:bc5607c1911c92530cb402ea90d818933cffb28bd8de9b453a2542279816d8c7", size = 103157, upload-time = "2026-09-27T01:40:59.995Z" }, { url = "https://files.pythonhosted.org/packages/e6/80/96cc2da58cbc0893f5165f6a0f4f9cb75d7574f409022ad792aa80a0ff3f/wrapt-2.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:7138b0e7990e5555a905c519e8dad17c1da1f20e08b283e202c414229065740f", size = 109683, upload-time = "2026-09-27T01:41:01.43Z" }, { url = "https://files.pythonhosted.org/packages/c7/70/10dab499970e66c926ba6d404ff456318b68092b8ad0c4608a52160e43a2/wrapt-2.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:a5bb346a34499e091e4fa23df58251ad192173c088e5413d893ca1c730c133c7", size = 105953, upload-time = "2026-09-27T01:41:03.041Z" }, { url = "https://files.pythonhosted.org/packages/ab/18/5154954f69afdbf5bdeddc07ed60f30bf6e83ed1e9fb6f96c66cc20e4223/wrapt-2.5.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:a45a5249a6965d91aac9f991fda7c17e6b8b41fe91592a6099f182bf53c82724", size = 106186, upload-time = "2026-09-27T01:41:04.472Z" }, { url = "https://files.pythonhosted.org/packages/5d/43/7db9952d26b1a89afcf22da8ec7948e6ca55c48721488d53f84754eed89d/wrapt-2.5.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:cbd45dfba6b5c1bfbabe1feb3c0f117fbd62416e98268d9a7cd9ad8802875356", size = 106155, upload-time = "2026-09-27T01:41:05.795Z" }, { url = "https://files.pythonhosted.org/packages/57/b6/41a0d7f9cf1f8e6aaecbf4b5b4eaacf4036fa3396c7d814364e07728a041/wrapt-2.5.0-cp315-cp315-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bc6491d3008ecabf685b0746f03ad8241a0950336939b14addb03af39b51a316", size = 251754, upload-time = "2026-09-27T01:41:07.26Z" }, { url = "https://files.pythonhosted.org/packages/55/d4/dd2de1260a490cd55d083b3c1bc47a36aff0e8363249d108d3b34c091c0e/wrapt-2.5.0-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:47abb2bb7f15b416e72fbe5e68e49a6f09331dae6af1ca6055f5aa2251d2bd2f", size = 250768, upload-time = "2026-09-27T01:41:08.681Z" }, { url = "https://files.pythonhosted.org/packages/4c/40/d08297feb5728cd6d3c1133633cad0249c2b7a82eb0213062ebab9cc1266/wrapt-2.5.0-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7e25e9697f60af41fb86b08697e470b1e7eb6cd6ac0eb25e4b1f519839adc271", size = 232618, upload-time = "2026-09-27T01:41:10.293Z" }, { url = "https://files.pythonhosted.org/packages/54/52/d8ca61b26c2a34927cc999fc250f1018f415741691581280e6a76cced736/wrapt-2.5.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:bcd42e7b69c8c1e33a29b79b28de03bdc08876a49745830f9162a3af860e06d0", size = 248043, upload-time = "2026-09-27T01:41:12.132Z" }, { url = "https://files.pythonhosted.org/packages/8c/5e/ba02904736e2d3b05afd7447b4ddff677ce61762265939555541adb8c668/wrapt-2.5.0-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:36703cafc2ec059e118c2175e6cb7ad7299c2924aecdcb1b7a7ebbf7a3e20c19", size = 229577, upload-time = "2026-09-27T01:41:13.746Z" }, { url = "https://files.pythonhosted.org/packages/a0/94/23968c18a6e37a8a130706dc52ccf4344a71f1fe53c99965eb0a7715a459/wrapt-2.5.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:1ebc0d09906057ada57a32158a657364ca40b8f86e604da3e7d979069b601502", size = 239372, upload-time = "2026-09-27T01:41:15.459Z" }, { url = "https://files.pythonhosted.org/packages/ee/ee/8437e73fffa57c96a5f0f6721f942ccf7b1461b64cd965f83e2181e25252/wrapt-2.5.0-cp315-cp315-win32.whl", hash = "sha256:76fb341d5a707a4f211631b8c77259b2df149147e9d9c245ae6ba3dd936bfdfb", size = 100673, upload-time = "2026-09-27T01:41:16.925Z" }, { url = "https://files.pythonhosted.org/packages/37/6d/6d640f98197d68e61fbeaded20478e4aa840f9c88d11e7a49c8d6d14ae15/wrapt-2.5.0-cp315-cp315-win_amd64.whl", hash = "sha256:6269637d9a54990430b4a769df15833935a46c4d004d9fe8a153bbadf0b9a097", size = 105999, upload-time = "2026-09-27T01:41:18.372Z" }, { url = "https://files.pythonhosted.org/packages/0d/3e/8b8a0c94f2698c99afb499510824b107c81e9c4a34b3db7e877233a634b7/wrapt-2.5.0-cp315-cp315-win_arm64.whl", hash = "sha256:1122f4f9e363da804ccba05a9f0f39baa3716eb82452c929258bc3f1420c899b", size = 103728, upload-time = "2026-09-27T01:41:19.729Z" }, { url = "https://files.pythonhosted.org/packages/37/96/88f08f58759ee3739544cc51941853e946df1f400ba60c6efbeccfb589d7/wrapt-2.5.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:e3c6fb1c1a516881353186bed9cfcb8899f968c03b3509720c79db0d967acf3b", size = 109275, upload-time = "2026-09-27T01:41:21.213Z" }, { url = "https://files.pythonhosted.org/packages/29/cc/68846aa92814d0704d4b128a30d7707368be6951e8a8f42f1254ce4ab31c/wrapt-2.5.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:0a7a369e7fca9fc8c2c50df634382009b09af416853da3d4515e4bb048a5b9ee", size = 109610, upload-time = "2026-09-27T01:41:22.647Z" }, { url = "https://files.pythonhosted.org/packages/fe/87/bbaa188dace348b6a403bbf3cc483f3f419ac97700274340e17f2dbc700e/wrapt-2.5.0-cp315-cp315t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:88bb24b9fdccb1d805258d5648533206eb58c58b6554985c47db53a89c11be85", size = 284602, upload-time = "2026-09-27T01:41:24.094Z" }, { url = "https://files.pythonhosted.org/packages/be/2e/8a3309b0cbd3ab809ee6b76812c3be211f5a08732f321f131d26bb4f078a/wrapt-2.5.0-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2e3d110d7f99644946f249927c346d9dba507a78815d1bcebdbf0d94c14c5649", size = 294893, upload-time = "2026-09-27T01:41:25.654Z" }, { url = "https://files.pythonhosted.org/packages/15/b7/eda8bbdb6a3b7343d2c71e23fb0ebfc15c12fd470e3cce7ea42f7a57aaac/wrapt-2.5.0-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1ffb2823c95dbeb8a47fedfba9636b2afeb0a8ef94b66df97bd081bdfe5a263f", size = 264658, upload-time = "2026-09-27T01:41:27.375Z" }, { url = "https://files.pythonhosted.org/packages/41/f0/589bad71ca3ce5444a626ee10d657fd6aa5080ada76b3bcd4550468aa16e/wrapt-2.5.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:557ebf4ce5568588368675014a2540405db687c2e4c7ad1eb83aa7e857be1864", size = 289974, upload-time = "2026-09-27T01:41:29.032Z" }, { url = "https://files.pythonhosted.org/packages/dc/97/c48f3c820ae6687e87b537041cd49ad4caa41e05a8f0d8ec08e449ca303c/wrapt-2.5.0-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:05246a100da68259af521b88788f131ba005465f1c95d358cc3c03ec5e351b52", size = 261945, upload-time = "2026-09-27T01:41:30.941Z" }, { url = "https://files.pythonhosted.org/packages/fd/ad/d96898f500cb1e4185474bac6cb14bb7ea670a32f37e8c354a0c647e3a92/wrapt-2.5.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:c932273bc43b068538f3874fa5e6c2a60f33fa0b11c1ebc7768652f6a0608943", size = 279744, upload-time = "2026-09-27T01:41:32.617Z" }, { url = "https://files.pythonhosted.org/packages/5d/8b/7981d2ac838d0dc07e81145cb1c9911812e060b98e8c5a47c84fb92f8f81/wrapt-2.5.0-cp315-cp315t-win32.whl", hash = "sha256:2fd8a61c31220840c7f52621cf51c961af5058bd009a55bcdf4a6732bdb13b35", size = 103124, upload-time = "2026-09-27T01:41:34.117Z" }, { url = "https://files.pythonhosted.org/packages/2a/1d/374cec175b6087e1067a780374d81d74ca966e1e5e39e666402eaee19a65/wrapt-2.5.0-cp315-cp315t-win_amd64.whl", hash = "sha256:1d4da5f0e9a719471502b0db80d5c97503aeca796b7aeb9ab8f47403b2be76e6", size = 109671, upload-time = "2026-09-27T01:41:35.553Z" }, { url = "https://files.pythonhosted.org/packages/c7/93/fc9e477a1771bec52d7677eee5e8404afe662a47efe1859405a18fff206c/wrapt-2.5.0-cp315-cp315t-win_arm64.whl", hash = "sha256:78b7bdaa8b27b7f7607c66bdb6ab15c1dcbd9e9a1556a253a347dad511f615d1", size = 105969, upload-time = "2026-09-27T01:41:36.973Z" }, { url = "https://files.pythonhosted.org/packages/87/7d/5ed859fad4b5eddd598a846150aaab2703730ed4886c5c5e03b0df0cfdd5/wrapt-2.5.0-py3-none-any.whl", hash = "sha256:107eea1a511e98a3a5033b0c2cb403fbb37f05dee6ac1fb85c0460d311ec278c", size = 81586, upload-time = "2026-09-27T01:41:55.479Z" }, ]