pax_global_header00006660000000000000000000000064151515300540014511gustar00rootroot0000000000000052 comment=d4e736f61feb4c4519ad277fb250d4b094717c58 p1c2u-jsonschema-path-d08c0e1/000077500000000000000000000000001515153005400161335ustar00rootroot00000000000000p1c2u-jsonschema-path-d08c0e1/.github/000077500000000000000000000000001515153005400174735ustar00rootroot00000000000000p1c2u-jsonschema-path-d08c0e1/.github/FUNDING.yml000066400000000000000000000000201515153005400213000ustar00rootroot00000000000000github: [p1c2u] p1c2u-jsonschema-path-d08c0e1/.github/dependabot.yml000066400000000000000000000003151515153005400223220ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: "pip" directory: "/" schedule: interval: "weekly" - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" p1c2u-jsonschema-path-d08c0e1/.github/workflows/000077500000000000000000000000001515153005400215305ustar00rootroot00000000000000p1c2u-jsonschema-path-d08c0e1/.github/workflows/bench.yml000066400000000000000000000044031515153005400233330ustar00rootroot00000000000000name: Benchmarks on: workflow_dispatch: inputs: quick: description: "Run a shorter benchmark (fewer iterations)" required: false default: false type: boolean repeats: description: "Repeats per scenario (median is reported)" required: false default: "5" type: string warmup_loops: description: "Warmup passes before timing" required: false default: "1" type: string jobs: bench: name: "Bench" runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - name: Set up Python uses: actions/setup-python@v6 with: python-version: "3.12" - name: Set up poetry uses: Gr1N/setup-poetry@v9 with: poetry-version: "2.2.1" - name: Configure poetry run: poetry config virtualenvs.in-project true - name: Set up cache uses: actions/cache@v5 id: cache with: path: .venv key: venv-bench-${{ runner.os }}-py3.12-${{ hashFiles('**/poetry.lock') }} - name: Ensure cache is healthy if: steps.cache.outputs.cache-hit == 'true' shell: bash run: timeout 10s poetry run pip --version || rm -rf .venv - name: Install dependencies run: poetry install --all-extras - name: Run benchmarks env: PYTHONHASHSEED: "0" shell: bash run: | set -euo pipefail quick_flag="" if [[ "${{ inputs.quick }}" == "true" ]]; then quick_flag="--quick" fi repeats="${{ inputs.repeats }}" warmup="${{ inputs.warmup_loops }}" poetry run python -m tests.benchmarks.bench_parse \ --output reports/bench-parse.json \ $quick_flag \ --repeats "$repeats" \ --warmup-loops "$warmup" poetry run python -m tests.benchmarks.bench_lookup \ --output reports/bench-lookup.json \ $quick_flag \ --repeats "$repeats" \ --warmup-loops "$warmup" - name: Upload benchmark results uses: actions/upload-artifact@v7 with: name: jsonschema-path-bench-results path: reports/bench-*.json p1c2u-jsonschema-path-d08c0e1/.github/workflows/python-publish.yml000066400000000000000000000015331515153005400252420ustar00rootroot00000000000000# This workflow will upload a Python Package using Twine when a release is created # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries name: Publish python packages on: workflow_dispatch: release: types: - published jobs: publish: runs-on: ubuntu-latest permissions: id-token: write steps: - uses: actions/checkout@v6 - name: Set up Python uses: actions/setup-python@v6 with: python-version: '3.x' - name: Set up poetry uses: Gr1N/setup-poetry@v9 with: poetry-version: "2.2.1" - name: Build run: poetry build - name: Publish uses: pypa/gh-action-pypi-publish@release/v1 with: packages-dir: dist/p1c2u-jsonschema-path-d08c0e1/.github/workflows/python-test.yml000066400000000000000000000060451515153005400245560ustar00rootroot00000000000000# This workflow will install Python dependencies, run tests and lint with a variety of Python versions # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions name: Test python code on: push: pull_request: types: [opened, synchronize] jobs: test: name: "Tests" runs-on: ubuntu-latest strategy: matrix: python-version: ['3.10', '3.11', '3.12', '3.13', '3.14'] fail-fast: false steps: - uses: actions/checkout@v6 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} - name: Get full Python version id: full-python-version run: echo ::set-output name=version::$(python -c "import sys; print('-'.join(str(v) for v in sys.version_info))") - name: Set up poetry uses: Gr1N/setup-poetry@v9 with: poetry-version: "2.2.1" - name: Configure poetry run: poetry config virtualenvs.in-project true - name: Set up cache uses: actions/cache@v5 id: cache with: path: .venv key: venv-${{ github.event_name }}-${{ runner.os }}-${{ steps.full-python-version.outputs.version }}-${{ hashFiles('**/poetry.lock') }} - name: Ensure cache is healthy if: steps.cache.outputs.cache-hit == 'true' shell: bash run: timeout 10s poetry run pip --version || rm -rf .venv - name: Install dependencies run: poetry install --all-extras - name: Test env: PYTEST_ADDOPTS: "--color=yes" run: poetry run pytest - name: Static type check run: poetry run mypy - name: Check dependencies run: poetry run deptry . - name: Upload coverage uses: codecov/codecov-action@v5 static-checks: name: "Static checks" runs-on: ubuntu-latest steps: - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )" uses: actions/checkout@v6 - name: "Setup Python" uses: actions/setup-python@v6 with: python-version: "3.13" - name: Get full Python version id: full-python-version run: echo ::set-output name=version::$(python -c "import sys; print('-'.join(str(v) for v in sys.version_info))") - name: Set up poetry uses: Gr1N/setup-poetry@v9 with: poetry-version: "2.2.1" - name: Configure poetry run: poetry config virtualenvs.in-project true - name: Set up cache uses: actions/cache@v5 id: cache with: path: .venv key: venv-${{ steps.full-python-version.outputs.version }}-${{ hashFiles('**/poetry.lock') }} - name: Ensure cache is healthy if: steps.cache.outputs.cache-hit == 'true' run: timeout 10s poetry run pip --version || rm -rf .venv - name: Install dependencies run: poetry install - name: Run static checks run: poetry run pre-commit run -a p1c2u-jsonschema-path-d08c0e1/.gitignore000066400000000000000000000034071515153005400201270ustar00rootroot00000000000000# 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/ pip-wheel-metadata/ share/python-wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .nox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover *.py,cover .hypothesis/ .pytest_cache/ # 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 target/ # Jupyter Notebook .ipynb_checkpoints # IPython profile_default/ ipython_config.py # pyenv .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 # PEP 582; used by e.g. github.com/David-OConnor/pyflow __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/ p1c2u-jsonschema-path-d08c0e1/.pre-commit-config.yaml000066400000000000000000000017671515153005400224270ustar00rootroot00000000000000--- default_stages: [pre-commit, pre-push] default_language_version: # force all unspecified python hooks to run python3 python: python3 minimum_pre_commit_version: "1.20.0" repos: - repo: meta hooks: - id: check-hooks-apply - repo: https://github.com/asottile/pyupgrade rev: v3.21.0 hooks: - id: pyupgrade args: ["--py310-plus"] - repo: local hooks: - id: flynt name: Convert to f-strings with flynt entry: flynt language: python additional_dependencies: ['flynt==1.0.6'] - id: black name: black entry: black language: system require_serial: true types: [python] - id: isort name: isort entry: isort args: ['--filter-files'] language: system require_serial: true types: [python] - id: pyflakes name: pyflakes entry: pyflakes language: system require_serial: true types: [python] p1c2u-jsonschema-path-d08c0e1/LICENSE000066400000000000000000000261351515153005400171470ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. p1c2u-jsonschema-path-d08c0e1/MANIFEST.in000066400000000000000000000001041515153005400176640ustar00rootroot00000000000000include LICENSE include README.rst include jsonschema_path/py.typed p1c2u-jsonschema-path-d08c0e1/README.rst000066400000000000000000000111161515153005400176220ustar00rootroot00000000000000*************** JSONSchema Path *************** .. image:: https://img.shields.io/pypi/v/jsonschema-path.svg :target: https://pypi.python.org/pypi/jsonschema-path .. image:: https://travis-ci.org/p1c2u/jsonschema-path.svg?branch=master :target: https://travis-ci.org/p1c2u/jsonschema-path .. image:: https://img.shields.io/codecov/c/github/p1c2u/jsonschema-path/master.svg?style=flat :target: https://codecov.io/github/p1c2u/jsonschema-path?branch=master .. image:: https://img.shields.io/pypi/pyversions/jsonschema-path.svg :target: https://pypi.python.org/pypi/jsonschema-path .. image:: https://img.shields.io/pypi/format/jsonschema-path.svg :target: https://pypi.python.org/pypi/jsonschema-path .. image:: https://img.shields.io/pypi/status/jsonschema-path.svg :target: https://pypi.python.org/pypi/jsonschema-path About ##### Object-oriented JSONSchema Key features ############ * Traverse schema like paths * Access schema on demand with separate dereferencing accessor layer Installation ############ .. code-block:: console pip install jsonschema-path Alternatively you can download the code and install from the repository: .. code-block:: console pip install -e git+https://github.com/p1c2u/jsonschema-path.git#egg=jsonschema_path Usage ##### .. code-block:: python >>> from jsonschema_path import SchemaPath >>> d = { ... "properties": { ... "info": { ... "$ref": "#/$defs/Info", ... }, ... }, ... "$defs": { ... "Info": { ... "properties": { ... "title": { ... "$ref": "http://example.com", ... }, ... "version": { ... "type": "string", ... "default": "1.0", ... }, ... }, ... }, ... }, ... } >>> path = SchemaPath.from_dict(d) >>> # Stat keys >>> "properties" in path True >>> # Concatenate paths with / >>> info_path = path / "properties" / "info" >>> # Stat keys with implicit dereferencing >>> "properties" in info_path True >>> # Concatenate paths with implicit dereferencing >>> version_path = info_path / "properties" / "version" >>> # Open content with implicit dereferencing >>> with version_path.open() as contents: ... print(contents) {'type': 'string', 'default': '1.0'} Resolved cache ############## The resolved-path cache is intended for repeated path lookups and may significantly improve ``read_value``/membership hot paths. Cache entries are invalidated when the resolver registry evolves during reference resolution. This cache is optional and disabled by default (``resolved_cache_maxsize=0``). You can enable it when creating paths or accessors, for example: .. code-block:: python >>> path = SchemaPath.from_dict(d, resolved_cache_maxsize=64) Benchmarks ########## Benchmarks mirror the lightweight (dependency-free) JSON output format used in `pathable`. Run locally with Poetry: .. code-block:: console poetry run python -m tests.benchmarks.bench_parse --output reports/bench-parse.json poetry run python -m tests.benchmarks.bench_lookup --output reports/bench-lookup.json For a quick smoke run: .. code-block:: console poetry run python -m tests.benchmarks.bench_parse --output reports/bench-parse.quick.json --quick poetry run python -m tests.benchmarks.bench_lookup --output reports/bench-lookup.quick.json --quick You can also control repeats/warmup and resolved cache maxsize via env vars: .. code-block:: console export JSONSCHEMA_PATH_BENCH_REPEATS=5 export JSONSCHEMA_PATH_BENCH_WARMUP=1 export JSONSCHEMA_PATH_BENCH_RESOLVED_CACHE_MAXSIZE=64 Compare two results: .. code-block:: console poetry run python -m tests.benchmarks.compare_results \ --baseline reports/bench-lookup-master.json \ --candidate reports/bench-lookup.json \ --tolerance 0.20 Related projects ################ * `openapi-core `__ Python library that adds client-side and server-side support for the OpenAPI. * `openapi-spec-validator `__ Python library that validates OpenAPI Specs against the OpenAPI 2.0 (aka Swagger) and OpenAPI 3.0 specification * `openapi-schema-validator `__ Python library that validates schema against the OpenAPI Schema Specification v3.0. License ####### Copyright (c) 2017-2025, Artur Maciag, All rights reserved. Apache-2.0 p1c2u-jsonschema-path-d08c0e1/jsonschema_path/000077500000000000000000000000001515153005400213015ustar00rootroot00000000000000p1c2u-jsonschema-path-d08c0e1/jsonschema_path/__init__.py000066400000000000000000000006001515153005400234060ustar00rootroot00000000000000from jsonschema_path.accessors import SchemaAccessor from jsonschema_path.handlers import default_handlers from jsonschema_path.paths import SchemaPath __author__ = "Artur Maciag" __email__ = "maciag.artur@gmail.com" __version__ = "0.4.5" __url__ = "https://github.com/p1c2u/jsonschema-path" __license__ = "Apache-2.0" __all__ = ["SchemaAccessor", "SchemaPath", "default_handlers"] p1c2u-jsonschema-path-d08c0e1/jsonschema_path/accessors.py000066400000000000000000000135531515153005400236470ustar00rootroot00000000000000"""JSONSchema spec accessors module.""" import warnings from collections.abc import Hashable from collections.abc import Iterator from collections.abc import Sequence from contextlib import contextmanager from typing import Any from typing import cast from pathable.accessors import LookupAccessor from pathable.types import LookupKey from pathable.types import LookupNode from pathable.types import LookupValue from referencing import Registry from referencing import Specification from referencing._core import Resolved from referencing._core import Resolver from referencing.jsonschema import DRAFT202012 from jsonschema_path.caches import FullPathResolvedCache from jsonschema_path.handlers import default_handlers from jsonschema_path.resolvers import CachedPathResolver from jsonschema_path.retrievers import SchemaRetriever from jsonschema_path.typing import ResolverHandlers from jsonschema_path.typing import Schema class SchemaAccessor(LookupAccessor): def __init__( self, schema: Schema, resolver: Resolver[Schema], resolved_cache_maxsize: int = 0, ): if resolved_cache_maxsize < 0: raise ValueError("resolved_cache_maxsize must be >= 0") super().__init__(cast(LookupNode, schema)) self._path_resolver: CachedPathResolver = CachedPathResolver( resolver, ) self._resolved_cache_maxsize = resolved_cache_maxsize self._resolved_cache: FullPathResolvedCache = FullPathResolvedCache( maxsize=resolved_cache_maxsize ) @classmethod def from_schema( cls, schema: Schema, specification: Specification[Schema] = DRAFT202012, base_uri: str = "", handlers: ResolverHandlers | None = None, resolved_cache_maxsize: int = 0, ) -> "SchemaAccessor": if handlers is None: handlers = default_handlers retriever = SchemaRetriever(handlers, specification) base_resource = specification.create_resource(schema) registry: Registry[Schema] = Registry( retrieve=retriever, # type: ignore ) registry = registry.with_resource(base_uri, base_resource) resolver = registry.resolver(base_uri=base_uri) return cls( schema, resolver, resolved_cache_maxsize=resolved_cache_maxsize, ) @property def base_uri(self) -> str: return self._path_resolver.resolver._base_uri @property def resolver(self) -> Resolver[Schema]: warnings.warn( "SchemaAccessor.resolver is deprecated. " "Use SchemaPath.base_uri to access the base URI and " "SchemaPath.resolve() to resolve paths.", DeprecationWarning, ) return self._path_resolver.resolver def __getitem__(self, parts: Sequence[LookupKey]) -> LookupNode: resolved = self.get_resolved(parts) return resolved.contents def stat(self, parts: Sequence[Hashable]) -> dict[str, Any] | None: try: node = self[cast(Sequence[LookupKey], parts)] except (KeyError, IndexError, TypeError): return None if self._is_traversable_node(node): return { "type": type(node).__name__, "length": len(node), } try: length = len(cast(Any, node)) except TypeError: length = None return { "type": type(node).__name__, "length": length, } def keys(self, parts: Sequence[LookupKey]) -> Sequence[LookupKey]: node = self[parts] if isinstance(node, dict): # dict_keys has O(1) membership, no allocation. return cast(Sequence[LookupKey], node.keys()) if isinstance(node, list): # range has O(1) membership and supports iteration. return cast(Sequence[LookupKey], range(len(node))) # Non-traversable leaf. if parts: raise KeyError(parts[-1]) raise KeyError def len(self, parts: Sequence[LookupKey]) -> int: node = self[parts] if isinstance(node, (dict, list)): return len(node) if parts: raise KeyError(parts[-1]) raise KeyError def contains(self, parts: Sequence[LookupKey], key: LookupKey) -> bool: try: node = self[parts] except (KeyError, IndexError, TypeError): return False if isinstance(node, dict): return key in node if isinstance(node, list): return isinstance(key, int) and 0 <= key < len(node) return False def require_child( self, parts: Sequence[LookupKey], key: LookupKey ) -> None: # Validate parent path for intermediate diagnostics. node = self[parts] if isinstance(node, dict): if key not in node: raise KeyError(key) return if isinstance(node, list): if not (isinstance(key, int) and 0 <= key < len(node)): raise KeyError(key) return raise KeyError(key) def read(self, parts: Sequence[LookupKey]) -> LookupValue: node = self[parts] return self._read_node(node) @contextmanager def resolve( self, parts: Sequence[LookupKey] ) -> Iterator[Resolved[LookupNode]]: try: yield self.get_resolved(parts) finally: pass def get_resolved(self, parts: Sequence[LookupKey]) -> Resolved[LookupNode]: cached_resolved = self._resolved_cache.get(parts) if cached_resolved is not None: return cached_resolved result = self._path_resolver.resolve(self.node, parts) if result.registry_changed: self._resolved_cache.invalidate() self._resolved_cache.set(parts, result.resolved) return result.resolved p1c2u-jsonschema-path-d08c0e1/jsonschema_path/caches.py000066400000000000000000000052331515153005400231040ustar00rootroot00000000000000"""JSONSchema path caches module.""" from collections import OrderedDict from collections.abc import Sequence from pathable.types import LookupKey from pathable.types import LookupNode from referencing._core import Resolved class FullPathResolvedCache: def __init__(self, maxsize: int): self._maxsize = maxsize self._generation = 0 self._cache: OrderedDict[ tuple[tuple[LookupKey, ...], int], Resolved[LookupNode], ] = OrderedDict() def _make_key( self, parts: Sequence[LookupKey], ) -> tuple[tuple[LookupKey, ...], int] | None: if self._maxsize <= 0: return None parts_tuple = tuple(parts) try: hash(parts_tuple) except TypeError: return None return (parts_tuple, self._generation) def get( self, parts: Sequence[LookupKey], ) -> Resolved[LookupNode] | None: key = self._make_key(parts) if key is None: return None cached = self._cache.get(key) if cached is None: return None self._cache.move_to_end(key) return cached def set( self, parts: Sequence[LookupKey], resolved: Resolved[LookupNode], ) -> None: key = self._make_key(parts) if key is None: return self._cache[key] = resolved self._cache.move_to_end(key) if len(self._cache) > self._maxsize: self._cache.popitem(last=False) def invalidate(self) -> None: self._generation += 1 self._cache.clear() class PrefixResolvedCache: def __init__(self) -> None: self._cache: dict[tuple[LookupKey, ...], Resolved[LookupNode]] = {} def seed_root(self, resolved: Resolved[LookupNode]) -> None: self._cache[()] = resolved def longest_prefix_hit( self, parts: tuple[LookupKey, ...], ) -> tuple[int, Resolved[LookupNode]] | None: for idx in range(len(parts) - 1, -1, -1): prefix = parts[:idx] try: cached = self._cache.get(prefix) except TypeError: continue if cached is not None: return idx, cached return None def store_intermediate( self, parts: tuple[LookupKey, ...], index: int, resolved: Resolved[LookupNode], ) -> None: if index >= len(parts) - 1: return prefix = parts[: index + 1] try: self._cache[prefix] = resolved except TypeError: pass def invalidate(self) -> None: self._cache.clear() p1c2u-jsonschema-path-d08c0e1/jsonschema_path/handlers/000077500000000000000000000000001515153005400231015ustar00rootroot00000000000000p1c2u-jsonschema-path-d08c0e1/jsonschema_path/handlers/__init__.py000066400000000000000000000013711515153005400252140ustar00rootroot00000000000000from typing import TYPE_CHECKING from jsonschema_path.handlers.file import FileHandler from jsonschema_path.handlers.urllib import UrllibHandler if TYPE_CHECKING: from jsonschema_path.handlers.urllib import UrllibHandler as UrlHandler else: try: from jsonschema_path.handlers.requests import ( UrlRequestsHandler as UrlHandler, ) except ImportError: from jsonschema_path.handlers.urllib import UrllibHandler as UrlHandler __all__ = ["FileHandler", "UrlHandler"] file_handler = FileHandler() all_urls_handler = UrllibHandler("http", "https", "file") default_handlers = { "": all_urls_handler, "http": UrlHandler("http"), "https": UrlHandler("https"), "file": UrllibHandler("file"), } p1c2u-jsonschema-path-d08c0e1/jsonschema_path/handlers/file.py000066400000000000000000000037161515153005400244010ustar00rootroot00000000000000"""JSONSchema spec handlers file module.""" from json import dumps from json import loads from typing import Any from typing import ContextManager from urllib.parse import urlparse from yaml import load from jsonschema_path.handlers.protocols import SupportsRead from jsonschema_path.handlers.utils import uri_to_path from jsonschema_path.loaders import JsonschemaSafeLoader class FileHandler: """File-like object handler.""" def __init__(self, loader: Any = JsonschemaSafeLoader): self.loader = loader def __call__(self, stream: SupportsRead) -> Any: data = self._load(stream) return loads(dumps(data)) def _load(self, stream: SupportsRead) -> Any: return load(stream, self.loader) class BaseFilePathHandler: """Base file path handler.""" allowed_schemes: tuple[str, ...] = NotImplemented def __init__( self, *allowed_schemes: str, file_handler: FileHandler | None = None ): self.allowed_schemes = allowed_schemes or self.allowed_schemes self.file_handler = file_handler or FileHandler() def __call__(self, uri: str) -> Any: parsed_url = urlparse(uri) if parsed_url.scheme not in self.allowed_schemes: raise ValueError(f"Scheme {parsed_url.scheme} not allowed") with self._open(uri) as stream: return self.file_handler(stream) def _open(self, uri: str) -> ContextManager[SupportsRead]: raise NotImplementedError class FilePathHandler(BaseFilePathHandler): """File path handler.""" allowed_schemes = ("file",) def __init__( self, *allowed_schemes: str, file_handler: FileHandler | None = None, encoding: str = "utf-8", ): super().__init__(*allowed_schemes, file_handler=file_handler) self.encoding = encoding def _open(self, uri: str) -> ContextManager[SupportsRead]: filepath = uri_to_path(uri) return open(filepath, encoding=self.encoding) p1c2u-jsonschema-path-d08c0e1/jsonschema_path/handlers/protocols.py000066400000000000000000000001631515153005400254770ustar00rootroot00000000000000from typing import Protocol class SupportsRead(Protocol): def read(self, amount: int | None = 0) -> str: ... p1c2u-jsonschema-path-d08c0e1/jsonschema_path/handlers/requests.py000066400000000000000000000017351515153005400253340ustar00rootroot00000000000000"""JSONSchema spec handlers requests module.""" from contextlib import closing from io import StringIO from typing import ContextManager import requests from jsonschema_path.handlers.file import BaseFilePathHandler from jsonschema_path.handlers.file import FileHandler from jsonschema_path.handlers.protocols import SupportsRead class UrlRequestsHandler(BaseFilePathHandler): """URL (requests) scheme handler.""" def __init__( self, *allowed_schemes: str, file_handler: FileHandler | None = None, timeout: int = 10, verify: bool | str | None = True, ): super().__init__(*allowed_schemes, file_handler=file_handler) self.timeout = timeout self.verify = verify def _open(self, uri: str) -> ContextManager[SupportsRead]: response = requests.get(uri, timeout=self.timeout, verify=self.verify) response.raise_for_status() data = StringIO(response.text) return closing(data) p1c2u-jsonschema-path-d08c0e1/jsonschema_path/handlers/urllib.py000066400000000000000000000014111515153005400247410ustar00rootroot00000000000000"""JSONSchema spec handlers urllib module.""" from contextlib import closing from typing import ContextManager from urllib.request import urlopen from jsonschema_path.handlers.file import BaseFilePathHandler from jsonschema_path.handlers.file import FileHandler from jsonschema_path.handlers.protocols import SupportsRead class UrllibHandler(BaseFilePathHandler): """URL (urllib) scheme handler.""" def __init__( self, *allowed_schemes: str, file_handler: FileHandler | None = None, timeout: int = 10 ): super().__init__(*allowed_schemes, file_handler=file_handler) self.timeout = timeout def _open(self, uri: str) -> ContextManager[SupportsRead]: return closing(urlopen(uri, timeout=self.timeout)) p1c2u-jsonschema-path-d08c0e1/jsonschema_path/handlers/utils.py000066400000000000000000000005511515153005400246140ustar00rootroot00000000000000import os.path import urllib.parse import urllib.request def uri_to_path(uri: str) -> str: parsed = urllib.parse.urlparse(uri) host = "{0}{0}{mnt}{0}".format(os.path.sep, mnt=parsed.netloc) return os.path.normpath( os.path.join( host, urllib.request.url2pathname(urllib.parse.unquote(parsed.path)), ) ) p1c2u-jsonschema-path-d08c0e1/jsonschema_path/loaders.py000066400000000000000000000024601515153005400233060ustar00rootroot00000000000000# Use CSafeFile if available from collections.abc import Iterable from typing import TYPE_CHECKING from typing import Any if TYPE_CHECKING: from yaml import SafeLoader else: try: from yaml import CSafeLoader as SafeLoader except ImportError: from yaml import SafeLoader __all__ = [ "SafeLoader", ] class LimitedSafeLoader(type): """Meta YAML loader that skips the resolution of the specified YAML tags.""" def __new__( cls, name: str, bases: tuple[type, ...], namespace: dict[str, Any], exclude_resolvers: Iterable[str], ) -> "LimitedSafeLoader": exclude_resolvers = set(exclude_resolvers) implicit_resolvers = { key: [ (tag, regex) for tag, regex in mappings if tag not in exclude_resolvers ] for key, mappings in SafeLoader.yaml_implicit_resolvers.items() } return super().__new__( cls, name, (SafeLoader, *bases), {**namespace, "yaml_implicit_resolvers": implicit_resolvers}, ) class JsonschemaSafeLoader( metaclass=LimitedSafeLoader, exclude_resolvers={"tag:yaml.org,2002:timestamp"}, ): """A safe YAML loader that leaves timestamps as strings.""" p1c2u-jsonschema-path-d08c0e1/jsonschema_path/nodes.py000066400000000000000000000015141515153005400227640ustar00rootroot00000000000000"""JSONSchema spec nodes module.""" from typing import cast from pathable.accessors import LookupAccessor from pathable.types import LookupNode from referencing._core import Resolved from referencing._core import Resolver from jsonschema_path.typing import Schema from jsonschema_path.utils import is_ref class SchemaNode(LookupAccessor): @classmethod def _resolve_node( cls, node: LookupNode, resolver: Resolver[Schema], ) -> Resolved[Schema]: if is_ref(node): ref_node = cls._get_subnode(node, "$ref") ref = cls._read_node(ref_node) resolved = resolver.lookup(ref) return cls._resolve_node( resolved.contents, resolved.resolver, ) return Resolved(cast(Schema, node), resolver) # type: ignore p1c2u-jsonschema-path-d08c0e1/jsonschema_path/paths.py000066400000000000000000000204571515153005400230020ustar00rootroot00000000000000"""JSONSchema spec paths module.""" from __future__ import annotations import os import warnings from collections.abc import Iterator from collections.abc import Sequence from contextlib import contextmanager from functools import cached_property from pathlib import Path from typing import Any from typing import TypeVar from typing import overload from pathable import AccessorPath from referencing import Specification from referencing._core import Resolved from referencing.jsonschema import DRAFT202012 from jsonschema_path.accessors import SchemaAccessor from jsonschema_path.handlers import default_handlers from jsonschema_path.handlers.protocols import SupportsRead from jsonschema_path.readers import FilePathReader from jsonschema_path.readers import FileReader from jsonschema_path.readers import PathReader from jsonschema_path.typing import ResolverHandlers from jsonschema_path.typing import Schema from jsonschema_path.typing import SchemaKey from jsonschema_path.typing import SchemaNode from jsonschema_path.typing import SchemaValue from jsonschema_path.typing import is_str_sequence TDefault = TypeVar("TDefault") # Python 3.11+ shortcut: typing.Self TSchemaPath = TypeVar("TSchemaPath", bound="SchemaPath") SPEC_SEPARATOR = "#" NOTSET = object() class SchemaPath(AccessorPath[SchemaNode, SchemaKey, SchemaValue]): @classmethod def _parse_args( cls, args: Sequence[Any], sep: str = SPEC_SEPARATOR, ) -> tuple[SchemaKey, ...]: parts: list[SchemaKey] = [] append = parts.append extend = parts.extend for a in args: if isinstance(a, cls): extend(a.parts) continue # Fast-path: benchmarks overwhelmingly pass `str`/`int` parts. if isinstance(a, int): append(a) continue if isinstance(a, bytes): a = a.decode("ascii") if isinstance(a, str): if a and a != ".": if sep in a: for x in a.split(sep): if x and x != ".": append(x) else: append(a) continue # PathLike is relatively expensive to check; keep it after common types. if isinstance(a, os.PathLike): a = os.fspath(a) if isinstance(a, bytes): a = a.decode("ascii") if isinstance(a, str): if a and a != ".": if sep in a: for x in a.split(sep): if x and x != ".": append(x) else: append(a) continue raise TypeError( "argument must be str, int, bytes, os.PathLike, or SchemaPath; got %r" % (type(a),) ) return tuple(parts) @classmethod def from_dict( cls: type[TSchemaPath], data: Schema, *args: Any, separator: str = SPEC_SEPARATOR, specification: Specification[Schema] = DRAFT202012, base_uri: str = "", handlers: ResolverHandlers = default_handlers, resolved_cache_maxsize: int = 0, spec_url: str | None = None, ref_resolver_handlers: ResolverHandlers | None = None, ) -> TSchemaPath: if spec_url is not None: warnings.warn( "spec_url parameter is deprecated. " "Use base_uri instead.", DeprecationWarning, ) base_uri = spec_url if ref_resolver_handlers is not None: warnings.warn( "ref_resolver_handlers parameter is deprecated. " "Use handlers instead.", DeprecationWarning, ) handlers = ref_resolver_handlers accessor: SchemaAccessor = SchemaAccessor.from_schema( data, specification=specification, base_uri=base_uri, handlers=handlers, resolved_cache_maxsize=resolved_cache_maxsize, ) return cls(accessor, *args, separator=separator) @classmethod def from_path( cls: type[TSchemaPath], path: Path, resolved_cache_maxsize: int = 0, ) -> TSchemaPath: reader = PathReader(path) data, base_uri = reader.read() return cls.from_dict( data, base_uri=base_uri, resolved_cache_maxsize=resolved_cache_maxsize, ) @classmethod def from_file_path( cls: type[TSchemaPath], file_path: str, resolved_cache_maxsize: int = 0, ) -> TSchemaPath: reader = FilePathReader(file_path) data, base_uri = reader.read() return cls.from_dict( data, base_uri=base_uri, resolved_cache_maxsize=resolved_cache_maxsize, ) @classmethod def from_file( cls: type[TSchemaPath], fileobj: SupportsRead, base_uri: str = "", spec_url: str | None = None, resolved_cache_maxsize: int = 0, ) -> TSchemaPath: reader = FileReader(fileobj) data, _ = reader.read() return cls.from_dict( data, base_uri=base_uri, spec_url=spec_url, resolved_cache_maxsize=resolved_cache_maxsize, ) @property def base_uri(self) -> str: assert isinstance(self.accessor, SchemaAccessor) return self.accessor.base_uri def str_keys(self) -> Sequence[str]: keys = list(self.keys()) if not is_str_sequence(keys): raise TypeError( f"Expected string keys, got {[type(x) for x in keys]}" ) return keys def str_items(self) -> Iterator[tuple[str, SchemaPath]]: for key, value in self.items(): if not isinstance(key, str): raise TypeError(f"Expected string keys, got {type(key)}") yield key, value @overload def read_str(self) -> str: ... @overload def read_str(self, default: TDefault) -> str | TDefault: ... def read_str(self, default: object = NOTSET) -> object: try: value = self.read_value() except KeyError: if default is not NOTSET: return default raise if not isinstance(value, str): raise TypeError(f"Expected a string value, got {type(value)}") return value @overload def read_str_or_list(self) -> str | list[str]: ... @overload def read_str_or_list( self, default: TDefault ) -> str | list[str] | TDefault: ... def read_str_or_list(self, default: object = NOTSET) -> object: try: value = self.read_value() except KeyError: if default is not NOTSET: return default raise if not isinstance(value, (str, list)): raise TypeError( f"Expected a string or a list of strings, got {type(value)}" ) return value @overload def read_bool(self) -> bool: ... @overload def read_bool(self, default: TDefault) -> bool | TDefault: ... def read_bool(self, default: object = NOTSET) -> object: try: value = self.read_value() except KeyError: if default is not NOTSET: return default raise if not isinstance(value, bool): if default is not NOTSET: return default raise TypeError(f"Expected a bool value, got {type(value)}") return value def as_uri(self) -> str: return f"#/{str(self)}" @contextmanager def open(self) -> Any: """Open the path.""" # Cached path content with self.resolve() as resolved: yield resolved.contents @contextmanager def resolve(self) -> Iterator[Resolved[SchemaNode]]: """Resolve the path.""" # Cached path content yield self._get_resolved @cached_property def _get_resolved(self) -> Resolved[SchemaNode]: assert isinstance(self.accessor, SchemaAccessor) with self.accessor.resolve(self.parts) as resolved: return resolved p1c2u-jsonschema-path-d08c0e1/jsonschema_path/py.typed000066400000000000000000000000001515153005400227660ustar00rootroot00000000000000p1c2u-jsonschema-path-d08c0e1/jsonschema_path/readers.py000066400000000000000000000017731515153005400233100ustar00rootroot00000000000000"""JSONSchema spec readers module.""" from pathlib import Path from jsonschema_path.handlers import all_urls_handler from jsonschema_path.handlers import file_handler from jsonschema_path.handlers.protocols import SupportsRead from jsonschema_path.typing import Schema class BaseReader: def read(self) -> tuple[Schema, str]: raise NotImplementedError class FileReader(BaseReader): def __init__(self, fileobj: SupportsRead): self.fileobj = fileobj def read(self) -> tuple[Schema, str]: return file_handler(self.fileobj), "" class PathReader(BaseReader): def __init__(self, path: Path): self.path = path def read(self) -> tuple[Schema, str]: if not self.path.is_file(): raise OSError(f"No such file: {self.path}") uri = self.path.as_uri() return all_urls_handler(uri), uri class FilePathReader(PathReader): def __init__(self, file_path: str): path = Path(file_path).absolute() super().__init__(path) p1c2u-jsonschema-path-d08c0e1/jsonschema_path/resolvers.py000066400000000000000000000056211515153005400237030ustar00rootroot00000000000000from collections.abc import Sequence from dataclasses import dataclass from typing import cast from pathable.types import LookupKey from pathable.types import LookupNode from referencing import Registry from referencing._core import Resolved from referencing._core import Resolver from jsonschema_path.caches import PrefixResolvedCache from jsonschema_path.nodes import SchemaNode from jsonschema_path.typing import Schema @dataclass(frozen=True) class ResolveResult: resolved: Resolved[LookupNode] registry_changed: bool class CachedPathResolver: def __init__(self, resolver: Resolver[Schema]): self.resolver = resolver self.prefix_cache = PrefixResolvedCache() def resolve( self, node: LookupNode, parts: Sequence[LookupKey], ) -> ResolveResult: resolved = self._resolve_with_prefix_cache(node, parts) registry_changed = self._sync_registry(resolved.resolver._registry) return ResolveResult( resolved=resolved, registry_changed=registry_changed, ) def _resolve_with_prefix_cache( self, node: LookupNode, parts: Sequence[LookupKey], ) -> Resolved[LookupNode]: parts_tuple = tuple(parts) cached_prefix = self.prefix_cache.longest_prefix_hit(parts_tuple) if cached_prefix is None: root_resolved_schema = SchemaNode._resolve_node( node, self.resolver, ) resolved = cast(Resolved[LookupNode], root_resolved_schema) current_node = cast(LookupNode, root_resolved_schema.contents) current_resolver: Resolver[Schema] = root_resolved_schema.resolver start = 0 self.prefix_cache.seed_root(resolved) else: start, resolved = cached_prefix current_node = resolved.contents current_resolver = cast(Resolver[Schema], resolved.resolver) for index in range(start, len(parts_tuple)): part = parts_tuple[index] current_node = SchemaNode._get_subnode(current_node, part) resolved_schema = SchemaNode._resolve_node( current_node, current_resolver, ) resolved = cast(Resolved[LookupNode], resolved_schema) current_node, current_resolver = ( resolved.contents, resolved_schema.resolver, ) self.prefix_cache.store_intermediate( parts_tuple, index, resolved, ) return resolved def _sync_registry(self, registry: Registry[LookupNode]) -> bool: if registry is self.resolver._registry: return False self.resolver = self.resolver._evolve( self.resolver._base_uri, registry=registry, ) self.prefix_cache.invalidate() return True p1c2u-jsonschema-path-d08c0e1/jsonschema_path/retrievers.py000066400000000000000000000026771515153005400240610ustar00rootroot00000000000000from json import loads from urllib.parse import urlsplit from urllib.request import urlopen from referencing import Resource from referencing import Specification from referencing.typing import URI from referencing.typing import Retrieve from jsonschema_path.typing import ResolverHandlers from jsonschema_path.typing import Schema USE_REQUESTS = False try: import requests except ImportError: pass else: USE_REQUESTS = True class SchemaRetriever(Retrieve[Schema]): def __init__( self, handlers: ResolverHandlers, specification: Specification[Schema] ): self.handlers = handlers self.specification = specification def __call__(self, uri: URI) -> Resource[Schema]: scheme = urlsplit(uri).scheme if scheme in self.handlers: handler = self.handlers[scheme] contents = handler(uri) return self.specification.create_resource(contents) else: if scheme in ["http", "https"] and USE_REQUESTS: # Requests has support for detecting the correct encoding of # json over http contents = requests.get(uri).json() return self.specification.create_resource(contents) # Otherwise, pass off to urllib and assume utf-8 with urlopen(uri) as url: contents = loads(url.read().decode("utf-8")) return self.specification.create_resource(contents) p1c2u-jsonschema-path-d08c0e1/jsonschema_path/typing.py000066400000000000000000000012011515153005400231570ustar00rootroot00000000000000from collections.abc import Mapping from collections.abc import Sequence from typing import Any from typing import TypeGuard from pathable.types import LookupKey as SchemaKey from pathable.types import LookupNode as SchemaNode from pathable.types import LookupValue as SchemaValue __all__ = [ "ResolverHandlers", "Schema", "SchemaNode", "SchemaKey", "SchemaValue", ] ResolverHandlers = Mapping[str, Any] Schema = Mapping[str, Any] def is_str_sequence(val: Sequence[object]) -> TypeGuard[Sequence[str]]: """Determines whether all objects in the list are strings""" return all(isinstance(x, str) for x in val) p1c2u-jsonschema-path-d08c0e1/jsonschema_path/utils.py000066400000000000000000000002571515153005400230170ustar00rootroot00000000000000from typing import Any def is_ref(item: Any) -> bool: return ( isinstance(item, dict) and "$ref" in item and isinstance(item["$ref"], str) ) p1c2u-jsonschema-path-d08c0e1/poetry.lock000066400000000000000000003521641515153005400203420ustar00rootroot00000000000000# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand. [[package]] name = "asttokens" version = "3.0.0" description = "Annotate AST trees with source code positions" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ {file = "asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2"}, {file = "asttokens-3.0.0.tar.gz", hash = "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7"}, ] [package.extras] astroid = ["astroid (>=2,<4)"] test = ["astroid (>=2,<4)", "pytest", "pytest-cov", "pytest-xdist"] [[package]] name = "attrs" version = "25.3.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=3.8" groups = ["main"] files = [ {file = "attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3"}, {file = "attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b"}, ] [package.extras] benchmark = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] cov = ["cloudpickle ; platform_python_implementation == \"CPython\"", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] dev = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier"] tests = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] tests-mypy = ["mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\""] [[package]] name = "black" version = "26.1.0" description = "The uncompromising code formatter." optional = false python-versions = ">=3.10" groups = ["dev"] files = [ {file = "black-26.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ca699710dece84e3ebf6e92ee15f5b8f72870ef984bf944a57a777a48357c168"}, {file = "black-26.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5e8e75dabb6eb83d064b0db46392b25cabb6e784ea624219736e8985a6b3675d"}, {file = "black-26.1.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eb07665d9a907a1a645ee41a0df8a25ffac8ad9c26cdb557b7b88eeeeec934e0"}, {file = "black-26.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:7ed300200918147c963c87700ccf9966dceaefbbb7277450a8d646fc5646bf24"}, {file = "black-26.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:c5b7713daea9bf943f79f8c3b46f361cc5229e0e604dcef6a8bb6d1c37d9df89"}, {file = "black-26.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3cee1487a9e4c640dc7467aaa543d6c0097c391dc8ac74eb313f2fbf9d7a7cb5"}, {file = "black-26.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d62d14ca31c92adf561ebb2e5f2741bf8dea28aef6deb400d49cca011d186c68"}, {file = "black-26.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fb1dafbbaa3b1ee8b4550a84425aac8874e5f390200f5502cf3aee4a2acb2f14"}, {file = "black-26.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:101540cb2a77c680f4f80e628ae98bd2bd8812fb9d72ade4f8995c5ff019e82c"}, {file = "black-26.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:6f3977a16e347f1b115662be07daa93137259c711e526402aa444d7a88fdc9d4"}, {file = "black-26.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6eeca41e70b5f5c84f2f913af857cf2ce17410847e1d54642e658e078da6544f"}, {file = "black-26.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:dd39eef053e58e60204f2cdf059e2442e2eb08f15989eefe259870f89614c8b6"}, {file = "black-26.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9459ad0d6cd483eacad4c6566b0f8e42af5e8b583cee917d90ffaa3778420a0a"}, {file = "black-26.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:a19915ec61f3a8746e8b10adbac4a577c6ba9851fa4a9e9fbfbcf319887a5791"}, {file = "black-26.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:643d27fb5facc167c0b1b59d0315f2674a6e950341aed0fc05cf307d22bf4954"}, {file = "black-26.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ba1d768fbfb6930fc93b0ecc32a43d8861ded16f47a40f14afa9bb04ab93d304"}, {file = "black-26.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2b807c240b64609cb0e80d2200a35b23c7df82259f80bef1b2c96eb422b4aac9"}, {file = "black-26.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1de0f7d01cc894066a1153b738145b194414cc6eeaad8ef4397ac9abacf40f6b"}, {file = "black-26.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:91a68ae46bf07868963671e4d05611b179c2313301bd756a89ad4e3b3db2325b"}, {file = "black-26.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:be5e2fe860b9bd9edbf676d5b60a9282994c03fbbd40fe8f5e75d194f96064ca"}, {file = "black-26.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:9dc8c71656a79ca49b8d3e2ce8103210c9481c57798b48deeb3a8bb02db5f115"}, {file = "black-26.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b22b3810451abe359a964cc88121d57f7bce482b53a066de0f1584988ca36e79"}, {file = "black-26.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:53c62883b3f999f14e5d30b5a79bd437236658ad45b2f853906c7cbe79de00af"}, {file = "black-26.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:f016baaadc423dc960cdddf9acae679e71ee02c4c341f78f3179d7e4819c095f"}, {file = "black-26.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:66912475200b67ef5a0ab665011964bf924745103f51977a78b4fb92a9fc1bf0"}, {file = "black-26.1.0-py3-none-any.whl", hash = "sha256:1054e8e47ebd686e078c0bb0eaf31e6ce69c966058d122f2c0c950311f9f3ede"}, {file = "black-26.1.0.tar.gz", hash = "sha256:d294ac3340eef9c9eb5d29288e96dc719ff269a88e27b396340459dd85da4c58"}, ] [package.dependencies] click = ">=8.0.0" mypy-extensions = ">=0.4.3" packaging = ">=22.0" pathspec = ">=1.0.0" platformdirs = ">=2" pytokens = ">=0.3.0" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} [package.extras] colorama = ["colorama (>=0.4.3)"] d = ["aiohttp (>=3.10)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "certifi" version = "2025.4.26" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" groups = ["main", "dev"] files = [ {file = "certifi-2025.4.26-py3-none-any.whl", hash = "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3"}, {file = "certifi-2025.4.26.tar.gz", hash = "sha256:0a816057ea3cdefcef70270d2c515e4506bbc954f417fa5ade2021213bb8f0c6"}, ] markers = {main = "extra == \"requests\""} [[package]] name = "cfgv" version = "3.4.0" description = "Validate configuration and produce human readable error messages." optional = false python-versions = ">=3.8" groups = ["dev"] files = [ {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, ] [[package]] name = "charset-normalizer" version = "3.4.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7" groups = ["main", "dev"] files = [ {file = "charset_normalizer-3.4.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c48ed483eb946e6c04ccbe02c6b4d1d48e51944b6db70f697e089c193404941"}, {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2d318c11350e10662026ad0eb71bb51c7812fc8590825304ae0bdd4ac283acd"}, {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9cbfacf36cb0ec2897ce0ebc5d08ca44213af24265bd56eca54bee7923c48fd6"}, {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18dd2e350387c87dabe711b86f83c9c78af772c748904d372ade190b5c7c9d4d"}, {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8075c35cd58273fee266c58c0c9b670947c19df5fb98e7b66710e04ad4e9ff86"}, {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5bf4545e3b962767e5c06fe1738f951f77d27967cb2caa64c28be7c4563e162c"}, {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a6ab32f7210554a96cd9e33abe3ddd86732beeafc7a28e9955cdf22ffadbab0"}, {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b33de11b92e9f75a2b545d6e9b6f37e398d86c3e9e9653c4864eb7e89c5773ef"}, {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8755483f3c00d6c9a77f490c17e6ab0c8729e39e6390328e42521ef175380ae6"}, {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:68a328e5f55ec37c57f19ebb1fdc56a248db2e3e9ad769919a58672958e8f366"}, {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:21b2899062867b0e1fde9b724f8aecb1af14f2778d69aacd1a5a1853a597a5db"}, {file = "charset_normalizer-3.4.2-cp310-cp310-win32.whl", hash = "sha256:e8082b26888e2f8b36a042a58307d5b917ef2b1cacab921ad3323ef91901c71a"}, {file = "charset_normalizer-3.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:f69a27e45c43520f5487f27627059b64aaf160415589230992cec34c5e18a509"}, {file = "charset_normalizer-3.4.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2"}, {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645"}, {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd"}, {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8"}, {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f"}, {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7"}, {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9"}, {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544"}, {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82"}, {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0"}, {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5"}, {file = "charset_normalizer-3.4.2-cp311-cp311-win32.whl", hash = "sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a"}, {file = "charset_normalizer-3.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28"}, {file = "charset_normalizer-3.4.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7"}, {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3"}, {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a"}, {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214"}, {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a"}, {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd"}, {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981"}, {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c"}, {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b"}, {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d"}, {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f"}, {file = "charset_normalizer-3.4.2-cp312-cp312-win32.whl", hash = "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c"}, {file = "charset_normalizer-3.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e"}, {file = "charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0"}, {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf"}, {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e"}, {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1"}, {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c"}, {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691"}, {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0"}, {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b"}, {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff"}, {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b"}, {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148"}, {file = "charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7"}, {file = "charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980"}, {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cad5f45b3146325bb38d6855642f6fd609c3f7cad4dbaf75549bf3b904d3184"}, {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2680962a4848b3c4f155dc2ee64505a9c57186d0d56b43123b17ca3de18f0fa"}, {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:36b31da18b8890a76ec181c3cf44326bf2c48e36d393ca1b72b3f484113ea344"}, {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f4074c5a429281bf056ddd4c5d3b740ebca4d43ffffe2ef4bf4d2d05114299da"}, {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9e36a97bee9b86ef9a1cf7bb96747eb7a15c2f22bdb5b516434b00f2a599f02"}, {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:1b1bde144d98e446b056ef98e59c256e9294f6b74d7af6846bf5ffdafd687a7d"}, {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:915f3849a011c1f593ab99092f3cecfcb4d65d8feb4a64cf1bf2d22074dc0ec4"}, {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:fb707f3e15060adf5b7ada797624a6c6e0138e2a26baa089df64c68ee98e040f"}, {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:25a23ea5c7edc53e0f29bae2c44fcb5a1aa10591aae107f2a2b2583a9c5cbc64"}, {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:770cab594ecf99ae64c236bc9ee3439c3f46be49796e265ce0cc8bc17b10294f"}, {file = "charset_normalizer-3.4.2-cp37-cp37m-win32.whl", hash = "sha256:6a0289e4589e8bdfef02a80478f1dfcb14f0ab696b5a00e1f4b8a14a307a3c58"}, {file = "charset_normalizer-3.4.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6fc1f5b51fa4cecaa18f2bd7a003f3dd039dd615cd69a2afd6d3b19aed6775f2"}, {file = "charset_normalizer-3.4.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:76af085e67e56c8816c3ccf256ebd136def2ed9654525348cfa744b6802b69eb"}, {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e45ba65510e2647721e35323d6ef54c7974959f6081b58d4ef5d87c60c84919a"}, {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:046595208aae0120559a67693ecc65dd75d46f7bf687f159127046628178dc45"}, {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75d10d37a47afee94919c4fab4c22b9bc2a8bf7d4f46f87363bcf0573f3ff4f5"}, {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6333b3aa5a12c26b2a4d4e7335a28f1475e0e5e17d69d55141ee3cab736f66d1"}, {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e8323a9b031aa0393768b87f04b4164a40037fb2a3c11ac06a03ffecd3618027"}, {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:24498ba8ed6c2e0b56d4acbf83f2d989720a93b41d712ebd4f4979660db4417b"}, {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:844da2b5728b5ce0e32d863af26f32b5ce61bc4273a9c720a9f3aa9df73b1455"}, {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:65c981bdbd3f57670af8b59777cbfae75364b483fa8a9f420f08094531d54a01"}, {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:3c21d4fca343c805a52c0c78edc01e3477f6dd1ad7c47653241cf2a206d4fc58"}, {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:dc7039885fa1baf9be153a0626e337aa7ec8bf96b0128605fb0d77788ddc1681"}, {file = "charset_normalizer-3.4.2-cp38-cp38-win32.whl", hash = "sha256:8272b73e1c5603666618805fe821edba66892e2870058c94c53147602eab29c7"}, {file = "charset_normalizer-3.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:70f7172939fdf8790425ba31915bfbe8335030f05b9913d7ae00a87d4395620a"}, {file = "charset_normalizer-3.4.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:005fa3432484527f9732ebd315da8da8001593e2cf46a3d817669f062c3d9ed4"}, {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e92fca20c46e9f5e1bb485887d074918b13543b1c2a1185e69bb8d17ab6236a7"}, {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50bf98d5e563b83cc29471fa114366e6806bc06bc7a25fd59641e41445327836"}, {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:721c76e84fe669be19c5791da68232ca2e05ba5185575086e384352e2c309597"}, {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82d8fd25b7f4675d0c47cf95b594d4e7b158aca33b76aa63d07186e13c0e0ab7"}, {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3daeac64d5b371dea99714f08ffc2c208522ec6b06fbc7866a450dd446f5c0f"}, {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dccab8d5fa1ef9bfba0590ecf4d46df048d18ffe3eec01eeb73a42e0d9e7a8ba"}, {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:aaf27faa992bfee0264dc1f03f4c75e9fcdda66a519db6b957a3f826e285cf12"}, {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:eb30abc20df9ab0814b5a2524f23d75dcf83cde762c161917a2b4b7b55b1e518"}, {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:c72fbbe68c6f32f251bdc08b8611c7b3060612236e960ef848e0a517ddbe76c5"}, {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:982bb1e8b4ffda883b3d0a521e23abcd6fd17418f6d2c4118d257a10199c0ce3"}, {file = "charset_normalizer-3.4.2-cp39-cp39-win32.whl", hash = "sha256:43e0933a0eff183ee85833f341ec567c0980dae57c464d8a508e1b2ceb336471"}, {file = "charset_normalizer-3.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:d11b54acf878eef558599658b0ffca78138c8c3655cf4f3a4a673c437e67732e"}, {file = "charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0"}, {file = "charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63"}, ] markers = {main = "extra == \"requests\""} [[package]] name = "cli-ui" version = "0.19.0" description = "Build Nice User Interfaces In The Terminal" optional = false python-versions = "<4.0,>=3.9" groups = ["dev"] files = [ {file = "cli_ui-0.19.0-py3-none-any.whl", hash = "sha256:1cf1b93328f7377730db29507e10bcb29ccc1427ceef45714b522d1f2055e7cd"}, {file = "cli_ui-0.19.0.tar.gz", hash = "sha256:59cdab0c6a2a6703c61b31cb75a1943076888907f015fffe15c5a8eb41a933aa"}, ] [package.dependencies] colorama = ">=0.4.1,<0.5.0" tabulate = ">=0.9.0,<0.10.0" unidecode = ">=1.3.6,<2.0.0" [[package]] name = "click" version = "8.1.8" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" groups = ["dev"] files = [ {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, ] [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} [[package]] name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" groups = ["dev"] files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] [[package]] name = "coverage" version = "7.8.1" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ {file = "coverage-7.8.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d7af3990490982fbd2437156c69edbe82b7edf99bc60302cceeeaf79afb886b8"}, {file = "coverage-7.8.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c5757a7b25fe48040fa120ba6597f5f885b01e323e0d13fe21ff95a70c0f76b7"}, {file = "coverage-7.8.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b8f105631835fdf191c971c4da93d27e732e028d73ecaa1a88f458d497d026cf"}, {file = "coverage-7.8.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:21645788c5c2afa3df2d4b607638d86207b84cb495503b71e80e16b4c6b44e80"}, {file = "coverage-7.8.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e93f36a5c9d995f40e9c4cd9bbabd83fd78705792fa250980256c93accd07bb6"}, {file = "coverage-7.8.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d591f2ddad432b794f77dc1e94334a80015a3fc7fa07fd6aed8f40362083be5b"}, {file = "coverage-7.8.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:be2b1a455b3ecfee20638289bb091a95216887d44924a41c28a601efac0916e8"}, {file = "coverage-7.8.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:061a3bf679dc38fe34d3822f10a9977d548de86b440010beb1e3b44ba93d20f7"}, {file = "coverage-7.8.1-cp310-cp310-win32.whl", hash = "sha256:12950b6373dc9dfe1ce22a8506ec29c82bfc5b38146ced0a222f38cf5d99a56d"}, {file = "coverage-7.8.1-cp310-cp310-win_amd64.whl", hash = "sha256:11e5ea0acd8cc5d23030c34dfb2eb6638ad886328df18cc69f8eefab73d1ece5"}, {file = "coverage-7.8.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc6bebc15c3b275174c66cf4e1c949a94c5c2a3edaa2f193a1225548c52c771"}, {file = "coverage-7.8.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d8a6c35afd5b912101fabf42975d92d750cfce33c571508a82ff334a133c40d5"}, {file = "coverage-7.8.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b37729ba34c116a3b2b6fb99df5c37a4ca40e96f430070488fd7a1077ad44907"}, {file = "coverage-7.8.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b6424c716f4c38ff8f62b602e6b94cde478dadda542a1cb3fe2fe2520cc2aae3"}, {file = "coverage-7.8.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8bcfafb2809cd01be8ffe5f962e01b0fbe4cc1d74513434c52ff2dd05b86d492"}, {file = "coverage-7.8.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e3f65da9701648d226b6b24ded3e2528b72075e48d7540968cd857c3bd4c5321"}, {file = "coverage-7.8.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:173e16969f990688aae4b4487717c44330bc57fd8b61a6216ce8eeb827eb5c0d"}, {file = "coverage-7.8.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3763b9a4bc128f72da5dcfd7fcc7c7d6644ed28e8f2db473ce1ef0dd37a43fa9"}, {file = "coverage-7.8.1-cp311-cp311-win32.whl", hash = "sha256:d074380f587360d2500f3b065232c67ae248aaf739267807adbcd29b88bdf864"}, {file = "coverage-7.8.1-cp311-cp311-win_amd64.whl", hash = "sha256:cd21de85aa0e247b79c6c41f8b5541b54285550f2da6a9448d82b53234d3611b"}, {file = "coverage-7.8.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2d8f844e837374a9497e11722d9eb9dfeb33b1b5d31136786c39a4c1a3073c6d"}, {file = "coverage-7.8.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9cd54a762667c32112df5d6f059c5d61fa532ee06460948cc5bcbf60c502f5c9"}, {file = "coverage-7.8.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:958b513e23286178b513a6b4d975fe9e7cddbcea6e5ebe8d836e4ef067577154"}, {file = "coverage-7.8.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9b31756ea647b6ef53190f6b708ad0c4c2ea879bc17799ba5b0699eee59ecf7b"}, {file = "coverage-7.8.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ccad4e29ac1b6f75bfeedb2cac4860fe5bd9e0a2f04c3e3218f661fa389ab101"}, {file = "coverage-7.8.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:452f3831c64f5f50260e18a89e613594590d6ceac5206a9b7d76ba43586b01b3"}, {file = "coverage-7.8.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9296df6a33b8539cd753765eb5b47308602263a14b124a099cbcf5f770d7cf90"}, {file = "coverage-7.8.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d52d79dfd3b410b153b6d65b0e3afe834eca2b969377f55ad73c67156d35af0d"}, {file = "coverage-7.8.1-cp312-cp312-win32.whl", hash = "sha256:ebdf212e1ed85af63fa1a76d556c0a3c7b34348ffba6e145a64b15f003ad0a2b"}, {file = "coverage-7.8.1-cp312-cp312-win_amd64.whl", hash = "sha256:c04a7903644ccea8fa07c3e76db43ca31c8d453f93c5c94c0f9b82efca225543"}, {file = "coverage-7.8.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dd5c305faa2e69334a53061b3168987847dadc2449bab95735242a9bde92fde8"}, {file = "coverage-7.8.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:af6b8cdf0857fd4e6460dd6639c37c3f82163127f6112c1942b5e6a52a477676"}, {file = "coverage-7.8.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e233a56bbf99e4cb134c4f8e63b16c77714e3987daf2c5aa10c3ba8c4232d730"}, {file = "coverage-7.8.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9dabc70012fd7b58a8040a7bc1b5f71fd0e62e2138aefdd8367d3d24bf82c349"}, {file = "coverage-7.8.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1f8e96455907496b3e4ea16f63bb578da31e17d2805278b193525e7714f17f2"}, {file = "coverage-7.8.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0034ceec8e91fdaf77350901cc48f47efd00f23c220a3f9fc1187774ddf307cb"}, {file = "coverage-7.8.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:82db9344a07dd9106796b9fe8805425633146a7ea7fed5ed07c65a64d0bb79e1"}, {file = "coverage-7.8.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9772c9e266b2ca4999180c12b90c8efb4c5c9ad3e55f301d78bc579af6467ad9"}, {file = "coverage-7.8.1-cp313-cp313-win32.whl", hash = "sha256:6f24a1e2c373a77afae21bc512466a91e31251685c271c5309ee3e557f6e3e03"}, {file = "coverage-7.8.1-cp313-cp313-win_amd64.whl", hash = "sha256:76a4e1d62505a21971968be61ae17cbdc5e0c483265a37f7ddbbc050f9c0b8ec"}, {file = "coverage-7.8.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:35dd5d405a1d378c39f3f30f628a25b0b99f1b8e5bdd78275df2e7b0404892d7"}, {file = "coverage-7.8.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:87b86a87f8de2e1bd0bcd45faf1b1edf54f988c8857157300e0336efcfb8ede6"}, {file = "coverage-7.8.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce4553a573edb363d5db12be1c044826878bec039159d6d4eafe826ef773396d"}, {file = "coverage-7.8.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db181a1896e0bad75b3bf4916c49fd3cf6751f9cc203fe0e0ecbee1fc43590fa"}, {file = "coverage-7.8.1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ce2606a171f9cf7c15a77ca61f979ffc0e0d92cd2fb18767cead58c1d19f58e"}, {file = "coverage-7.8.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4fc4f7cff2495d6d112353c33a439230a6de0b7cd0c2578f1e8d75326f63d783"}, {file = "coverage-7.8.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:ff619c58322d9d6df0a859dc76c3532d7bdbc125cb040f7cd642141446b4f654"}, {file = "coverage-7.8.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c0d6290a466a6f3fadf6add2dd4ec11deba4e1a6e3db2dd284edd497aadf802f"}, {file = "coverage-7.8.1-cp313-cp313t-win32.whl", hash = "sha256:e4e893c7f7fb12271a667d5c1876710fae06d7580343afdb5f3fc4488b73209e"}, {file = "coverage-7.8.1-cp313-cp313t-win_amd64.whl", hash = "sha256:41d142eefbc0bb3be160a77b2c0fbec76f345387676265052e224eb6c67b7af3"}, {file = "coverage-7.8.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d5102e17b81158de17d4b5bc363fcffd15231a38ef3f50b8e6fa01f0c6911194"}, {file = "coverage-7.8.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3bd8e3753257e95e94f38c058627aba1581d51f674e3badf226283b2bdb8f8ca"}, {file = "coverage-7.8.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d616b5a543c7d4deffa25eb8d8ae3d0d95097f08ac8b131600bb7fbf967ea0e2"}, {file = "coverage-7.8.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f7a95b0dce364535a63fde0ec1b1ca36400037175d3b62ce04d85dbca5e33832"}, {file = "coverage-7.8.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f82c1a1c1897d2293cb6c50f20fe8a9ea2add1a228eff479380917a1fe7bbb68"}, {file = "coverage-7.8.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:62a13b372b65fa6e11685df9ca924bed23bab1d0f277f9b67be7536f253aaf17"}, {file = "coverage-7.8.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:fe4877c24711458f7990392181be30166cc3ae72158036ecb48a73c30c99fb6f"}, {file = "coverage-7.8.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:ae5e557aa92565d72f6d3196e878e7cbd6a6380e02a15eafe0af781bd767c10d"}, {file = "coverage-7.8.1-cp39-cp39-win32.whl", hash = "sha256:87284f272746e31919302ab6211b16b41135109822c498f6e7b40a2f828e7836"}, {file = "coverage-7.8.1-cp39-cp39-win_amd64.whl", hash = "sha256:07fff2f2ce465fae27447432d39ce733476fbf8478de51fb4034c201e0c5da6d"}, {file = "coverage-7.8.1-pp39.pp310.pp311-none-any.whl", hash = "sha256:adafe9d71a940927dd3ad8d487f521f11277f133568b7da622666ebd08923191"}, {file = "coverage-7.8.1-py3-none-any.whl", hash = "sha256:e54b80885b0e61d346accc5709daf8762471a452345521cc9281604a907162c2"}, {file = "coverage-7.8.1.tar.gz", hash = "sha256:d41d4da5f2871b1782c6b74948d2d37aac3a5b39b43a6ba31d736b97a02ae1f1"}, ] [package.dependencies] tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} [package.extras] toml = ["tomli ; python_full_version <= \"3.11.0a6\""] [[package]] name = "decorator" version = "5.2.1" description = "Decorators for Humans" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ {file = "decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a"}, {file = "decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360"}, ] [[package]] name = "deptry" version = "0.23.1" description = "A command line utility to check for unused, missing and transitive dependencies in a Python project." optional = false python-versions = ">=3.9" groups = ["dev"] files = [ {file = "deptry-0.23.1-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:f0b231d098fb5b48d8973c9f192c353ffdd395770063424969fa7f15ddfea7d8"}, {file = "deptry-0.23.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:bf057f514bb2fa18a2b192a7f7372bd14577ff46b11486933e8383dfef461983"}, {file = "deptry-0.23.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ee3f5663bb1c048e2aaf25a4d9e6d09cc1f3b3396ee248980878c6a6c9c0e21"}, {file = "deptry-0.23.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae0366dc5f50a5fb29cf90de1110c5e368513de6c1b2dac439f2817f3f752616"}, {file = "deptry-0.23.1-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:ab156a90a9eda5819aeb1c1da585dd4d5ec509029399a38771a49e78f40db90f"}, {file = "deptry-0.23.1-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:651c7eb168233755152fcc468713c024d64a03069645187edb4a17ba61ce6133"}, {file = "deptry-0.23.1-cp39-abi3-win_amd64.whl", hash = "sha256:8da1e8f70e7086ebc228f3a4a3cfb5aa127b09b5eef60d694503d6bb79809025"}, {file = "deptry-0.23.1-cp39-abi3-win_arm64.whl", hash = "sha256:f589497a5809717db4dcf2aa840f2847c0a4c489331608e538850b6a9ab1c30b"}, {file = "deptry-0.23.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:6af91d86380ef703adb6ae65f273d88e3cca7fd315c4c309da857a0cfa728244"}, {file = "deptry-0.23.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:42a249d317c3128c286035a1f7aaa41a0c3c967f17848817c2e07ca50d5ed450"}, {file = "deptry-0.23.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d988c7c75201997970bae1e8d564b4c7a14d350556c4f7c269fd33f3b081c314"}, {file = "deptry-0.23.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae13d8e65ae88b77632c45edb4038301a6f9efcac06715abfde9a029e5879698"}, {file = "deptry-0.23.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:40058a7a3fe9dacb745668897ee992e58daf5aac406b668ff2eaaf0f6f586550"}, {file = "deptry-0.23.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:d111cf4261eeadbdb20051d8d542f04deb3cfced0cb280ece8d654f7f6055921"}, {file = "deptry-0.23.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:9f9bbb92f95ada9ccfa5ecefee05ba3c39cfa0734b5483a3a1a3c4eeb9c99054"}, {file = "deptry-0.23.1.tar.gz", hash = "sha256:5d23e0ef25f3c56405c05383a476edda55944563c5c47a3e9249ed3ec860d382"}, ] [package.dependencies] click = ">=8.0.0,<9" colorama = {version = ">=0.4.6", markers = "sys_platform == \"win32\""} packaging = ">=23.2" requirements-parser = ">=0.11.0,<1" tomli = {version = ">=2.0.1", markers = "python_full_version < \"3.11.0\""} [[package]] name = "distlib" version = "0.3.9" description = "Distribution utilities" optional = false python-versions = "*" groups = ["dev"] files = [ {file = "distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87"}, {file = "distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403"}, ] [[package]] name = "docopt" version = "0.6.2" description = "Pythonic argument parser, that will make you smile" optional = false python-versions = "*" groups = ["dev"] files = [ {file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"}, ] [[package]] name = "exceptiongroup" version = "1.3.0" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" groups = ["dev"] markers = "python_version == \"3.10\"" files = [ {file = "exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10"}, {file = "exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88"}, ] [package.dependencies] typing-extensions = {version = ">=4.6.0", markers = "python_version < \"3.13\""} [package.extras] test = ["pytest (>=6)"] [[package]] name = "executing" version = "2.2.0" description = "Get the currently executing AST node of a frame, and other information" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ {file = "executing-2.2.0-py2.py3-none-any.whl", hash = "sha256:11387150cad388d62750327a53d3339fad4888b39a6fe233c3afbb54ecffd3aa"}, {file = "executing-2.2.0.tar.gz", hash = "sha256:5d108c028108fe2551d1a7b2e8b713341e2cb4fc0aa7dcf966fa4327a5226755"}, ] [package.extras] tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipython", "littleutils", "pytest", "rich ; python_version >= \"3.11\""] [[package]] name = "filelock" version = "3.21.2" description = "A platform independent file lock." optional = false python-versions = ">=3.10" groups = ["dev"] files = [ {file = "filelock-3.21.2-py3-none-any.whl", hash = "sha256:d6cd4dbef3e1bb63bc16500fc5aa100f16e405bbff3fb4231711851be50c1560"}, {file = "filelock-3.21.2.tar.gz", hash = "sha256:cfd218cfccf8b947fce7837da312ec3359d10ef2a47c8602edd59e0bacffb708"}, ] [[package]] name = "flake8" version = "7.3.0" description = "the modular source code checker: pep8 pyflakes and co" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ {file = "flake8-7.3.0-py2.py3-none-any.whl", hash = "sha256:b9696257b9ce8beb888cdbe31cf885c90d31928fe202be0889a7cdafad32f01e"}, {file = "flake8-7.3.0.tar.gz", hash = "sha256:fe044858146b9fc69b551a4b490d69cf960fcb78ad1edcb84e7fbb1b4a8e3872"}, ] [package.dependencies] mccabe = ">=0.7.0,<0.8.0" pycodestyle = ">=2.14.0,<2.15.0" pyflakes = ">=3.4.0,<3.5.0" [[package]] name = "flynt" version = "1.0.6" description = "CLI tool to convert a python project's %-formatted strings to f-strings." optional = false python-versions = ">=3.9" groups = ["dev"] files = [ {file = "flynt-1.0.6-py3-none-any.whl", hash = "sha256:4e837c9597036b634a347855a89acf1483c4f8b73daa82c49372b10b6e1d1778"}, {file = "flynt-1.0.6.tar.gz", hash = "sha256:471b7ff00756678e2912d4261dcbcd8fc1395129b66bf6977f88a3b3ad220c90"}, ] [package.dependencies] tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} [package.extras] dev = ["build", "pre-commit", "pytest", "pytest-cov", "ruff", "twine"] [[package]] name = "identify" version = "2.6.10" description = "File identification library for Python" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ {file = "identify-2.6.10-py2.py3-none-any.whl", hash = "sha256:5f34248f54136beed1a7ba6a6b5c4b6cf21ff495aac7c359e1ef831ae3b8ab25"}, {file = "identify-2.6.10.tar.gz", hash = "sha256:45e92fd704f3da71cc3880036633f48b4b7265fd4de2b57627cb157216eb7eb8"}, ] [package.extras] license = ["ukkonen"] [[package]] name = "idna" version = "3.10" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.6" groups = ["main", "dev"] files = [ {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, ] markers = {main = "extra == \"requests\""} [package.extras] all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] [[package]] name = "iniconfig" version = "2.1.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, ] [[package]] name = "ipdb" version = "0.13.13" description = "IPython-enabled pdb" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" groups = ["dev"] files = [ {file = "ipdb-0.13.13-py3-none-any.whl", hash = "sha256:45529994741c4ab6d2388bfa5d7b725c2cf7fe9deffabdb8a6113aa5ed449ed4"}, {file = "ipdb-0.13.13.tar.gz", hash = "sha256:e3ac6018ef05126d442af680aad863006ec19d02290561ac88b8b1c0b0cfc726"}, ] [package.dependencies] decorator = {version = "*", markers = "python_version > \"3.6\""} ipython = {version = ">=7.31.1", markers = "python_version > \"3.6\""} tomli = {version = "*", markers = "python_version > \"3.6\" and python_version < \"3.11\""} [[package]] name = "ipython" version = "8.18.1" description = "IPython: Productive Interactive Computing" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ {file = "ipython-8.18.1-py3-none-any.whl", hash = "sha256:e8267419d72d81955ec1177f8a29aaa90ac80ad647499201119e2f05e99aa397"}, {file = "ipython-8.18.1.tar.gz", hash = "sha256:ca6f079bb33457c66e233e4580ebfc4128855b4cf6370dddd73842a9563e8a27"}, ] [package.dependencies] colorama = {version = "*", markers = "sys_platform == \"win32\""} decorator = "*" exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} jedi = ">=0.16" matplotlib-inline = "*" pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""} prompt-toolkit = ">=3.0.41,<3.1.0" pygments = ">=2.4.0" stack-data = "*" traitlets = ">=5" [package.extras] all = ["black", "curio", "docrepr", "exceptiongroup", "ipykernel", "ipyparallel", "ipywidgets", "matplotlib", "matplotlib (!=3.2.0)", "nbconvert", "nbformat", "notebook", "numpy (>=1.22)", "pandas", "pickleshare", "pytest (<7)", "pytest (<7.1)", "pytest-asyncio (<0.22)", "qtconsole", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "trio", "typing-extensions"] black = ["black"] doc = ["docrepr", "exceptiongroup", "ipykernel", "matplotlib", "pickleshare", "pytest (<7)", "pytest (<7.1)", "pytest-asyncio (<0.22)", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "typing-extensions"] kernel = ["ipykernel"] nbconvert = ["nbconvert"] nbformat = ["nbformat"] notebook = ["ipywidgets", "notebook"] parallel = ["ipyparallel"] qtconsole = ["qtconsole"] test = ["pickleshare", "pytest (<7.1)", "pytest-asyncio (<0.22)", "testpath"] test-extra = ["curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.22)", "pandas", "pickleshare", "pytest (<7.1)", "pytest-asyncio (<0.22)", "testpath", "trio"] [[package]] name = "isort" version = "8.0.0" description = "A Python utility / library to sort Python imports." optional = false python-versions = ">=3.10.0" groups = ["dev"] files = [ {file = "isort-8.0.0-py3-none-any.whl", hash = "sha256:184916a933041c7cf718787f7e52064f3c06272aff69a5cb4dc46497bd8911d9"}, {file = "isort-8.0.0.tar.gz", hash = "sha256:fddea59202f231e170e52e71e3510b99c373b6e571b55d9c7b31b679c0fed47c"}, ] [package.extras] colors = ["colorama"] [[package]] name = "jedi" version = "0.19.2" description = "An autocompletion tool for Python that can be used for text editors." optional = false python-versions = ">=3.6" groups = ["dev"] files = [ {file = "jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9"}, {file = "jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0"}, ] [package.dependencies] parso = ">=0.8.4,<0.9.0" [package.extras] docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alabaster (==0.7.12)", "babel (==2.9.1)", "chardet (==4.0.0)", "commonmark (==0.8.1)", "docutils (==0.17.1)", "future (==0.18.2)", "idna (==2.10)", "imagesize (==1.2.0)", "mock (==1.0.1)", "packaging (==20.9)", "pyparsing (==2.4.7)", "pytz (==2021.1)", "readthedocs-sphinx-ext (==2.1.4)", "recommonmark (==0.5.0)", "requests (==2.25.1)", "six (==1.15.0)", "snowballstemmer (==2.1.0)", "sphinx (==1.8.5)", "sphinx-rtd-theme (==0.4.3)", "sphinxcontrib-serializinghtml (==1.1.4)", "sphinxcontrib-websupport (==1.2.4)", "urllib3 (==1.26.4)"] qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] testing = ["Django", "attrs", "colorama", "docopt", "pytest (<9.0.0)"] [[package]] name = "matplotlib-inline" version = "0.1.7" description = "Inline Matplotlib backend for Jupyter" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ {file = "matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca"}, {file = "matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90"}, ] [package.dependencies] traitlets = "*" [[package]] name = "mccabe" version = "0.7.0" description = "McCabe checker, plugin for flake8" optional = false python-versions = ">=3.6" groups = ["dev"] files = [ {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, ] [[package]] name = "mypy" version = "1.18.2" description = "Optional static typing for Python" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ {file = "mypy-1.18.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c1eab0cf6294dafe397c261a75f96dc2c31bffe3b944faa24db5def4e2b0f77c"}, {file = "mypy-1.18.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7a780ca61fc239e4865968ebc5240bb3bf610ef59ac398de9a7421b54e4a207e"}, {file = "mypy-1.18.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:448acd386266989ef11662ce3c8011fd2a7b632e0ec7d61a98edd8e27472225b"}, {file = "mypy-1.18.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f9e171c465ad3901dc652643ee4bffa8e9fef4d7d0eece23b428908c77a76a66"}, {file = "mypy-1.18.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:592ec214750bc00741af1f80cbf96b5013d81486b7bb24cb052382c19e40b428"}, {file = "mypy-1.18.2-cp310-cp310-win_amd64.whl", hash = "sha256:7fb95f97199ea11769ebe3638c29b550b5221e997c63b14ef93d2e971606ebed"}, {file = "mypy-1.18.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:807d9315ab9d464125aa9fcf6d84fde6e1dc67da0b6f80e7405506b8ac72bc7f"}, {file = "mypy-1.18.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:776bb00de1778caf4db739c6e83919c1d85a448f71979b6a0edd774ea8399341"}, {file = "mypy-1.18.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1379451880512ffce14505493bd9fe469e0697543717298242574882cf8cdb8d"}, {file = "mypy-1.18.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1331eb7fd110d60c24999893320967594ff84c38ac6d19e0a76c5fd809a84c86"}, {file = "mypy-1.18.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3ca30b50a51e7ba93b00422e486cbb124f1c56a535e20eff7b2d6ab72b3b2e37"}, {file = "mypy-1.18.2-cp311-cp311-win_amd64.whl", hash = "sha256:664dc726e67fa54e14536f6e1224bcfce1d9e5ac02426d2326e2bb4e081d1ce8"}, {file = "mypy-1.18.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:33eca32dd124b29400c31d7cf784e795b050ace0e1f91b8dc035672725617e34"}, {file = "mypy-1.18.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a3c47adf30d65e89b2dcd2fa32f3aeb5e94ca970d2c15fcb25e297871c8e4764"}, {file = "mypy-1.18.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d6c838e831a062f5f29d11c9057c6009f60cb294fea33a98422688181fe2893"}, {file = "mypy-1.18.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01199871b6110a2ce984bde85acd481232d17413868c9807e95c1b0739a58914"}, {file = "mypy-1.18.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a2afc0fa0b0e91b4599ddfe0f91e2c26c2b5a5ab263737e998d6817874c5f7c8"}, {file = "mypy-1.18.2-cp312-cp312-win_amd64.whl", hash = "sha256:d8068d0afe682c7c4897c0f7ce84ea77f6de953262b12d07038f4d296d547074"}, {file = "mypy-1.18.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:07b8b0f580ca6d289e69209ec9d3911b4a26e5abfde32228a288eb79df129fcc"}, {file = "mypy-1.18.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ed4482847168439651d3feee5833ccedbf6657e964572706a2adb1f7fa4dfe2e"}, {file = "mypy-1.18.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3ad2afadd1e9fea5cf99a45a822346971ede8685cc581ed9cd4d42eaf940986"}, {file = "mypy-1.18.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a431a6f1ef14cf8c144c6b14793a23ec4eae3db28277c358136e79d7d062f62d"}, {file = "mypy-1.18.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7ab28cc197f1dd77a67e1c6f35cd1f8e8b73ed2217e4fc005f9e6a504e46e7ba"}, {file = "mypy-1.18.2-cp313-cp313-win_amd64.whl", hash = "sha256:0e2785a84b34a72ba55fb5daf079a1003a34c05b22238da94fcae2bbe46f3544"}, {file = "mypy-1.18.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:62f0e1e988ad41c2a110edde6c398383a889d95b36b3e60bcf155f5164c4fdce"}, {file = "mypy-1.18.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8795a039bab805ff0c1dfdb8cd3344642c2b99b8e439d057aba30850b8d3423d"}, {file = "mypy-1.18.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6ca1e64b24a700ab5ce10133f7ccd956a04715463d30498e64ea8715236f9c9c"}, {file = "mypy-1.18.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d924eef3795cc89fecf6bedc6ed32b33ac13e8321344f6ddbf8ee89f706c05cb"}, {file = "mypy-1.18.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:20c02215a080e3a2be3aa50506c67242df1c151eaba0dcbc1e4e557922a26075"}, {file = "mypy-1.18.2-cp314-cp314-win_amd64.whl", hash = "sha256:749b5f83198f1ca64345603118a6f01a4e99ad4bf9d103ddc5a3200cc4614adf"}, {file = "mypy-1.18.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:25a9c8fb67b00599f839cf472713f54249a62efd53a54b565eb61956a7e3296b"}, {file = "mypy-1.18.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c2b9c7e284ee20e7598d6f42e13ca40b4928e6957ed6813d1ab6348aa3f47133"}, {file = "mypy-1.18.2-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d6985ed057513e344e43a26cc1cd815c7a94602fb6a3130a34798625bc2f07b6"}, {file = "mypy-1.18.2-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22f27105f1525ec024b5c630c0b9f36d5c1cc4d447d61fe51ff4bd60633f47ac"}, {file = "mypy-1.18.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:030c52d0ea8144e721e49b1f68391e39553d7451f0c3f8a7565b59e19fcb608b"}, {file = "mypy-1.18.2-cp39-cp39-win_amd64.whl", hash = "sha256:aa5e07ac1a60a253445797e42b8b2963c9675563a94f11291ab40718b016a7a0"}, {file = "mypy-1.18.2-py3-none-any.whl", hash = "sha256:22a1748707dd62b58d2ae53562ffc4d7f8bcc727e8ac7cbc69c053ddc874d47e"}, {file = "mypy-1.18.2.tar.gz", hash = "sha256:06a398102a5f203d7477b2923dda3634c36727fa5c237d8f859ef90c42a9924b"}, ] [package.dependencies] mypy_extensions = ">=1.0.0" pathspec = ">=0.9.0" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} typing_extensions = ">=4.6.0" [package.extras] dmypy = ["psutil (>=4.0)"] faster-cache = ["orjson"] install-types = ["pip"] mypyc = ["setuptools (>=50)"] reports = ["lxml"] [[package]] name = "mypy-extensions" version = "1.1.0" description = "Type system extensions for programs checked with the mypy type checker." optional = false python-versions = ">=3.8" groups = ["dev"] files = [ {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, ] [[package]] name = "nodeenv" version = "1.9.1" description = "Node.js virtual environment builder" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" groups = ["dev"] files = [ {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, ] [[package]] name = "packaging" version = "25.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, ] [[package]] name = "parso" version = "0.8.4" description = "A Python Parser" optional = false python-versions = ">=3.6" groups = ["dev"] files = [ {file = "parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18"}, {file = "parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d"}, ] [package.extras] qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] testing = ["docopt", "pytest"] [[package]] name = "pathable" version = "0.5.0" description = "Object-oriented paths" optional = false python-versions = "<4.0,>=3.10" groups = ["main"] files = [ {file = "pathable-0.5.0-py3-none-any.whl", hash = "sha256:646e3d09491a6351a0c82632a09c02cdf70a252e73196b36d8a15ba0a114f0a6"}, {file = "pathable-0.5.0.tar.gz", hash = "sha256:d81938348a1cacb525e7c75166270644782c0fb9c8cecc16be033e71427e0ef1"}, ] [[package]] name = "pathspec" version = "1.0.4" description = "Utility library for gitignore style pattern matching of file paths." optional = false python-versions = ">=3.9" groups = ["dev"] files = [ {file = "pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723"}, {file = "pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645"}, ] [package.extras] hyperscan = ["hyperscan (>=0.7)"] optional = ["typing-extensions (>=4)"] re2 = ["google-re2 (>=1.1)"] tests = ["pytest (>=9)", "typing-extensions (>=4.15)"] [[package]] name = "pexpect" version = "4.9.0" description = "Pexpect allows easy control of interactive console applications." optional = false python-versions = "*" groups = ["dev"] markers = "sys_platform != \"win32\"" files = [ {file = "pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523"}, {file = "pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f"}, ] [package.dependencies] ptyprocess = ">=0.5" [[package]] name = "platformdirs" version = "4.3.8" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.9" groups = ["dev"] files = [ {file = "platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4"}, {file = "platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc"}, ] [package.extras] docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"] test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.4)", "pytest-cov (>=6)", "pytest-mock (>=3.14)"] type = ["mypy (>=1.14.1)"] [[package]] name = "pluggy" version = "1.6.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, ] [package.extras] dev = ["pre-commit", "tox"] testing = ["coverage", "pytest", "pytest-benchmark"] [[package]] name = "pre-commit" version = "4.5.1" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false python-versions = ">=3.10" groups = ["dev"] files = [ {file = "pre_commit-4.5.1-py2.py3-none-any.whl", hash = "sha256:3b3afd891e97337708c1674210f8eba659b52a38ea5f822ff142d10786221f77"}, {file = "pre_commit-4.5.1.tar.gz", hash = "sha256:eb545fcff725875197837263e977ea257a402056661f09dae08e4b149b030a61"}, ] [package.dependencies] cfgv = ">=2.0.0" identify = ">=1.0.0" nodeenv = ">=0.11.1" pyyaml = ">=5.1" virtualenv = ">=20.10.0" [[package]] name = "prompt-toolkit" version = "3.0.51" description = "Library for building powerful interactive command lines in Python" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ {file = "prompt_toolkit-3.0.51-py3-none-any.whl", hash = "sha256:52742911fde84e2d423e2f9a4cf1de7d7ac4e51958f648d9540e0fb8db077b07"}, {file = "prompt_toolkit-3.0.51.tar.gz", hash = "sha256:931a162e3b27fc90c86f1b48bb1fb2c528c2761475e57c9c06de13311c7b54ed"}, ] [package.dependencies] wcwidth = "*" [[package]] name = "ptyprocess" version = "0.7.0" description = "Run a subprocess in a pseudo terminal" optional = false python-versions = "*" groups = ["dev"] markers = "sys_platform != \"win32\"" files = [ {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, ] [[package]] name = "pure-eval" version = "0.2.3" description = "Safely evaluate AST nodes without side effects" optional = false python-versions = "*" groups = ["dev"] files = [ {file = "pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0"}, {file = "pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42"}, ] [package.extras] tests = ["pytest"] [[package]] name = "pycodestyle" version = "2.14.0" description = "Python style guide checker" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ {file = "pycodestyle-2.14.0-py2.py3-none-any.whl", hash = "sha256:dd6bf7cb4ee77f8e016f9c8e74a35ddd9f67e1d5fd4184d86c3b98e07099f42d"}, {file = "pycodestyle-2.14.0.tar.gz", hash = "sha256:c4b5b517d278089ff9d0abdec919cd97262a3367449ea1c8b49b91529167b783"}, ] [[package]] name = "pyflakes" version = "3.4.0" description = "passive checker of Python programs" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ {file = "pyflakes-3.4.0-py2.py3-none-any.whl", hash = "sha256:f742a7dbd0d9cb9ea41e9a24a918996e8170c799fa528688d40dd582c8265f4f"}, {file = "pyflakes-3.4.0.tar.gz", hash = "sha256:b24f96fafb7d2ab0ec5075b7350b3d2d2218eab42003821c06344973d3ea2f58"}, ] [[package]] name = "pygments" version = "2.19.1" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.8" groups = ["dev"] files = [ {file = "pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c"}, {file = "pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f"}, ] [package.extras] windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pytest" version = "8.4.0" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ {file = "pytest-8.4.0-py3-none-any.whl", hash = "sha256:f40f825768ad76c0977cbacdf1fd37c6f7a468e460ea6a0636078f8972d4517e"}, {file = "pytest-8.4.0.tar.gz", hash = "sha256:14d920b48472ea0dbf68e45b96cd1ffda4705f33307dcc86c676c1b5104838a6"}, ] [package.dependencies] colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""} exceptiongroup = {version = ">=1", markers = "python_version < \"3.11\""} iniconfig = ">=1" packaging = ">=20" pluggy = ">=1.5,<2" pygments = ">=2.7.2" tomli = {version = ">=1", markers = "python_version < \"3.11\""} [package.extras] dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"] [[package]] name = "pytest-cov" version = "6.1.1" description = "Pytest plugin for measuring coverage." optional = false python-versions = ">=3.9" groups = ["dev"] files = [ {file = "pytest_cov-6.1.1-py3-none-any.whl", hash = "sha256:bddf29ed2d0ab6f4df17b4c55b0a657287db8684af9c42ea546b21b1041b3dde"}, {file = "pytest_cov-6.1.1.tar.gz", hash = "sha256:46935f7aaefba760e716c2ebfbe1c216240b9592966e7da99ea8292d4d3e2a0a"}, ] [package.dependencies] coverage = {version = ">=7.5", extras = ["toml"]} pytest = ">=4.6" [package.extras] testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] [[package]] name = "pytest-flake8" version = "1.3.0" description = "pytest plugin to check FLAKE8 requirements" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ {file = "pytest_flake8-1.3.0-py3-none-any.whl", hash = "sha256:de10517c59fce25c0a7abb2a2b2a9d0b0ceb59ff0add7fa8e654d613bb25e218"}, {file = "pytest_flake8-1.3.0.tar.gz", hash = "sha256:88fb35562ce32d915c6ba41ef0d5e1cfcdd8ff884a32b7d46aa99fc77a3d1fe6"}, ] [package.dependencies] flake8 = ">=4.0" pytest = ">=7.0" [package.extras] check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] test = ["pytest (>=6,!=8.1.*)"] type = ["pytest-mypy"] [[package]] name = "pytokens" version = "0.4.1" description = "A Fast, spec compliant Python 3.14+ tokenizer that runs on older Pythons." optional = false python-versions = ">=3.8" groups = ["dev"] files = [ {file = "pytokens-0.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2a44ed93ea23415c54f3face3b65ef2b844d96aeb3455b8a69b3df6beab6acc5"}, {file = "pytokens-0.4.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:add8bf86b71a5d9fb5b89f023a80b791e04fba57960aa790cc6125f7f1d39dfe"}, {file = "pytokens-0.4.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:670d286910b531c7b7e3c0b453fd8156f250adb140146d234a82219459b9640c"}, {file = "pytokens-0.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:4e691d7f5186bd2842c14813f79f8884bb03f5995f0575272009982c5ac6c0f7"}, {file = "pytokens-0.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:27b83ad28825978742beef057bfe406ad6ed524b2d28c252c5de7b4a6dd48fa2"}, {file = "pytokens-0.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d70e77c55ae8380c91c0c18dea05951482e263982911fc7410b1ffd1dadd3440"}, {file = "pytokens-0.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a58d057208cb9075c144950d789511220b07636dd2e4708d5645d24de666bdc"}, {file = "pytokens-0.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b49750419d300e2b5a3813cf229d4e5a4c728dae470bcc89867a9ad6f25a722d"}, {file = "pytokens-0.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d9907d61f15bf7261d7e775bd5d7ee4d2930e04424bab1972591918497623a16"}, {file = "pytokens-0.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:ee44d0f85b803321710f9239f335aafe16553b39106384cef8e6de40cb4ef2f6"}, {file = "pytokens-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:140709331e846b728475786df8aeb27d24f48cbcf7bcd449f8de75cae7a45083"}, {file = "pytokens-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d6c4268598f762bc8e91f5dbf2ab2f61f7b95bdc07953b602db879b3c8c18e1"}, {file = "pytokens-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:24afde1f53d95348b5a0eb19488661147285ca4dd7ed752bbc3e1c6242a304d1"}, {file = "pytokens-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5ad948d085ed6c16413eb5fec6b3e02fa00dc29a2534f088d3302c47eb59adf9"}, {file = "pytokens-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:3f901fe783e06e48e8cbdc82d631fca8f118333798193e026a50ce1b3757ea68"}, {file = "pytokens-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8bdb9d0ce90cbf99c525e75a2fa415144fd570a1ba987380190e8b786bc6ef9b"}, {file = "pytokens-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5502408cab1cb18e128570f8d598981c68a50d0cbd7c61312a90507cd3a1276f"}, {file = "pytokens-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:29d1d8fb1030af4d231789959f21821ab6325e463f0503a61d204343c9b355d1"}, {file = "pytokens-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:970b08dd6b86058b6dc07efe9e98414f5102974716232d10f32ff39701e841c4"}, {file = "pytokens-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:9bd7d7f544d362576be74f9d5901a22f317efc20046efe2034dced238cbbfe78"}, {file = "pytokens-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4a14d5f5fc78ce85e426aa159489e2d5961acf0e47575e08f35584009178e321"}, {file = "pytokens-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f50fd18543be72da51dd505e2ed20d2228c74e0464e4262e4899797803d7fa"}, {file = "pytokens-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dc74c035f9bfca0255c1af77ddd2d6ae8419012805453e4b0e7513e17904545d"}, {file = "pytokens-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f66a6bbe741bd431f6d741e617e0f39ec7257ca1f89089593479347cc4d13324"}, {file = "pytokens-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:b35d7e5ad269804f6697727702da3c517bb8a5228afa450ab0fa787732055fc9"}, {file = "pytokens-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8fcb9ba3709ff77e77f1c7022ff11d13553f3c30299a9fe246a166903e9091eb"}, {file = "pytokens-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:79fc6b8699564e1f9b521582c35435f1bd32dd06822322ec44afdeba666d8cb3"}, {file = "pytokens-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d31b97b3de0f61571a124a00ffe9a81fb9939146c122c11060725bd5aea79975"}, {file = "pytokens-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:967cf6e3fd4adf7de8fc73cd3043754ae79c36475c1c11d514fc72cf5490094a"}, {file = "pytokens-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:584c80c24b078eec1e227079d56dc22ff755e0ba8654d8383b2c549107528918"}, {file = "pytokens-0.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:da5baeaf7116dced9c6bb76dc31ba04a2dc3695f3d9f74741d7910122b456edc"}, {file = "pytokens-0.4.1-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:11edda0942da80ff58c4408407616a310adecae1ddd22eef8c692fe266fa5009"}, {file = "pytokens-0.4.1-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0fc71786e629cef478cbf29d7ea1923299181d0699dbe7c3c0f4a583811d9fc1"}, {file = "pytokens-0.4.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:dcafc12c30dbaf1e2af0490978352e0c4041a7cde31f4f81435c2a5e8b9cabb6"}, {file = "pytokens-0.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:42f144f3aafa5d92bad964d471a581651e28b24434d184871bd02e3a0d956037"}, {file = "pytokens-0.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:34bcc734bd2f2d5fe3b34e7b3c0116bfb2397f2d9666139988e7a3eb5f7400e3"}, {file = "pytokens-0.4.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:941d4343bf27b605e9213b26bfa1c4bf197c9c599a9627eb7305b0defcfe40c1"}, {file = "pytokens-0.4.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3ad72b851e781478366288743198101e5eb34a414f1d5627cdd585ca3b25f1db"}, {file = "pytokens-0.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:682fa37ff4d8e95f7df6fe6fe6a431e8ed8e788023c6bcc0f0880a12eab80ad1"}, {file = "pytokens-0.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:30f51edd9bb7f85c748979384165601d028b84f7bd13fe14d3e065304093916a"}, {file = "pytokens-0.4.1-py3-none-any.whl", hash = "sha256:26cef14744a8385f35d0e095dc8b3a7583f6c953c2e3d269c7f82484bf5ad2de"}, {file = "pytokens-0.4.1.tar.gz", hash = "sha256:292052fe80923aae2260c073f822ceba21f3872ced9a68bb7953b348e561179a"}, ] [package.extras] dev = ["black", "build", "mypy", "pytest", "pytest-cov", "setuptools", "tox", "twine", "wheel"] [[package]] name = "pyyaml" version = "6.0.3" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" groups = ["main", "dev"] files = [ {file = "PyYAML-6.0.3-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:c2514fceb77bc5e7a2f7adfaa1feb2fb311607c9cb518dbc378688ec73d8292f"}, {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c57bb8c96f6d1808c030b1687b9b5fb476abaa47f0db9c0101f5e9f394e97f4"}, {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:efd7b85f94a6f21e4932043973a7ba2613b059c4a000551892ac9f1d11f5baf3"}, {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22ba7cfcad58ef3ecddc7ed1db3409af68d023b7f940da23c6c2a1890976eda6"}, {file = "PyYAML-6.0.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:6344df0d5755a2c9a276d4473ae6b90647e216ab4757f8426893b5dd2ac3f369"}, {file = "PyYAML-6.0.3-cp38-cp38-win32.whl", hash = "sha256:3ff07ec89bae51176c0549bc4c63aa6202991da2d9a6129d7aef7f1407d3f295"}, {file = "PyYAML-6.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:5cf4e27da7e3fbed4d6c3d8e797387aaad68102272f8f9752883bc32d61cb87b"}, {file = "pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b"}, {file = "pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956"}, {file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8"}, {file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198"}, {file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b"}, {file = "pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0"}, {file = "pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69"}, {file = "pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e"}, {file = "pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c"}, {file = "pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e"}, {file = "pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824"}, {file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c"}, {file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00"}, {file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d"}, {file = "pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a"}, {file = "pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4"}, {file = "pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b"}, {file = "pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf"}, {file = "pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196"}, {file = "pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0"}, {file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28"}, {file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c"}, {file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc"}, {file = "pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e"}, {file = "pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea"}, {file = "pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5"}, {file = "pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b"}, {file = "pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd"}, {file = "pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8"}, {file = "pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1"}, {file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c"}, {file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5"}, {file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6"}, {file = "pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6"}, {file = "pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be"}, {file = "pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26"}, {file = "pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c"}, {file = "pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb"}, {file = "pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac"}, {file = "pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310"}, {file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7"}, {file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788"}, {file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5"}, {file = "pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764"}, {file = "pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35"}, {file = "pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac"}, {file = "pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3"}, {file = "pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3"}, {file = "pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba"}, {file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c"}, {file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702"}, {file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c"}, {file = "pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065"}, {file = "pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65"}, {file = "pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9"}, {file = "pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b"}, {file = "pyyaml-6.0.3-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:b865addae83924361678b652338317d1bd7e79b1f4596f96b96c77a5a34b34da"}, {file = "pyyaml-6.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c3355370a2c156cffb25e876646f149d5d68f5e0a3ce86a5084dd0b64a994917"}, {file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3c5677e12444c15717b902a5798264fa7909e41153cdf9ef7ad571b704a63dd9"}, {file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5ed875a24292240029e4483f9d4a4b8a1ae08843b9c54f43fcc11e404532a8a5"}, {file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0150219816b6a1fa26fb4699fb7daa9caf09eb1999f3b70fb6e786805e80375a"}, {file = "pyyaml-6.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fa160448684b4e94d80416c0fa4aac48967a969efe22931448d853ada8baf926"}, {file = "pyyaml-6.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:27c0abcb4a5dac13684a37f76e701e054692a9b2d3064b70f5e4eb54810553d7"}, {file = "pyyaml-6.0.3-cp39-cp39-win32.whl", hash = "sha256:1ebe39cb5fc479422b83de611d14e2c0d3bb2a18bbcb01f229ab3cfbd8fee7a0"}, {file = "pyyaml-6.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:2e71d11abed7344e42a8849600193d15b6def118602c4c176f748e4583246007"}, {file = "pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f"}, ] [[package]] name = "referencing" version = "0.37.0" description = "JSON Referencing + Python" optional = false python-versions = ">=3.10" groups = ["main"] files = [ {file = "referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231"}, {file = "referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8"}, ] [package.dependencies] attrs = ">=22.2.0" rpds-py = ">=0.7.0" typing-extensions = {version = ">=4.4.0", markers = "python_version < \"3.13\""} [[package]] name = "requests" version = "2.32.5" description = "Python HTTP for Humans." optional = false python-versions = ">=3.9" groups = ["main", "dev"] files = [ {file = "requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6"}, {file = "requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf"}, ] markers = {main = "extra == \"requests\""} [package.dependencies] certifi = ">=2017.4.17" charset_normalizer = ">=2,<4" idna = ">=2.5,<4" urllib3 = ">=1.21.1,<3" [package.extras] socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "requirements-parser" version = "0.13.0" description = "This is a small Python module for parsing Pip requirement files." optional = false python-versions = "<4.0,>=3.8" groups = ["dev"] files = [ {file = "requirements_parser-0.13.0-py3-none-any.whl", hash = "sha256:2b3173faecf19ec5501971b7222d38f04cb45bb9d87d0ad629ca71e2e62ded14"}, {file = "requirements_parser-0.13.0.tar.gz", hash = "sha256:0843119ca2cb2331de4eb31b10d70462e39ace698fd660a915c247d2301a4418"}, ] [package.dependencies] packaging = ">=23.2" [[package]] name = "responses" version = "0.26.0" description = "A utility library for mocking out the `requests` Python library." optional = false python-versions = ">=3.8" groups = ["dev"] files = [ {file = "responses-0.26.0-py3-none-any.whl", hash = "sha256:03ec4409088cd5c66b71ecbbbd27fe2c58ddfad801c66203457b3e6a04868c37"}, {file = "responses-0.26.0.tar.gz", hash = "sha256:c7f6923e6343ef3682816ba421c006626777893cb0d5e1434f674b649bac9eb4"}, ] [package.dependencies] pyyaml = "*" requests = ">=2.30.0,<3.0" urllib3 = ">=1.25.10,<3.0" [package.extras] tests = ["coverage (>=6.0.0)", "flake8", "mypy", "pytest (>=7.0.0)", "pytest-asyncio", "pytest-cov", "pytest-httpserver", "tomli ; python_version < \"3.11\"", "tomli-w", "types-PyYAML", "types-requests"] [[package]] name = "rpds-py" version = "0.25.1" description = "Python bindings to Rust's persistent data structures (rpds)" optional = false python-versions = ">=3.9" groups = ["main"] files = [ {file = "rpds_py-0.25.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:f4ad628b5174d5315761b67f212774a32f5bad5e61396d38108bd801c0a8f5d9"}, {file = "rpds_py-0.25.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8c742af695f7525e559c16f1562cf2323db0e3f0fbdcabdf6865b095256b2d40"}, {file = "rpds_py-0.25.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:605ffe7769e24b1800b4d024d24034405d9404f0bc2f55b6db3362cd34145a6f"}, {file = "rpds_py-0.25.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ccc6f3ddef93243538be76f8e47045b4aad7a66a212cd3a0f23e34469473d36b"}, {file = "rpds_py-0.25.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f70316f760174ca04492b5ab01be631a8ae30cadab1d1081035136ba12738cfa"}, {file = "rpds_py-0.25.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e1dafef8df605fdb46edcc0bf1573dea0d6d7b01ba87f85cd04dc855b2b4479e"}, {file = "rpds_py-0.25.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0701942049095741a8aeb298a31b203e735d1c61f4423511d2b1a41dcd8a16da"}, {file = "rpds_py-0.25.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e87798852ae0b37c88babb7f7bbbb3e3fecc562a1c340195b44c7e24d403e380"}, {file = "rpds_py-0.25.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3bcce0edc1488906c2d4c75c94c70a0417e83920dd4c88fec1078c94843a6ce9"}, {file = "rpds_py-0.25.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e2f6a2347d3440ae789505693a02836383426249d5293541cd712e07e7aecf54"}, {file = "rpds_py-0.25.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:4fd52d3455a0aa997734f3835cbc4c9f32571345143960e7d7ebfe7b5fbfa3b2"}, {file = "rpds_py-0.25.1-cp310-cp310-win32.whl", hash = "sha256:3f0b1798cae2bbbc9b9db44ee068c556d4737911ad53a4e5093d09d04b3bbc24"}, {file = "rpds_py-0.25.1-cp310-cp310-win_amd64.whl", hash = "sha256:3ebd879ab996537fc510a2be58c59915b5dd63bccb06d1ef514fee787e05984a"}, {file = "rpds_py-0.25.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:5f048bbf18b1f9120685c6d6bb70cc1a52c8cc11bdd04e643d28d3be0baf666d"}, {file = "rpds_py-0.25.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4fbb0dbba559959fcb5d0735a0f87cdbca9e95dac87982e9b95c0f8f7ad10255"}, {file = "rpds_py-0.25.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4ca54b9cf9d80b4016a67a0193ebe0bcf29f6b0a96f09db942087e294d3d4c2"}, {file = "rpds_py-0.25.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1ee3e26eb83d39b886d2cb6e06ea701bba82ef30a0de044d34626ede51ec98b0"}, {file = "rpds_py-0.25.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:89706d0683c73a26f76a5315d893c051324d771196ae8b13e6ffa1ffaf5e574f"}, {file = "rpds_py-0.25.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c2013ee878c76269c7b557a9a9c042335d732e89d482606990b70a839635feb7"}, {file = "rpds_py-0.25.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45e484db65e5380804afbec784522de84fa95e6bb92ef1bd3325d33d13efaebd"}, {file = "rpds_py-0.25.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:48d64155d02127c249695abb87d39f0faf410733428d499867606be138161d65"}, {file = "rpds_py-0.25.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:048893e902132fd6548a2e661fb38bf4896a89eea95ac5816cf443524a85556f"}, {file = "rpds_py-0.25.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0317177b1e8691ab5879f4f33f4b6dc55ad3b344399e23df2e499de7b10a548d"}, {file = "rpds_py-0.25.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bffcf57826d77a4151962bf1701374e0fc87f536e56ec46f1abdd6a903354042"}, {file = "rpds_py-0.25.1-cp311-cp311-win32.whl", hash = "sha256:cda776f1967cb304816173b30994faaf2fd5bcb37e73118a47964a02c348e1bc"}, {file = "rpds_py-0.25.1-cp311-cp311-win_amd64.whl", hash = "sha256:dc3c1ff0abc91444cd20ec643d0f805df9a3661fcacf9c95000329f3ddf268a4"}, {file = "rpds_py-0.25.1-cp311-cp311-win_arm64.whl", hash = "sha256:5a3ddb74b0985c4387719fc536faced33cadf2172769540c62e2a94b7b9be1c4"}, {file = "rpds_py-0.25.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:b5ffe453cde61f73fea9430223c81d29e2fbf412a6073951102146c84e19e34c"}, {file = "rpds_py-0.25.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:115874ae5e2fdcfc16b2aedc95b5eef4aebe91b28e7e21951eda8a5dc0d3461b"}, {file = "rpds_py-0.25.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a714bf6e5e81b0e570d01f56e0c89c6375101b8463999ead3a93a5d2a4af91fa"}, {file = "rpds_py-0.25.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:35634369325906bcd01577da4c19e3b9541a15e99f31e91a02d010816b49bfda"}, {file = "rpds_py-0.25.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d4cb2b3ddc16710548801c6fcc0cfcdeeff9dafbc983f77265877793f2660309"}, {file = "rpds_py-0.25.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9ceca1cf097ed77e1a51f1dbc8d174d10cb5931c188a4505ff9f3e119dfe519b"}, {file = "rpds_py-0.25.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c2cd1a4b0c2b8c5e31ffff50d09f39906fe351389ba143c195566056c13a7ea"}, {file = "rpds_py-0.25.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1de336a4b164c9188cb23f3703adb74a7623ab32d20090d0e9bf499a2203ad65"}, {file = "rpds_py-0.25.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9fca84a15333e925dd59ce01da0ffe2ffe0d6e5d29a9eeba2148916d1824948c"}, {file = "rpds_py-0.25.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:88ec04afe0c59fa64e2f6ea0dd9657e04fc83e38de90f6de201954b4d4eb59bd"}, {file = "rpds_py-0.25.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a8bd2f19e312ce3e1d2c635618e8a8d8132892bb746a7cf74780a489f0f6cdcb"}, {file = "rpds_py-0.25.1-cp312-cp312-win32.whl", hash = "sha256:e5e2f7280d8d0d3ef06f3ec1b4fd598d386cc6f0721e54f09109a8132182fbfe"}, {file = "rpds_py-0.25.1-cp312-cp312-win_amd64.whl", hash = "sha256:db58483f71c5db67d643857404da360dce3573031586034b7d59f245144cc192"}, {file = "rpds_py-0.25.1-cp312-cp312-win_arm64.whl", hash = "sha256:6d50841c425d16faf3206ddbba44c21aa3310a0cebc3c1cdfc3e3f4f9f6f5728"}, {file = "rpds_py-0.25.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:659d87430a8c8c704d52d094f5ba6fa72ef13b4d385b7e542a08fc240cb4a559"}, {file = "rpds_py-0.25.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:68f6f060f0bbdfb0245267da014d3a6da9be127fe3e8cc4a68c6f833f8a23bb1"}, {file = "rpds_py-0.25.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:083a9513a33e0b92cf6e7a6366036c6bb43ea595332c1ab5c8ae329e4bcc0a9c"}, {file = "rpds_py-0.25.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:816568614ecb22b18a010c7a12559c19f6fe993526af88e95a76d5a60b8b75fb"}, {file = "rpds_py-0.25.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3c6564c0947a7f52e4792983f8e6cf9bac140438ebf81f527a21d944f2fd0a40"}, {file = "rpds_py-0.25.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c4a128527fe415d73cf1f70a9a688d06130d5810be69f3b553bf7b45e8acf79"}, {file = "rpds_py-0.25.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a49e1d7a4978ed554f095430b89ecc23f42014a50ac385eb0c4d163ce213c325"}, {file = "rpds_py-0.25.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d74ec9bc0e2feb81d3f16946b005748119c0f52a153f6db6a29e8cd68636f295"}, {file = "rpds_py-0.25.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3af5b4cc10fa41e5bc64e5c198a1b2d2864337f8fcbb9a67e747e34002ce812b"}, {file = "rpds_py-0.25.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:79dc317a5f1c51fd9c6a0c4f48209c6b8526d0524a6904fc1076476e79b00f98"}, {file = "rpds_py-0.25.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1521031351865e0181bc585147624d66b3b00a84109b57fcb7a779c3ec3772cd"}, {file = "rpds_py-0.25.1-cp313-cp313-win32.whl", hash = "sha256:5d473be2b13600b93a5675d78f59e63b51b1ba2d0476893415dfbb5477e65b31"}, {file = "rpds_py-0.25.1-cp313-cp313-win_amd64.whl", hash = "sha256:a7b74e92a3b212390bdce1d93da9f6488c3878c1d434c5e751cbc202c5e09500"}, {file = "rpds_py-0.25.1-cp313-cp313-win_arm64.whl", hash = "sha256:dd326a81afe332ede08eb39ab75b301d5676802cdffd3a8f287a5f0b694dc3f5"}, {file = "rpds_py-0.25.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:a58d1ed49a94d4183483a3ce0af22f20318d4a1434acee255d683ad90bf78129"}, {file = "rpds_py-0.25.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f251bf23deb8332823aef1da169d5d89fa84c89f67bdfb566c49dea1fccfd50d"}, {file = "rpds_py-0.25.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8dbd586bfa270c1103ece2109314dd423df1fa3d9719928b5d09e4840cec0d72"}, {file = "rpds_py-0.25.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6d273f136e912aa101a9274c3145dcbddbe4bac560e77e6d5b3c9f6e0ed06d34"}, {file = "rpds_py-0.25.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:666fa7b1bd0a3810a7f18f6d3a25ccd8866291fbbc3c9b912b917a6715874bb9"}, {file = "rpds_py-0.25.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:921954d7fbf3fccc7de8f717799304b14b6d9a45bbeec5a8d7408ccbf531faf5"}, {file = "rpds_py-0.25.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3d86373ff19ca0441ebeb696ef64cb58b8b5cbacffcda5a0ec2f3911732a194"}, {file = "rpds_py-0.25.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c8980cde3bb8575e7c956a530f2c217c1d6aac453474bf3ea0f9c89868b531b6"}, {file = "rpds_py-0.25.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:8eb8c84ecea987a2523e057c0d950bcb3f789696c0499290b8d7b3107a719d78"}, {file = "rpds_py-0.25.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:e43a005671a9ed5a650f3bc39e4dbccd6d4326b24fb5ea8be5f3a43a6f576c72"}, {file = "rpds_py-0.25.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:58f77c60956501a4a627749a6dcb78dac522f249dd96b5c9f1c6af29bfacfb66"}, {file = "rpds_py-0.25.1-cp313-cp313t-win32.whl", hash = "sha256:2cb9e5b5e26fc02c8a4345048cd9998c2aca7c2712bd1b36da0c72ee969a3523"}, {file = "rpds_py-0.25.1-cp313-cp313t-win_amd64.whl", hash = "sha256:401ca1c4a20cc0510d3435d89c069fe0a9ae2ee6495135ac46bdd49ec0495763"}, {file = "rpds_py-0.25.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:ce4c8e485a3c59593f1a6f683cf0ea5ab1c1dc94d11eea5619e4fb5228b40fbd"}, {file = "rpds_py-0.25.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d8222acdb51a22929c3b2ddb236b69c59c72af4019d2cba961e2f9add9b6e634"}, {file = "rpds_py-0.25.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4593c4eae9b27d22df41cde518b4b9e4464d139e4322e2127daa9b5b981b76be"}, {file = "rpds_py-0.25.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bd035756830c712b64725a76327ce80e82ed12ebab361d3a1cdc0f51ea21acb0"}, {file = "rpds_py-0.25.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:114a07e85f32b125404f28f2ed0ba431685151c037a26032b213c882f26eb908"}, {file = "rpds_py-0.25.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dec21e02e6cc932538b5203d3a8bd6aa1480c98c4914cb88eea064ecdbc6396a"}, {file = "rpds_py-0.25.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:09eab132f41bf792c7a0ea1578e55df3f3e7f61888e340779b06050a9a3f16e9"}, {file = "rpds_py-0.25.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c98f126c4fc697b84c423e387337d5b07e4a61e9feac494362a59fd7a2d9ed80"}, {file = "rpds_py-0.25.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0e6a327af8ebf6baba1c10fadd04964c1965d375d318f4435d5f3f9651550f4a"}, {file = "rpds_py-0.25.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:bc120d1132cff853ff617754196d0ac0ae63befe7c8498bd67731ba368abe451"}, {file = "rpds_py-0.25.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:140f61d9bed7839446bdd44852e30195c8e520f81329b4201ceead4d64eb3a9f"}, {file = "rpds_py-0.25.1-cp39-cp39-win32.whl", hash = "sha256:9c006f3aadeda131b438c3092124bd196b66312f0caa5823ef09585a669cf449"}, {file = "rpds_py-0.25.1-cp39-cp39-win_amd64.whl", hash = "sha256:a61d0b2c7c9a0ae45732a77844917b427ff16ad5464b4d4f5e4adb955f582890"}, {file = "rpds_py-0.25.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b24bf3cd93d5b6ecfbedec73b15f143596c88ee249fa98cefa9a9dc9d92c6f28"}, {file = "rpds_py-0.25.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:0eb90e94f43e5085623932b68840b6f379f26db7b5c2e6bcef3179bd83c9330f"}, {file = "rpds_py-0.25.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d50e4864498a9ab639d6d8854b25e80642bd362ff104312d9770b05d66e5fb13"}, {file = "rpds_py-0.25.1-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7c9409b47ba0650544b0bb3c188243b83654dfe55dcc173a86832314e1a6a35d"}, {file = "rpds_py-0.25.1-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:796ad874c89127c91970652a4ee8b00d56368b7e00d3477f4415fe78164c8000"}, {file = "rpds_py-0.25.1-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:85608eb70a659bf4c1142b2781083d4b7c0c4e2c90eff11856a9754e965b2540"}, {file = "rpds_py-0.25.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4feb9211d15d9160bc85fa72fed46432cdc143eb9cf6d5ca377335a921ac37b"}, {file = "rpds_py-0.25.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ccfa689b9246c48947d31dd9d8b16d89a0ecc8e0e26ea5253068efb6c542b76e"}, {file = "rpds_py-0.25.1-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:3c5b317ecbd8226887994852e85de562f7177add602514d4ac40f87de3ae45a8"}, {file = "rpds_py-0.25.1-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:454601988aab2c6e8fd49e7634c65476b2b919647626208e376afcd22019eeb8"}, {file = "rpds_py-0.25.1-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:1c0c434a53714358532d13539272db75a5ed9df75a4a090a753ac7173ec14e11"}, {file = "rpds_py-0.25.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:f73ce1512e04fbe2bc97836e89830d6b4314c171587a99688082d090f934d20a"}, {file = "rpds_py-0.25.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ee86d81551ec68a5c25373c5643d343150cc54672b5e9a0cafc93c1870a53954"}, {file = "rpds_py-0.25.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:89c24300cd4a8e4a51e55c31a8ff3918e6651b241ee8876a42cc2b2a078533ba"}, {file = "rpds_py-0.25.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:771c16060ff4e79584dc48902a91ba79fd93eade3aa3a12d6d2a4aadaf7d542b"}, {file = "rpds_py-0.25.1-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:785ffacd0ee61c3e60bdfde93baa6d7c10d86f15655bd706c89da08068dc5038"}, {file = "rpds_py-0.25.1-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2a40046a529cc15cef88ac5ab589f83f739e2d332cb4d7399072242400ed68c9"}, {file = "rpds_py-0.25.1-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:85fc223d9c76cabe5d0bff82214459189720dc135db45f9f66aa7cffbf9ff6c1"}, {file = "rpds_py-0.25.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b0be9965f93c222fb9b4cc254235b3b2b215796c03ef5ee64f995b1b69af0762"}, {file = "rpds_py-0.25.1-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8378fa4a940f3fb509c081e06cb7f7f2adae8cf46ef258b0e0ed7519facd573e"}, {file = "rpds_py-0.25.1-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:33358883a4490287e67a2c391dfaea4d9359860281db3292b6886bf0be3d8692"}, {file = "rpds_py-0.25.1-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:1d1fadd539298e70cac2f2cb36f5b8a65f742b9b9f1014dd4ea1f7785e2470bf"}, {file = "rpds_py-0.25.1-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:9a46c2fb2545e21181445515960006e85d22025bd2fe6db23e76daec6eb689fe"}, {file = "rpds_py-0.25.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:50f2c501a89c9a5f4e454b126193c5495b9fb441a75b298c60591d8a2eb92e1b"}, {file = "rpds_py-0.25.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:7d779b325cc8238227c47fbc53964c8cc9a941d5dbae87aa007a1f08f2f77b23"}, {file = "rpds_py-0.25.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:036ded36bedb727beeabc16dc1dad7cb154b3fa444e936a03b67a86dc6a5066e"}, {file = "rpds_py-0.25.1-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:245550f5a1ac98504147cba96ffec8fabc22b610742e9150138e5d60774686d7"}, {file = "rpds_py-0.25.1-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ff7c23ba0a88cb7b104281a99476cccadf29de2a0ef5ce864959a52675b1ca83"}, {file = "rpds_py-0.25.1-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e37caa8cdb3b7cf24786451a0bdb853f6347b8b92005eeb64225ae1db54d1c2b"}, {file = "rpds_py-0.25.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f2f48ab00181600ee266a095fe815134eb456163f7d6699f525dee471f312cf"}, {file = "rpds_py-0.25.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9e5fc7484fa7dce57e25063b0ec9638ff02a908304f861d81ea49273e43838c1"}, {file = "rpds_py-0.25.1-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:d3c10228d6cf6fe2b63d2e7985e94f6916fa46940df46b70449e9ff9297bd3d1"}, {file = "rpds_py-0.25.1-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:5d9e40f32745db28c1ef7aad23f6fc458dc1e29945bd6781060f0d15628b8ddf"}, {file = "rpds_py-0.25.1-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:35a8d1a24b5936b35c5003313bc177403d8bdef0f8b24f28b1c4a255f94ea992"}, {file = "rpds_py-0.25.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:6099263f526efff9cf3883dfef505518730f7a7a93049b1d90d42e50a22b4793"}, {file = "rpds_py-0.25.1.tar.gz", hash = "sha256:8960b6dac09b62dac26e75d7e2c4a22efb835d827a7278c34f72b2b84fa160e3"}, ] [[package]] name = "schema" version = "0.7.7" description = "Simple data validation library" optional = false python-versions = "*" groups = ["dev"] files = [ {file = "schema-0.7.7-py2.py3-none-any.whl", hash = "sha256:5d976a5b50f36e74e2157b47097b60002bd4d42e65425fcc9c9befadb4255dde"}, {file = "schema-0.7.7.tar.gz", hash = "sha256:7da553abd2958a19dc2547c388cde53398b39196175a9be59ea1caf5ab0a1807"}, ] [[package]] name = "stack-data" version = "0.6.3" description = "Extract data from python stack frames and tracebacks for informative displays" optional = false python-versions = "*" groups = ["dev"] files = [ {file = "stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695"}, {file = "stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9"}, ] [package.dependencies] asttokens = ">=2.1.0" executing = ">=1.2.0" pure-eval = "*" [package.extras] tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] [[package]] name = "tabulate" version = "0.9.0" description = "Pretty-print tabular data" optional = false python-versions = ">=3.7" groups = ["dev"] files = [ {file = "tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f"}, {file = "tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c"}, ] [package.extras] widechars = ["wcwidth"] [[package]] name = "tbump" version = "6.11.0" description = "Bump software releases" optional = false python-versions = ">=3.7,<4.0" groups = ["dev"] files = [ {file = "tbump-6.11.0-py3-none-any.whl", hash = "sha256:6b181fe6f3ae84ce0b9af8cc2009a8bca41ded34e73f623a7413b9684f1b4526"}, {file = "tbump-6.11.0.tar.gz", hash = "sha256:385e710eedf0a8a6ff959cf1e9f3cfd17c873617132fc0ec5f629af0c355c870"}, ] [package.dependencies] cli-ui = ">=0.10.3" docopt = ">=0.6.2,<0.7.0" schema = ">=0.7.1,<0.8.0" tomlkit = ">=0.11,<0.12" [[package]] name = "tomli" version = "2.2.1" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" groups = ["dev"] markers = "python_full_version <= \"3.11.0a6\"" files = [ {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"}, {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"}, {file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"}, {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"}, {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"}, {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"}, {file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"}, {file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"}, {file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"}, {file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"}, {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"}, {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"}, {file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"}, {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"}, {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"}, {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"}, {file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"}, {file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"}, {file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"}, {file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"}, {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"}, {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"}, {file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"}, {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"}, {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"}, {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"}, {file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"}, {file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"}, {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, ] [[package]] name = "tomlkit" version = "0.11.8" description = "Style preserving TOML library" optional = false python-versions = ">=3.7" groups = ["dev"] files = [ {file = "tomlkit-0.11.8-py3-none-any.whl", hash = "sha256:8c726c4c202bdb148667835f68d68780b9a003a9ec34167b6c673b38eff2a171"}, {file = "tomlkit-0.11.8.tar.gz", hash = "sha256:9330fc7faa1db67b541b28e62018c17d20be733177d290a13b24c62d1614e0c3"}, ] [[package]] name = "traitlets" version = "5.14.3" description = "Traitlets Python configuration system" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ {file = "traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f"}, {file = "traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7"}, ] [package.extras] docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0,<8.2)", "pytest-mock", "pytest-mypy-testing"] [[package]] name = "types-pyyaml" version = "6.0.12.20250516" description = "Typing stubs for PyYAML" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ {file = "types_pyyaml-6.0.12.20250516-py3-none-any.whl", hash = "sha256:8478208feaeb53a34cb5d970c56a7cd76b72659442e733e268a94dc72b2d0530"}, {file = "types_pyyaml-6.0.12.20250516.tar.gz", hash = "sha256:9f21a70216fc0fa1b216a8176db5f9e0af6eb35d2f2932acb87689d03a5bf6ba"}, ] [[package]] name = "types-requests" version = "2.32.4.20250913" description = "Typing stubs for requests" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ {file = "types_requests-2.32.4.20250913-py3-none-any.whl", hash = "sha256:78c9c1fffebbe0fa487a418e0fa5252017e9c60d1a2da394077f1780f655d7e1"}, {file = "types_requests-2.32.4.20250913.tar.gz", hash = "sha256:abd6d4f9ce3a9383f269775a9835a4c24e5cd6b9f647d64f88aa4613c33def5d"}, ] [package.dependencies] urllib3 = ">=2" [[package]] name = "typing-extensions" version = "4.15.0" description = "Backported and Experimental Type Hints for Python 3.9+" optional = false python-versions = ">=3.9" groups = ["main", "dev"] files = [ {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, ] markers = {main = "python_version < \"3.13\""} [[package]] name = "unidecode" version = "1.4.0" description = "ASCII transliterations of Unicode text" optional = false python-versions = ">=3.7" groups = ["dev"] files = [ {file = "Unidecode-1.4.0-py3-none-any.whl", hash = "sha256:c3c7606c27503ad8d501270406e345ddb480a7b5f38827eafe4fa82a137f0021"}, {file = "Unidecode-1.4.0.tar.gz", hash = "sha256:ce35985008338b676573023acc382d62c264f307c8f7963733405add37ea2b23"}, ] [[package]] name = "urllib3" version = "2.6.3" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.9" groups = ["main", "dev"] files = [ {file = "urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4"}, {file = "urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed"}, ] markers = {main = "extra == \"requests\""} [package.extras] brotli = ["brotli (>=1.2.0) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=1.2.0.0) ; platform_python_implementation != \"CPython\""] h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["backports-zstd (>=1.0.0) ; python_version < \"3.14\""] [[package]] name = "virtualenv" version = "20.36.1" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ {file = "virtualenv-20.36.1-py3-none-any.whl", hash = "sha256:575a8d6b124ef88f6f51d56d656132389f961062a9177016a50e4f507bbcc19f"}, {file = "virtualenv-20.36.1.tar.gz", hash = "sha256:8befb5c81842c641f8ee658481e42641c68b5eab3521d8e092d18320902466ba"}, ] [package.dependencies] distlib = ">=0.3.7,<1" filelock = {version = ">=3.20.1,<4", markers = "python_version >= \"3.10\""} platformdirs = ">=3.9.1,<5" typing-extensions = {version = ">=4.13.2", markers = "python_version < \"3.11\""} [package.extras] docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8) ; platform_python_implementation == \"PyPy\" or platform_python_implementation == \"GraalVM\" or platform_python_implementation == \"CPython\" and sys_platform == \"win32\" and python_version >= \"3.13\"", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10) ; platform_python_implementation == \"CPython\""] [[package]] name = "wcwidth" version = "0.2.13" description = "Measures the displayed width of unicode strings in a terminal" optional = false python-versions = "*" groups = ["dev"] files = [ {file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"}, {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"}, ] [extras] requests = ["requests"] [metadata] lock-version = "2.1" python-versions = ">=3.10,<4.0.0" content-hash = "7827ad3deef0b8ae31b769b44e6d894dbcae39b518d70490fec10884b7965bde" p1c2u-jsonschema-path-d08c0e1/pyproject.toml000066400000000000000000000046201515153005400210510ustar00rootroot00000000000000[build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" [tool.coverage.run] branch = true source =["jsonschema_path"] [tool.coverage.xml] output = "reports/coverage.xml" [tool.mypy] files = "jsonschema_path" strict = true [[tool.mypy.overrides]] module = "jsonschema_specifications" ignore_missing_imports = true [tool.poetry] name = "jsonschema-path" version = "0.4.5" description = "JSONSchema Spec with object-oriented paths" authors = ["Artur Maciag "] license = "Apache-2.0" readme = "README.rst" repository = "https://github.com/p1c2u/jsonschema-path" keywords = ["jsonschema", "swagger", "spec"] classifiers = [ "Development Status :: 4 - Beta", "Intended Audience :: Developers", "Topic :: Software Development :: Libraries :: Python Modules", "Operating System :: OS Independent", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", "Topic :: Software Development :: Libraries", ] [tool.poetry.dependencies] pathable = "^0.5.0" python = ">=3.10,<4.0.0" PyYAML = ">=5.1" requests = {version = "^2.31.0", optional = true} referencing = "<0.38.0" [tool.poetry.group.dev.dependencies] tbump = "^6.11.0" pre-commit = "*" pytest = "^8.2.1" pytest-flake8 = "=1.3.0" pytest-cov = ">=5,<7" isort = ">=5.13.2,<9.0.0" black = ">=25.1,<27.0" flynt = "1.0.6" mypy = "^1.14.1" types-PyYAML = "^6.0.12" types-requests = "^2.31.0" responses = ">=0.25,<0.27" deptry = ">=0.19.1,<0.24.0" pyflakes = ">=2.5,<4.0" ipdb = "^0.13.13" [tool.poetry.extras] requests = ["requests"] [tool.pytest.ini_options] addopts = """ --capture=no --verbose --showlocals --junitxml=reports/junit.xml --cov=jsonschema_path --cov-report=term-missing --cov-report=xml """ [tool.black] line-length = 79 [tool.isort] profile = "black" line_length = 79 force_single_line = true [tool.tbump] [tool.tbump.git] message_template = "Version {new_version}" tag_template = "{new_version}" [tool.tbump.version] current = "0.4.5" regex = ''' (?P\d+) \. (?P\d+) \. (?P\d+) (?P[a-z]+\d+)? ''' [[tool.tbump.file]] src = "jsonschema_path/__init__.py" [[tool.tbump.file]] src = "pyproject.toml" search = 'version = "{current_version}"' p1c2u-jsonschema-path-d08c0e1/tests/000077500000000000000000000000001515153005400172755ustar00rootroot00000000000000p1c2u-jsonschema-path-d08c0e1/tests/__init__.py000066400000000000000000000002551515153005400214100ustar00rootroot00000000000000"""Test package. This exists so benchmarks can be executed as modules, e.g.: python -m tests.benchmarks.bench_parse Pytest works fine with tests being a package. """ p1c2u-jsonschema-path-d08c0e1/tests/benchmarks/000077500000000000000000000000001515153005400214125ustar00rootroot00000000000000p1c2u-jsonschema-path-d08c0e1/tests/benchmarks/__init__.py000066400000000000000000000000321515153005400235160ustar00rootroot00000000000000"""Benchmarks package.""" p1c2u-jsonschema-path-d08c0e1/tests/benchmarks/bench_lookup.py000066400000000000000000000173751515153005400244510ustar00rootroot00000000000000"""Benchmarks for SchemaPath / SchemaAccessor hot paths. Focus areas: - deep traversal without refs - ref resolution cost (local #/$defs/...) - membership / keys / iteration on large mappings - SchemaPath.open() cache-hit behavior (cached resolved) """ import argparse from collections.abc import Iterable from typing import Any from jsonschema_path.paths import SchemaPath try: # Prefer module execution: `python -m tests.benchmarks.bench_lookup ...` from .bench_utils import BenchmarkResult from .bench_utils import add_common_args from .bench_utils import default_meta from .bench_utils import results_to_json from .bench_utils import run_benchmark from .bench_utils import safe_nonnegative_int_env from .bench_utils import write_json except ImportError: # pragma: no cover # Allow direct execution: `python tests/benchmarks/bench_lookup.py ...` from bench_utils import BenchmarkResult # type: ignore[no-redef] from bench_utils import add_common_args # type: ignore[no-redef] from bench_utils import default_meta # type: ignore[no-redef] from bench_utils import results_to_json # type: ignore[no-redef] from bench_utils import run_benchmark # type: ignore[no-redef] from bench_utils import safe_nonnegative_int_env # type: ignore[no-redef] from bench_utils import write_json # type: ignore[no-redef] def _build_deep_tree(depth: int) -> dict[str, Any]: node: dict[str, Any] = {"value": 1} for i in range(depth - 1, -1, -1): node = {f"k{i}": node} return node def _deep_keys(depth: int) -> tuple[str, ...]: return tuple(f"k{i}" for i in range(depth)) def _make_deep_path(root: SchemaPath, depth: int) -> SchemaPath: p = root for k in _deep_keys(depth): p = p / k return p def _build_mapping(size: int) -> dict[str, int]: return {f"k{i}": i for i in range(size)} def _schema_with_sibling_prefix(depth: int, width: int) -> dict[str, Any]: schemas: dict[str, Any] = {} for i in range(width): schemas[f"N{i}"] = {"value": i} node: dict[str, Any] = {"schemas": schemas} for i in range(depth - 1, -1, -1): node = {f"k{i}": node} return node def _schema_with_local_ref(depth: int) -> dict[str, Any]: # Root contains an object whose value is a $ref to local $defs. target = _build_deep_tree(depth) return { "$defs": {"Target": target}, "root": {"$ref": "#/$defs/Target"}, } def main(argv: Iterable[str] | None = None) -> int: parser = argparse.ArgumentParser() add_common_args(parser) args = parser.parse_args(list(argv) if argv is not None else None) repeats: int = args.repeats warmup_loops: int = args.warmup_loops resolved_cache_maxsize = safe_nonnegative_int_env( "JSONSCHEMA_PATH_BENCH_RESOLVED_CACHE_MAXSIZE" ) results: list[BenchmarkResult] = [] depth = 25 if not args.quick else 10 loops_read = 120_000 if not args.quick else 15_000 # --- Deep traversal without $ref --- plain_schema = _build_deep_tree(depth) plain_root = SchemaPath.from_dict( plain_schema, resolved_cache_maxsize=resolved_cache_maxsize, ) plain_deep = _make_deep_path(plain_root, depth) results.append( run_benchmark( f"schema.read_value.plain.depth{depth}", plain_deep.read_value, loops=loops_read, repeats=repeats, warmup_loops=warmup_loops, ) ) def open_plain_deep() -> None: with plain_deep.open() as _: return # SchemaPath.open() uses a cached resolved object per-path instance. results.append( run_benchmark( f"schema.open.cache_hit.plain.depth{depth}", open_plain_deep, loops=loops_read, repeats=repeats, warmup_loops=warmup_loops, ) ) # --- Deep traversal with a local $ref --- ref_schema = _schema_with_local_ref(depth) ref_root = SchemaPath.from_dict( ref_schema, resolved_cache_maxsize=resolved_cache_maxsize, ) ref_deep = _make_deep_path(ref_root / "root", depth) results.append( run_benchmark( f"schema.read_value.local_ref.depth{depth}", ref_deep.read_value, loops=loops_read, repeats=repeats, warmup_loops=warmup_loops, ) ) def open_ref_deep() -> None: with ref_deep.open() as _: return results.append( run_benchmark( f"schema.open.cache_hit.local_ref.depth{depth}", open_ref_deep, loops=loops_read, repeats=repeats, warmup_loops=warmup_loops, ) ) # --- Sibling paths sharing long prefix --- sibling_depth = 10 if not args.quick else 5 sibling_width = 64 if not args.quick else 16 sibling_loops = 5_000 if not args.quick else 800 sibling_schema = _schema_with_sibling_prefix(sibling_depth, sibling_width) sibling_root = SchemaPath.from_dict( sibling_schema, resolved_cache_maxsize=resolved_cache_maxsize, ) sibling_prefix = _make_deep_path(sibling_root, sibling_depth) / "schemas" sibling_paths = tuple( sibling_prefix / f"N{i}" / "value" for i in range(sibling_width) ) def read_sibling_batch( _paths: tuple[SchemaPath, ...] = sibling_paths, ) -> None: for _p in _paths: _ = _p.read_value() results.append( run_benchmark( ( "schema.read_value.sibling_prefix" f".depth{sibling_depth}.width{sibling_width}" ), read_sibling_batch, loops=sibling_loops, repeats=repeats, warmup_loops=warmup_loops, ) ) # --- Large mapping operations (no filesystem I/O) --- sizes = [10, 1_000, 50_000] if not args.quick else [10, 1_000] for size in sizes: mapping = _build_mapping(size) p = ( SchemaPath.from_dict( {"root": mapping}, resolved_cache_maxsize=resolved_cache_maxsize, ) / "root" ) loops_keys = 5_000 if size <= 1_000 else 200 if args.quick: loops_keys = min(loops_keys, 500) results.append( run_benchmark( f"schema.keys.mapping.size{size}", p.keys, loops=loops_keys, repeats=repeats, warmup_loops=warmup_loops, ) ) probe_key = f"k{size - 1}" if size else "k0" loops_contains = 40_000 if size <= 1_000 else 2_000 if args.quick: loops_contains = min(loops_contains, 5_000) def contains_probe(_p: SchemaPath = p, _key: str = probe_key) -> None: _ = _key in _p results.append( run_benchmark( f"schema.contains.mapping.size{size}", contains_probe, loops=loops_contains, repeats=repeats, warmup_loops=warmup_loops, ) ) loops_iter = 500 if size <= 1_000 else 3 if args.quick: loops_iter = min(loops_iter, 50) def iter_children(_p: SchemaPath = p) -> None: for _ in _p: pass results.append( run_benchmark( f"schema.iter_children.mapping.size{size}", iter_children, loops=loops_iter, repeats=repeats, warmup_loops=warmup_loops, ) ) meta = default_meta() meta["resolved_cache_maxsize"] = resolved_cache_maxsize payload = results_to_json(results=results, meta=meta) write_json(args.output, payload) return 0 if __name__ == "__main__": raise SystemExit(main()) p1c2u-jsonschema-path-d08c0e1/tests/benchmarks/bench_parse.py000066400000000000000000000077011515153005400242420ustar00rootroot00000000000000"""Benchmarks for parsing and SchemaPath construction.""" import argparse from collections.abc import Iterable from typing import Any from pathable import parsers from jsonschema_path.accessors import SchemaAccessor from jsonschema_path.paths import SPEC_SEPARATOR from jsonschema_path.paths import SchemaPath try: # Prefer module execution: `python -m tests.benchmarks.bench_parse ...` from .bench_utils import BenchmarkResult from .bench_utils import add_common_args from .bench_utils import default_meta from .bench_utils import results_to_json from .bench_utils import run_benchmark from .bench_utils import safe_nonnegative_int_env from .bench_utils import write_json except ImportError: # pragma: no cover # Allow direct execution: `python tests/benchmarks/bench_parse.py ...` from bench_utils import BenchmarkResult # type: ignore[no-redef] from bench_utils import add_common_args # type: ignore[no-redef] from bench_utils import default_meta # type: ignore[no-redef] from bench_utils import results_to_json # type: ignore[no-redef] from bench_utils import run_benchmark # type: ignore[no-redef] from bench_utils import safe_nonnegative_int_env # type: ignore[no-redef] from bench_utils import write_json # type: ignore[no-redef] def _build_args(n: int, *, sep: str) -> list[object]: out: list[object] = [] for i in range(n): if i % 11 == 0: out.append(".") elif i % 11 == 2: out.append(i) elif i % 11 == 3: out.append(f"a{sep}{i}{sep}b") else: out.append(f"seg{i}") return out def main(argv: Iterable[str] | None = None) -> int: parser = argparse.ArgumentParser() add_common_args(parser) args = parser.parse_args(list(argv) if argv is not None else None) repeats: int = args.repeats warmup_loops: int = args.warmup_loops resolved_cache_maxsize = safe_nonnegative_int_env( "JSONSCHEMA_PATH_BENCH_RESOLVED_CACHE_MAXSIZE" ) # Keep accessor construction out of the hot loop. accessor = SchemaAccessor.from_schema( {"type": "object"}, resolved_cache_maxsize=resolved_cache_maxsize, ) sep: str = SPEC_SEPARATOR results: list[BenchmarkResult] = [] sizes = [10, 100, 1_000] if not args.quick else [10, 100] for n in sizes: inputs = _build_args(n, sep=sep) inputs_t = tuple(inputs) # Note: older pathable versions (e.g. 0.5.0b2) do not expose # BasePath._parse_args publicly. We benchmark `parse_parts` (core of # segment splitting/filtering) and then the full SchemaPath constructor. loops_parse_parts = 80_000 if n <= 100 else 10_000 if args.quick: loops_parse_parts = min(loops_parse_parts, 10_000) def do_parse_parts(_inputs: tuple[Any, ...] = inputs_t) -> None: parsers.parse_parts(_inputs, sep=sep) results.append( run_benchmark( f"parse.parse_parts.size{n}", do_parse_parts, loops=loops_parse_parts, repeats=repeats, warmup_loops=warmup_loops, ) ) loops_constructor = 60_000 if n <= 100 else 3_000 if args.quick: loops_constructor = min(loops_constructor, 5_000) def do_constructor(_inputs: tuple[Any, ...] = inputs_t) -> None: SchemaPath(accessor, *_inputs, separator=sep) results.append( run_benchmark( f"paths.SchemaPath.constructor.size{n}", do_constructor, loops=loops_constructor, repeats=repeats, warmup_loops=warmup_loops, ) ) meta = default_meta() meta["resolved_cache_maxsize"] = resolved_cache_maxsize payload = results_to_json(results=results, meta=meta) write_json(args.output, payload) return 0 if __name__ == "__main__": raise SystemExit(main()) p1c2u-jsonschema-path-d08c0e1/tests/benchmarks/bench_utils.py000066400000000000000000000113151515153005400242640ustar00rootroot00000000000000"""Minimal benchmark utilities (dependency-free). Mirrors the lightweight benchmark harness used in the `pathable` repo. """ import argparse import json import os import platform import statistics import sys import time from collections.abc import Callable from collections.abc import Iterable from collections.abc import Mapping from collections.abc import MutableMapping from dataclasses import dataclass from typing import Any @dataclass(frozen=True) class BenchmarkResult: name: str loops: int repeats: int warmup_loops: int times_s: tuple[float, ...] @property def total_s_median(self) -> float: return statistics.median(self.times_s) @property def per_loop_s_median(self) -> float: if self.loops <= 0: return float("inf") return self.total_s_median / self.loops @property def ops_per_sec_median(self) -> float: per = self.per_loop_s_median if per <= 0: return float("inf") return 1.0 / per def _safe_int_env(name: str) -> int | None: value = os.environ.get(name) if value is None: return None try: return int(value) except ValueError: return None def safe_nonnegative_int_env(name: str, *, default: int = 0) -> int: value = os.environ.get(name) if value is None: return default try: parsed = int(value) except ValueError as exc: raise ValueError(f"{name} must be an integer") from exc if parsed < 0: raise ValueError(f"{name} must be >= 0") return parsed def default_meta() -> dict[str, Any]: return { "python": sys.version, "python_implementation": platform.python_implementation(), "platform": platform.platform(), "machine": platform.machine(), "processor": platform.processor(), "pythondotorg": platform.python_build(), "py_hash_seed": os.environ.get("PYTHONHASHSEED"), "github_sha": os.environ.get("GITHUB_SHA"), "github_ref": os.environ.get("GITHUB_REF"), "ci": os.environ.get("CI"), } def run_benchmark( name: str, func: Callable[[], Any], *, loops: int, repeats: int = 5, warmup_loops: int = 1, ) -> BenchmarkResult: if loops <= 0: raise ValueError("loops must be > 0") if repeats <= 0: raise ValueError("repeats must be > 0") if warmup_loops < 0: raise ValueError("warmup_loops must be >= 0") for _ in range(warmup_loops): for __ in range(loops): func() times: list[float] = [] for _ in range(repeats): start = time.perf_counter() for __ in range(loops): func() end = time.perf_counter() times.append(end - start) return BenchmarkResult( name=name, loops=loops, repeats=repeats, warmup_loops=warmup_loops, times_s=tuple(times), ) def results_to_json( *, results: Iterable[BenchmarkResult], meta: Mapping[str, Any] | None = None, ) -> dict[str, Any]: out: dict[str, Any] = { "meta": dict(meta or default_meta()), "benchmarks": {}, } bench: MutableMapping[str, Any] = out["benchmarks"] for r in results: bench[r.name] = { "loops": r.loops, "repeats": r.repeats, "warmup_loops": r.warmup_loops, "times_s": list(r.times_s), "median_total_s": r.total_s_median, "median_per_loop_s": r.per_loop_s_median, "median_ops_per_sec": r.ops_per_sec_median, } return out def add_common_args(parser: argparse.ArgumentParser) -> None: parser.add_argument( "--output", required=True, help="Write JSON results to this file.", ) parser.add_argument( "--quick", action="store_true", help="Run fewer iterations for a fast sanity check.", ) repeats_env = ( _safe_int_env("JSONSCHEMA_PATH_BENCH_REPEATS") or _safe_int_env("PATHABLE_BENCH_REPEATS") or 5 ) warmup_env = ( _safe_int_env("JSONSCHEMA_PATH_BENCH_WARMUP") or _safe_int_env("PATHABLE_BENCH_WARMUP") or 1 ) parser.add_argument( "--repeats", type=int, default=repeats_env, help="Number of repeats per scenario (median is reported).", ) parser.add_argument( "--warmup-loops", type=int, default=warmup_env, help="Warmup passes before timing.", ) def write_json(path: str, payload: Mapping[str, Any]) -> None: os.makedirs(os.path.dirname(path) or ".", exist_ok=True) with open(path, "w", encoding="utf-8") as f: json.dump(payload, f, indent=2, sort_keys=True) f.write("\n") p1c2u-jsonschema-path-d08c0e1/tests/benchmarks/compare_results.py000066400000000000000000000112371515153005400251770ustar00rootroot00000000000000"""Compare two jsonschema-path benchmark JSON results. Exits non-zero if candidate regresses beyond the configured tolerance. This mirrors `pathable/tests/benchmarks/compare_results.py`. """ import argparse import json from collections.abc import Iterable from collections.abc import Mapping from dataclasses import dataclass from typing import Any from typing import cast @dataclass(frozen=True) class ScenarioComparison: name: str baseline_ops: float candidate_ops: float ratio: float baseline_scenario: str candidate_scenario: str def _canonicalize_scenario_name(name: str) -> str: """Return a stable scenario identifier across benchmark renames.""" aliases: tuple[tuple[str, str], ...] = ( # jsonschema-path bench_parse canonicalization ("parse.parse_parts.", "parse.parts."), # If we ever rename the constructor bench, keep it stable. ("paths.SchemaPath.constructor.", "paths.SchemaPath.constructor."), ) for prefix, replacement in aliases: if name.startswith(prefix): return replacement + name[len(prefix) :] return name def _load(path: str) -> Mapping[str, Any]: with open(path, encoding="utf-8") as f: data_any = json.load(f) if not isinstance(data_any, dict): raise ValueError("Invalid report: expected top-level JSON object") return cast(dict[str, Any], data_any) def _extract_ops(report: Mapping[str, Any]) -> dict[str, float]: benchmarks = report.get("benchmarks") if not isinstance(benchmarks, dict): raise ValueError("Invalid report: missing 'benchmarks' dict") benchmarks_d = cast(dict[str, Any], benchmarks) out: dict[str, float] = {} for scenario_name, payload in benchmarks_d.items(): if not isinstance(payload, dict): continue payload_d = cast(dict[str, Any], payload) ops_any = payload_d.get("median_ops_per_sec") ops = ops_any if isinstance(ops_any, (int, float)) else None if ops is not None: out[scenario_name] = float(ops) return out def compare( *, baseline: Mapping[str, Any], candidate: Mapping[str, Any], tolerance: float, ) -> tuple[list[ScenarioComparison], list[ScenarioComparison]]: if tolerance < 0: raise ValueError("tolerance must be >= 0") b_raw = _extract_ops(baseline) c_raw = _extract_ops(candidate) b: dict[str, tuple[str, float]] = {} c: dict[str, tuple[str, float]] = {} for scenario_name, ops in b_raw.items(): canon = _canonicalize_scenario_name(scenario_name) b.setdefault(canon, (scenario_name, ops)) for scenario_name, ops in c_raw.items(): canon = _canonicalize_scenario_name(scenario_name) c.setdefault(canon, (scenario_name, ops)) comparisons: list[ScenarioComparison] = [] for canon_name in sorted(set(b) & set(c)): b_name, bops = b[canon_name] c_name, cops = c[canon_name] ratio = cops / bops if bops > 0 else float("inf") comparisons.append( ScenarioComparison( name=canon_name, baseline_ops=bops, candidate_ops=cops, ratio=ratio, baseline_scenario=b_name, candidate_scenario=c_name, ) ) # Regression if candidate is slower by more than tolerance: # candidate_ops < baseline_ops * (1 - tolerance) floor_ratio = 1.0 - tolerance regressions = [x for x in comparisons if x.ratio < floor_ratio] return comparisons, regressions def main(argv: Iterable[str] | None = None) -> int: parser = argparse.ArgumentParser() parser.add_argument("--baseline", required=True) parser.add_argument("--candidate", required=True) parser.add_argument( "--tolerance", type=float, default=0.20, help="Allowed slowdown (e.g. 0.20 means 20% slower allowed).", ) args = parser.parse_args(list(argv) if argv is not None else None) baseline = _load(args.baseline) candidate = _load(args.candidate) comparisons, regressions = compare( baseline=baseline, candidate=candidate, tolerance=args.tolerance, ) print("scenario\tbaseline_ops/s\tcandidate_ops/s\tratio") for c in comparisons: print( f"{c.name}\t{c.baseline_ops:.2f}\t{c.candidate_ops:.2f}\t{c.ratio:.3f}" ) if regressions: print("\nREGRESSIONS:") for r in regressions: print( f"- {r.name}: {r.ratio:.3f}x (baseline {r.baseline_ops:.2f} ops/s, candidate {r.candidate_ops:.2f} ops/s)" ) return 1 return 0 if __name__ == "__main__": raise SystemExit(main()) p1c2u-jsonschema-path-d08c0e1/tests/conftest.py000066400000000000000000000006511515153005400214760ustar00rootroot00000000000000from json import dumps from os import unlink from tempfile import NamedTemporaryFile import pytest @pytest.fixture def create_file(): files = [] def create(schema): contents = dumps(schema).encode("utf-8") with NamedTemporaryFile(delete=False) as tf: files.append(tf) tf.write(contents) return tf.name yield create for tf in files: unlink(tf.name) p1c2u-jsonschema-path-d08c0e1/tests/integration/000077500000000000000000000000001515153005400216205ustar00rootroot00000000000000p1c2u-jsonschema-path-d08c0e1/tests/integration/conftest.py000066400000000000000000000010001515153005400240060ustar00rootroot00000000000000from os import path import pytest @pytest.fixture def data_resource_path_getter(): def get_full_path(data_file): directory = path.abspath(path.dirname(__file__)) return path.join(directory, data_file) return get_full_path @pytest.fixture(scope="session") def defs(): return { "Info": { "properties": { "version": { "type": "string", "default": "1.0", }, }, }, } p1c2u-jsonschema-path-d08c0e1/tests/integration/data/000077500000000000000000000000001515153005400225315ustar00rootroot00000000000000p1c2u-jsonschema-path-d08c0e1/tests/integration/data/v3.0/000077500000000000000000000000001515153005400232175ustar00rootroot00000000000000p1c2u-jsonschema-path-d08c0e1/tests/integration/data/v3.0/datetime.yaml000066400000000000000000000005561515153005400257050ustar00rootroot00000000000000openapi: 3.0.0 info: title: Example OpenAPI version: 0.0.1 paths: /mypath: get: parameters: - name: myparam in: query schema: format: date-time type: string example: 1997-07-16T19:20:30.45Z responses: '200': description: Success p1c2u-jsonschema-path-d08c0e1/tests/integration/data/v3.0/parent-reference/000077500000000000000000000000001515153005400264445ustar00rootroot00000000000000p1c2u-jsonschema-path-d08c0e1/tests/integration/data/v3.0/parent-reference/common.yaml000066400000000000000000000004621515153005400306220ustar00rootroot00000000000000schemas: Model: type: object required: - id properties: id: type: integer format: int32 Error: type: object required: - code - message properties: code: type: integer format: int32 message: type: stringp1c2u-jsonschema-path-d08c0e1/tests/integration/data/v3.0/parent-reference/openapi.yaml000066400000000000000000000016521515153005400307670ustar00rootroot00000000000000openapi: "3.0.0" info: version: 1.0.0 title: OpenAPI Petstore license: name: MIT servers: - url: http://petstore.swagger.io/v1 paths: /pets: get: summary: List all pets operationId: listPets tags: - pets parameters: - $ref: "recursive.yaml#/parameters/RecursiveReference" - name: limit in: query description: How many items to return at one time (max 100) required: false schema: type: integer format: int32 responses: '200': description: Expected response to a valid request content: application/json: schema: $ref: "spec/components.yaml#/schemas/Pet" default: description: unexpected error content: application/json: schema: $ref: "common.yaml#/schemas/Error" p1c2u-jsonschema-path-d08c0e1/tests/integration/data/v3.0/parent-reference/recursive.yaml000066400000000000000000000001361515153005400313370ustar00rootroot00000000000000parameters: RecursiveReference: $ref : "recursive2.yaml#/parameters/RecursiveReference" p1c2u-jsonschema-path-d08c0e1/tests/integration/data/v3.0/parent-reference/recursive2.yaml000066400000000000000000000002371515153005400314230ustar00rootroot00000000000000parameters: RecursiveReference: name: sampleParameter in: query description: Tests recursion required: false schema: type: boolean p1c2u-jsonschema-path-d08c0e1/tests/integration/data/v3.0/parent-reference/spec/000077500000000000000000000000001515153005400273765ustar00rootroot00000000000000p1c2u-jsonschema-path-d08c0e1/tests/integration/data/v3.0/parent-reference/spec/components.yaml000066400000000000000000000003061515153005400324460ustar00rootroot00000000000000schemas: Pet: type: object allOf: - $ref: "../common.yaml#/schemas/Model" required: - name properties: name: type: string tag: type: string p1c2u-jsonschema-path-d08c0e1/tests/integration/data/v3.0/petstore-separate/000077500000000000000000000000001515153005400266665ustar00rootroot00000000000000p1c2u-jsonschema-path-d08c0e1/tests/integration/data/v3.0/petstore-separate/common/000077500000000000000000000000001515153005400301565ustar00rootroot00000000000000p1c2u-jsonschema-path-d08c0e1/tests/integration/data/v3.0/petstore-separate/common/schemas/000077500000000000000000000000001515153005400316015ustar00rootroot00000000000000Error.yaml000066400000000000000000000001771515153005400335040ustar00rootroot00000000000000p1c2u-jsonschema-path-d08c0e1/tests/integration/data/v3.0/petstore-separate/common/schemastype: object required: - code - message properties: code: type: integer format: int32 message: type: stringp1c2u-jsonschema-path-d08c0e1/tests/integration/data/v3.0/petstore-separate/spec/000077500000000000000000000000001515153005400276205ustar00rootroot00000000000000p1c2u-jsonschema-path-d08c0e1/tests/integration/data/v3.0/petstore-separate/spec/openapi.yaml000066400000000000000000000037651515153005400321520ustar00rootroot00000000000000openapi: "3.0.0" info: version: 1.0.0 title: Swagger Petstore license: name: MIT servers: - url: http://petstore.swagger.io/v1 paths: /pets: get: summary: List all pets operationId: listPets tags: - pets parameters: - name: limit in: query description: How many items to return at one time (max 100) required: false schema: type: integer format: int32 responses: '200': description: An paged array of pets headers: x-next: description: A link to the next page of responses schema: type: string content: application/json: schema: $ref: "schemas/Pets.yaml" default: description: unexpected error content: application/json: schema: $ref: "../common/schemas/Error.yaml" post: summary: Create a pet operationId: createPets tags: - pets responses: '201': description: Null response default: description: unexpected error content: application/json: schema: $ref: "../common/schemas/Error.yaml" /pets/{petId}: get: summary: Info for a specific pet operationId: showPetById tags: - pets parameters: - name: petId in: path required: true description: The id of the pet to retrieve schema: type: string responses: '200': description: Expected response to a valid request content: application/json: schema: $ref: "schemas/Pets.yaml" default: description: unexpected error content: application/json: schema: $ref: "../common/schemas/Error.yaml"p1c2u-jsonschema-path-d08c0e1/tests/integration/data/v3.0/petstore-separate/spec/schemas/000077500000000000000000000000001515153005400312435ustar00rootroot00000000000000p1c2u-jsonschema-path-d08c0e1/tests/integration/data/v3.0/petstore-separate/spec/schemas/Pet.yaml000066400000000000000000000002001515153005400326470ustar00rootroot00000000000000required: - id - name properties: id: type: integer format: int64 name: type: string tag: type: stringp1c2u-jsonschema-path-d08c0e1/tests/integration/data/v3.0/petstore-separate/spec/schemas/Pets.yaml000066400000000000000000000000451515153005400330410ustar00rootroot00000000000000type: array items: $ref: "Pet.yaml"p1c2u-jsonschema-path-d08c0e1/tests/integration/test_schema_path.py000066400000000000000000000275371515153005400255230ustar00rootroot00000000000000from io import BytesIO from json import dumps from unittest import mock import responses from jsonschema_path import SchemaPath class TestSchemaPathFromDict: def test_dict(self): schema = { "properties": { "info": { "type": "object", "properties": {}, }, }, } path = SchemaPath.from_dict(schema) assert "properties" in path info_path = path / "properties" / "info" assert "properties" in info_path class TestSchemaPathFromFilePath: def test_file_path(self, data_resource_path_getter): fp = data_resource_path_getter( "data/v3.0/petstore-separate/spec/openapi.yaml" ) path = SchemaPath.from_file_path(fp) assert "paths" in path def test_file_path_relative(self): fp = "tests/integration/data/v3.0/petstore-separate/spec/openapi.yaml" path = SchemaPath.from_file_path(fp) assert "paths" in path class TestSchemaPathFromFile: def test_ref_recursive(self, data_resource_path_getter): fp = data_resource_path_getter( "data/v3.0/parent-reference/openapi.yaml" ) base_uri = "file://" + fp with open(fp) as f: path = SchemaPath.from_file(f, base_uri=base_uri) assert "paths" in path class TestSchemaPathExists: def test_existing(self): schema = { "properties": { "info": { "$ref": "#/$defs/Info", }, }, } path = SchemaPath.from_dict(schema, "properties") assert path.exists() is True def test_non_existing(self): schema = { "properties": { "info": { "$ref": "#/$defs/Info", }, }, } path = SchemaPath.from_dict(schema, "invalid") assert path.exists() is False class TestSchemaPathAsUri: def test_root(self): schema = { "properties": { "info": { "$ref": "#/$defs/Info", }, }, } path = SchemaPath.from_dict(schema) assert path.as_uri() == "#/" def test_simple(self): schema = { "properties": { "info": { "$ref": "#/$defs/Info", }, }, } path = SchemaPath.from_dict(schema, "properties", "info") assert path.as_uri() == "#/properties#info" def test_non_existing(self): schema = { "properties": { "info": { "$ref": "#/$defs/Info", }, }, } path = SchemaPath.from_dict(schema, "properties", "info", "properties") assert path.as_uri() == "#/properties#info#properties" class TestSchemaPathGetitem: def test_getitem_resolves_ref(self, defs): """Thsi tests verifies that path["key"] on a $ref node resolves correctly to the referenced node, and that the returned node is cached for subsequent access.""" schema = { "properties": { "info": { "$ref": "#/$defs/Info", }, }, "$defs": defs, } path = SchemaPath.from_dict(schema) info_ref_path = path["properties"]["info"] child_path = info_ref_path["properties"] with child_path.open() as contents: assert contents == { "version": { "type": "string", "default": "1.0", }, } def test_getitem_keys_consistent_with_ref(self, defs): """This tests verifies __getitem__ and keys() return consistent results for a $ref node, and that the keys are consistent with the referenced node.""" schema = { "properties": { "info": { "$ref": "#/$defs/Info", }, }, "$defs": defs, } path = SchemaPath.from_dict(schema) info_ref_path = path["properties"]["info"] with info_ref_path.open() as contents: keys_via_open = list(contents.keys()) keys_via_keys = list(info_ref_path.keys()) assert keys_via_open == keys_via_keys == ["properties"] def test_getitem_updates_resolver_state(self, defs): """Test that multiple __getitem__ calls properly update resolver state.""" schema = { "properties": { "info": { "$ref": "#/$defs/Info", }, }, "$defs": defs, } path = SchemaPath.from_dict(schema) info_path = path["properties"]["info"] with info_path.open() as contents: assert contents == { "properties": { "version": { "type": "string", "default": "1.0", }, }, } class TestSchemaPathOpen: def test_dict(self, defs): schema = { "properties": { "info": { "$ref": "#/$defs/Info", }, }, "$defs": defs, } path = SchemaPath.from_dict(schema) assert "properties" in path info_path = path / "properties" / "info" assert "properties" in info_path version_path = info_path / "properties" / "version" expected_contents = {"type": "string", "default": "1.0"} with version_path.resolve() as resolved, version_path.open() as contents: assert resolved.contents == expected_contents assert contents == expected_contents assert id(resolved.contents) == id(contents) def test_file(self, defs, create_file): defs_file = create_file(defs) schema = { "properties": { "info": { "$ref": f"{defs_file}#/Info", }, }, } path = SchemaPath.from_dict(schema, base_uri="file:///") assert "properties" in path info_path = path / "properties" / "info" assert "properties" in info_path version_path = info_path / "properties" / "version" expected_contents = {"type": "string", "default": "1.0"} with version_path.resolve() as resolved, version_path.open() as contents: assert resolved.contents == expected_contents assert contents == expected_contents assert id(resolved.contents) == id(contents) @responses.activate def test_remote(self, defs): schema = { "properties": { "info": { "$ref": "https://example.com/defs.json#/Info", }, }, } responses.add( responses.GET, "https://example.com/defs.json", json=defs, ) path = SchemaPath.from_dict(schema) assert "properties" in path info_path = path / "properties" / "info" assert "properties" in info_path version_path = info_path / "properties" / "version" expected_contents = {"type": "string", "default": "1.0"} with version_path.resolve() as resolved, version_path.open() as contents: assert resolved.contents == expected_contents assert contents == expected_contents assert id(resolved.contents) == id(contents) @responses.activate def test_remote_fallback_requests(self, defs): schema = { "properties": { "info": { "$ref": "https://example.com/defs.json#/Info", }, }, } responses.add( responses.GET, "https://example.com/defs.json", json=defs, ) path = SchemaPath.from_dict(schema, handlers={}) assert "properties" in path info_path = path / "properties" / "info" assert "properties" in info_path version_path = info_path / "properties" / "version" expected_contents = {"type": "string", "default": "1.0"} with version_path.resolve() as resolved, version_path.open() as contents: assert resolved.contents == expected_contents assert contents == expected_contents assert id(resolved.contents) == id(contents) @mock.patch("jsonschema_path.retrievers.USE_REQUESTS", False) @mock.patch("jsonschema_path.retrievers.urlopen") def test_remote_fallback_urllib(self, mock_urlopen, defs): schema = { "properties": { "info": { "$ref": "https://example.com/defs.json#/Info", }, }, } data_bytes = dumps(defs).encode() mock_urlopen.side_effect = lambda m: BytesIO(data_bytes) path = SchemaPath.from_dict(schema, handlers={}) assert "properties" in path info_path = path / "properties" / "info" assert "properties" in info_path version_path = info_path / "properties" / "version" expected_contents = {"type": "string", "default": "1.0"} with version_path.resolve() as resolved, version_path.open() as contents: assert resolved.contents == expected_contents assert contents == expected_contents assert id(resolved.contents) == id(contents) def test_file_ref(self, data_resource_path_getter): fp = data_resource_path_getter( "data/v3.0/petstore-separate/spec/openapi.yaml" ) path = SchemaPath.from_file_path(fp) paths = path / "paths" for _, path in paths.items(): properties = ( path / "get#responses#default#content#application/json#schema#properties" ) with properties.open() as properties_dict: assert properties_dict == { "code": { "format": "int32", "type": "integer", }, "message": { "type": "string", }, } def test_double_ref(self, data_resource_path_getter): fp = data_resource_path_getter( "data/v3.0/petstore-separate/spec/openapi.yaml" ) path = SchemaPath.from_file_path(fp) paths_path = path / "paths" for _, path_path in paths_path.items(): properties_path = ( path_path / "get#responses#200#content#application/json#schema#items#properties" ) with properties_path.open() as contents: assert contents == { "id": { "type": "integer", "format": "int64", }, "name": { "type": "string", }, "tag": { "type": "string", }, } def test_ref_recursive(self, data_resource_path_getter): fp = data_resource_path_getter( "data/v3.0/parent-reference/openapi.yaml" ) base_uri = "file://" + fp with open(fp) as f: path = SchemaPath.from_file(f, base_uri=base_uri) property_schema_path = path._make_child( [ "paths", "/pets", "get", "parameters", 0, "schema", ] ) expected_contents = {"type": "boolean"} with property_schema_path.resolve() as resolved: assert resolved.contents == expected_contents with property_schema_path.open() as contents: assert contents == expected_contents p1c2u-jsonschema-path-d08c0e1/tests/unit/000077500000000000000000000000001515153005400202545ustar00rootroot00000000000000p1c2u-jsonschema-path-d08c0e1/tests/unit/conftest.py000066400000000000000000000040751515153005400224610ustar00rootroot00000000000000import pytest from referencing import Registry from referencing._core import Resolver from referencing.jsonschema import DRAFT202012 from jsonschema_path.accessors import SchemaAccessor from jsonschema_path.handlers import default_handlers from jsonschema_path.paths import SPEC_SEPARATOR from jsonschema_path.retrievers import SchemaRetriever @pytest.fixture def assert_ra(): def func( sa, schema, base_uri="", handlers=default_handlers, specification=DRAFT202012, ): assert sa.content == schema resolver = sa._path_resolver.resolver assert type(resolver) is Resolver assert resolver._base_uri == base_uri registry = resolver._registry assert type(registry) is Registry assert type(registry._retrieve) is SchemaRetriever assert registry._retrieve.handlers == handlers assert registry._retrieve.specification == specification return func @pytest.fixture def assert_sa(): def func( sa, schema, base_uri="", handlers=default_handlers, specification=DRAFT202012, ): assert type(sa) is SchemaAccessor assert sa.node == schema resolver = sa._path_resolver.resolver assert type(resolver) is Resolver assert resolver._base_uri == base_uri registry = resolver._registry assert type(registry) is Registry assert type(registry._retrieve) is SchemaRetriever assert registry._retrieve.handlers == handlers assert registry._retrieve.specification == specification return func @pytest.fixture def assert_sp(assert_sa): def func( sp, schema, separator=SPEC_SEPARATOR, base_uri="", handlers=default_handlers, specification=DRAFT202012, ): assert sp.separator == separator assert sp.parts == () assert_sa( sp.accessor, schema, base_uri=base_uri, handlers=handlers, specification=specification, ) return func p1c2u-jsonschema-path-d08c0e1/tests/unit/handlers/000077500000000000000000000000001515153005400220545ustar00rootroot00000000000000p1c2u-jsonschema-path-d08c0e1/tests/unit/handlers/test_file.py000066400000000000000000000010141515153005400244000ustar00rootroot00000000000000from pathlib import Path import pytest from jsonschema_path.handlers.file import FilePathHandler class TestFilePathHandler: def test_invalid_scheme(self): uri = "invalid:///" handler = FilePathHandler() with pytest.raises(ValueError): handler(uri) def test_valid(self, create_file): test_file = create_file({}) test_file_uri = Path(test_file).as_uri() handler = FilePathHandler() result = handler(test_file_uri) assert result == {} p1c2u-jsonschema-path-d08c0e1/tests/unit/test_accessors.py000066400000000000000000000236171515153005400236630ustar00rootroot00000000000000from unittest.mock import Mock from unittest.mock import patch import pytest from jsonschema_path.accessors import SchemaAccessor from jsonschema_path.nodes import SchemaNode class TestSchemaAccessorOpen: def test_dereferences_once(self): retrieve = Mock(return_value={"value": "tested"}) accessor = SchemaAccessor.from_schema( { "one": { "$ref": "x://testref", }, "two": { "$ref": "x://testref", }, }, handlers={"x": retrieve}, ) assert accessor.read(["one", "value"]) == "tested" assert accessor.read(["two", "value"]) == "tested" retrieve.assert_called_once_with("x://testref") class TestSchemaAccessorKeys: def test_dereferences_once(self): retrieve = Mock(return_value={"value": "tested"}) accessor = SchemaAccessor.from_schema( { "one": { "$ref": "x://testref", }, "two": { "$ref": "x://testref", }, }, handlers={"x": retrieve}, ) assert list(accessor.keys(["one"])) == ["value"] assert accessor.read(["two", "value"]) == "tested" retrieve.assert_called_once_with("x://testref") class TestSchemaAccessorLen: def test_empty_path_dict(self): accessor = SchemaAccessor.from_schema({"a": 1, "b": 2}) assert accessor.len([]) == 2 def test_list_node(self): accessor = SchemaAccessor.from_schema({"arr": ["x", "y", "z"]}) assert accessor.len(["arr"]) == 3 def test_primitive_raises(self): accessor = SchemaAccessor.from_schema({"scalar": 123}) with pytest.raises(KeyError): accessor.len(["scalar"]) def test_missing_path_raises(self): accessor = SchemaAccessor.from_schema({"a": {"b": 1}}) with pytest.raises(KeyError): accessor.len(["missing"]) # type: ignore[list-item] def test_dereferences(self): retrieve = Mock(return_value={"inner": {"x": 1, "y": 2}}) accessor = SchemaAccessor.from_schema( {"one": {"$ref": "x://testref"}}, handlers={"x": retrieve}, ) assert accessor.len(["one", "inner"]) == 2 retrieve.assert_called_once_with("x://testref") class TestSchemaAccessorContains: def test_dict_membership(self): accessor = SchemaAccessor.from_schema({"a": 1}) assert accessor.contains([], "a") is True assert accessor.contains([], "missing") is False def test_list_membership(self): accessor = SchemaAccessor.from_schema({"arr": [10, 20]}) assert accessor.contains(["arr"], 0) is True assert accessor.contains(["arr"], 1) is True assert accessor.contains(["arr"], 2) is False assert accessor.contains(["arr"], "0") is False def test_primitive_is_not_traversable(self): accessor = SchemaAccessor.from_schema({"scalar": 123}) assert accessor.contains(["scalar"], "x") is False def test_missing_path_is_false(self): accessor = SchemaAccessor.from_schema({"a": 1}) assert accessor.contains(["missing"], "x") is False def test_dereferences(self): retrieve = Mock(return_value={"value": "tested"}) accessor = SchemaAccessor.from_schema( {"one": {"$ref": "x://testref"}}, handlers={"x": retrieve}, ) assert accessor.contains(["one"], "value") is True assert accessor.contains(["one"], "missing") is False retrieve.assert_called_once_with("x://testref") class TestSchemaAccessorRequireChild: def test_dict_child(self): accessor = SchemaAccessor.from_schema({"a": 1}) accessor.require_child([], "a") with pytest.raises(KeyError): accessor.require_child([], "missing") def test_list_child(self): accessor = SchemaAccessor.from_schema({"arr": [10, 20]}) accessor.require_child(["arr"], 0) accessor.require_child(["arr"], 1) with pytest.raises(KeyError): accessor.require_child(["arr"], 2) with pytest.raises(KeyError): accessor.require_child(["arr"], "0") def test_primitive_raises(self): accessor = SchemaAccessor.from_schema({"scalar": 123}) with pytest.raises(KeyError): accessor.require_child(["scalar"], "x") def test_missing_parent_path_raises(self): accessor = SchemaAccessor.from_schema({"a": 1}) with pytest.raises(KeyError): accessor.require_child(["missing"], "x") def test_dereferences(self): retrieve = Mock(return_value={"value": {"k": "v"}}) accessor = SchemaAccessor.from_schema( {"one": {"$ref": "x://testref"}}, handlers={"x": retrieve}, ) accessor.require_child(["one"], "value") with pytest.raises(KeyError): accessor.require_child(["one"], "missing") retrieve.assert_called_once_with("x://testref") class TestSchemaAccessorResolverEvolution: def test_does_not_evolve_resolver_when_registry_unchanged(self): accessor = SchemaAccessor.from_schema( {"a": {"b": 1}}, ) initial_resolver = accessor._path_resolver.resolver assert accessor.read(["a", "b"]) == 1 assert accessor._path_resolver.resolver is initial_resolver assert accessor.read(["a", "b"]) == 1 assert accessor._path_resolver.resolver is initial_resolver def test_evolves_once_when_registry_changes(self): retrieve = Mock(return_value={"value": "tested"}) accessor = SchemaAccessor.from_schema( { "one": { "$ref": "x://testref", }, }, handlers={"x": retrieve}, ) initial_resolver = accessor._path_resolver.resolver assert accessor.read(["one", "value"]) == "tested" evolved_resolver = accessor._path_resolver.resolver assert evolved_resolver is not initial_resolver assert accessor.read(["one", "value"]) == "tested" assert accessor._path_resolver.resolver is evolved_resolver retrieve.assert_called_once_with("x://testref") class TestSchemaAccessorResolvedCache: def test_disabled_by_default(self): accessor = SchemaAccessor.from_schema({"a": {"b": 1}}) first = accessor.get_resolved(["a", "b"]) second = accessor.get_resolved(["a", "b"]) assert first is not second def test_cache_hit_for_same_parts(self): accessor = SchemaAccessor.from_schema( {"a": {"b": 1}}, resolved_cache_maxsize=2, ) first = accessor.get_resolved(["a", "b"]) second = accessor.get_resolved(["a", "b"]) assert first is second def test_lru_eviction(self): accessor = SchemaAccessor.from_schema( {"a": 1, "b": 2}, resolved_cache_maxsize=1, ) first_a = accessor.get_resolved(["a"]) _ = accessor.get_resolved(["b"]) second_a = accessor.get_resolved(["a"]) assert first_a is not second_a def test_registry_evolution_invalidates_previous_generation(self): retrieve = Mock(side_effect=[{"value": 1}, {"value": 2}]) accessor = SchemaAccessor.from_schema( { "one": { "$ref": "x://one", }, "two": { "$ref": "x://two", }, }, handlers={"x": retrieve}, resolved_cache_maxsize=8, ) first_one = accessor.get_resolved(["one", "value"]) assert first_one.contents == 1 second_two = accessor.get_resolved(["two", "value"]) assert second_two.contents == 2 second_one = accessor.get_resolved(["one", "value"]) assert second_one.contents == 1 assert second_one is not first_one assert retrieve.call_count == 2 class TestSchemaAccessorPrefixCache: def test_reuses_longest_resolved_prefix_for_siblings(self): accessor = SchemaAccessor.from_schema( { "components": { "schemas": { "A": {"type": "string"}, "B": {"type": "integer"}, } } } ) with patch.object( SchemaNode, "_resolve_node", wraps=SchemaNode._resolve_node, ) as resolve_node: accessor.get_resolved(["components", "schemas", "A"]) first_call_count = resolve_node.call_count accessor.get_resolved(["components", "schemas", "B"]) second_call_increment = resolve_node.call_count - first_call_count assert first_call_count == 4 assert second_call_increment == 1 def test_keeps_disabled_full_path_identity_behavior(self): accessor = SchemaAccessor.from_schema( { "components": { "schemas": { "A": {"type": "string"}, } } } ) first = accessor.get_resolved(["components", "schemas", "A"]) second = accessor.get_resolved(["components", "schemas", "A"]) assert first is not second def test_prefix_cache_is_cleared_when_registry_evolves(self): retrieve = Mock(return_value={"value": "tested"}) accessor = SchemaAccessor.from_schema( { "one": { "$ref": "x://testref", }, }, handlers={"x": retrieve}, ) prefix_cache = accessor._path_resolver.prefix_cache _ = accessor.get_resolved(["one", "value"]) assert accessor._path_resolver.prefix_cache is prefix_cache assert accessor._path_resolver.prefix_cache._cache == {} p1c2u-jsonschema-path-d08c0e1/tests/unit/test_paths.py000066400000000000000000000276511515153005400230170ustar00rootroot00000000000000from pathlib import Path from typing import Any from typing import cast from unittest import mock import pytest from referencing import Specification from jsonschema_path.accessors import SchemaAccessor from jsonschema_path.paths import SchemaPath class TestSchemaPathFromDict: def test_no_kwargs(self, assert_sp): schema = mock.sentinel.schema sp = SchemaPath.from_dict(schema) assert_sp(sp, schema) def test_separator(self, assert_sp): schema = mock.sentinel.schema separator = mock.sentinel.separator sp = SchemaPath.from_dict(schema, separator=separator) assert_sp(sp, schema, separator=separator) def test_specification(self, assert_sp): schema = mock.sentinel.schema specification = mock.Mock(spec=Specification) sp = SchemaPath.from_dict(schema, specification=specification) assert_sp(sp, schema, specification=specification) def test_base_uri(self, assert_sp): schema = mock.sentinel.schema base_uri = "mock.sentinel.base_uri" sp = SchemaPath.from_dict(schema, base_uri=base_uri) assert_sp(sp, schema, base_uri=base_uri) def test_handlers(self, assert_sp): schema = mock.sentinel.schema handlers = mock.sentinel.handlers sp = SchemaPath.from_dict(schema, handlers=handlers) assert_sp(sp, schema, handlers=handlers) def test_resolved_cache_maxsize(self): sp = SchemaPath.from_dict( {"name": "test"}, resolved_cache_maxsize=3, ) assert isinstance(sp.accessor, SchemaAccessor) assert sp.accessor._resolved_cache_maxsize == 3 def test_spec_url(self, assert_sp): schema = mock.sentinel.schema spec_url = "mock.sentinel.spec_url" with pytest.warns(DeprecationWarning): sp = SchemaPath.from_dict(schema, spec_url=spec_url) assert_sp(sp, schema, base_uri=spec_url) def test_ref_resolver_handlers(self, assert_sp): schema = mock.sentinel.schema handlers = mock.sentinel.handlers with pytest.warns(DeprecationWarning): sp = SchemaPath.from_dict(schema, ref_resolver_handlers=handlers) assert_sp(sp, schema, handlers=handlers) def test_negative_resolved_cache_maxsize_raises(self): with pytest.raises(ValueError): SchemaPath.from_dict( {"type": "integer"}, resolved_cache_maxsize=-1, ) class TestSchemaPathFromFile: def test_no_kwargs(self, create_file, assert_sp): schema = {"type": "integer"} schema_file_path_str = create_file(schema) schema_file_path = Path(schema_file_path_str) schema_file_obj = cast(Any, schema_file_path.open()) sp = SchemaPath.from_file(schema_file_obj) # type: ignore[arg-type] assert_sp(sp, schema) def test_base_uri(self, create_file, assert_sp): schema = {"type": "integer"} schema_file_path_str = create_file(schema) schema_file_path = Path(schema_file_path_str) schema_file_obj = cast(Any, schema_file_path.open()) base_uri = "mock.sentinel.base_uri" sp = SchemaPath.from_file( # type: ignore[arg-type] schema_file_obj, base_uri=base_uri, ) assert_sp(sp, schema, base_uri=base_uri) def test_spec_url(self, create_file, assert_sp): schema = {"type": "integer"} schema_file_path_str = create_file(schema) schema_file_path = Path(schema_file_path_str) schema_file_obj = cast(Any, schema_file_path.open()) spec_url = "mock.sentinel.spec_url" with pytest.warns(DeprecationWarning): sp = SchemaPath.from_file( # type: ignore[arg-type] schema_file_obj, spec_url=spec_url, ) assert_sp(sp, schema, base_uri=spec_url) def test_resolved_cache_maxsize(self, create_file): schema = {"type": "integer"} schema_file_path_str = create_file(schema) schema_file_path = Path(schema_file_path_str) schema_file_obj = cast(Any, schema_file_path.open()) sp = SchemaPath.from_file( schema_file_obj, # type: ignore[arg-type] resolved_cache_maxsize=3, ) assert isinstance(sp.accessor, SchemaAccessor) assert sp.accessor._resolved_cache_maxsize == 3 def test_negative_resolved_cache_maxsize_raises(self, create_file): schema = {"type": "integer"} schema_file_path_str = create_file(schema) schema_file_path = Path(schema_file_path_str) schema_file_obj = cast(Any, schema_file_path.open()) with pytest.raises(ValueError): SchemaPath.from_file( schema_file_obj, # type: ignore[arg-type] resolved_cache_maxsize=-1, ) class TestSchemaPathFromPath: def test_file_no_exist(self, create_file): schema_file_path_str = "/invalid/file" schema_file_path = Path(schema_file_path_str) with pytest.raises(OSError): SchemaPath.from_path(schema_file_path) def test_no_kwargs(self, create_file, assert_sp): schema = {"type": "integer"} schema_file_path_str = create_file(schema) schema_file_path = Path(schema_file_path_str) schema_file_uri = schema_file_path.as_uri() sp = SchemaPath.from_path(schema_file_path) assert_sp(sp, schema, base_uri=schema_file_uri) def test_resolved_cache_maxsize(self, create_file): schema = {"type": "integer"} schema_file_path_str = create_file(schema) schema_file_path = Path(schema_file_path_str) sp = SchemaPath.from_path( schema_file_path, resolved_cache_maxsize=3, ) assert isinstance(sp.accessor, SchemaAccessor) assert sp.accessor._resolved_cache_maxsize == 3 def test_negative_resolved_cache_maxsize_raises(self, create_file): schema = {"type": "integer"} schema_file_path_str = create_file(schema) schema_file_path = Path(schema_file_path_str) with pytest.raises(ValueError): SchemaPath.from_path( schema_file_path, resolved_cache_maxsize=-1, ) class TestSchemaPathFromFilePath: def test_no_kwargs(self, create_file, assert_sp): schema = {"type": "integer"} schema_file_path_str = create_file(schema) schema_file_uri = Path(schema_file_path_str).as_uri() sp = SchemaPath.from_file_path(schema_file_path_str) assert_sp(sp, schema, base_uri=schema_file_uri) def test_resolved_cache_maxsize(self, create_file): schema = {"type": "integer"} schema_file_path_str = create_file(schema) sp = SchemaPath.from_file_path( schema_file_path_str, resolved_cache_maxsize=3, ) assert isinstance(sp.accessor, SchemaAccessor) assert sp.accessor._resolved_cache_maxsize == 3 def test_negative_resolved_cache_maxsize_raises(self, create_file): schema = {"type": "integer"} schema_file_path_str = create_file(schema) with pytest.raises(ValueError): SchemaPath.from_file_path( schema_file_path_str, resolved_cache_maxsize=-1, ) class TestSchemaPathGetkey: def test_returns_nested_value(self, create_file): schema = {"a": {"b": 1}} schema_file_path_str = create_file(schema) sp = SchemaPath.from_file_path(schema_file_path_str) value = (sp // "a").read_value() assert value == {"b": 1} def test_returns_value(self, create_file): schema = {"a": {"b": 1}} schema_file_path_str = create_file(schema) sp = SchemaPath.from_file_path(schema_file_path_str) // "a" value = sp.get("b") assert value == 1 class TestSchemaPathReadStr: def test_returns_string_value(self): sp = SchemaPath.from_dict({"name": "test"}) // "name" assert sp.read_str() == "test" def test_missing_key_raises(self): sp = SchemaPath.from_dict({}) with pytest.raises(KeyError): (sp // "missing").read_str() def test_missing_key_returns_default(self): sp = SchemaPath.from_dict({}) default = mock.sentinel.default assert (sp / "missing").read_str(default=default) is default def test_non_string_raises(self): sp = SchemaPath.from_dict({"name": 1}) // "name" with pytest.raises(TypeError): sp.read_str() class TestSchemaPathReadBool: def test_returns_bool_value(self): sp = SchemaPath.from_dict({"enabled": True}) // "enabled" assert sp.read_bool() is True def test_missing_key_raises(self): sp = SchemaPath.from_dict({}) with pytest.raises(KeyError): (sp // "missing").read_bool() def test_missing_key_returns_default(self): sp = SchemaPath.from_dict({}) default = mock.sentinel.default assert (sp / "missing").read_bool(default=default) is default def test_non_bool_raises_without_default(self): sp = SchemaPath.from_dict({"enabled": "yes"}) // "enabled" with pytest.raises(TypeError): sp.read_bool() def test_non_bool_returns_default(self): sp = SchemaPath.from_dict({"enabled": "yes"}) // "enabled" default = mock.sentinel.default assert sp.read_bool(default=default) is default class TestSchemaPathReadStrOrList: def test_returns_string_value(self): sp = SchemaPath.from_dict({"value": "test"}) // "value" assert sp.read_str_or_list() == "test" def test_returns_list_value(self): sp = SchemaPath.from_dict({"value": ["a", "b"]}) // "value" assert sp.read_str_or_list() == ["a", "b"] def test_missing_key_raises(self): sp = SchemaPath.from_dict({}) with pytest.raises(KeyError): (sp // "missing").read_str_or_list() def test_missing_key_returns_default(self): sp = SchemaPath.from_dict({}) default = mock.sentinel.default assert (sp / "missing").read_str_or_list(default=default) is default def test_non_string_or_list_raises(self): sp = SchemaPath.from_dict({"value": 1}) // "value" with pytest.raises(TypeError): sp.read_str_or_list() class TestSchemaPathHelpers: def test_base_uri(self): base_uri = "https://example.com/openapi.json" sp = SchemaPath.from_dict({"a": {"b": 1}}, base_uri=base_uri) assert sp.base_uri == base_uri def test_base_uri_for_child_path(self): base_uri = "https://example.com/openapi.json" sp = SchemaPath.from_dict({"a": {"b": 1}}, base_uri=base_uri) child = sp / "a" / "b" assert child.base_uri == base_uri def test_as_uri(self): sp = SchemaPath.from_dict({"a": {"b": 1}}) // "a" // "b" assert sp.as_uri() == "#/a#b" def test_str_keys(self): sp = SchemaPath.from_dict({"a": 1, "b": 2}) assert set(sp.str_keys()) == {"a", "b"} def test_str_items(self): sp = SchemaPath.from_dict({"a": 1}) key, child = list(sp.str_items())[0] assert key == "a" assert isinstance(child, SchemaPath) assert child.read_value() == 1 class TestSchemaPathParseArgs: def test_flattens_schema_path(self): base = SchemaPath.from_dict({"a": {"b": 1}}) // "a" parsed = SchemaPath._parse_args([base, "b"]) assert parsed == ("a", "b") def test_accepts_bytes(self): parsed = SchemaPath._parse_args([b"a"]) assert parsed == ("a",) def test_accepts_pathlike(self): parsed = SchemaPath._parse_args([Path("a")]) assert parsed == ("a",) def test_skips_empty_and_dot(self): parsed = SchemaPath._parse_args(["", ".", "a#.#b##"]) assert parsed == ("a", "b") def test_raises_on_unsupported_type(self): with pytest.raises(TypeError): SchemaPath._parse_args([object()])