From 7713759874626fb9595c58d7e7f8e6a6cee2c7ce Mon Sep 17 00:00:00 2001 From: sean2077 <37324769+sean2077@users.noreply.github.com> Date: Sun, 23 Nov 2025 20:19:33 +0800 Subject: [PATCH 01/11] fix: modernize project structure and fix path output (#7) --- .editorconfig | 42 + .github/workflows/ci.yml | 67 + .github/workflows/docs.yml | 59 + .github/workflows/release.yml | 68 + .gitignore | 87 +- .pre-commit-config.yaml | 52 + .releaserc.yml | 36 + Makefile | 58 - jsonpath/__init__.py | 338 +---- jsonpath/jsonpath.py | 317 ++++ pyproject.toml | 87 ++ setup.py | 23 - test/data/1.json | 2358 ------------------------------ test/data/2.json | 69 - {test => tests}/__init__.py | 0 {test => tests}/conftest.py | 64 +- tests/data/1.json | 2358 ++++++++++++++++++++++++++++++ tests/data/2.json | 69 + {test => tests}/test_jsonpath.py | 0 uv.lock | 632 ++++++++ 20 files changed, 3905 insertions(+), 2879 deletions(-) create mode 100644 .editorconfig create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/docs.yml create mode 100644 .github/workflows/release.yml create mode 100644 .pre-commit-config.yaml create mode 100644 .releaserc.yml delete mode 100644 Makefile create mode 100644 jsonpath/jsonpath.py create mode 100644 pyproject.toml delete mode 100644 setup.py delete mode 100644 test/data/1.json delete mode 100644 test/data/2.json rename {test => tests}/__init__.py (100%) rename {test => tests}/conftest.py (73%) create mode 100644 tests/data/1.json create mode 100644 tests/data/2.json rename {test => tests}/test_jsonpath.py (100%) create mode 100644 uv.lock diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..5c2d91b --- /dev/null +++ b/.editorconfig @@ -0,0 +1,42 @@ +# EditorConfig for a Python project +# Adjust as needed for your team's conventions. + +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +indent_style = space +indent_size = 4 + +# Python source files +[*.{py,pyi,pyx,pxd}] +indent_style = space +indent_size = 4 +# Common hint matching Black's default line length +max_line_length = 120 + +# Config and data files +[*.{json,jsonc,toml,yml,yaml}] +indent_style = space +indent_size = 2 + +# Markdown: keep trailing spaces (they can mean a line break) +[*.md] +trim_trailing_whitespace = false + +# Makefile requires tabs +[Makefile] +indent_style = tab + +# Shell scripts +[*.{sh,bash,zsh}] +indent_style = space +indent_size = 2 + +# This file itself +[*.editorconfig] +indent_style = space +indent_size = 2 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..2c0080b --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,67 @@ +name: CI + +on: + push: + branches: [main, dev] + pull_request: + branches: [main, dev] + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v6 + + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: "3.12" + + - name: Install uv + uses: astral-sh/setup-uv@v7 + with: + version: "latest" + + - name: Install dependencies + run: uv sync --extra dev + + - name: Run ruff format check + run: uv run ruff format --check . + + - name: Run ruff lint + run: uv run ruff check . + + test: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] + steps: + - name: Checkout code + uses: actions/checkout@v6 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v6 + with: + python-version: ${{ matrix.python-version }} + + - name: Install uv + uses: astral-sh/setup-uv@v7 + with: + version: "latest" + + - name: Install dependencies + run: uv sync --extra dev + + - name: Run tests + run: uv run pytest -v --cov=jsonpath --cov-report=xml + + - name: Upload coverage + if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.12' + uses: codecov/codecov-action@v5 + with: + file: ./coverage.xml + flags: unittests + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..7e908d1 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,59 @@ +name: Deploy Documentation + +on: + push: + branches: + - main + - dev + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 # Fetch all history for git info + + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: "3.12" + + - name: Install uv + uses: astral-sh/setup-uv@v7 + with: + version: "latest" + + - name: Install dependencies + run: uv sync --extra docs + + - name: Build documentation + run: uv run mkdocs build --strict + + - name: Upload artifact + uses: actions/upload-pages-artifact@v4 + with: + path: ./site + + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: build + # Temporarily allow deployment from dev branch for testing + if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev' + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4.0.5 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..fb51dea --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,68 @@ +name: Release + +on: + workflow_run: + workflows: ["CI"] + types: + - completed + branches: + - main + - dev + +permissions: + contents: write + issues: write + pull-requests: write + id-token: write + +jobs: + release: + runs-on: ubuntu-latest + if: github.event.workflow_run.conclusion == 'success' + environment: + name: pypi + url: https://pypi.org/project/jsonpath-python/ + + steps: + - name: Checkout code + uses: actions/checkout@v6 + with: + fetch-depth: 0 + persist-credentials: false + + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: "3.12" + + - name: Set up Node.js + uses: actions/setup-node@v6 + with: + node-version: "lts/*" + + - name: Install semantic-release + run: | + npm install -g semantic-release@latest + npm install -g @semantic-release/changelog@latest + npm install -g @semantic-release/exec@latest + npm install -g @semantic-release/git@latest + npm install -g @semantic-release/github@latest + npm install -g conventional-changelog-conventionalcommits@latest + + - name: Install uv + uses: astral-sh/setup-uv@v7 + with: + version: "latest" + + - name: Release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GIT_AUTHOR_NAME: github-actions[bot] + GIT_AUTHOR_EMAIL: github-actions[bot]@users.noreply.github.com + GIT_COMMITTER_NAME: github-actions[bot] + GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com + run: npx semantic-release + + - name: Publish to PyPI + if: success() && hashFiles('dist/*') != '' + uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.gitignore b/.gitignore index 06243fa..115a9ef 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,11 @@ -.vscode/ -.idea/ -tmp.py -### Python template +/.ref/ +/AGENTS.md +/.serena/ +/cohn_credentials.json + # Byte-compiled / optimized / DLL files __pycache__/ -*.py[cod] +*.py[codz] *$py.class # C extensions @@ -50,7 +51,7 @@ htmlcov/ nosetests.xml coverage.xml *.cover -*.py,cover +*.py.cover .hypothesis/ .pytest_cache/ cover/ @@ -75,6 +76,9 @@ instance/ # Sphinx documentation docs/_build/ +# MkDocs +site/ + # PyBuilder .pybuilder/ target/ @@ -98,7 +102,37 @@ ipython_config.py # install all needed dependencies. #Pipfile.lock -# PEP 582; used by e.g. github.com/David-OConnor/pyflow +# UV +# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +#uv.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock +#poetry.toml + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python. +# https://pdm-project.org/en/latest/usage/project/#working-with-version-control +#pdm.lock +#pdm.toml +.pdm-python +.pdm-build/ + +# pixi +# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control. +#pixi.lock +# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one +# in the .venv directory. It is recommended not to include this directory in version control. +.pixi + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm __pypackages__/ # Celery stuff @@ -110,6 +144,7 @@ celerybeat.pid # Environments .env +.envrc .venv env/ venv/ @@ -140,3 +175,41 @@ dmypy.json # Cython debug symbols cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +# Abstra +# Abstra is an AI-powered process automation framework. +# Ignore directories containing user credentials, local state, and settings. +# Learn more at https://abstra.io/docs +.abstra/ + +# Visual Studio Code +# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore +# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore +# and can be added to the global gitignore or merged into this file. However, if you prefer, +# you could uncomment the following to ignore the entire vscode folder +# .vscode/ + +# Ruff stuff: +.ruff_cache/ + +# PyPI configuration file +.pypirc + +# Cursor +# Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to +# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data +# refer to https://docs.cursor.com/context/ignore-files +.cursorignore +.cursorindexingignore + +# Marimo +marimo/_static/ +marimo/_lsp/ +__marimo__/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..3ea8016 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,52 @@ +exclude: (^vendor/) + +repos: + # Basic file checks + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v6.0.0 + hooks: + # Remove trailing whitespace + - id: trailing-whitespace + # Ensure files end with newline + - id: end-of-file-fixer + # Check for merge conflict markers + # - id: check-merge-conflict + # Check for large files being added + - id: check-added-large-files + args: ["--maxkb=200000"] + + # Ruff - Python linting and formatting + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.14.6 + hooks: + # Run ruff linter + - id: ruff-check + args: ["--fix"] + types_or: [python, pyi] + exclude: ^(vendor/|scripts/) + # Run ruff formatter + - id: ruff-format + types_or: [python, pyi] + + # shfmt - Shell script formatting + - repo: https://github.com/scop/pre-commit-shfmt + rev: v3.12.0-2 + hooks: + - id: shfmt + args: ["-i", "2", "-w"] + types_or: [shell] + + # Prettier (local) - JSON/YAML/TOML formatting + - repo: local + hooks: + - id: prettier-local + name: prettier (local) + alias: prettier + entry: npx --yes prettier + language: system + types_or: + - json + - yaml + args: + - "-w" + - "--tab-width=2" diff --git a/.releaserc.yml b/.releaserc.yml new file mode 100644 index 0000000..8caaa39 --- /dev/null +++ b/.releaserc.yml @@ -0,0 +1,36 @@ +branches: + - main + - name: dev + channel: dev + prerelease: dev +tagFormat: ${version} +preset: conventionalcommits +plugins: + - "@semantic-release/commit-analyzer" + - - "@semantic-release/release-notes-generator" + - presetConfig: + types: + - type: "fix" + section: "Fixes" + - type: "feat" + section: "Features" + - type: "perf" + section: "Performance Improvements" + - - "@semantic-release/changelog" + - changelogFile: CHANGELOG.md + - - "@semantic-release/exec" + - prepareCmd: | + sed -i 's/__version__ = ".*"/__version__ = "${nextRelease.version}"/' jsonpath/__init__.py + uv version "${nextRelease.version}" + publishCmd: uv build + - - "@semantic-release/git" + - assets: + - CHANGELOG.md + - jsonpath/__init__.py + - pyproject.toml + - uv.lock + message: |- + chore(release): ${nextRelease.version} [skip ci] + + ${nextRelease.notes} + - "@semantic-release/github" diff --git a/Makefile b/Makefile deleted file mode 100644 index 0731dec..0000000 --- a/Makefile +++ /dev/null @@ -1,58 +0,0 @@ -# project metadata -NAME := `python setup.py --name` -FULLNAME := `python setup.py --fullname` -VERSION := `python setup.py --version` -BUILD := `git rev-parse --short HEAD` - -.PHONY: info help clean dist docs -.DEFAULT_GOAL := help - -define PRINT_HELP_PYSCRIPT -import re, sys - -for line in sys.stdin: - match = re.match(r'^([a-zA-Z_-]+):.*?## (.*)$$', line) - if match: - target, help = match.groups() - print("%-20s %s" % (target, help)) -endef -export PRINT_HELP_PYSCRIPT - -info: ## project info - @echo $(FULLNAME) at $(BUILD) - -help: - @python -c "$$PRINT_HELP_PYSCRIPT" < $(MAKEFILE_LIST) - -clean: clean-build clean-pyc ## remove all build, test, coverage and Python artifacts - -clean-build: ## remove build artifacts - rm -fr build/ - rm -fr dist/ - rm -fr .eggs/ - find . -name '*.egg-info' -exec rm -fr {} + - find . -name '*.egg' -exec rm -f {} + - -clean-pyc: ## remove Python file artifacts - find . -name '*.pyc' -exec rm -f {} + - find . -name '*.pyo' -exec rm -f {} + - find . -name '*~' -exec rm -f {} + - find . -name '__pycache__' -exec rm -fr {} + - -lint: ## check style with black - find $(NAME) -name '*.py' -type f -not -path "*/pb/*" -not -path "*/data/*" -exec black {} + - -docs: ## format docs - doctoc --gitlab README.md - -install: ## install the package to the active Python's site-packages - pip install . -U --no-index - -uninstall: ## uninstall the package - pip uninstall $(NAME) - -dist: clean ## package - python3 setup.py sdist bdist_wheel - -upload: dist # upload the package to pypi - twine upload dist/* diff --git a/jsonpath/__init__.py b/jsonpath/__init__.py index ad81fc0..3c8271c 100644 --- a/jsonpath/__init__.py +++ b/jsonpath/__init__.py @@ -1,337 +1,13 @@ """ -Author : sean2077 -Date : 2020-12-27 09:22:14 -LastEditors : sean2077 -LastEditTime : 2022-03-14 10:33:55 -Description : JSONPath +JSONPath +======== + +A more powerful JSONPath implementation in modern python. """ + __version__ = "1.0.6" __author__ = "sean2077" -import json -import logging -import os -import re -import sys -from collections import defaultdict -from typing import Union - - -def create_logger(name: str = None, level: Union[int, str] = logging.INFO): - """Get or create a logger used for local debug.""" - - formater = logging.Formatter( - f"%(asctime)s-%(levelname)s-[{name}] %(message)s", datefmt="[%Y-%m-%d %H:%M:%S]" - ) - - handler = logging.StreamHandler() - handler.setLevel(level) - handler.setFormatter(formater) - - logger = logging.getLogger(name) - logger.setLevel(level) - logger.addHandler(handler) - - return logger - - -logger = create_logger("jsonpath", os.getenv("PYLOGLEVEL", "INFO")) - - -class ExprSyntaxError(Exception): - pass - - -class JSONPath: - RESULT_TYPE = { - "VALUE": "A list of specific values.", - "PATH": "All path of specific values.", - } - - # common patterns - SEP = ";" - REP_DOUBLEDOT = re.compile(r"\.\.") - REP_DOT = re.compile(r"(?=|==|!=|>|<| in| not| is)|len\(@\.(.*?)\)" - ) - - # annotations - f: list - segments: list - lpath: int - subx = defaultdict(list) - result: list - result_type: str - eval_func: callable - - def __init__(self, expr: str): - expr = self._parse_expr(expr) - self.segments = expr.split(JSONPath.SEP) - self.lpath = len(self.segments) - logger.debug(f"segments : {self.segments}") - - self.caller_globals = sys._getframe(1).f_globals - - def parse(self, obj, result_type="VALUE", eval_func=eval): - if not isinstance(obj, (list, dict)): - raise TypeError("obj must be a list or a dict.") - - if result_type not in JSONPath.RESULT_TYPE: - raise ValueError( - f"result_type must be one of {tuple(JSONPath.RESULT_TYPE.keys())}" - ) - self.result_type = result_type - self.eval_func = eval_func - - self.result = [] - self._trace(obj, 0, "$") - - return self.result - - def search(self, obj, result_type="VALUE"): - return self.parse(obj, result_type) - - def _parse_expr(self, expr): - logger.debug(f"before expr : {expr}") - # pick up special patterns - expr = JSONPath.REP_GET_QUOTE.sub(self._get_quote, expr) - expr = JSONPath.REP_GET_BACKQUOTE.sub(self._get_backquote, expr) - expr = JSONPath.REP_GET_BRACKET.sub(self._get_bracket, expr) - expr = JSONPath.REP_GET_PAREN.sub(self._get_paren, expr) - # split - expr = JSONPath.REP_DOUBLEDOT.sub(f"{JSONPath.SEP}..{JSONPath.SEP}", expr) - expr = JSONPath.REP_DOT.sub(JSONPath.SEP, expr) - # put back - expr = JSONPath.REP_PUT_PAREN.sub(self._put_paren, expr) - expr = JSONPath.REP_PUT_BRACKET.sub(self._put_bracket, expr) - expr = JSONPath.REP_PUT_BACKQUOTE.sub(self._put_backquote, expr) - expr = JSONPath.REP_PUT_QUOTE.sub(self._put_quote, expr) - if expr.startswith("$;"): - expr = expr[2:] - - logger.debug(f"after expr : {expr}") - return expr - - # TODO abstract get and put procedures - def _get_quote(self, m): - n = len(self.subx["#Q"]) - self.subx["#Q"].append(m.group(1)) - return f"#Q{n}" - - def _put_quote(self, m): - return self.subx["#Q"][int(m.group(1))] - - def _get_backquote(self, m): - n = len(self.subx["#BQ"]) - self.subx["#BQ"].append(m.group(1)) - return f"`#BQ{n}`" - - def _put_backquote(self, m): - return self.subx["#BQ"][int(m.group(1))] - - def _get_bracket(self, m): - n = len(self.subx["#B"]) - self.subx["#B"].append(m.group(1)) - return f".#B{n}" - - def _put_bracket(self, m): - return self.subx["#B"][int(m.group(1))] - - def _get_paren(self, m): - n = len(self.subx["#P"]) - self.subx["#P"].append(m.group(1)) - return f"(#P{n})" - - def _put_paren(self, m): - return self.subx["#P"][int(m.group(1))] - - @staticmethod - def _gen_obj(m): - ret = "__obj" - for e in m.group(1).split("."): - ret += '["%s"]' % e - return ret - - @staticmethod - def _traverse(f, obj, i: int, path: str, *args): - if isinstance(obj, list): - for idx, v in enumerate(obj): - f(v, i, f"{path}{JSONPath.SEP}{idx}", *args) - elif isinstance(obj, dict): - for k, v in obj.items(): - f(v, i, f"{path}{JSONPath.SEP}{k}", *args) - - @staticmethod - def _getattr(obj: dict, path: str, *, convert_number_str=False): - r = obj - for k in path.split("."): - try: - r = r.get(k) - except (AttributeError, KeyError) as err: - logger.error(err) - return None - if convert_number_str and isinstance(r, str): - try: - if r.isdigit(): - return int(r) - return float(r) - except ValueError: - pass - return r - - @staticmethod - def _sorter(obj, sortbys): - for sortby in sortbys.split(",")[::-1]: - if sortby.startswith("~"): - obj.sort( - key=lambda t, k=sortby: JSONPath._getattr( - t[1], k[1:], convert_number_str=True - ), - reverse=True, - ) - else: - obj.sort( - key=lambda t, k=sortby: JSONPath._getattr( - t[1], k, convert_number_str=True - ) - ) - - def _filter(self, obj, i: int, path: str, step: str): - r = False - try: - r = self.eval_func(step, None, {"__obj": obj}) - except Exception as err: - logger.error(err) - if r: - self._trace(obj, i, path) - - def _trace(self, obj, i: int, path): - """Perform operation on object. - - Args: - obj ([type]): current operating object - i (int): current operation specified by index in self.segments - """ - - # store - if i >= self.lpath: - if self.result_type == "VALUE": - self.result.append(obj) - elif self.result_type == "PATH": - self.result.append(path) - logger.debug(f"path: {path} | value: {obj}") - return - - step = self.segments[i] - - # wildcard - if step == "*": - self._traverse(self._trace, obj, i + 1, path) - return - - # recursive descent - if step == "..": - self._trace(obj, i + 1, path) - self._traverse(self._trace, obj, i, path) - return - - # get value from list - if isinstance(obj, list) and step.isdigit(): - ikey = int(step) - if ikey < len(obj): - self._trace(obj[ikey], i + 1, f"{path}{JSONPath.SEP}{step}") - return - - # get value from dict - if isinstance(obj, dict) and step in obj: - self._trace(obj[step], i + 1, f"{path}{JSONPath.SEP}{step}") - return - - # slice - if isinstance(obj, list) and JSONPath.REP_SLICE_CONTENT.fullmatch(step): - obj = list(enumerate(obj)) - vals = self.eval_func(f"obj[{step}]") - for idx, v in vals: - self._trace(v, i + 1, f"{path}{JSONPath.SEP}{idx}") - return - - # select - if isinstance(obj, dict) and JSONPath.REP_SELECT_CONTENT.fullmatch(step): - for k in step.split(","): - if k in obj: - self._trace(obj[k], i + 1, f"{path}{JSONPath.SEP}{k}") - return - - # filter - if step.startswith("?(") and step.endswith(")"): - step = step[2:-1] - step = JSONPath.REP_FILTER_CONTENT.sub(self._gen_obj, step) - self._traverse(self._filter, obj, i + 1, path, step) - return - - # sorter - if step.startswith("/(") and step.endswith(")"): - if isinstance(obj, list): - obj = list(enumerate(obj)) - self._sorter(obj, step[2:-1]) - for idx, v in obj: - self._trace(v, i + 1, f"{path}{JSONPath.SEP}{idx}") - elif isinstance(obj, dict): - obj = list(obj.items()) - self._sorter(obj, step[2:-1]) - for k, v in obj: - self._trace(v, i + 1, f"{path}{JSONPath.SEP}{k}") - else: - raise ExprSyntaxError("sorter must acting on list or dict") - return - - # field-extractor - if step.startswith("(") and step.endswith(")"): - if isinstance(obj, dict): - obj_ = {} - for k in step[1:-1].split(","): - obj_[k] = self._getattr(obj, k) - self._trace(obj_, i + 1, path) - else: - raise ExprSyntaxError("field-extractor must acting on dict") - - return - - -def compile(expr): - return JSONPath(expr) - - -# global cache -_jsonpath_cache = {} - - -def search(expr, data): - global _jsonpath_cache - if expr not in _jsonpath_cache: - _jsonpath_cache[expr] = JSONPath(expr) - return _jsonpath_cache[expr].parse(data) - +from .jsonpath import ExprSyntaxError, JSONPath, compile, search -if __name__ == "__main__": - with open("test/data/2.json", "rb") as f: - d = json.load(f) - D = JSONPath("$.scores[/(score)].(score)").parse(d, "VALUE") - print(D) - for v in D: - print(v) +__all__ = ["JSONPath", "ExprSyntaxError", "compile", "search"] diff --git a/jsonpath/jsonpath.py b/jsonpath/jsonpath.py new file mode 100644 index 0000000..3641dd6 --- /dev/null +++ b/jsonpath/jsonpath.py @@ -0,0 +1,317 @@ +import logging +import os +import re +import sys +from collections import defaultdict +from typing import Union + + +def create_logger(name: str = None, level: Union[int, str] = logging.INFO): + """Get or create a logger used for local debug.""" + + formater = logging.Formatter(f"%(asctime)s-%(levelname)s-[{name}] %(message)s", datefmt="[%Y-%m-%d %H:%M:%S]") + + handler = logging.StreamHandler() + handler.setLevel(level) + handler.setFormatter(formater) + + logger = logging.getLogger(name) + logger.setLevel(level) + logger.addHandler(handler) + + return logger + + +logger = create_logger("jsonpath", os.getenv("PYLOGLEVEL", "INFO")) + + +class ExprSyntaxError(Exception): + pass + + +class JSONPath: + RESULT_TYPE = { + "VALUE": "A list of specific values.", + "PATH": "All path of specific values.", + } + + # common patterns + SEP = ";" + REP_DOUBLEDOT = re.compile(r"\.\.") + REP_DOT = re.compile(r"(?=|==|!=|>|<| in| not| is)|len\(@\.(.*?)\)") + + # annotations + f: list + segments: list + lpath: int + subx = defaultdict(list) + result: list + result_type: str + eval_func: callable + + def __init__(self, expr: str): + expr = self._parse_expr(expr) + self.segments = expr.split(JSONPath.SEP) + self.lpath = len(self.segments) + logger.debug(f"segments : {self.segments}") + + self.caller_globals = sys._getframe(1).f_globals + + def parse(self, obj, result_type="VALUE", eval_func=eval): + if not isinstance(obj, (list, dict)): + raise TypeError("obj must be a list or a dict.") + + if result_type not in JSONPath.RESULT_TYPE: + raise ValueError(f"result_type must be one of {tuple(JSONPath.RESULT_TYPE.keys())}") + self.result_type = result_type + self.eval_func = eval_func + + self.result = [] + self._trace(obj, 0, "$") + + return self.result + + def search(self, obj, result_type="VALUE"): + return self.parse(obj, result_type) + + def _parse_expr(self, expr): + logger.debug(f"before expr : {expr}") + # pick up special patterns + expr = JSONPath.REP_GET_QUOTE.sub(self._get_quote, expr) + expr = JSONPath.REP_GET_BACKQUOTE.sub(self._get_backquote, expr) + expr = JSONPath.REP_GET_BRACKET.sub(self._get_bracket, expr) + expr = JSONPath.REP_GET_PAREN.sub(self._get_paren, expr) + # split + expr = JSONPath.REP_DOUBLEDOT.sub(f"{JSONPath.SEP}..{JSONPath.SEP}", expr) + expr = JSONPath.REP_DOT.sub(JSONPath.SEP, expr) + # put back + expr = JSONPath.REP_PUT_PAREN.sub(self._put_paren, expr) + expr = JSONPath.REP_PUT_BRACKET.sub(self._put_bracket, expr) + expr = JSONPath.REP_PUT_BACKQUOTE.sub(self._put_backquote, expr) + expr = JSONPath.REP_PUT_QUOTE.sub(self._put_quote, expr) + if expr.startswith("$;"): + expr = expr[2:] + + logger.debug(f"after expr : {expr}") + return expr + + # TODO abstract get and put procedures + def _get_quote(self, m): + n = len(self.subx["#Q"]) + self.subx["#Q"].append(m.group(1)) + return f"#Q{n}" + + def _put_quote(self, m): + return self.subx["#Q"][int(m.group(1))] + + def _get_backquote(self, m): + n = len(self.subx["#BQ"]) + self.subx["#BQ"].append(m.group(1)) + return f"`#BQ{n}`" + + def _put_backquote(self, m): + return self.subx["#BQ"][int(m.group(1))] + + def _get_bracket(self, m): + n = len(self.subx["#B"]) + self.subx["#B"].append(m.group(1)) + return f".#B{n}" + + def _put_bracket(self, m): + return self.subx["#B"][int(m.group(1))] + + def _get_paren(self, m): + n = len(self.subx["#P"]) + self.subx["#P"].append(m.group(1)) + return f"(#P{n})" + + def _put_paren(self, m): + return self.subx["#P"][int(m.group(1))] + + @staticmethod + def _gen_obj(m): + ret = "__obj" + for e in m.group(1).split("."): + ret += f'["{e}"]' + return ret + + @staticmethod + def _traverse(f, obj, i: int, path: str, *args): + if isinstance(obj, list): + for idx, v in enumerate(obj): + f(v, i, f"{path}[{idx}]", *args) + elif isinstance(obj, dict): + for k, v in obj.items(): + if re.match(r"^\w+$", k): + f(v, i, f"{path}.{k}", *args) + else: + f(v, i, f"{path}['{k}']", *args) + + @staticmethod + def _getattr(obj: dict, path: str, *, convert_number_str=False): + r = obj + for k in path.split("."): + try: + r = r.get(k) + except (AttributeError, KeyError) as err: + logger.error(err) + return None + if convert_number_str and isinstance(r, str): + try: + if r.isdigit(): + return int(r) + return float(r) + except ValueError: + pass + return r + + @staticmethod + def _sorter(obj, sortbys): + for sortby in sortbys.split(",")[::-1]: + if sortby.startswith("~"): + obj.sort( + key=lambda t, k=sortby: JSONPath._getattr(t[1], k[1:], convert_number_str=True), + reverse=True, + ) + else: + obj.sort(key=lambda t, k=sortby: JSONPath._getattr(t[1], k, convert_number_str=True)) + + def _filter(self, obj, i: int, path: str, step: str): + r = False + try: + r = self.eval_func(step, None, {"__obj": obj}) + except Exception as err: + logger.error(err) + if r: + self._trace(obj, i, path) + + def _trace(self, obj, i: int, path): + """Perform operation on object. + + Args: + obj ([type]): current operating object + i (int): current operation specified by index in self.segments + """ + + # store + if i >= self.lpath: + if self.result_type == "VALUE": + self.result.append(obj) + elif self.result_type == "PATH": + self.result.append(path) + logger.debug(f"path: {path} | value: {obj}") + return + + step = self.segments[i] + + # wildcard + if step == "*": + self._traverse(self._trace, obj, i + 1, path) + return + + # recursive descent + if step == "..": + self._trace(obj, i + 1, path) + self._traverse(self._trace, obj, i, path) + return + + # get value from list + if isinstance(obj, list) and step.isdigit(): + ikey = int(step) + if ikey < len(obj): + self._trace(obj[ikey], i + 1, f"{path}[{step}]") + return + + # get value from dict + if isinstance(obj, dict) and step in obj: + if re.match(r"^\w+$", step): + self._trace(obj[step], i + 1, f"{path}.{step}") + else: + self._trace(obj[step], i + 1, f"{path}['{step}']") + return + + # slice + if isinstance(obj, list) and JSONPath.REP_SLICE_CONTENT.fullmatch(step): + obj = list(enumerate(obj)) + vals = self.eval_func(f"obj[{step}]") + for idx, v in vals: + self._trace(v, i + 1, f"{path}[{idx}]") + return + + # select + if isinstance(obj, dict) and JSONPath.REP_SELECT_CONTENT.fullmatch(step): + for k in step.split(","): + if k in obj: + if re.match(r"^\w+$", k): + self._trace(obj[k], i + 1, f"{path}.{k}") + else: + self._trace(obj[k], i + 1, f"{path}['{k}']") + return + + # filter + if step.startswith("?(") and step.endswith(")"): + step = step[2:-1] + step = JSONPath.REP_FILTER_CONTENT.sub(self._gen_obj, step) + self._traverse(self._filter, obj, i + 1, path, step) + return + + # sorter + if step.startswith("/(") and step.endswith(")"): + if isinstance(obj, list): + obj = list(enumerate(obj)) + self._sorter(obj, step[2:-1]) + for idx, v in obj: + self._trace(v, i + 1, f"{path}[{idx}]") + elif isinstance(obj, dict): + obj = list(obj.items()) + self._sorter(obj, step[2:-1]) + for k, v in obj: + if re.match(r"^\w+$", k): + self._trace(v, i + 1, f"{path}.{k}") + else: + self._trace(v, i + 1, f"{path}['{k}']") + else: + raise ExprSyntaxError("sorter must acting on list or dict") + return + + # field-extractor + if step.startswith("(") and step.endswith(")"): + if isinstance(obj, dict): + obj_ = {} + for k in step[1:-1].split(","): + obj_[k] = self._getattr(obj, k) + self._trace(obj_, i + 1, path) + else: + raise ExprSyntaxError("field-extractor must acting on dict") + + return + + +def compile(expr): + return JSONPath(expr) + + +# global cache +_jsonpath_cache = {} + + +def search(expr, data): + global _jsonpath_cache + if expr not in _jsonpath_cache: + _jsonpath_cache[expr] = JSONPath(expr) + return _jsonpath_cache[expr].parse(data) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..08860b8 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,87 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "jsonpath-python" +version = "1.0.6" +description = "A more powerful JSONPath implementation in modern python" +readme = "README.md" +license = { file = "LICENSE" } +authors = [{ name = "sean2077", email = "sean2077@users.noreply.github.com" }] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Topic :: Software Development :: Libraries :: Python Modules", + "Topic :: Text Processing :: Markup", +] +keywords = ["jsonpath", "json", "path", "parser", "extractor"] +requires-python = ">=3.8" +dependencies = [] + +[project.optional-dependencies] +dev = ["pytest>=8.0", "pytest-cov>=5.0", "ruff>=0.3"] + +[project.urls] +Homepage = "https://github.com/sean2077/jsonpath-python" +Repository = "https://github.com/sean2077/jsonpath-python" +Issues = "https://github.com/sean2077/jsonpath-python/issues" + +[tool.hatch.build.targets.wheel] +packages = ["jsonpath"] + +[tool.ruff] +target-version = "py38" +line-length = 120 +fix = true + +[tool.ruff.lint] +select = [ + "E", # pycodestyle errors + "F", # pyflakes + "W", # pycodestyle warnings + "B", # flake8-bugbear + "I", # isort + "N", # pep8-naming + "UP", # pyupgrade + "C4", # flake8-comprehensions + "PLC", # pylint conventions +] +ignore = [ + "E501", # line length handled by ruff format + "B008", # function calls in argument defaults +] + +[tool.ruff.lint.per-file-ignores] +"tests/**/*.py" = [ + "PLC0415", # allow imports inside functions in tests +] + +[tool.pytest.ini_options] +testpaths = ["tests"] +python_files = ["test_*.py"] +python_classes = ["Test*"] +# Development Tasks (using poethepoet) +[tool.poe.tasks.format] +cmd = "ruff format . && ruff check . --fix --select I" +help = "Format code and organize imports" + +[tool.poe.tasks.lint] +cmd = "ruff check ." +help = "Run linter" + +[tool.poe.tasks.test] +cmd = "pytest" +help = "Run tests" + +[tool.poe.tasks.update-deps] +cmd = "uv sync --all-extras --upgrade --index-url https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple/" +help = "Update all dependencies" diff --git a/setup.py b/setup.py deleted file mode 100644 index 5ff5441..0000000 --- a/setup.py +++ /dev/null @@ -1,23 +0,0 @@ -from setuptools import setup, find_packages -from jsonpath import __version__, __author__ - -with open("README.md", "r", encoding="utf-8") as fh: - long_description = fh.read() - -setup( - name="jsonpath-python", - version=__version__, - author=__author__, - description="A more powerful JSONPath implementation in modern python", - long_description=long_description, - long_description_content_type="text/markdown", - url="https://github.com/sean2077/jsonpath-python", - packages=find_packages(include=["jsonpath*"]), - classifiers=[ - "Development Status :: 3 - Alpha", - "Intended Audience :: Developers", - "License :: OSI Approved :: MIT License", - "Programming Language :: Python :: 3.6", - ], - python_requires=">=3.6", -) diff --git a/test/data/1.json b/test/data/1.json deleted file mode 100644 index 06d92ac..0000000 --- a/test/data/1.json +++ /dev/null @@ -1,2358 +0,0 @@ -{ - "state": 1, - "message": "success", - "content": { - "data": { - "allCitySearchLabels": { - "A": [ - { - "id": 723, - "name": "安阳", - "parentId": 545, - "code": "171500000", - "isSelected": false - }, - { - "id": 601, - "name": "鞍山", - "parentId": 535, - "code": "081600000", - "isSelected": false - }, - { - "id": 671, - "name": "安庆", - "parentId": 541, - "code": "131800000", - "isSelected": false - }, - { - "id": 825, - "name": "安顺", - "parentId": 553, - "code": "240400000", - "isSelected": false - }, - { - "id": 862, - "name": "安康", - "parentId": 556, - "code": "270400000", - "isSelected": false - }, - { - "id": 897, - "name": "阿克苏", - "parentId": 560, - "code": "311800000", - "isSelected": false - }, - { - "id": 598, - "name": "阿拉善盟", - "parentId": 534, - "code": "070300000", - "isSelected": false - }, - { - "id": 105795, - "name": "澳门", - "parentId": 562, - "code": "330100000", - "isSelected": false - }, - { - "id": 903, - "name": "阿勒泰", - "parentId": 560, - "code": "310400000", - "isSelected": false - }, - { - "id": 819, - "name": "阿坝藏族羌族自治州", - "parentId": 552, - "code": "230700000", - "isSelected": false - } - ], - "B": [ - { - "id": 5, - "name": "北京", - "parentId": 1, - "code": "010100000", - "isSelected": false - }, - { - "id": 570, - "name": "保定", - "parentId": 532, - "code": "051100000", - "isSelected": false - }, - { - "id": 666, - "name": "蚌埠", - "parentId": 541, - "code": "131300000", - "isSelected": false - }, - { - "id": 588, - "name": "包头", - "parentId": 534, - "code": "071300000", - "isSelected": false - }, - { - "id": 717, - "name": "滨州", - "parentId": 544, - "code": "161400000", - "isSelected": false - }, - { - "id": 856, - "name": "宝鸡", - "parentId": 556, - "code": "271000000", - "isSelected": false - }, - { - "id": 678, - "name": "亳州", - "parentId": 541, - "code": "130500000", - "isSelected": false - }, - { - "id": 789, - "name": "北海", - "parentId": 549, - "code": "211000000", - "isSelected": false - }, - { - "id": 828, - "name": "毕节", - "parentId": 553, - "code": "240700000", - "isSelected": false - }, - { - "id": 794, - "name": "百色", - "parentId": 549, - "code": "210500000", - "isSelected": false - }, - { - "id": 817, - "name": "巴中", - "parentId": 552, - "code": "230500000", - "isSelected": false - }, - { - "id": 834, - "name": "保山", - "parentId": 554, - "code": "251400000", - "isSelected": false - }, - { - "id": 597, - "name": "巴彦淖尔", - "parentId": 534, - "code": "070400000", - "isSelected": false - }, - { - "id": 867, - "name": "白银", - "parentId": 557, - "code": "281100000", - "isSelected": false - }, - { - "id": 620, - "name": "白城", - "parentId": 536, - "code": "090400000", - "isSelected": false - }, - { - "id": 603, - "name": "本溪", - "parentId": 535, - "code": "081400000", - "isSelected": false - }, - { - "id": 896, - "name": "巴音郭楞", - "parentId": 560, - "code": "311700000", - "isSelected": false - }, - { - "id": 618, - "name": "白山", - "parentId": 536, - "code": "090600000", - "isSelected": false - } - ], - "C": [ - { - "id": 801, - "name": "成都", - "parentId": 552, - "code": "230100000", - "isSelected": false - }, - { - "id": 749, - "name": "长沙", - "parentId": 547, - "code": "190100000", - "isSelected": false - }, - { - "id": 8, - "name": "重庆", - "parentId": 4, - "code": "040100000", - "isSelected": false - }, - { - "id": 613, - "name": "长春", - "parentId": 536, - "code": "090100000", - "isSelected": false - }, - { - "id": 638, - "name": "常州", - "parentId": 539, - "code": "112000000", - "isSelected": false - }, - { - "id": 573, - "name": "沧州", - "parentId": 532, - "code": "050800000", - "isSelected": false - }, - { - "id": 758, - "name": "郴州", - "parentId": 547, - "code": "190500000", - "isSelected": false - }, - { - "id": 590, - "name": "赤峰", - "parentId": 534, - "code": "071100000", - "isSelected": false - }, - { - "id": 673, - "name": "滁州", - "parentId": 541, - "code": "131100000", - "isSelected": false - }, - { - "id": 755, - "name": "常德", - "parentId": 547, - "code": "190800000", - "isSelected": false - }, - { - "id": 572, - "name": "承德", - "parentId": 532, - "code": "050900000", - "isSelected": false - }, - { - "id": 781, - "name": "潮州", - "parentId": 548, - "code": "200500000", - "isSelected": false - }, - { - "id": 611, - "name": "朝阳", - "parentId": 535, - "code": "080600000", - "isSelected": false - }, - { - "id": 905, - "name": "崇左", - "parentId": 549, - "code": "211400000", - "isSelected": false - }, - { - "id": 679, - "name": "池州", - "parentId": 541, - "code": "130600000", - "isSelected": false - }, - { - "id": 836, - "name": "楚雄", - "parentId": 554, - "code": "251200000", - "isSelected": false - }, - { - "id": 894, - "name": "昌吉", - "parentId": 560, - "code": "311500000", - "isSelected": false - }, - { - "id": 848, - "name": "昌都", - "parentId": 555, - "code": "260200000", - "isSelected": false - } - ], - "D": [ - { - "id": 779, - "name": "东莞", - "parentId": 548, - "code": "200300000", - "isSelected": false - }, - { - "id": 600, - "name": "大连", - "parentId": 535, - "code": "081700000", - "isSelected": false - }, - { - "id": 715, - "name": "德州", - "parentId": 544, - "code": "161600000", - "isSelected": false - }, - { - "id": 805, - "name": "德阳", - "parentId": 552, - "code": "231700000", - "isSelected": false - }, - { - "id": 577, - "name": "大同", - "parentId": 533, - "code": "061200000", - "isSelected": false - }, - { - "id": 706, - "name": "东营", - "parentId": 544, - "code": "162000000", - "isSelected": false - }, - { - "id": 815, - "name": "达州", - "parentId": 552, - "code": "230300000", - "isSelected": false - }, - { - "id": 627, - "name": "大庆", - "parentId": 537, - "code": "101300000", - "isSelected": false - }, - { - "id": 604, - "name": "丹东", - "parentId": 535, - "code": "081300000", - "isSelected": false - }, - { - "id": 841, - "name": "大理", - "parentId": 554, - "code": "250700000", - "isSelected": false - }, - { - "id": 874, - "name": "定西", - "parentId": 557, - "code": "280400000", - "isSelected": false - }, - { - "id": 107620, - "name": "儋州", - "parentId": 550, - "code": "220201000", - "isSelected": false - }, - { - "id": 842, - "name": "德宏", - "parentId": 554, - "code": "250600000", - "isSelected": false - } - ], - "E": [ - { - "id": 592, - "name": "鄂尔多斯", - "parentId": 534, - "code": "070900000", - "isSelected": false - }, - { - "id": 741, - "name": "鄂州", - "parentId": 546, - "code": "181600000", - "isSelected": false - }, - { - "id": 748, - "name": "恩施", - "parentId": 546, - "code": "180300000", - "isSelected": false - } - ], - "F": [ - { - "id": 768, - "name": "佛山", - "parentId": 548, - "code": "202000000", - "isSelected": false - }, - { - "id": 681, - "name": "福州", - "parentId": 542, - "code": "140100000", - "isSelected": false - }, - { - "id": 674, - "name": "阜阳", - "parentId": 541, - "code": "131000000", - "isSelected": false - }, - { - "id": 700, - "name": "抚州", - "parentId": 543, - "code": "150200000", - "isSelected": false - }, - { - "id": 602, - "name": "抚顺", - "parentId": 535, - "code": "081500000", - "isSelected": false - }, - { - "id": 790, - "name": "防城港", - "parentId": 549, - "code": "210900000", - "isSelected": false - }, - { - "id": 607, - "name": "阜新", - "parentId": 535, - "code": "081000000", - "isSelected": false - } - ], - "G": [ - { - "id": 763, - "name": "广州", - "parentId": 548, - "code": "200100000", - "isSelected": false - }, - { - "id": 822, - "name": "贵阳", - "parentId": 553, - "code": "240100000", - "isSelected": false - }, - { - "id": 697, - "name": "赣州", - "parentId": 543, - "code": "150500000", - "isSelected": false - }, - { - "id": 787, - "name": "桂林", - "parentId": 549, - "code": "211200000", - "isSelected": false - }, - { - "id": 807, - "name": "广元", - "parentId": 552, - "code": "231900000", - "isSelected": false - }, - { - "id": 792, - "name": "贵港", - "parentId": 549, - "code": "210700000", - "isSelected": false - }, - { - "id": 814, - "name": "广安", - "parentId": 552, - "code": "230200000", - "isSelected": false - }, - { - "id": 820, - "name": "甘孜藏族自治州", - "parentId": 552, - "code": "230800000", - "isSelected": false - }, - { - "id": 889, - "name": "固原", - "parentId": 559, - "code": "300400000", - "isSelected": false - } - ], - "H": [ - { - "id": 653, - "name": "杭州", - "parentId": 540, - "code": "120100000", - "isSelected": false - }, - { - "id": 664, - "name": "合肥", - "parentId": 541, - "code": "130100000", - "isSelected": false - }, - { - "id": 773, - "name": "惠州", - "parentId": 548, - "code": "202500000", - "isSelected": false - }, - { - "id": 622, - "name": "哈尔滨", - "parentId": 537, - "code": "100100000", - "isSelected": false - }, - { - "id": 799, - "name": "海口", - "parentId": 550, - "code": "220100000", - "isSelected": false - }, - { - "id": 587, - "name": "呼和浩特", - "parentId": 534, - "code": "070100000", - "isSelected": false - }, - { - "id": 568, - "name": "邯郸", - "parentId": 532, - "code": "051300000", - "isSelected": false - }, - { - "id": 643, - "name": "淮安", - "parentId": 539, - "code": "112500000", - "isSelected": false - }, - { - "id": 718, - "name": "菏泽", - "parentId": 544, - "code": "160200000", - "isSelected": false - }, - { - "id": 657, - "name": "湖州", - "parentId": 540, - "code": "122200000", - "isSelected": false - }, - { - "id": 752, - "name": "衡阳", - "parentId": 547, - "code": "191100000", - "isSelected": false - }, - { - "id": 575, - "name": "衡水", - "parentId": 532, - "code": "050600000", - "isSelected": false - }, - { - "id": 776, - "name": "河源", - "parentId": 548, - "code": "201400000", - "isSelected": false - }, - { - "id": 108353, - "name": "海外", - "parentId": 108352, - "code": "350100000", - "isSelected": false - }, - { - "id": 737, - "name": "黄石", - "parentId": 546, - "code": "181200000", - "isSelected": false - }, - { - "id": 745, - "name": "黄冈", - "parentId": 546, - "code": "181100000", - "isSelected": false - }, - { - "id": 724, - "name": "鹤壁", - "parentId": 545, - "code": "171600000", - "isSelected": false - }, - { - "id": 760, - "name": "怀化", - "parentId": 547, - "code": "190300000", - "isSelected": false - }, - { - "id": 860, - "name": "汉中", - "parentId": 556, - "code": "270600000", - "isSelected": false - }, - { - "id": 667, - "name": "淮南", - "parentId": 541, - "code": "131400000", - "isSelected": false - }, - { - "id": 669, - "name": "淮北", - "parentId": 541, - "code": "131600000", - "isSelected": false - }, - { - "id": 612, - "name": "葫芦岛", - "parentId": 535, - "code": "080500000", - "isSelected": false - }, - { - "id": 795, - "name": "贺州", - "parentId": 549, - "code": "210400000", - "isSelected": false - }, - { - "id": 593, - "name": "呼伦贝尔", - "parentId": 534, - "code": "070800000", - "isSelected": false - }, - { - "id": 672, - "name": "黄山", - "parentId": 541, - "code": "131900000", - "isSelected": false - }, - { - "id": 796, - "name": "河池", - "parentId": 549, - "code": "210300000", - "isSelected": false - }, - { - "id": 625, - "name": "鹤岗", - "parentId": 537, - "code": "101500000", - "isSelected": false - }, - { - "id": 879, - "name": "海东", - "parentId": 558, - "code": "290200000", - "isSelected": false - }, - { - "id": 837, - "name": "红河", - "parentId": 554, - "code": "251100000", - "isSelected": false - }, - { - "id": 632, - "name": "黑河", - "parentId": 537, - "code": "100800000", - "isSelected": false - }, - { - "id": 893, - "name": "哈密", - "parentId": 560, - "code": "311400000", - "isSelected": false - } - ], - "J": [ - { - "id": 702, - "name": "济南", - "parentId": 544, - "code": "160100000", - "isSelected": false - }, - { - "id": 659, - "name": "金华", - "parentId": 540, - "code": "122400000", - "isSelected": false - }, - { - "id": 656, - "name": "嘉兴", - "parentId": 540, - "code": "122100000", - "isSelected": false - }, - { - "id": 769, - "name": "江门", - "parentId": 548, - "code": "202100000", - "isSelected": false - }, - { - "id": 709, - "name": "济宁", - "parentId": 544, - "code": "162300000", - "isSelected": false - }, - { - "id": 694, - "name": "九江", - "parentId": 543, - "code": "150800000", - "isSelected": false - }, - { - "id": 782, - "name": "揭阳", - "parentId": 548, - "code": "200600000", - "isSelected": false - }, - { - "id": 582, - "name": "晋中", - "parentId": 533, - "code": "060700000", - "isSelected": false - }, - { - "id": 744, - "name": "荆州", - "parentId": 546, - "code": "181900000", - "isSelected": false - }, - { - "id": 614, - "name": "吉林", - "parentId": 536, - "code": "091000000", - "isSelected": false - }, - { - "id": 726, - "name": "焦作", - "parentId": 545, - "code": "171800000", - "isSelected": false - }, - { - "id": 605, - "name": "锦州", - "parentId": 535, - "code": "081200000", - "isSelected": false - }, - { - "id": 698, - "name": "吉安", - "parentId": 543, - "code": "150400000", - "isSelected": false - }, - { - "id": 580, - "name": "晋城", - "parentId": 533, - "code": "060900000", - "isSelected": false - }, - { - "id": 692, - "name": "景德镇", - "parentId": 543, - "code": "151000000", - "isSelected": false - }, - { - "id": 629, - "name": "佳木斯", - "parentId": 537, - "code": "101100000", - "isSelected": false - }, - { - "id": 742, - "name": "荆门", - "parentId": 546, - "code": "181700000", - "isSelected": false - }, - { - "id": 872, - "name": "酒泉", - "parentId": 557, - "code": "280600000", - "isSelected": false - }, - { - "id": 866, - "name": "金昌", - "parentId": 557, - "code": "281200000", - "isSelected": false - }, - { - "id": 107292, - "name": "济源", - "parentId": 545, - "code": "170711000", - "isSelected": false - }, - { - "id": 624, - "name": "鸡西", - "parentId": 537, - "code": "101600000", - "isSelected": false - }, - { - "id": 865, - "name": "嘉峪关", - "parentId": 557, - "code": "281300000", - "isSelected": false - } - ], - "K": [ - { - "id": 831, - "name": "昆明", - "parentId": 554, - "code": "250100000", - "isSelected": false - }, - { - "id": 720, - "name": "开封", - "parentId": 545, - "code": "171200000", - "isSelected": false - }, - { - "id": 899, - "name": "喀什", - "parentId": 560, - "code": "311100000", - "isSelected": false - }, - { - "id": 891, - "name": "克拉玛依", - "parentId": 560, - "code": "311200000", - "isSelected": false - } - ], - "L": [ - { - "id": 574, - "name": "廊坊", - "parentId": 532, - "code": "050700000", - "isSelected": false - }, - { - "id": 864, - "name": "兰州", - "parentId": 557, - "code": "280100000", - "isSelected": false - }, - { - "id": 714, - "name": "临沂", - "parentId": 544, - "code": "162800000", - "isSelected": false - }, - { - "id": 721, - "name": "洛阳", - "parentId": 545, - "code": "171300000", - "isSelected": false - }, - { - "id": 786, - "name": "柳州", - "parentId": 549, - "code": "211300000", - "isSelected": false - }, - { - "id": 716, - "name": "聊城", - "parentId": 544, - "code": "161500000", - "isSelected": false - }, - { - "id": 642, - "name": "连云港", - "parentId": 539, - "code": "112400000", - "isSelected": false - }, - { - "id": 677, - "name": "六安", - "parentId": 541, - "code": "130400000", - "isSelected": false - }, - { - "id": 804, - "name": "泸州", - "parentId": 552, - "code": "231600000", - "isSelected": false - }, - { - "id": 729, - "name": "漯河", - "parentId": 545, - "code": "171000000", - "isSelected": false - }, - { - "id": 585, - "name": "临汾", - "parentId": 533, - "code": "060400000", - "isSelected": false - }, - { - "id": 810, - "name": "乐山", - "parentId": 552, - "code": "232200000", - "isSelected": false - }, - { - "id": 689, - "name": "龙岩", - "parentId": 542, - "code": "140400000", - "isSelected": false - }, - { - "id": 663, - "name": "丽水", - "parentId": 540, - "code": "122800000", - "isSelected": false - }, - { - "id": 608, - "name": "辽阳", - "parentId": 535, - "code": "080900000", - "isSelected": false - }, - { - "id": 823, - "name": "六盘水", - "parentId": 553, - "code": "240200000", - "isSelected": false - }, - { - "id": 847, - "name": "拉萨", - "parentId": 555, - "code": "260100000", - "isSelected": false - }, - { - "id": 761, - "name": "娄底", - "parentId": 547, - "code": "190200000", - "isSelected": false - }, - { - "id": 843, - "name": "丽江", - "parentId": 554, - "code": "250500000", - "isSelected": false - }, - { - "id": 586, - "name": "吕梁", - "parentId": 533, - "code": "060300000", - "isSelected": false - }, - { - "id": 821, - "name": "凉山彝族自治州", - "parentId": 552, - "code": "230900000", - "isSelected": false - }, - { - "id": 616, - "name": "辽源", - "parentId": 536, - "code": "090800000", - "isSelected": false - }, - { - "id": 713, - "name": "莱芜", - "parentId": 544, - "code": "162700000", - "isSelected": false - }, - { - "id": 876, - "name": "临夏", - "parentId": 557, - "code": "280200000", - "isSelected": false - }, - { - "id": 904, - "name": "来宾", - "parentId": 549, - "code": "210200000", - "isSelected": false - }, - { - "id": 875, - "name": "陇南", - "parentId": 557, - "code": "280300000", - "isSelected": false - }, - { - "id": 853, - "name": "林芝", - "parentId": 555, - "code": "260700000", - "isSelected": false - }, - { - "id": 846, - "name": "临沧", - "parentId": 554, - "code": "250200000", - "isSelected": false - } - ], - "M": [ - { - "id": 806, - "name": "绵阳", - "parentId": 552, - "code": "231800000", - "isSelected": false - }, - { - "id": 774, - "name": "梅州", - "parentId": 548, - "code": "202600000", - "isSelected": false - }, - { - "id": 668, - "name": "马鞍山", - "parentId": 541, - "code": "131500000", - "isSelected": false - }, - { - "id": 812, - "name": "眉山", - "parentId": 552, - "code": "231300000", - "isSelected": false - }, - { - "id": 771, - "name": "茂名", - "parentId": 548, - "code": "202300000", - "isSelected": false - }, - { - "id": 631, - "name": "牡丹江", - "parentId": 537, - "code": "100900000", - "isSelected": false - } - ], - "N": [ - { - "id": 635, - "name": "南京", - "parentId": 539, - "code": "110100000", - "isSelected": false - }, - { - "id": 691, - "name": "南昌", - "parentId": 543, - "code": "150100000", - "isSelected": false - }, - { - "id": 654, - "name": "宁波", - "parentId": 540, - "code": "121900000", - "isSelected": false - }, - { - "id": 785, - "name": "南宁", - "parentId": 549, - "code": "210100000", - "isSelected": false - }, - { - "id": 641, - "name": "南通", - "parentId": 539, - "code": "112300000", - "isSelected": false - }, - { - "id": 731, - "name": "南阳", - "parentId": 545, - "code": "170300000", - "isSelected": false - }, - { - "id": 811, - "name": "南充", - "parentId": 552, - "code": "232300000", - "isSelected": false - }, - { - "id": 690, - "name": "宁德", - "parentId": 542, - "code": "140300000", - "isSelected": false - }, - { - "id": 809, - "name": "内江", - "parentId": 552, - "code": "232100000", - "isSelected": false - }, - { - "id": 688, - "name": "南平", - "parentId": 542, - "code": "140500000", - "isSelected": false - } - ], - "P": [ - { - "id": 683, - "name": "莆田", - "parentId": 542, - "code": "141000000", - "isSelected": false - }, - { - "id": 722, - "name": "平顶山", - "parentId": 545, - "code": "171400000", - "isSelected": false - }, - { - "id": 727, - "name": "濮阳", - "parentId": 545, - "code": "171900000", - "isSelected": false - }, - { - "id": 609, - "name": "盘锦", - "parentId": 535, - "code": "080800000", - "isSelected": false - }, - { - "id": 693, - "name": "萍乡", - "parentId": 543, - "code": "150900000", - "isSelected": false - }, - { - "id": 803, - "name": "攀枝花", - "parentId": 552, - "code": "231500000", - "isSelected": false - }, - { - "id": 10163, - "name": "普洱", - "parentId": 554, - "code": "251700000", - "isSelected": false - }, - { - "id": 871, - "name": "平凉", - "parentId": 557, - "code": "280700000", - "isSelected": false - } - ], - "Q": [ - { - "id": 703, - "name": "青岛", - "parentId": 544, - "code": "161700000", - "isSelected": false - }, - { - "id": 685, - "name": "泉州", - "parentId": 542, - "code": "140800000", - "isSelected": false - }, - { - "id": 778, - "name": "清远", - "parentId": 548, - "code": "200200000", - "isSelected": false - }, - { - "id": 567, - "name": "秦皇岛", - "parentId": 532, - "code": "051400000", - "isSelected": false - }, - { - "id": 832, - "name": "曲靖", - "parentId": 554, - "code": "251600000", - "isSelected": false - }, - { - "id": 660, - "name": "衢州", - "parentId": 540, - "code": "122500000", - "isSelected": false - }, - { - "id": 623, - "name": "齐齐哈尔", - "parentId": 537, - "code": "101700000", - "isSelected": false - }, - { - "id": 791, - "name": "钦州", - "parentId": 549, - "code": "210800000", - "isSelected": false - }, - { - "id": 873, - "name": "庆阳", - "parentId": 557, - "code": "280500000", - "isSelected": false - }, - { - "id": 827, - "name": "黔西南", - "parentId": 553, - "code": "240600000", - "isSelected": false - }, - { - "id": 829, - "name": "黔东南", - "parentId": 553, - "code": "240800000", - "isSelected": false - }, - { - "id": 830, - "name": "黔南", - "parentId": 553, - "code": "240900000", - "isSelected": false - }, - { - "id": 630, - "name": "七台河", - "parentId": 537, - "code": "101000000", - "isSelected": false - } - ], - "R": [ - { - "id": 712, - "name": "日照", - "parentId": 544, - "code": "162600000", - "isSelected": false - }, - { - "id": 850, - "name": "日喀则", - "parentId": 555, - "code": "260400000", - "isSelected": false - } - ], - "S": [ - { - "id": 765, - "name": "深圳", - "parentId": 548, - "code": "201700000", - "isSelected": false - }, - { - "id": 6, - "name": "上海", - "parentId": 2, - "code": "020100000", - "isSelected": false - }, - { - "id": 639, - "name": "苏州", - "parentId": 539, - "code": "112100000", - "isSelected": false - }, - { - "id": 599, - "name": "沈阳", - "parentId": 535, - "code": "080100000", - "isSelected": false - }, - { - "id": 565, - "name": "石家庄", - "parentId": 532, - "code": "050100000", - "isSelected": false - }, - { - "id": 658, - "name": "绍兴", - "parentId": 540, - "code": "122300000", - "isSelected": false - }, - { - "id": 648, - "name": "宿迁", - "parentId": 539, - "code": "113000000", - "isSelected": false - }, - { - "id": 767, - "name": "汕头", - "parentId": 548, - "code": "201900000", - "isSelected": false - }, - { - "id": 800, - "name": "三亚", - "parentId": 550, - "code": "221800000", - "isSelected": false - }, - { - "id": 732, - "name": "商丘", - "parentId": 545, - "code": "170400000", - "isSelected": false - }, - { - "id": 701, - "name": "上饶", - "parentId": 543, - "code": "151100000", - "isSelected": false - }, - { - "id": 675, - "name": "宿州", - "parentId": 541, - "code": "130200000", - "isSelected": false - }, - { - "id": 764, - "name": "韶关", - "parentId": 548, - "code": "201600000", - "isSelected": false - }, - { - "id": 738, - "name": "十堰", - "parentId": 546, - "code": "181300000", - "isSelected": false - }, - { - "id": 808, - "name": "遂宁", - "parentId": 552, - "code": "232000000", - "isSelected": false - }, - { - "id": 753, - "name": "邵阳", - "parentId": 547, - "code": "191000000", - "isSelected": false - }, - { - "id": 775, - "name": "汕尾", - "parentId": 548, - "code": "201500000", - "isSelected": false - }, - { - "id": 684, - "name": "三明", - "parentId": 542, - "code": "140900000", - "isSelected": false - }, - { - "id": 747, - "name": "随州", - "parentId": 546, - "code": "180200000", - "isSelected": false - }, - { - "id": 633, - "name": "绥化", - "parentId": 537, - "code": "100700000", - "isSelected": false - }, - { - "id": 619, - "name": "松原", - "parentId": 536, - "code": "090500000", - "isSelected": false - }, - { - "id": 730, - "name": "三门峡", - "parentId": 545, - "code": "170200000", - "isSelected": false - }, - { - "id": 615, - "name": "四平", - "parentId": 536, - "code": "090900000", - "isSelected": false - }, - { - "id": 581, - "name": "朔州", - "parentId": 533, - "code": "060800000", - "isSelected": false - }, - { - "id": 863, - "name": "商洛", - "parentId": 556, - "code": "270300000", - "isSelected": false - }, - { - "id": 108393, - "name": "石河子", - "parentId": 560, - "code": "310500000", - "isSelected": false - }, - { - "id": 907, - "name": "三沙", - "parentId": 550, - "code": "220200000", - "isSelected": false - }, - { - "id": 887, - "name": "石嘴山", - "parentId": 559, - "code": "300200000", - "isSelected": false - }, - { - "id": 626, - "name": "双鸭山", - "parentId": 537, - "code": "101400000", - "isSelected": false - }, - { - "id": 849, - "name": "山南", - "parentId": 555, - "code": "260300000", - "isSelected": false - } - ], - "T": [ - { - "id": 7, - "name": "天津", - "parentId": 3, - "code": "030100000", - "isSelected": false - }, - { - "id": 576, - "name": "太原", - "parentId": 533, - "code": "060100000", - "isSelected": false - }, - { - "id": 566, - "name": "唐山", - "parentId": 532, - "code": "051500000", - "isSelected": false - }, - { - "id": 662, - "name": "台州", - "parentId": 540, - "code": "122700000", - "isSelected": false - }, - { - "id": 647, - "name": "泰州", - "parentId": 539, - "code": "112900000", - "isSelected": false - }, - { - "id": 710, - "name": "泰安", - "parentId": 544, - "code": "162400000", - "isSelected": false - }, - { - "id": 591, - "name": "通辽", - "parentId": 534, - "code": "071000000", - "isSelected": false - }, - { - "id": 670, - "name": "铜陵", - "parentId": 541, - "code": "131700000", - "isSelected": false - }, - { - "id": 868, - "name": "天水", - "parentId": 557, - "code": "281000000", - "isSelected": false - }, - { - "id": 826, - "name": "铜仁", - "parentId": 553, - "code": "240500000", - "isSelected": false - }, - { - "id": 610, - "name": "铁岭", - "parentId": 535, - "code": "080700000", - "isSelected": false - }, - { - "id": 855, - "name": "铜川", - "parentId": 556, - "code": "271100000", - "isSelected": false - }, - { - "id": 108355, - "name": "台湾", - "parentId": 563, - "code": "340100000", - "isSelected": false - }, - { - "id": 617, - "name": "通化", - "parentId": 536, - "code": "090700000", - "isSelected": false - }, - { - "id": 902, - "name": "塔城", - "parentId": 560, - "code": "310300000", - "isSelected": false - }, - { - "id": 107384, - "name": "天门", - "parentId": 546, - "code": "180311000", - "isSelected": false - } - ], - "W": [ - { - "id": 736, - "name": "武汉", - "parentId": 546, - "code": "180100000", - "isSelected": false - }, - { - "id": 636, - "name": "无锡", - "parentId": 539, - "code": "111800000", - "isSelected": false - }, - { - "id": 655, - "name": "温州", - "parentId": 540, - "code": "122000000", - "isSelected": false - }, - { - "id": 708, - "name": "潍坊", - "parentId": 544, - "code": "162200000", - "isSelected": false - }, - { - "id": 665, - "name": "芜湖", - "parentId": 541, - "code": "131200000", - "isSelected": false - }, - { - "id": 890, - "name": "乌鲁木齐", - "parentId": 560, - "code": "310100000", - "isSelected": false - }, - { - "id": 711, - "name": "威海", - "parentId": 544, - "code": "162500000", - "isSelected": false - }, - { - "id": 858, - "name": "渭南", - "parentId": 556, - "code": "270800000", - "isSelected": false - }, - { - "id": 788, - "name": "梧州", - "parentId": 549, - "code": "211100000", - "isSelected": false - }, - { - "id": 596, - "name": "乌兰察布", - "parentId": 534, - "code": "070500000", - "isSelected": false - }, - { - "id": 869, - "name": "武威", - "parentId": 557, - "code": "280900000", - "isSelected": false - }, - { - "id": 589, - "name": "乌海", - "parentId": 534, - "code": "071200000", - "isSelected": false - }, - { - "id": 888, - "name": "吴忠", - "parentId": 559, - "code": "300300000", - "isSelected": false - }, - { - "id": 838, - "name": "文山", - "parentId": 554, - "code": "251000000", - "isSelected": false - } - ], - "X": [ - { - "id": 854, - "name": "西安", - "parentId": 556, - "code": "270100000", - "isSelected": false - }, - { - "id": 682, - "name": "厦门", - "parentId": 542, - "code": "141100000", - "isSelected": false - }, - { - "id": 637, - "name": "徐州", - "parentId": 539, - "code": "111900000", - "isSelected": false - }, - { - "id": 857, - "name": "咸阳", - "parentId": 556, - "code": "270900000", - "isSelected": false - }, - { - "id": 725, - "name": "新乡", - "parentId": 545, - "code": "171700000", - "isSelected": false - }, - { - "id": 740, - "name": "襄阳", - "parentId": 546, - "code": "181500000", - "isSelected": false - }, - { - "id": 728, - "name": "许昌", - "parentId": 545, - "code": "171100000", - "isSelected": false - }, - { - "id": 569, - "name": "邢台", - "parentId": 532, - "code": "051200000", - "isSelected": false - }, - { - "id": 878, - "name": "西宁", - "parentId": 558, - "code": "290100000", - "isSelected": false - }, - { - "id": 733, - "name": "信阳", - "parentId": 545, - "code": "170500000", - "isSelected": false - }, - { - "id": 743, - "name": "孝感", - "parentId": 546, - "code": "181800000", - "isSelected": false - }, - { - "id": 751, - "name": "湘潭", - "parentId": 547, - "code": "191200000", - "isSelected": false - }, - { - "id": 695, - "name": "新余", - "parentId": 543, - "code": "150700000", - "isSelected": false - }, - { - "id": 746, - "name": "咸宁", - "parentId": 546, - "code": "181000000", - "isSelected": false - }, - { - "id": 680, - "name": "宣城", - "parentId": 541, - "code": "130700000", - "isSelected": false - }, - { - "id": 584, - "name": "忻州", - "parentId": 533, - "code": "060500000", - "isSelected": false - }, - { - "id": 105794, - "name": "香港", - "parentId": 561, - "code": "320100000", - "isSelected": false - }, - { - "id": 840, - "name": "西双版纳", - "parentId": 554, - "code": "250800000", - "isSelected": false - }, - { - "id": 762, - "name": "湘西土家族苗族自治州", - "parentId": 547, - "code": "191400000", - "isSelected": false - }, - { - "id": 594, - "name": "兴安盟", - "parentId": 534, - "code": "070700000", - "isSelected": false - }, - { - "id": 595, - "name": "锡林郭勒盟", - "parentId": 534, - "code": "070600000", - "isSelected": false - } - ], - "Y": [ - { - "id": 707, - "name": "烟台", - "parentId": 544, - "code": "162100000", - "isSelected": false - }, - { - "id": 886, - "name": "银川", - "parentId": 559, - "code": "300100000", - "isSelected": false - }, - { - "id": 645, - "name": "扬州", - "parentId": 539, - "code": "112700000", - "isSelected": false - }, - { - "id": 644, - "name": "盐城", - "parentId": 539, - "code": "112600000", - "isSelected": false - }, - { - "id": 739, - "name": "宜昌", - "parentId": 546, - "code": "181400000", - "isSelected": false - }, - { - "id": 583, - "name": "运城", - "parentId": 533, - "code": "060600000", - "isSelected": false - }, - { - "id": 813, - "name": "宜宾", - "parentId": 552, - "code": "231200000", - "isSelected": false - }, - { - "id": 754, - "name": "岳阳", - "parentId": 547, - "code": "190900000", - "isSelected": false - }, - { - "id": 777, - "name": "阳江", - "parentId": 548, - "code": "201300000", - "isSelected": false - }, - { - "id": 699, - "name": "宜春", - "parentId": 543, - "code": "150300000", - "isSelected": false - }, - { - "id": 606, - "name": "营口", - "parentId": 535, - "code": "081100000", - "isSelected": false - }, - { - "id": 793, - "name": "玉林", - "parentId": 549, - "code": "210600000", - "isSelected": false - }, - { - "id": 757, - "name": "益阳", - "parentId": 547, - "code": "190600000", - "isSelected": false - }, - { - "id": 861, - "name": "榆林", - "parentId": 556, - "code": "270500000", - "isSelected": false - }, - { - "id": 759, - "name": "永州", - "parentId": 547, - "code": "190400000", - "isSelected": false - }, - { - "id": 833, - "name": "玉溪", - "parentId": 554, - "code": "251500000", - "isSelected": false - }, - { - "id": 859, - "name": "延安", - "parentId": 556, - "code": "270700000", - "isSelected": false - }, - { - "id": 578, - "name": "阳泉", - "parentId": 533, - "code": "061100000", - "isSelected": false - }, - { - "id": 696, - "name": "鹰潭", - "parentId": 543, - "code": "150600000", - "isSelected": false - }, - { - "id": 816, - "name": "雅安", - "parentId": 552, - "code": "230400000", - "isSelected": false - }, - { - "id": 783, - "name": "云浮", - "parentId": 548, - "code": "200700000", - "isSelected": false - }, - { - "id": 901, - "name": "伊犁", - "parentId": 560, - "code": "310200000", - "isSelected": false - }, - { - "id": 621, - "name": "延边", - "parentId": 536, - "code": "090300000", - "isSelected": false - }, - { - "id": 628, - "name": "伊春", - "parentId": 537, - "code": "101200000", - "isSelected": false - } - ], - "Z": [ - { - "id": 719, - "name": "郑州", - "parentId": 545, - "code": "170100000", - "isSelected": false - }, - { - "id": 766, - "name": "珠海", - "parentId": 548, - "code": "201800000", - "isSelected": false - }, - { - "id": 780, - "name": "中山", - "parentId": 548, - "code": "200400000", - "isSelected": false - }, - { - "id": 704, - "name": "淄博", - "parentId": 544, - "code": "161800000", - "isSelected": false - }, - { - "id": 646, - "name": "镇江", - "parentId": 539, - "code": "112800000", - "isSelected": false - }, - { - "id": 824, - "name": "遵义", - "parentId": 553, - "code": "240300000", - "isSelected": false - }, - { - "id": 750, - "name": "株洲", - "parentId": 547, - "code": "191300000", - "isSelected": false - }, - { - "id": 770, - "name": "湛江", - "parentId": 548, - "code": "202200000", - "isSelected": false - }, - { - "id": 772, - "name": "肇庆", - "parentId": 548, - "code": "202400000", - "isSelected": false - }, - { - "id": 687, - "name": "漳州", - "parentId": 542, - "code": "140600000", - "isSelected": false - }, - { - "id": 734, - "name": "周口", - "parentId": 545, - "code": "170600000", - "isSelected": false - }, - { - "id": 571, - "name": "张家口", - "parentId": 532, - "code": "051000000", - "isSelected": false - }, - { - "id": 705, - "name": "枣庄", - "parentId": 544, - "code": "161900000", - "isSelected": false - }, - { - "id": 579, - "name": "长治", - "parentId": 533, - "code": "061000000", - "isSelected": false - }, - { - "id": 735, - "name": "驻马店", - "parentId": 545, - "code": "170700000", - "isSelected": false - }, - { - "id": 835, - "name": "昭通", - "parentId": 554, - "code": "251300000", - "isSelected": false - }, - { - "id": 818, - "name": "资阳", - "parentId": 552, - "code": "230600000", - "isSelected": false - }, - { - "id": 802, - "name": "自贡", - "parentId": 552, - "code": "231400000", - "isSelected": false - }, - { - "id": 661, - "name": "舟山", - "parentId": 540, - "code": "122600000", - "isSelected": false - }, - { - "id": 756, - "name": "张家界", - "parentId": 547, - "code": "190700000", - "isSelected": false - }, - { - "id": 906, - "name": "中卫", - "parentId": 559, - "code": "300500000", - "isSelected": false - }, - { - "id": 870, - "name": "张掖", - "parentId": 557, - "code": "280800000", - "isSelected": false - } - ] - } - }, - "rows": [] - } -} \ No newline at end of file diff --git a/test/data/2.json b/test/data/2.json deleted file mode 100644 index 0d3b906..0000000 --- a/test/data/2.json +++ /dev/null @@ -1,69 +0,0 @@ -{ - "a.b c": "a.b c", - "book": [ - { - "category": "reference", - "author": "Nigel Rees", - "title": "Sayings of the Century", - "price": 8.95, - "brand": { - "version": "v1.0.0" - } - }, - { - "category": "fiction", - "author": "Evelyn Waugh", - "title": "Sword of Honour", - "price": 12.99, - "brand": { - "version": "v0.0.1" - } - }, - { - "category": "fiction", - "author": "Herman Melville", - "title": "Moby Dick", - "isbn": "0-553-21311-3", - "price": 8.99, - "brand": { - "version": "v1.0.2" - } - }, - { - "category": "fiction", - "author": "J. R. R. Tolkien", - "title": "The Lord of the Rings", - "isbn": "0-395-19395-8", - "price": 22.99, - "brand": { - "version": "v1.0.3" - } - } - ], - "bicycle": { - "color": "red", - "price": 19.95 - }, - "scores": { - "math": { - "score": 100, - "avg": 60 - }, - "english": { - "score": 95, - "avg": 80 - }, - "physic": { - "score": 90, - "avg": 70 - }, - "chemistry": { - "score": 85, - "avg": 80 - }, - "chinese": { - "score": 60, - "avg": 75 - } - } -} \ No newline at end of file diff --git a/test/__init__.py b/tests/__init__.py similarity index 100% rename from test/__init__.py rename to tests/__init__.py diff --git a/test/conftest.py b/tests/conftest.py similarity index 73% rename from test/conftest.py rename to tests/conftest.py index 9feebae..31af0de 100644 --- a/test/conftest.py +++ b/tests/conftest.py @@ -5,7 +5,7 @@ TestCase = namedtuple("TestCase", ("expr", "data", "result")) -with open("test/data/2.json", "rb") as f: +with open("tests/data/2.json", "rb") as f: data = json.load(f) @@ -97,84 +97,82 @@ def value_cases(request): @pytest.fixture( params=[ - TestCase("$.*", data, ["$;a.b c", "$;book", "$;bicycle", "$;scores"]), - TestCase("$.book", data, ["$;book"]), - TestCase("$[book]", data, ["$;book"]), - TestCase("$.'a.b c'", data, ["$;a.b c"]), - TestCase("$['a.b c']", data, ["$;a.b c"]), + TestCase("$.*", data, ["$['a.b c']", "$.book", "$.bicycle", "$.scores"]), + TestCase("$.book", data, ["$.book"]), + TestCase("$[book]", data, ["$.book"]), + TestCase("$.'a.b c'", data, ["$['a.b c']"]), + TestCase("$['a.b c']", data, ["$['a.b c']"]), # recursive descent TestCase( "$..price", data, [ - "$;book;0;price", - "$;book;1;price", - "$;book;2;price", - "$;book;3;price", - "$;bicycle;price", + "$.book[0].price", + "$.book[1].price", + "$.book[2].price", + "$.book[3].price", + "$.bicycle.price", ], ), # slice - TestCase("$.book[1:3]", data, ["$;book;1", "$;book;2"]), - TestCase("$.book[1:-1]", data, ["$;book;1", "$;book;2"]), - TestCase("$.book[0:-1:2]", data, ["$;book;0", "$;book;2"]), + TestCase("$.book[1:3]", data, ["$.book[1]", "$.book[2]"]), + TestCase("$.book[1:-1]", data, ["$.book[1]", "$.book[2]"]), + TestCase("$.book[0:-1:2]", data, ["$.book[0]", "$.book[2]"]), TestCase("$.book[-1:1]", data, []), TestCase("$.book[-1:-11:3]", data, []), - TestCase("$.book[:]", data, ["$;book;0", "$;book;1", "$;book;2", "$;book;3"]), + TestCase("$.book[:]", data, ["$.book[0]", "$.book[1]", "$.book[2]", "$.book[3]"]), # filter TestCase( "$.book[?(@.price>8 and @.price<9)].price", data, - ["$;book;0;price", "$;book;2;price"], - ), - TestCase( - '$.book[?(@.category=="reference")].category', data, ["$;book;0;category"] + ["$.book[0].price", "$.book[2].price"], ), + TestCase('$.book[?(@.category=="reference")].category', data, ["$.book[0].category"]), TestCase( '$.book[?(@.category!="reference" and @.price<9)].title', data, - ["$;book;2;title"], + ["$.book[2].title"], ), TestCase( '$.book[?(@.author=="Herman Melville" or @.author=="Evelyn Waugh")].author', data, - ["$;book;1;author", "$;book;2;author"], + ["$.book[1].author", "$.book[2].author"], ), # sort TestCase( "$.book[/(price)].price", data, - ["$;book;0;price", "$;book;2;price", "$;book;1;price", "$;book;3;price"], + ["$.book[0].price", "$.book[2].price", "$.book[1].price", "$.book[3].price"], ), TestCase( "$.book[/(~price)].price", data, - ["$;book;3;price", "$;book;1;price", "$;book;2;price", "$;book;0;price"], + ["$.book[3].price", "$.book[1].price", "$.book[2].price", "$.book[0].price"], ), TestCase( "$.book[/(category,price)].price", data, - ["$;book;2;price", "$;book;1;price", "$;book;3;price", "$;book;0;price"], + ["$.book[2].price", "$.book[1].price", "$.book[3].price", "$.book[0].price"], ), TestCase( "$.book[/(brand.version)].brand.version", data, [ - "$;book;1;brand;version", - "$;book;0;brand;version", - "$;book;2;brand;version", - "$;book;3;brand;version", + "$.book[1].brand.version", + "$.book[0].brand.version", + "$.book[2].brand.version", + "$.book[3].brand.version", ], ), TestCase( "$.scores[/(score)].score", data, [ - "$;scores;chinese;score", - "$;scores;chemistry;score", - "$;scores;physic;score", - "$;scores;english;score", - "$;scores;math;score", + "$.scores.chinese.score", + "$.scores.chemistry.score", + "$.scores.physic.score", + "$.scores.english.score", + "$.scores.math.score", ], ), ] diff --git a/tests/data/1.json b/tests/data/1.json new file mode 100644 index 0000000..84b503d --- /dev/null +++ b/tests/data/1.json @@ -0,0 +1,2358 @@ +{ + "state": 1, + "message": "success", + "content": { + "data": { + "allCitySearchLabels": { + "A": [ + { + "id": 723, + "name": "安阳", + "parentId": 545, + "code": "171500000", + "isSelected": false + }, + { + "id": 601, + "name": "鞍山", + "parentId": 535, + "code": "081600000", + "isSelected": false + }, + { + "id": 671, + "name": "安庆", + "parentId": 541, + "code": "131800000", + "isSelected": false + }, + { + "id": 825, + "name": "安顺", + "parentId": 553, + "code": "240400000", + "isSelected": false + }, + { + "id": 862, + "name": "安康", + "parentId": 556, + "code": "270400000", + "isSelected": false + }, + { + "id": 897, + "name": "阿克苏", + "parentId": 560, + "code": "311800000", + "isSelected": false + }, + { + "id": 598, + "name": "阿拉善盟", + "parentId": 534, + "code": "070300000", + "isSelected": false + }, + { + "id": 105795, + "name": "澳门", + "parentId": 562, + "code": "330100000", + "isSelected": false + }, + { + "id": 903, + "name": "阿勒泰", + "parentId": 560, + "code": "310400000", + "isSelected": false + }, + { + "id": 819, + "name": "阿坝藏族羌族自治州", + "parentId": 552, + "code": "230700000", + "isSelected": false + } + ], + "B": [ + { + "id": 5, + "name": "北京", + "parentId": 1, + "code": "010100000", + "isSelected": false + }, + { + "id": 570, + "name": "保定", + "parentId": 532, + "code": "051100000", + "isSelected": false + }, + { + "id": 666, + "name": "蚌埠", + "parentId": 541, + "code": "131300000", + "isSelected": false + }, + { + "id": 588, + "name": "包头", + "parentId": 534, + "code": "071300000", + "isSelected": false + }, + { + "id": 717, + "name": "滨州", + "parentId": 544, + "code": "161400000", + "isSelected": false + }, + { + "id": 856, + "name": "宝鸡", + "parentId": 556, + "code": "271000000", + "isSelected": false + }, + { + "id": 678, + "name": "亳州", + "parentId": 541, + "code": "130500000", + "isSelected": false + }, + { + "id": 789, + "name": "北海", + "parentId": 549, + "code": "211000000", + "isSelected": false + }, + { + "id": 828, + "name": "毕节", + "parentId": 553, + "code": "240700000", + "isSelected": false + }, + { + "id": 794, + "name": "百色", + "parentId": 549, + "code": "210500000", + "isSelected": false + }, + { + "id": 817, + "name": "巴中", + "parentId": 552, + "code": "230500000", + "isSelected": false + }, + { + "id": 834, + "name": "保山", + "parentId": 554, + "code": "251400000", + "isSelected": false + }, + { + "id": 597, + "name": "巴彦淖尔", + "parentId": 534, + "code": "070400000", + "isSelected": false + }, + { + "id": 867, + "name": "白银", + "parentId": 557, + "code": "281100000", + "isSelected": false + }, + { + "id": 620, + "name": "白城", + "parentId": 536, + "code": "090400000", + "isSelected": false + }, + { + "id": 603, + "name": "本溪", + "parentId": 535, + "code": "081400000", + "isSelected": false + }, + { + "id": 896, + "name": "巴音郭楞", + "parentId": 560, + "code": "311700000", + "isSelected": false + }, + { + "id": 618, + "name": "白山", + "parentId": 536, + "code": "090600000", + "isSelected": false + } + ], + "C": [ + { + "id": 801, + "name": "成都", + "parentId": 552, + "code": "230100000", + "isSelected": false + }, + { + "id": 749, + "name": "长沙", + "parentId": 547, + "code": "190100000", + "isSelected": false + }, + { + "id": 8, + "name": "重庆", + "parentId": 4, + "code": "040100000", + "isSelected": false + }, + { + "id": 613, + "name": "长春", + "parentId": 536, + "code": "090100000", + "isSelected": false + }, + { + "id": 638, + "name": "常州", + "parentId": 539, + "code": "112000000", + "isSelected": false + }, + { + "id": 573, + "name": "沧州", + "parentId": 532, + "code": "050800000", + "isSelected": false + }, + { + "id": 758, + "name": "郴州", + "parentId": 547, + "code": "190500000", + "isSelected": false + }, + { + "id": 590, + "name": "赤峰", + "parentId": 534, + "code": "071100000", + "isSelected": false + }, + { + "id": 673, + "name": "滁州", + "parentId": 541, + "code": "131100000", + "isSelected": false + }, + { + "id": 755, + "name": "常德", + "parentId": 547, + "code": "190800000", + "isSelected": false + }, + { + "id": 572, + "name": "承德", + "parentId": 532, + "code": "050900000", + "isSelected": false + }, + { + "id": 781, + "name": "潮州", + "parentId": 548, + "code": "200500000", + "isSelected": false + }, + { + "id": 611, + "name": "朝阳", + "parentId": 535, + "code": "080600000", + "isSelected": false + }, + { + "id": 905, + "name": "崇左", + "parentId": 549, + "code": "211400000", + "isSelected": false + }, + { + "id": 679, + "name": "池州", + "parentId": 541, + "code": "130600000", + "isSelected": false + }, + { + "id": 836, + "name": "楚雄", + "parentId": 554, + "code": "251200000", + "isSelected": false + }, + { + "id": 894, + "name": "昌吉", + "parentId": 560, + "code": "311500000", + "isSelected": false + }, + { + "id": 848, + "name": "昌都", + "parentId": 555, + "code": "260200000", + "isSelected": false + } + ], + "D": [ + { + "id": 779, + "name": "东莞", + "parentId": 548, + "code": "200300000", + "isSelected": false + }, + { + "id": 600, + "name": "大连", + "parentId": 535, + "code": "081700000", + "isSelected": false + }, + { + "id": 715, + "name": "德州", + "parentId": 544, + "code": "161600000", + "isSelected": false + }, + { + "id": 805, + "name": "德阳", + "parentId": 552, + "code": "231700000", + "isSelected": false + }, + { + "id": 577, + "name": "大同", + "parentId": 533, + "code": "061200000", + "isSelected": false + }, + { + "id": 706, + "name": "东营", + "parentId": 544, + "code": "162000000", + "isSelected": false + }, + { + "id": 815, + "name": "达州", + "parentId": 552, + "code": "230300000", + "isSelected": false + }, + { + "id": 627, + "name": "大庆", + "parentId": 537, + "code": "101300000", + "isSelected": false + }, + { + "id": 604, + "name": "丹东", + "parentId": 535, + "code": "081300000", + "isSelected": false + }, + { + "id": 841, + "name": "大理", + "parentId": 554, + "code": "250700000", + "isSelected": false + }, + { + "id": 874, + "name": "定西", + "parentId": 557, + "code": "280400000", + "isSelected": false + }, + { + "id": 107620, + "name": "儋州", + "parentId": 550, + "code": "220201000", + "isSelected": false + }, + { + "id": 842, + "name": "德宏", + "parentId": 554, + "code": "250600000", + "isSelected": false + } + ], + "E": [ + { + "id": 592, + "name": "鄂尔多斯", + "parentId": 534, + "code": "070900000", + "isSelected": false + }, + { + "id": 741, + "name": "鄂州", + "parentId": 546, + "code": "181600000", + "isSelected": false + }, + { + "id": 748, + "name": "恩施", + "parentId": 546, + "code": "180300000", + "isSelected": false + } + ], + "F": [ + { + "id": 768, + "name": "佛山", + "parentId": 548, + "code": "202000000", + "isSelected": false + }, + { + "id": 681, + "name": "福州", + "parentId": 542, + "code": "140100000", + "isSelected": false + }, + { + "id": 674, + "name": "阜阳", + "parentId": 541, + "code": "131000000", + "isSelected": false + }, + { + "id": 700, + "name": "抚州", + "parentId": 543, + "code": "150200000", + "isSelected": false + }, + { + "id": 602, + "name": "抚顺", + "parentId": 535, + "code": "081500000", + "isSelected": false + }, + { + "id": 790, + "name": "防城港", + "parentId": 549, + "code": "210900000", + "isSelected": false + }, + { + "id": 607, + "name": "阜新", + "parentId": 535, + "code": "081000000", + "isSelected": false + } + ], + "G": [ + { + "id": 763, + "name": "广州", + "parentId": 548, + "code": "200100000", + "isSelected": false + }, + { + "id": 822, + "name": "贵阳", + "parentId": 553, + "code": "240100000", + "isSelected": false + }, + { + "id": 697, + "name": "赣州", + "parentId": 543, + "code": "150500000", + "isSelected": false + }, + { + "id": 787, + "name": "桂林", + "parentId": 549, + "code": "211200000", + "isSelected": false + }, + { + "id": 807, + "name": "广元", + "parentId": 552, + "code": "231900000", + "isSelected": false + }, + { + "id": 792, + "name": "贵港", + "parentId": 549, + "code": "210700000", + "isSelected": false + }, + { + "id": 814, + "name": "广安", + "parentId": 552, + "code": "230200000", + "isSelected": false + }, + { + "id": 820, + "name": "甘孜藏族自治州", + "parentId": 552, + "code": "230800000", + "isSelected": false + }, + { + "id": 889, + "name": "固原", + "parentId": 559, + "code": "300400000", + "isSelected": false + } + ], + "H": [ + { + "id": 653, + "name": "杭州", + "parentId": 540, + "code": "120100000", + "isSelected": false + }, + { + "id": 664, + "name": "合肥", + "parentId": 541, + "code": "130100000", + "isSelected": false + }, + { + "id": 773, + "name": "惠州", + "parentId": 548, + "code": "202500000", + "isSelected": false + }, + { + "id": 622, + "name": "哈尔滨", + "parentId": 537, + "code": "100100000", + "isSelected": false + }, + { + "id": 799, + "name": "海口", + "parentId": 550, + "code": "220100000", + "isSelected": false + }, + { + "id": 587, + "name": "呼和浩特", + "parentId": 534, + "code": "070100000", + "isSelected": false + }, + { + "id": 568, + "name": "邯郸", + "parentId": 532, + "code": "051300000", + "isSelected": false + }, + { + "id": 643, + "name": "淮安", + "parentId": 539, + "code": "112500000", + "isSelected": false + }, + { + "id": 718, + "name": "菏泽", + "parentId": 544, + "code": "160200000", + "isSelected": false + }, + { + "id": 657, + "name": "湖州", + "parentId": 540, + "code": "122200000", + "isSelected": false + }, + { + "id": 752, + "name": "衡阳", + "parentId": 547, + "code": "191100000", + "isSelected": false + }, + { + "id": 575, + "name": "衡水", + "parentId": 532, + "code": "050600000", + "isSelected": false + }, + { + "id": 776, + "name": "河源", + "parentId": 548, + "code": "201400000", + "isSelected": false + }, + { + "id": 108353, + "name": "海外", + "parentId": 108352, + "code": "350100000", + "isSelected": false + }, + { + "id": 737, + "name": "黄石", + "parentId": 546, + "code": "181200000", + "isSelected": false + }, + { + "id": 745, + "name": "黄冈", + "parentId": 546, + "code": "181100000", + "isSelected": false + }, + { + "id": 724, + "name": "鹤壁", + "parentId": 545, + "code": "171600000", + "isSelected": false + }, + { + "id": 760, + "name": "怀化", + "parentId": 547, + "code": "190300000", + "isSelected": false + }, + { + "id": 860, + "name": "汉中", + "parentId": 556, + "code": "270600000", + "isSelected": false + }, + { + "id": 667, + "name": "淮南", + "parentId": 541, + "code": "131400000", + "isSelected": false + }, + { + "id": 669, + "name": "淮北", + "parentId": 541, + "code": "131600000", + "isSelected": false + }, + { + "id": 612, + "name": "葫芦岛", + "parentId": 535, + "code": "080500000", + "isSelected": false + }, + { + "id": 795, + "name": "贺州", + "parentId": 549, + "code": "210400000", + "isSelected": false + }, + { + "id": 593, + "name": "呼伦贝尔", + "parentId": 534, + "code": "070800000", + "isSelected": false + }, + { + "id": 672, + "name": "黄山", + "parentId": 541, + "code": "131900000", + "isSelected": false + }, + { + "id": 796, + "name": "河池", + "parentId": 549, + "code": "210300000", + "isSelected": false + }, + { + "id": 625, + "name": "鹤岗", + "parentId": 537, + "code": "101500000", + "isSelected": false + }, + { + "id": 879, + "name": "海东", + "parentId": 558, + "code": "290200000", + "isSelected": false + }, + { + "id": 837, + "name": "红河", + "parentId": 554, + "code": "251100000", + "isSelected": false + }, + { + "id": 632, + "name": "黑河", + "parentId": 537, + "code": "100800000", + "isSelected": false + }, + { + "id": 893, + "name": "哈密", + "parentId": 560, + "code": "311400000", + "isSelected": false + } + ], + "J": [ + { + "id": 702, + "name": "济南", + "parentId": 544, + "code": "160100000", + "isSelected": false + }, + { + "id": 659, + "name": "金华", + "parentId": 540, + "code": "122400000", + "isSelected": false + }, + { + "id": 656, + "name": "嘉兴", + "parentId": 540, + "code": "122100000", + "isSelected": false + }, + { + "id": 769, + "name": "江门", + "parentId": 548, + "code": "202100000", + "isSelected": false + }, + { + "id": 709, + "name": "济宁", + "parentId": 544, + "code": "162300000", + "isSelected": false + }, + { + "id": 694, + "name": "九江", + "parentId": 543, + "code": "150800000", + "isSelected": false + }, + { + "id": 782, + "name": "揭阳", + "parentId": 548, + "code": "200600000", + "isSelected": false + }, + { + "id": 582, + "name": "晋中", + "parentId": 533, + "code": "060700000", + "isSelected": false + }, + { + "id": 744, + "name": "荆州", + "parentId": 546, + "code": "181900000", + "isSelected": false + }, + { + "id": 614, + "name": "吉林", + "parentId": 536, + "code": "091000000", + "isSelected": false + }, + { + "id": 726, + "name": "焦作", + "parentId": 545, + "code": "171800000", + "isSelected": false + }, + { + "id": 605, + "name": "锦州", + "parentId": 535, + "code": "081200000", + "isSelected": false + }, + { + "id": 698, + "name": "吉安", + "parentId": 543, + "code": "150400000", + "isSelected": false + }, + { + "id": 580, + "name": "晋城", + "parentId": 533, + "code": "060900000", + "isSelected": false + }, + { + "id": 692, + "name": "景德镇", + "parentId": 543, + "code": "151000000", + "isSelected": false + }, + { + "id": 629, + "name": "佳木斯", + "parentId": 537, + "code": "101100000", + "isSelected": false + }, + { + "id": 742, + "name": "荆门", + "parentId": 546, + "code": "181700000", + "isSelected": false + }, + { + "id": 872, + "name": "酒泉", + "parentId": 557, + "code": "280600000", + "isSelected": false + }, + { + "id": 866, + "name": "金昌", + "parentId": 557, + "code": "281200000", + "isSelected": false + }, + { + "id": 107292, + "name": "济源", + "parentId": 545, + "code": "170711000", + "isSelected": false + }, + { + "id": 624, + "name": "鸡西", + "parentId": 537, + "code": "101600000", + "isSelected": false + }, + { + "id": 865, + "name": "嘉峪关", + "parentId": 557, + "code": "281300000", + "isSelected": false + } + ], + "K": [ + { + "id": 831, + "name": "昆明", + "parentId": 554, + "code": "250100000", + "isSelected": false + }, + { + "id": 720, + "name": "开封", + "parentId": 545, + "code": "171200000", + "isSelected": false + }, + { + "id": 899, + "name": "喀什", + "parentId": 560, + "code": "311100000", + "isSelected": false + }, + { + "id": 891, + "name": "克拉玛依", + "parentId": 560, + "code": "311200000", + "isSelected": false + } + ], + "L": [ + { + "id": 574, + "name": "廊坊", + "parentId": 532, + "code": "050700000", + "isSelected": false + }, + { + "id": 864, + "name": "兰州", + "parentId": 557, + "code": "280100000", + "isSelected": false + }, + { + "id": 714, + "name": "临沂", + "parentId": 544, + "code": "162800000", + "isSelected": false + }, + { + "id": 721, + "name": "洛阳", + "parentId": 545, + "code": "171300000", + "isSelected": false + }, + { + "id": 786, + "name": "柳州", + "parentId": 549, + "code": "211300000", + "isSelected": false + }, + { + "id": 716, + "name": "聊城", + "parentId": 544, + "code": "161500000", + "isSelected": false + }, + { + "id": 642, + "name": "连云港", + "parentId": 539, + "code": "112400000", + "isSelected": false + }, + { + "id": 677, + "name": "六安", + "parentId": 541, + "code": "130400000", + "isSelected": false + }, + { + "id": 804, + "name": "泸州", + "parentId": 552, + "code": "231600000", + "isSelected": false + }, + { + "id": 729, + "name": "漯河", + "parentId": 545, + "code": "171000000", + "isSelected": false + }, + { + "id": 585, + "name": "临汾", + "parentId": 533, + "code": "060400000", + "isSelected": false + }, + { + "id": 810, + "name": "乐山", + "parentId": 552, + "code": "232200000", + "isSelected": false + }, + { + "id": 689, + "name": "龙岩", + "parentId": 542, + "code": "140400000", + "isSelected": false + }, + { + "id": 663, + "name": "丽水", + "parentId": 540, + "code": "122800000", + "isSelected": false + }, + { + "id": 608, + "name": "辽阳", + "parentId": 535, + "code": "080900000", + "isSelected": false + }, + { + "id": 823, + "name": "六盘水", + "parentId": 553, + "code": "240200000", + "isSelected": false + }, + { + "id": 847, + "name": "拉萨", + "parentId": 555, + "code": "260100000", + "isSelected": false + }, + { + "id": 761, + "name": "娄底", + "parentId": 547, + "code": "190200000", + "isSelected": false + }, + { + "id": 843, + "name": "丽江", + "parentId": 554, + "code": "250500000", + "isSelected": false + }, + { + "id": 586, + "name": "吕梁", + "parentId": 533, + "code": "060300000", + "isSelected": false + }, + { + "id": 821, + "name": "凉山彝族自治州", + "parentId": 552, + "code": "230900000", + "isSelected": false + }, + { + "id": 616, + "name": "辽源", + "parentId": 536, + "code": "090800000", + "isSelected": false + }, + { + "id": 713, + "name": "莱芜", + "parentId": 544, + "code": "162700000", + "isSelected": false + }, + { + "id": 876, + "name": "临夏", + "parentId": 557, + "code": "280200000", + "isSelected": false + }, + { + "id": 904, + "name": "来宾", + "parentId": 549, + "code": "210200000", + "isSelected": false + }, + { + "id": 875, + "name": "陇南", + "parentId": 557, + "code": "280300000", + "isSelected": false + }, + { + "id": 853, + "name": "林芝", + "parentId": 555, + "code": "260700000", + "isSelected": false + }, + { + "id": 846, + "name": "临沧", + "parentId": 554, + "code": "250200000", + "isSelected": false + } + ], + "M": [ + { + "id": 806, + "name": "绵阳", + "parentId": 552, + "code": "231800000", + "isSelected": false + }, + { + "id": 774, + "name": "梅州", + "parentId": 548, + "code": "202600000", + "isSelected": false + }, + { + "id": 668, + "name": "马鞍山", + "parentId": 541, + "code": "131500000", + "isSelected": false + }, + { + "id": 812, + "name": "眉山", + "parentId": 552, + "code": "231300000", + "isSelected": false + }, + { + "id": 771, + "name": "茂名", + "parentId": 548, + "code": "202300000", + "isSelected": false + }, + { + "id": 631, + "name": "牡丹江", + "parentId": 537, + "code": "100900000", + "isSelected": false + } + ], + "N": [ + { + "id": 635, + "name": "南京", + "parentId": 539, + "code": "110100000", + "isSelected": false + }, + { + "id": 691, + "name": "南昌", + "parentId": 543, + "code": "150100000", + "isSelected": false + }, + { + "id": 654, + "name": "宁波", + "parentId": 540, + "code": "121900000", + "isSelected": false + }, + { + "id": 785, + "name": "南宁", + "parentId": 549, + "code": "210100000", + "isSelected": false + }, + { + "id": 641, + "name": "南通", + "parentId": 539, + "code": "112300000", + "isSelected": false + }, + { + "id": 731, + "name": "南阳", + "parentId": 545, + "code": "170300000", + "isSelected": false + }, + { + "id": 811, + "name": "南充", + "parentId": 552, + "code": "232300000", + "isSelected": false + }, + { + "id": 690, + "name": "宁德", + "parentId": 542, + "code": "140300000", + "isSelected": false + }, + { + "id": 809, + "name": "内江", + "parentId": 552, + "code": "232100000", + "isSelected": false + }, + { + "id": 688, + "name": "南平", + "parentId": 542, + "code": "140500000", + "isSelected": false + } + ], + "P": [ + { + "id": 683, + "name": "莆田", + "parentId": 542, + "code": "141000000", + "isSelected": false + }, + { + "id": 722, + "name": "平顶山", + "parentId": 545, + "code": "171400000", + "isSelected": false + }, + { + "id": 727, + "name": "濮阳", + "parentId": 545, + "code": "171900000", + "isSelected": false + }, + { + "id": 609, + "name": "盘锦", + "parentId": 535, + "code": "080800000", + "isSelected": false + }, + { + "id": 693, + "name": "萍乡", + "parentId": 543, + "code": "150900000", + "isSelected": false + }, + { + "id": 803, + "name": "攀枝花", + "parentId": 552, + "code": "231500000", + "isSelected": false + }, + { + "id": 10163, + "name": "普洱", + "parentId": 554, + "code": "251700000", + "isSelected": false + }, + { + "id": 871, + "name": "平凉", + "parentId": 557, + "code": "280700000", + "isSelected": false + } + ], + "Q": [ + { + "id": 703, + "name": "青岛", + "parentId": 544, + "code": "161700000", + "isSelected": false + }, + { + "id": 685, + "name": "泉州", + "parentId": 542, + "code": "140800000", + "isSelected": false + }, + { + "id": 778, + "name": "清远", + "parentId": 548, + "code": "200200000", + "isSelected": false + }, + { + "id": 567, + "name": "秦皇岛", + "parentId": 532, + "code": "051400000", + "isSelected": false + }, + { + "id": 832, + "name": "曲靖", + "parentId": 554, + "code": "251600000", + "isSelected": false + }, + { + "id": 660, + "name": "衢州", + "parentId": 540, + "code": "122500000", + "isSelected": false + }, + { + "id": 623, + "name": "齐齐哈尔", + "parentId": 537, + "code": "101700000", + "isSelected": false + }, + { + "id": 791, + "name": "钦州", + "parentId": 549, + "code": "210800000", + "isSelected": false + }, + { + "id": 873, + "name": "庆阳", + "parentId": 557, + "code": "280500000", + "isSelected": false + }, + { + "id": 827, + "name": "黔西南", + "parentId": 553, + "code": "240600000", + "isSelected": false + }, + { + "id": 829, + "name": "黔东南", + "parentId": 553, + "code": "240800000", + "isSelected": false + }, + { + "id": 830, + "name": "黔南", + "parentId": 553, + "code": "240900000", + "isSelected": false + }, + { + "id": 630, + "name": "七台河", + "parentId": 537, + "code": "101000000", + "isSelected": false + } + ], + "R": [ + { + "id": 712, + "name": "日照", + "parentId": 544, + "code": "162600000", + "isSelected": false + }, + { + "id": 850, + "name": "日喀则", + "parentId": 555, + "code": "260400000", + "isSelected": false + } + ], + "S": [ + { + "id": 765, + "name": "深圳", + "parentId": 548, + "code": "201700000", + "isSelected": false + }, + { + "id": 6, + "name": "上海", + "parentId": 2, + "code": "020100000", + "isSelected": false + }, + { + "id": 639, + "name": "苏州", + "parentId": 539, + "code": "112100000", + "isSelected": false + }, + { + "id": 599, + "name": "沈阳", + "parentId": 535, + "code": "080100000", + "isSelected": false + }, + { + "id": 565, + "name": "石家庄", + "parentId": 532, + "code": "050100000", + "isSelected": false + }, + { + "id": 658, + "name": "绍兴", + "parentId": 540, + "code": "122300000", + "isSelected": false + }, + { + "id": 648, + "name": "宿迁", + "parentId": 539, + "code": "113000000", + "isSelected": false + }, + { + "id": 767, + "name": "汕头", + "parentId": 548, + "code": "201900000", + "isSelected": false + }, + { + "id": 800, + "name": "三亚", + "parentId": 550, + "code": "221800000", + "isSelected": false + }, + { + "id": 732, + "name": "商丘", + "parentId": 545, + "code": "170400000", + "isSelected": false + }, + { + "id": 701, + "name": "上饶", + "parentId": 543, + "code": "151100000", + "isSelected": false + }, + { + "id": 675, + "name": "宿州", + "parentId": 541, + "code": "130200000", + "isSelected": false + }, + { + "id": 764, + "name": "韶关", + "parentId": 548, + "code": "201600000", + "isSelected": false + }, + { + "id": 738, + "name": "十堰", + "parentId": 546, + "code": "181300000", + "isSelected": false + }, + { + "id": 808, + "name": "遂宁", + "parentId": 552, + "code": "232000000", + "isSelected": false + }, + { + "id": 753, + "name": "邵阳", + "parentId": 547, + "code": "191000000", + "isSelected": false + }, + { + "id": 775, + "name": "汕尾", + "parentId": 548, + "code": "201500000", + "isSelected": false + }, + { + "id": 684, + "name": "三明", + "parentId": 542, + "code": "140900000", + "isSelected": false + }, + { + "id": 747, + "name": "随州", + "parentId": 546, + "code": "180200000", + "isSelected": false + }, + { + "id": 633, + "name": "绥化", + "parentId": 537, + "code": "100700000", + "isSelected": false + }, + { + "id": 619, + "name": "松原", + "parentId": 536, + "code": "090500000", + "isSelected": false + }, + { + "id": 730, + "name": "三门峡", + "parentId": 545, + "code": "170200000", + "isSelected": false + }, + { + "id": 615, + "name": "四平", + "parentId": 536, + "code": "090900000", + "isSelected": false + }, + { + "id": 581, + "name": "朔州", + "parentId": 533, + "code": "060800000", + "isSelected": false + }, + { + "id": 863, + "name": "商洛", + "parentId": 556, + "code": "270300000", + "isSelected": false + }, + { + "id": 108393, + "name": "石河子", + "parentId": 560, + "code": "310500000", + "isSelected": false + }, + { + "id": 907, + "name": "三沙", + "parentId": 550, + "code": "220200000", + "isSelected": false + }, + { + "id": 887, + "name": "石嘴山", + "parentId": 559, + "code": "300200000", + "isSelected": false + }, + { + "id": 626, + "name": "双鸭山", + "parentId": 537, + "code": "101400000", + "isSelected": false + }, + { + "id": 849, + "name": "山南", + "parentId": 555, + "code": "260300000", + "isSelected": false + } + ], + "T": [ + { + "id": 7, + "name": "天津", + "parentId": 3, + "code": "030100000", + "isSelected": false + }, + { + "id": 576, + "name": "太原", + "parentId": 533, + "code": "060100000", + "isSelected": false + }, + { + "id": 566, + "name": "唐山", + "parentId": 532, + "code": "051500000", + "isSelected": false + }, + { + "id": 662, + "name": "台州", + "parentId": 540, + "code": "122700000", + "isSelected": false + }, + { + "id": 647, + "name": "泰州", + "parentId": 539, + "code": "112900000", + "isSelected": false + }, + { + "id": 710, + "name": "泰安", + "parentId": 544, + "code": "162400000", + "isSelected": false + }, + { + "id": 591, + "name": "通辽", + "parentId": 534, + "code": "071000000", + "isSelected": false + }, + { + "id": 670, + "name": "铜陵", + "parentId": 541, + "code": "131700000", + "isSelected": false + }, + { + "id": 868, + "name": "天水", + "parentId": 557, + "code": "281000000", + "isSelected": false + }, + { + "id": 826, + "name": "铜仁", + "parentId": 553, + "code": "240500000", + "isSelected": false + }, + { + "id": 610, + "name": "铁岭", + "parentId": 535, + "code": "080700000", + "isSelected": false + }, + { + "id": 855, + "name": "铜川", + "parentId": 556, + "code": "271100000", + "isSelected": false + }, + { + "id": 108355, + "name": "台湾", + "parentId": 563, + "code": "340100000", + "isSelected": false + }, + { + "id": 617, + "name": "通化", + "parentId": 536, + "code": "090700000", + "isSelected": false + }, + { + "id": 902, + "name": "塔城", + "parentId": 560, + "code": "310300000", + "isSelected": false + }, + { + "id": 107384, + "name": "天门", + "parentId": 546, + "code": "180311000", + "isSelected": false + } + ], + "W": [ + { + "id": 736, + "name": "武汉", + "parentId": 546, + "code": "180100000", + "isSelected": false + }, + { + "id": 636, + "name": "无锡", + "parentId": 539, + "code": "111800000", + "isSelected": false + }, + { + "id": 655, + "name": "温州", + "parentId": 540, + "code": "122000000", + "isSelected": false + }, + { + "id": 708, + "name": "潍坊", + "parentId": 544, + "code": "162200000", + "isSelected": false + }, + { + "id": 665, + "name": "芜湖", + "parentId": 541, + "code": "131200000", + "isSelected": false + }, + { + "id": 890, + "name": "乌鲁木齐", + "parentId": 560, + "code": "310100000", + "isSelected": false + }, + { + "id": 711, + "name": "威海", + "parentId": 544, + "code": "162500000", + "isSelected": false + }, + { + "id": 858, + "name": "渭南", + "parentId": 556, + "code": "270800000", + "isSelected": false + }, + { + "id": 788, + "name": "梧州", + "parentId": 549, + "code": "211100000", + "isSelected": false + }, + { + "id": 596, + "name": "乌兰察布", + "parentId": 534, + "code": "070500000", + "isSelected": false + }, + { + "id": 869, + "name": "武威", + "parentId": 557, + "code": "280900000", + "isSelected": false + }, + { + "id": 589, + "name": "乌海", + "parentId": 534, + "code": "071200000", + "isSelected": false + }, + { + "id": 888, + "name": "吴忠", + "parentId": 559, + "code": "300300000", + "isSelected": false + }, + { + "id": 838, + "name": "文山", + "parentId": 554, + "code": "251000000", + "isSelected": false + } + ], + "X": [ + { + "id": 854, + "name": "西安", + "parentId": 556, + "code": "270100000", + "isSelected": false + }, + { + "id": 682, + "name": "厦门", + "parentId": 542, + "code": "141100000", + "isSelected": false + }, + { + "id": 637, + "name": "徐州", + "parentId": 539, + "code": "111900000", + "isSelected": false + }, + { + "id": 857, + "name": "咸阳", + "parentId": 556, + "code": "270900000", + "isSelected": false + }, + { + "id": 725, + "name": "新乡", + "parentId": 545, + "code": "171700000", + "isSelected": false + }, + { + "id": 740, + "name": "襄阳", + "parentId": 546, + "code": "181500000", + "isSelected": false + }, + { + "id": 728, + "name": "许昌", + "parentId": 545, + "code": "171100000", + "isSelected": false + }, + { + "id": 569, + "name": "邢台", + "parentId": 532, + "code": "051200000", + "isSelected": false + }, + { + "id": 878, + "name": "西宁", + "parentId": 558, + "code": "290100000", + "isSelected": false + }, + { + "id": 733, + "name": "信阳", + "parentId": 545, + "code": "170500000", + "isSelected": false + }, + { + "id": 743, + "name": "孝感", + "parentId": 546, + "code": "181800000", + "isSelected": false + }, + { + "id": 751, + "name": "湘潭", + "parentId": 547, + "code": "191200000", + "isSelected": false + }, + { + "id": 695, + "name": "新余", + "parentId": 543, + "code": "150700000", + "isSelected": false + }, + { + "id": 746, + "name": "咸宁", + "parentId": 546, + "code": "181000000", + "isSelected": false + }, + { + "id": 680, + "name": "宣城", + "parentId": 541, + "code": "130700000", + "isSelected": false + }, + { + "id": 584, + "name": "忻州", + "parentId": 533, + "code": "060500000", + "isSelected": false + }, + { + "id": 105794, + "name": "香港", + "parentId": 561, + "code": "320100000", + "isSelected": false + }, + { + "id": 840, + "name": "西双版纳", + "parentId": 554, + "code": "250800000", + "isSelected": false + }, + { + "id": 762, + "name": "湘西土家族苗族自治州", + "parentId": 547, + "code": "191400000", + "isSelected": false + }, + { + "id": 594, + "name": "兴安盟", + "parentId": 534, + "code": "070700000", + "isSelected": false + }, + { + "id": 595, + "name": "锡林郭勒盟", + "parentId": 534, + "code": "070600000", + "isSelected": false + } + ], + "Y": [ + { + "id": 707, + "name": "烟台", + "parentId": 544, + "code": "162100000", + "isSelected": false + }, + { + "id": 886, + "name": "银川", + "parentId": 559, + "code": "300100000", + "isSelected": false + }, + { + "id": 645, + "name": "扬州", + "parentId": 539, + "code": "112700000", + "isSelected": false + }, + { + "id": 644, + "name": "盐城", + "parentId": 539, + "code": "112600000", + "isSelected": false + }, + { + "id": 739, + "name": "宜昌", + "parentId": 546, + "code": "181400000", + "isSelected": false + }, + { + "id": 583, + "name": "运城", + "parentId": 533, + "code": "060600000", + "isSelected": false + }, + { + "id": 813, + "name": "宜宾", + "parentId": 552, + "code": "231200000", + "isSelected": false + }, + { + "id": 754, + "name": "岳阳", + "parentId": 547, + "code": "190900000", + "isSelected": false + }, + { + "id": 777, + "name": "阳江", + "parentId": 548, + "code": "201300000", + "isSelected": false + }, + { + "id": 699, + "name": "宜春", + "parentId": 543, + "code": "150300000", + "isSelected": false + }, + { + "id": 606, + "name": "营口", + "parentId": 535, + "code": "081100000", + "isSelected": false + }, + { + "id": 793, + "name": "玉林", + "parentId": 549, + "code": "210600000", + "isSelected": false + }, + { + "id": 757, + "name": "益阳", + "parentId": 547, + "code": "190600000", + "isSelected": false + }, + { + "id": 861, + "name": "榆林", + "parentId": 556, + "code": "270500000", + "isSelected": false + }, + { + "id": 759, + "name": "永州", + "parentId": 547, + "code": "190400000", + "isSelected": false + }, + { + "id": 833, + "name": "玉溪", + "parentId": 554, + "code": "251500000", + "isSelected": false + }, + { + "id": 859, + "name": "延安", + "parentId": 556, + "code": "270700000", + "isSelected": false + }, + { + "id": 578, + "name": "阳泉", + "parentId": 533, + "code": "061100000", + "isSelected": false + }, + { + "id": 696, + "name": "鹰潭", + "parentId": 543, + "code": "150600000", + "isSelected": false + }, + { + "id": 816, + "name": "雅安", + "parentId": 552, + "code": "230400000", + "isSelected": false + }, + { + "id": 783, + "name": "云浮", + "parentId": 548, + "code": "200700000", + "isSelected": false + }, + { + "id": 901, + "name": "伊犁", + "parentId": 560, + "code": "310200000", + "isSelected": false + }, + { + "id": 621, + "name": "延边", + "parentId": 536, + "code": "090300000", + "isSelected": false + }, + { + "id": 628, + "name": "伊春", + "parentId": 537, + "code": "101200000", + "isSelected": false + } + ], + "Z": [ + { + "id": 719, + "name": "郑州", + "parentId": 545, + "code": "170100000", + "isSelected": false + }, + { + "id": 766, + "name": "珠海", + "parentId": 548, + "code": "201800000", + "isSelected": false + }, + { + "id": 780, + "name": "中山", + "parentId": 548, + "code": "200400000", + "isSelected": false + }, + { + "id": 704, + "name": "淄博", + "parentId": 544, + "code": "161800000", + "isSelected": false + }, + { + "id": 646, + "name": "镇江", + "parentId": 539, + "code": "112800000", + "isSelected": false + }, + { + "id": 824, + "name": "遵义", + "parentId": 553, + "code": "240300000", + "isSelected": false + }, + { + "id": 750, + "name": "株洲", + "parentId": 547, + "code": "191300000", + "isSelected": false + }, + { + "id": 770, + "name": "湛江", + "parentId": 548, + "code": "202200000", + "isSelected": false + }, + { + "id": 772, + "name": "肇庆", + "parentId": 548, + "code": "202400000", + "isSelected": false + }, + { + "id": 687, + "name": "漳州", + "parentId": 542, + "code": "140600000", + "isSelected": false + }, + { + "id": 734, + "name": "周口", + "parentId": 545, + "code": "170600000", + "isSelected": false + }, + { + "id": 571, + "name": "张家口", + "parentId": 532, + "code": "051000000", + "isSelected": false + }, + { + "id": 705, + "name": "枣庄", + "parentId": 544, + "code": "161900000", + "isSelected": false + }, + { + "id": 579, + "name": "长治", + "parentId": 533, + "code": "061000000", + "isSelected": false + }, + { + "id": 735, + "name": "驻马店", + "parentId": 545, + "code": "170700000", + "isSelected": false + }, + { + "id": 835, + "name": "昭通", + "parentId": 554, + "code": "251300000", + "isSelected": false + }, + { + "id": 818, + "name": "资阳", + "parentId": 552, + "code": "230600000", + "isSelected": false + }, + { + "id": 802, + "name": "自贡", + "parentId": 552, + "code": "231400000", + "isSelected": false + }, + { + "id": 661, + "name": "舟山", + "parentId": 540, + "code": "122600000", + "isSelected": false + }, + { + "id": 756, + "name": "张家界", + "parentId": 547, + "code": "190700000", + "isSelected": false + }, + { + "id": 906, + "name": "中卫", + "parentId": 559, + "code": "300500000", + "isSelected": false + }, + { + "id": 870, + "name": "张掖", + "parentId": 557, + "code": "280800000", + "isSelected": false + } + ] + } + }, + "rows": [] + } +} diff --git a/tests/data/2.json b/tests/data/2.json new file mode 100644 index 0000000..e0ecee3 --- /dev/null +++ b/tests/data/2.json @@ -0,0 +1,69 @@ +{ + "a.b c": "a.b c", + "book": [ + { + "category": "reference", + "author": "Nigel Rees", + "title": "Sayings of the Century", + "price": 8.95, + "brand": { + "version": "v1.0.0" + } + }, + { + "category": "fiction", + "author": "Evelyn Waugh", + "title": "Sword of Honour", + "price": 12.99, + "brand": { + "version": "v0.0.1" + } + }, + { + "category": "fiction", + "author": "Herman Melville", + "title": "Moby Dick", + "isbn": "0-553-21311-3", + "price": 8.99, + "brand": { + "version": "v1.0.2" + } + }, + { + "category": "fiction", + "author": "J. R. R. Tolkien", + "title": "The Lord of the Rings", + "isbn": "0-395-19395-8", + "price": 22.99, + "brand": { + "version": "v1.0.3" + } + } + ], + "bicycle": { + "color": "red", + "price": 19.95 + }, + "scores": { + "math": { + "score": 100, + "avg": 60 + }, + "english": { + "score": 95, + "avg": 80 + }, + "physic": { + "score": 90, + "avg": 70 + }, + "chemistry": { + "score": 85, + "avg": 80 + }, + "chinese": { + "score": 60, + "avg": 75 + } + } +} diff --git a/test/test_jsonpath.py b/tests/test_jsonpath.py similarity index 100% rename from test/test_jsonpath.py rename to tests/test_jsonpath.py diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..4e176f3 --- /dev/null +++ b/uv.lock @@ -0,0 +1,632 @@ +version = 1 +revision = 3 +requires-python = ">=3.8" +resolution-markers = [ + "python_full_version >= '3.10'", + "python_full_version == '3.9.*'", + "python_full_version < '3.9'", +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "coverage" +version = "7.6.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.9'", +] +sdist = { url = "https://files.pythonhosted.org/packages/f7/08/7e37f82e4d1aead42a7443ff06a1e406aabf7302c4f00a546e4b320b994c/coverage-7.6.1.tar.gz", hash = "sha256:953510dfb7b12ab69d20135a0662397f077c59b1e6379a768e97c59d852ee51d", size = 798791, upload-time = "2024-08-04T19:45:30.9Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/61/eb7ce5ed62bacf21beca4937a90fe32545c91a3c8a42a30c6616d48fc70d/coverage-7.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b06079abebbc0e89e6163b8e8f0e16270124c154dc6e4a47b413dd538859af16", size = 206690, upload-time = "2024-08-04T19:43:07.695Z" }, + { url = "https://files.pythonhosted.org/packages/7d/73/041928e434442bd3afde5584bdc3f932fb4562b1597629f537387cec6f3d/coverage-7.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cf4b19715bccd7ee27b6b120e7e9dd56037b9c0681dcc1adc9ba9db3d417fa36", size = 207127, upload-time = "2024-08-04T19:43:10.15Z" }, + { url = "https://files.pythonhosted.org/packages/c7/c8/6ca52b5147828e45ad0242388477fdb90df2c6cbb9a441701a12b3c71bc8/coverage-7.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61c0abb4c85b095a784ef23fdd4aede7a2628478e7baba7c5e3deba61070a02", size = 235654, upload-time = "2024-08-04T19:43:12.405Z" }, + { url = "https://files.pythonhosted.org/packages/d5/da/9ac2b62557f4340270942011d6efeab9833648380109e897d48ab7c1035d/coverage-7.6.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd21f6ae3f08b41004dfb433fa895d858f3f5979e7762d052b12aef444e29afc", size = 233598, upload-time = "2024-08-04T19:43:14.078Z" }, + { url = "https://files.pythonhosted.org/packages/53/23/9e2c114d0178abc42b6d8d5281f651a8e6519abfa0ef460a00a91f80879d/coverage-7.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f59d57baca39b32db42b83b2a7ba6f47ad9c394ec2076b084c3f029b7afca23", size = 234732, upload-time = "2024-08-04T19:43:16.632Z" }, + { url = "https://files.pythonhosted.org/packages/0f/7e/a0230756fb133343a52716e8b855045f13342b70e48e8ad41d8a0d60ab98/coverage-7.6.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a1ac0ae2b8bd743b88ed0502544847c3053d7171a3cff9228af618a068ed9c34", size = 233816, upload-time = "2024-08-04T19:43:19.049Z" }, + { url = "https://files.pythonhosted.org/packages/28/7c/3753c8b40d232b1e5eeaed798c875537cf3cb183fb5041017c1fdb7ec14e/coverage-7.6.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e6a08c0be454c3b3beb105c0596ebdc2371fab6bb90c0c0297f4e58fd7e1012c", size = 232325, upload-time = "2024-08-04T19:43:21.246Z" }, + { url = "https://files.pythonhosted.org/packages/57/e3/818a2b2af5b7573b4b82cf3e9f137ab158c90ea750a8f053716a32f20f06/coverage-7.6.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f5796e664fe802da4f57a168c85359a8fbf3eab5e55cd4e4569fbacecc903959", size = 233418, upload-time = "2024-08-04T19:43:22.945Z" }, + { url = "https://files.pythonhosted.org/packages/c8/fb/4532b0b0cefb3f06d201648715e03b0feb822907edab3935112b61b885e2/coverage-7.6.1-cp310-cp310-win32.whl", hash = "sha256:7bb65125fcbef8d989fa1dd0e8a060999497629ca5b0efbca209588a73356232", size = 209343, upload-time = "2024-08-04T19:43:25.121Z" }, + { url = "https://files.pythonhosted.org/packages/5a/25/af337cc7421eca1c187cc9c315f0a755d48e755d2853715bfe8c418a45fa/coverage-7.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:3115a95daa9bdba70aea750db7b96b37259a81a709223c8448fa97727d546fe0", size = 210136, upload-time = "2024-08-04T19:43:26.851Z" }, + { url = "https://files.pythonhosted.org/packages/ad/5f/67af7d60d7e8ce61a4e2ddcd1bd5fb787180c8d0ae0fbd073f903b3dd95d/coverage-7.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7dea0889685db8550f839fa202744652e87c60015029ce3f60e006f8c4462c93", size = 206796, upload-time = "2024-08-04T19:43:29.115Z" }, + { url = "https://files.pythonhosted.org/packages/e1/0e/e52332389e057daa2e03be1fbfef25bb4d626b37d12ed42ae6281d0a274c/coverage-7.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed37bd3c3b063412f7620464a9ac1314d33100329f39799255fb8d3027da50d3", size = 207244, upload-time = "2024-08-04T19:43:31.285Z" }, + { url = "https://files.pythonhosted.org/packages/aa/cd/766b45fb6e090f20f8927d9c7cb34237d41c73a939358bc881883fd3a40d/coverage-7.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d85f5e9a5f8b73e2350097c3756ef7e785f55bd71205defa0bfdaf96c31616ff", size = 239279, upload-time = "2024-08-04T19:43:33.581Z" }, + { url = "https://files.pythonhosted.org/packages/70/6c/a9ccd6fe50ddaf13442a1e2dd519ca805cbe0f1fcd377fba6d8339b98ccb/coverage-7.6.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bc572be474cafb617672c43fe989d6e48d3c83af02ce8de73fff1c6bb3c198d", size = 236859, upload-time = "2024-08-04T19:43:35.301Z" }, + { url = "https://files.pythonhosted.org/packages/14/6f/8351b465febb4dbc1ca9929505202db909c5a635c6fdf33e089bbc3d7d85/coverage-7.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0420b573964c760df9e9e86d1a9a622d0d27f417e1a949a8a66dd7bcee7bc6", size = 238549, upload-time = "2024-08-04T19:43:37.578Z" }, + { url = "https://files.pythonhosted.org/packages/68/3c/289b81fa18ad72138e6d78c4c11a82b5378a312c0e467e2f6b495c260907/coverage-7.6.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f4aa8219db826ce6be7099d559f8ec311549bfc4046f7f9fe9b5cea5c581c56", size = 237477, upload-time = "2024-08-04T19:43:39.92Z" }, + { url = "https://files.pythonhosted.org/packages/ed/1c/aa1efa6459d822bd72c4abc0b9418cf268de3f60eeccd65dc4988553bd8d/coverage-7.6.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:fc5a77d0c516700ebad189b587de289a20a78324bc54baee03dd486f0855d234", size = 236134, upload-time = "2024-08-04T19:43:41.453Z" }, + { url = "https://files.pythonhosted.org/packages/fb/c8/521c698f2d2796565fe9c789c2ee1ccdae610b3aa20b9b2ef980cc253640/coverage-7.6.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b48f312cca9621272ae49008c7f613337c53fadca647d6384cc129d2996d1133", size = 236910, upload-time = "2024-08-04T19:43:43.037Z" }, + { url = "https://files.pythonhosted.org/packages/7d/30/033e663399ff17dca90d793ee8a2ea2890e7fdf085da58d82468b4220bf7/coverage-7.6.1-cp311-cp311-win32.whl", hash = "sha256:1125ca0e5fd475cbbba3bb67ae20bd2c23a98fac4e32412883f9bcbaa81c314c", size = 209348, upload-time = "2024-08-04T19:43:44.787Z" }, + { url = "https://files.pythonhosted.org/packages/20/05/0d1ccbb52727ccdadaa3ff37e4d2dc1cd4d47f0c3df9eb58d9ec8508ca88/coverage-7.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:8ae539519c4c040c5ffd0632784e21b2f03fc1340752af711f33e5be83a9d6c6", size = 210230, upload-time = "2024-08-04T19:43:46.707Z" }, + { url = "https://files.pythonhosted.org/packages/7e/d4/300fc921dff243cd518c7db3a4c614b7e4b2431b0d1145c1e274fd99bd70/coverage-7.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:95cae0efeb032af8458fc27d191f85d1717b1d4e49f7cb226cf526ff28179778", size = 206983, upload-time = "2024-08-04T19:43:49.082Z" }, + { url = "https://files.pythonhosted.org/packages/e1/ab/6bf00de5327ecb8db205f9ae596885417a31535eeda6e7b99463108782e1/coverage-7.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5621a9175cf9d0b0c84c2ef2b12e9f5f5071357c4d2ea6ca1cf01814f45d2391", size = 207221, upload-time = "2024-08-04T19:43:52.15Z" }, + { url = "https://files.pythonhosted.org/packages/92/8f/2ead05e735022d1a7f3a0a683ac7f737de14850395a826192f0288703472/coverage-7.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:260933720fdcd75340e7dbe9060655aff3af1f0c5d20f46b57f262ab6c86a5e8", size = 240342, upload-time = "2024-08-04T19:43:53.746Z" }, + { url = "https://files.pythonhosted.org/packages/0f/ef/94043e478201ffa85b8ae2d2c79b4081e5a1b73438aafafccf3e9bafb6b5/coverage-7.6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e2ca0ad381b91350c0ed49d52699b625aab2b44b65e1b4e02fa9df0e92ad2d", size = 237371, upload-time = "2024-08-04T19:43:55.993Z" }, + { url = "https://files.pythonhosted.org/packages/1f/0f/c890339dd605f3ebc269543247bdd43b703cce6825b5ed42ff5f2d6122c7/coverage-7.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c44fee9975f04b33331cb8eb272827111efc8930cfd582e0320613263ca849ca", size = 239455, upload-time = "2024-08-04T19:43:57.618Z" }, + { url = "https://files.pythonhosted.org/packages/d1/04/7fd7b39ec7372a04efb0f70c70e35857a99b6a9188b5205efb4c77d6a57a/coverage-7.6.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877abb17e6339d96bf08e7a622d05095e72b71f8afd8a9fefc82cf30ed944163", size = 238924, upload-time = "2024-08-04T19:44:00.012Z" }, + { url = "https://files.pythonhosted.org/packages/ed/bf/73ce346a9d32a09cf369f14d2a06651329c984e106f5992c89579d25b27e/coverage-7.6.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e0cadcf6733c09154b461f1ca72d5416635e5e4ec4e536192180d34ec160f8a", size = 237252, upload-time = "2024-08-04T19:44:01.713Z" }, + { url = "https://files.pythonhosted.org/packages/86/74/1dc7a20969725e917b1e07fe71a955eb34bc606b938316bcc799f228374b/coverage-7.6.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3c02d12f837d9683e5ab2f3d9844dc57655b92c74e286c262e0fc54213c216d", size = 238897, upload-time = "2024-08-04T19:44:03.898Z" }, + { url = "https://files.pythonhosted.org/packages/b6/e9/d9cc3deceb361c491b81005c668578b0dfa51eed02cd081620e9a62f24ec/coverage-7.6.1-cp312-cp312-win32.whl", hash = "sha256:e05882b70b87a18d937ca6768ff33cc3f72847cbc4de4491c8e73880766718e5", size = 209606, upload-time = "2024-08-04T19:44:05.532Z" }, + { url = "https://files.pythonhosted.org/packages/47/c8/5a2e41922ea6740f77d555c4d47544acd7dc3f251fe14199c09c0f5958d3/coverage-7.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:b5d7b556859dd85f3a541db6a4e0167b86e7273e1cdc973e5b175166bb634fdb", size = 210373, upload-time = "2024-08-04T19:44:07.079Z" }, + { url = "https://files.pythonhosted.org/packages/8c/f9/9aa4dfb751cb01c949c990d136a0f92027fbcc5781c6e921df1cb1563f20/coverage-7.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a4acd025ecc06185ba2b801f2de85546e0b8ac787cf9d3b06e7e2a69f925b106", size = 207007, upload-time = "2024-08-04T19:44:09.453Z" }, + { url = "https://files.pythonhosted.org/packages/b9/67/e1413d5a8591622a46dd04ff80873b04c849268831ed5c304c16433e7e30/coverage-7.6.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a6d3adcf24b624a7b778533480e32434a39ad8fa30c315208f6d3e5542aeb6e9", size = 207269, upload-time = "2024-08-04T19:44:11.045Z" }, + { url = "https://files.pythonhosted.org/packages/14/5b/9dec847b305e44a5634d0fb8498d135ab1d88330482b74065fcec0622224/coverage-7.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0c212c49b6c10e6951362f7c6df3329f04c2b1c28499563d4035d964ab8e08c", size = 239886, upload-time = "2024-08-04T19:44:12.83Z" }, + { url = "https://files.pythonhosted.org/packages/7b/b7/35760a67c168e29f454928f51f970342d23cf75a2bb0323e0f07334c85f3/coverage-7.6.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e81d7a3e58882450ec4186ca59a3f20a5d4440f25b1cff6f0902ad890e6748a", size = 237037, upload-time = "2024-08-04T19:44:15.393Z" }, + { url = "https://files.pythonhosted.org/packages/f7/95/d2fd31f1d638df806cae59d7daea5abf2b15b5234016a5ebb502c2f3f7ee/coverage-7.6.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78b260de9790fd81e69401c2dc8b17da47c8038176a79092a89cb2b7d945d060", size = 239038, upload-time = "2024-08-04T19:44:17.466Z" }, + { url = "https://files.pythonhosted.org/packages/6e/bd/110689ff5752b67924efd5e2aedf5190cbbe245fc81b8dec1abaffba619d/coverage-7.6.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a78d169acd38300060b28d600344a803628c3fd585c912cacc9ea8790fe96862", size = 238690, upload-time = "2024-08-04T19:44:19.336Z" }, + { url = "https://files.pythonhosted.org/packages/d3/a8/08d7b38e6ff8df52331c83130d0ab92d9c9a8b5462f9e99c9f051a4ae206/coverage-7.6.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2c09f4ce52cb99dd7505cd0fc8e0e37c77b87f46bc9c1eb03fe3bc9991085388", size = 236765, upload-time = "2024-08-04T19:44:20.994Z" }, + { url = "https://files.pythonhosted.org/packages/d6/6a/9cf96839d3147d55ae713eb2d877f4d777e7dc5ba2bce227167d0118dfe8/coverage-7.6.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6878ef48d4227aace338d88c48738a4258213cd7b74fd9a3d4d7582bb1d8a155", size = 238611, upload-time = "2024-08-04T19:44:22.616Z" }, + { url = "https://files.pythonhosted.org/packages/74/e4/7ff20d6a0b59eeaab40b3140a71e38cf52547ba21dbcf1d79c5a32bba61b/coverage-7.6.1-cp313-cp313-win32.whl", hash = "sha256:44df346d5215a8c0e360307d46ffaabe0f5d3502c8a1cefd700b34baf31d411a", size = 209671, upload-time = "2024-08-04T19:44:24.418Z" }, + { url = "https://files.pythonhosted.org/packages/35/59/1812f08a85b57c9fdb6d0b383d779e47b6f643bc278ed682859512517e83/coverage-7.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:8284cf8c0dd272a247bc154eb6c95548722dce90d098c17a883ed36e67cdb129", size = 210368, upload-time = "2024-08-04T19:44:26.276Z" }, + { url = "https://files.pythonhosted.org/packages/9c/15/08913be1c59d7562a3e39fce20661a98c0a3f59d5754312899acc6cb8a2d/coverage-7.6.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d3296782ca4eab572a1a4eca686d8bfb00226300dcefdf43faa25b5242ab8a3e", size = 207758, upload-time = "2024-08-04T19:44:29.028Z" }, + { url = "https://files.pythonhosted.org/packages/c4/ae/b5d58dff26cade02ada6ca612a76447acd69dccdbb3a478e9e088eb3d4b9/coverage-7.6.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:502753043567491d3ff6d08629270127e0c31d4184c4c8d98f92c26f65019962", size = 208035, upload-time = "2024-08-04T19:44:30.673Z" }, + { url = "https://files.pythonhosted.org/packages/b8/d7/62095e355ec0613b08dfb19206ce3033a0eedb6f4a67af5ed267a8800642/coverage-7.6.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a89ecca80709d4076b95f89f308544ec8f7b4727e8a547913a35f16717856cb", size = 250839, upload-time = "2024-08-04T19:44:32.412Z" }, + { url = "https://files.pythonhosted.org/packages/7c/1e/c2967cb7991b112ba3766df0d9c21de46b476d103e32bb401b1b2adf3380/coverage-7.6.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a318d68e92e80af8b00fa99609796fdbcdfef3629c77c6283566c6f02c6d6704", size = 246569, upload-time = "2024-08-04T19:44:34.547Z" }, + { url = "https://files.pythonhosted.org/packages/8b/61/a7a6a55dd266007ed3b1df7a3386a0d760d014542d72f7c2c6938483b7bd/coverage-7.6.1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13b0a73a0896988f053e4fbb7de6d93388e6dd292b0d87ee51d106f2c11b465b", size = 248927, upload-time = "2024-08-04T19:44:36.313Z" }, + { url = "https://files.pythonhosted.org/packages/c8/fa/13a6f56d72b429f56ef612eb3bc5ce1b75b7ee12864b3bd12526ab794847/coverage-7.6.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4421712dbfc5562150f7554f13dde997a2e932a6b5f352edcce948a815efee6f", size = 248401, upload-time = "2024-08-04T19:44:38.155Z" }, + { url = "https://files.pythonhosted.org/packages/75/06/0429c652aa0fb761fc60e8c6b291338c9173c6aa0f4e40e1902345b42830/coverage-7.6.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:166811d20dfea725e2e4baa71fffd6c968a958577848d2131f39b60043400223", size = 246301, upload-time = "2024-08-04T19:44:39.883Z" }, + { url = "https://files.pythonhosted.org/packages/52/76/1766bb8b803a88f93c3a2d07e30ffa359467810e5cbc68e375ebe6906efb/coverage-7.6.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:225667980479a17db1048cb2bf8bfb39b8e5be8f164b8f6628b64f78a72cf9d3", size = 247598, upload-time = "2024-08-04T19:44:41.59Z" }, + { url = "https://files.pythonhosted.org/packages/66/8b/f54f8db2ae17188be9566e8166ac6df105c1c611e25da755738025708d54/coverage-7.6.1-cp313-cp313t-win32.whl", hash = "sha256:170d444ab405852903b7d04ea9ae9b98f98ab6d7e63e1115e82620807519797f", size = 210307, upload-time = "2024-08-04T19:44:43.301Z" }, + { url = "https://files.pythonhosted.org/packages/9f/b0/e0dca6da9170aefc07515cce067b97178cefafb512d00a87a1c717d2efd5/coverage-7.6.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b9f222de8cded79c49bf184bdbc06630d4c58eec9459b939b4a690c82ed05657", size = 211453, upload-time = "2024-08-04T19:44:45.677Z" }, + { url = "https://files.pythonhosted.org/packages/81/d0/d9e3d554e38beea5a2e22178ddb16587dbcbe9a1ef3211f55733924bf7fa/coverage-7.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6db04803b6c7291985a761004e9060b2bca08da6d04f26a7f2294b8623a0c1a0", size = 206674, upload-time = "2024-08-04T19:44:47.694Z" }, + { url = "https://files.pythonhosted.org/packages/38/ea/cab2dc248d9f45b2b7f9f1f596a4d75a435cb364437c61b51d2eb33ceb0e/coverage-7.6.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f1adfc8ac319e1a348af294106bc6a8458a0f1633cc62a1446aebc30c5fa186a", size = 207101, upload-time = "2024-08-04T19:44:49.32Z" }, + { url = "https://files.pythonhosted.org/packages/ca/6f/f82f9a500c7c5722368978a5390c418d2a4d083ef955309a8748ecaa8920/coverage-7.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a95324a9de9650a729239daea117df21f4b9868ce32e63f8b650ebe6cef5595b", size = 236554, upload-time = "2024-08-04T19:44:51.631Z" }, + { url = "https://files.pythonhosted.org/packages/a6/94/d3055aa33d4e7e733d8fa309d9adf147b4b06a82c1346366fc15a2b1d5fa/coverage-7.6.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b43c03669dc4618ec25270b06ecd3ee4fa94c7f9b3c14bae6571ca00ef98b0d3", size = 234440, upload-time = "2024-08-04T19:44:53.464Z" }, + { url = "https://files.pythonhosted.org/packages/e4/6e/885bcd787d9dd674de4a7d8ec83faf729534c63d05d51d45d4fa168f7102/coverage-7.6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8929543a7192c13d177b770008bc4e8119f2e1f881d563fc6b6305d2d0ebe9de", size = 235889, upload-time = "2024-08-04T19:44:55.165Z" }, + { url = "https://files.pythonhosted.org/packages/f4/63/df50120a7744492710854860783d6819ff23e482dee15462c9a833cc428a/coverage-7.6.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:a09ece4a69cf399510c8ab25e0950d9cf2b42f7b3cb0374f95d2e2ff594478a6", size = 235142, upload-time = "2024-08-04T19:44:57.269Z" }, + { url = "https://files.pythonhosted.org/packages/3a/5d/9d0acfcded2b3e9ce1c7923ca52ccc00c78a74e112fc2aee661125b7843b/coverage-7.6.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9054a0754de38d9dbd01a46621636689124d666bad1936d76c0341f7d71bf569", size = 233805, upload-time = "2024-08-04T19:44:59.033Z" }, + { url = "https://files.pythonhosted.org/packages/c4/56/50abf070cb3cd9b1dd32f2c88f083aab561ecbffbcd783275cb51c17f11d/coverage-7.6.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0dbde0f4aa9a16fa4d754356a8f2e36296ff4d83994b2c9d8398aa32f222f989", size = 234655, upload-time = "2024-08-04T19:45:01.398Z" }, + { url = "https://files.pythonhosted.org/packages/25/ee/b4c246048b8485f85a2426ef4abab88e48c6e80c74e964bea5cd4cd4b115/coverage-7.6.1-cp38-cp38-win32.whl", hash = "sha256:da511e6ad4f7323ee5702e6633085fb76c2f893aaf8ce4c51a0ba4fc07580ea7", size = 209296, upload-time = "2024-08-04T19:45:03.819Z" }, + { url = "https://files.pythonhosted.org/packages/5c/1c/96cf86b70b69ea2b12924cdf7cabb8ad10e6130eab8d767a1099fbd2a44f/coverage-7.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:3f1156e3e8f2872197af3840d8ad307a9dd18e615dc64d9ee41696f287c57ad8", size = 210137, upload-time = "2024-08-04T19:45:06.25Z" }, + { url = "https://files.pythonhosted.org/packages/19/d3/d54c5aa83268779d54c86deb39c1c4566e5d45c155369ca152765f8db413/coverage-7.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:abd5fd0db5f4dc9289408aaf34908072f805ff7792632250dcb36dc591d24255", size = 206688, upload-time = "2024-08-04T19:45:08.358Z" }, + { url = "https://files.pythonhosted.org/packages/a5/fe/137d5dca72e4a258b1bc17bb04f2e0196898fe495843402ce826a7419fe3/coverage-7.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:547f45fa1a93154bd82050a7f3cddbc1a7a4dd2a9bf5cb7d06f4ae29fe94eaf8", size = 207120, upload-time = "2024-08-04T19:45:11.526Z" }, + { url = "https://files.pythonhosted.org/packages/78/5b/a0a796983f3201ff5485323b225d7c8b74ce30c11f456017e23d8e8d1945/coverage-7.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645786266c8f18a931b65bfcefdbf6952dd0dea98feee39bd188607a9d307ed2", size = 235249, upload-time = "2024-08-04T19:45:13.202Z" }, + { url = "https://files.pythonhosted.org/packages/4e/e1/76089d6a5ef9d68f018f65411fcdaaeb0141b504587b901d74e8587606ad/coverage-7.6.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e0b2df163b8ed01d515807af24f63de04bebcecbd6c3bfeff88385789fdf75a", size = 233237, upload-time = "2024-08-04T19:45:14.961Z" }, + { url = "https://files.pythonhosted.org/packages/9a/6f/eef79b779a540326fee9520e5542a8b428cc3bfa8b7c8f1022c1ee4fc66c/coverage-7.6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:609b06f178fe8e9f89ef676532760ec0b4deea15e9969bf754b37f7c40326dbc", size = 234311, upload-time = "2024-08-04T19:45:16.924Z" }, + { url = "https://files.pythonhosted.org/packages/75/e1/656d65fb126c29a494ef964005702b012f3498db1a30dd562958e85a4049/coverage-7.6.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:702855feff378050ae4f741045e19a32d57d19f3e0676d589df0575008ea5004", size = 233453, upload-time = "2024-08-04T19:45:18.672Z" }, + { url = "https://files.pythonhosted.org/packages/68/6a/45f108f137941a4a1238c85f28fd9d048cc46b5466d6b8dda3aba1bb9d4f/coverage-7.6.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:2bdb062ea438f22d99cba0d7829c2ef0af1d768d1e4a4f528087224c90b132cb", size = 231958, upload-time = "2024-08-04T19:45:20.63Z" }, + { url = "https://files.pythonhosted.org/packages/9b/e7/47b809099168b8b8c72ae311efc3e88c8d8a1162b3ba4b8da3cfcdb85743/coverage-7.6.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9c56863d44bd1c4fe2abb8a4d6f5371d197f1ac0ebdee542f07f35895fc07f36", size = 232938, upload-time = "2024-08-04T19:45:23.062Z" }, + { url = "https://files.pythonhosted.org/packages/52/80/052222ba7058071f905435bad0ba392cc12006380731c37afaf3fe749b88/coverage-7.6.1-cp39-cp39-win32.whl", hash = "sha256:6e2cd258d7d927d09493c8df1ce9174ad01b381d4729a9d8d4e38670ca24774c", size = 209352, upload-time = "2024-08-04T19:45:25.042Z" }, + { url = "https://files.pythonhosted.org/packages/b8/d8/1b92e0b3adcf384e98770a00ca095da1b5f7b483e6563ae4eb5e935d24a1/coverage-7.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:06a737c882bd26d0d6ee7269b20b12f14a8704807a01056c80bb881a4b2ce6ca", size = 210153, upload-time = "2024-08-04T19:45:27.079Z" }, + { url = "https://files.pythonhosted.org/packages/a5/2b/0354ed096bca64dc8e32a7cbcae28b34cb5ad0b1fe2125d6d99583313ac0/coverage-7.6.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:e9a6e0eb86070e8ccaedfbd9d38fec54864f3125ab95419970575b42af7541df", size = 198926, upload-time = "2024-08-04T19:45:28.875Z" }, +] + +[package.optional-dependencies] +toml = [ + { name = "tomli", marker = "python_full_version < '3.9'" }, +] + +[[package]] +name = "coverage" +version = "7.10.7" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.9.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/51/26/d22c300112504f5f9a9fd2297ce33c35f3d353e4aeb987c8419453b2a7c2/coverage-7.10.7.tar.gz", hash = "sha256:f4ab143ab113be368a3e9b795f9cd7906c5ef407d6173fe9675a902e1fffc239", size = 827704, upload-time = "2025-09-21T20:03:56.815Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/6c/3a3f7a46888e69d18abe3ccc6fe4cb16cccb1e6a2f99698931dafca489e6/coverage-7.10.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fc04cc7a3db33664e0c2d10eb8990ff6b3536f6842c9590ae8da4c614b9ed05a", size = 217987, upload-time = "2025-09-21T20:00:57.218Z" }, + { url = "https://files.pythonhosted.org/packages/03/94/952d30f180b1a916c11a56f5c22d3535e943aa22430e9e3322447e520e1c/coverage-7.10.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e201e015644e207139f7e2351980feb7040e6f4b2c2978892f3e3789d1c125e5", size = 218388, upload-time = "2025-09-21T20:01:00.081Z" }, + { url = "https://files.pythonhosted.org/packages/50/2b/9e0cf8ded1e114bcd8b2fd42792b57f1c4e9e4ea1824cde2af93a67305be/coverage-7.10.7-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:240af60539987ced2c399809bd34f7c78e8abe0736af91c3d7d0e795df633d17", size = 245148, upload-time = "2025-09-21T20:01:01.768Z" }, + { url = "https://files.pythonhosted.org/packages/19/20/d0384ac06a6f908783d9b6aa6135e41b093971499ec488e47279f5b846e6/coverage-7.10.7-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8421e088bc051361b01c4b3a50fd39a4b9133079a2229978d9d30511fd05231b", size = 246958, upload-time = "2025-09-21T20:01:03.355Z" }, + { url = "https://files.pythonhosted.org/packages/60/83/5c283cff3d41285f8eab897651585db908a909c572bdc014bcfaf8a8b6ae/coverage-7.10.7-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6be8ed3039ae7f7ac5ce058c308484787c86e8437e72b30bf5e88b8ea10f3c87", size = 248819, upload-time = "2025-09-21T20:01:04.968Z" }, + { url = "https://files.pythonhosted.org/packages/60/22/02eb98fdc5ff79f423e990d877693e5310ae1eab6cb20ae0b0b9ac45b23b/coverage-7.10.7-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e28299d9f2e889e6d51b1f043f58d5f997c373cc12e6403b90df95b8b047c13e", size = 245754, upload-time = "2025-09-21T20:01:06.321Z" }, + { url = "https://files.pythonhosted.org/packages/b4/bc/25c83bcf3ad141b32cd7dc45485ef3c01a776ca3aa8ef0a93e77e8b5bc43/coverage-7.10.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c4e16bd7761c5e454f4efd36f345286d6f7c5fa111623c355691e2755cae3b9e", size = 246860, upload-time = "2025-09-21T20:01:07.605Z" }, + { url = "https://files.pythonhosted.org/packages/3c/b7/95574702888b58c0928a6e982038c596f9c34d52c5e5107f1eef729399b5/coverage-7.10.7-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b1c81d0e5e160651879755c9c675b974276f135558cf4ba79fee7b8413a515df", size = 244877, upload-time = "2025-09-21T20:01:08.829Z" }, + { url = "https://files.pythonhosted.org/packages/47/b6/40095c185f235e085df0e0b158f6bd68cc6e1d80ba6c7721dc81d97ec318/coverage-7.10.7-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:606cc265adc9aaedcc84f1f064f0e8736bc45814f15a357e30fca7ecc01504e0", size = 245108, upload-time = "2025-09-21T20:01:10.527Z" }, + { url = "https://files.pythonhosted.org/packages/c8/50/4aea0556da7a4b93ec9168420d170b55e2eb50ae21b25062513d020c6861/coverage-7.10.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:10b24412692df990dbc34f8fb1b6b13d236ace9dfdd68df5b28c2e39cafbba13", size = 245752, upload-time = "2025-09-21T20:01:11.857Z" }, + { url = "https://files.pythonhosted.org/packages/6a/28/ea1a84a60828177ae3b100cb6723838523369a44ec5742313ed7db3da160/coverage-7.10.7-cp310-cp310-win32.whl", hash = "sha256:b51dcd060f18c19290d9b8a9dd1e0181538df2ce0717f562fff6cf74d9fc0b5b", size = 220497, upload-time = "2025-09-21T20:01:13.459Z" }, + { url = "https://files.pythonhosted.org/packages/fc/1a/a81d46bbeb3c3fd97b9602ebaa411e076219a150489bcc2c025f151bd52d/coverage-7.10.7-cp310-cp310-win_amd64.whl", hash = "sha256:3a622ac801b17198020f09af3eaf45666b344a0d69fc2a6ffe2ea83aeef1d807", size = 221392, upload-time = "2025-09-21T20:01:14.722Z" }, + { url = "https://files.pythonhosted.org/packages/d2/5d/c1a17867b0456f2e9ce2d8d4708a4c3a089947d0bec9c66cdf60c9e7739f/coverage-7.10.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a609f9c93113be646f44c2a0256d6ea375ad047005d7f57a5c15f614dc1b2f59", size = 218102, upload-time = "2025-09-21T20:01:16.089Z" }, + { url = "https://files.pythonhosted.org/packages/54/f0/514dcf4b4e3698b9a9077f084429681bf3aad2b4a72578f89d7f643eb506/coverage-7.10.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:65646bb0359386e07639c367a22cf9b5bf6304e8630b565d0626e2bdf329227a", size = 218505, upload-time = "2025-09-21T20:01:17.788Z" }, + { url = "https://files.pythonhosted.org/packages/20/f6/9626b81d17e2a4b25c63ac1b425ff307ecdeef03d67c9a147673ae40dc36/coverage-7.10.7-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5f33166f0dfcce728191f520bd2692914ec70fac2713f6bf3ce59c3deacb4699", size = 248898, upload-time = "2025-09-21T20:01:19.488Z" }, + { url = "https://files.pythonhosted.org/packages/b0/ef/bd8e719c2f7417ba03239052e099b76ea1130ac0cbb183ee1fcaa58aaff3/coverage-7.10.7-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:35f5e3f9e455bb17831876048355dca0f758b6df22f49258cb5a91da23ef437d", size = 250831, upload-time = "2025-09-21T20:01:20.817Z" }, + { url = "https://files.pythonhosted.org/packages/a5/b6/bf054de41ec948b151ae2b79a55c107f5760979538f5fb80c195f2517718/coverage-7.10.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4da86b6d62a496e908ac2898243920c7992499c1712ff7c2b6d837cc69d9467e", size = 252937, upload-time = "2025-09-21T20:01:22.171Z" }, + { url = "https://files.pythonhosted.org/packages/0f/e5/3860756aa6f9318227443c6ce4ed7bf9e70bb7f1447a0353f45ac5c7974b/coverage-7.10.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6b8b09c1fad947c84bbbc95eca841350fad9cbfa5a2d7ca88ac9f8d836c92e23", size = 249021, upload-time = "2025-09-21T20:01:23.907Z" }, + { url = "https://files.pythonhosted.org/packages/26/0f/bd08bd042854f7fd07b45808927ebcce99a7ed0f2f412d11629883517ac2/coverage-7.10.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4376538f36b533b46f8971d3a3e63464f2c7905c9800db97361c43a2b14792ab", size = 250626, upload-time = "2025-09-21T20:01:25.721Z" }, + { url = "https://files.pythonhosted.org/packages/8e/a7/4777b14de4abcc2e80c6b1d430f5d51eb18ed1d75fca56cbce5f2db9b36e/coverage-7.10.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:121da30abb574f6ce6ae09840dae322bef734480ceafe410117627aa54f76d82", size = 248682, upload-time = "2025-09-21T20:01:27.105Z" }, + { url = "https://files.pythonhosted.org/packages/34/72/17d082b00b53cd45679bad682fac058b87f011fd8b9fe31d77f5f8d3a4e4/coverage-7.10.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:88127d40df529336a9836870436fc2751c339fbaed3a836d42c93f3e4bd1d0a2", size = 248402, upload-time = "2025-09-21T20:01:28.629Z" }, + { url = "https://files.pythonhosted.org/packages/81/7a/92367572eb5bdd6a84bfa278cc7e97db192f9f45b28c94a9ca1a921c3577/coverage-7.10.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ba58bbcd1b72f136080c0bccc2400d66cc6115f3f906c499013d065ac33a4b61", size = 249320, upload-time = "2025-09-21T20:01:30.004Z" }, + { url = "https://files.pythonhosted.org/packages/2f/88/a23cc185f6a805dfc4fdf14a94016835eeb85e22ac3a0e66d5e89acd6462/coverage-7.10.7-cp311-cp311-win32.whl", hash = "sha256:972b9e3a4094b053a4e46832b4bc829fc8a8d347160eb39d03f1690316a99c14", size = 220536, upload-time = "2025-09-21T20:01:32.184Z" }, + { url = "https://files.pythonhosted.org/packages/fe/ef/0b510a399dfca17cec7bc2f05ad8bd78cf55f15c8bc9a73ab20c5c913c2e/coverage-7.10.7-cp311-cp311-win_amd64.whl", hash = "sha256:a7b55a944a7f43892e28ad4bc0561dfd5f0d73e605d1aa5c3c976b52aea121d2", size = 221425, upload-time = "2025-09-21T20:01:33.557Z" }, + { url = "https://files.pythonhosted.org/packages/51/7f/023657f301a276e4ba1850f82749bc136f5a7e8768060c2e5d9744a22951/coverage-7.10.7-cp311-cp311-win_arm64.whl", hash = "sha256:736f227fb490f03c6488f9b6d45855f8e0fd749c007f9303ad30efab0e73c05a", size = 220103, upload-time = "2025-09-21T20:01:34.929Z" }, + { url = "https://files.pythonhosted.org/packages/13/e4/eb12450f71b542a53972d19117ea5a5cea1cab3ac9e31b0b5d498df1bd5a/coverage-7.10.7-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7bb3b9ddb87ef7725056572368040c32775036472d5a033679d1fa6c8dc08417", size = 218290, upload-time = "2025-09-21T20:01:36.455Z" }, + { url = "https://files.pythonhosted.org/packages/37/66/593f9be12fc19fb36711f19a5371af79a718537204d16ea1d36f16bd78d2/coverage-7.10.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:18afb24843cbc175687225cab1138c95d262337f5473512010e46831aa0c2973", size = 218515, upload-time = "2025-09-21T20:01:37.982Z" }, + { url = "https://files.pythonhosted.org/packages/66/80/4c49f7ae09cafdacc73fbc30949ffe77359635c168f4e9ff33c9ebb07838/coverage-7.10.7-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:399a0b6347bcd3822be369392932884b8216d0944049ae22925631a9b3d4ba4c", size = 250020, upload-time = "2025-09-21T20:01:39.617Z" }, + { url = "https://files.pythonhosted.org/packages/a6/90/a64aaacab3b37a17aaedd83e8000142561a29eb262cede42d94a67f7556b/coverage-7.10.7-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:314f2c326ded3f4b09be11bc282eb2fc861184bc95748ae67b360ac962770be7", size = 252769, upload-time = "2025-09-21T20:01:41.341Z" }, + { url = "https://files.pythonhosted.org/packages/98/2e/2dda59afd6103b342e096f246ebc5f87a3363b5412609946c120f4e7750d/coverage-7.10.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c41e71c9cfb854789dee6fc51e46743a6d138b1803fab6cb860af43265b42ea6", size = 253901, upload-time = "2025-09-21T20:01:43.042Z" }, + { url = "https://files.pythonhosted.org/packages/53/dc/8d8119c9051d50f3119bb4a75f29f1e4a6ab9415cd1fa8bf22fcc3fb3b5f/coverage-7.10.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc01f57ca26269c2c706e838f6422e2a8788e41b3e3c65e2f41148212e57cd59", size = 250413, upload-time = "2025-09-21T20:01:44.469Z" }, + { url = "https://files.pythonhosted.org/packages/98/b3/edaff9c5d79ee4d4b6d3fe046f2b1d799850425695b789d491a64225d493/coverage-7.10.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a6442c59a8ac8b85812ce33bc4d05bde3fb22321fa8294e2a5b487c3505f611b", size = 251820, upload-time = "2025-09-21T20:01:45.915Z" }, + { url = "https://files.pythonhosted.org/packages/11/25/9a0728564bb05863f7e513e5a594fe5ffef091b325437f5430e8cfb0d530/coverage-7.10.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:78a384e49f46b80fb4c901d52d92abe098e78768ed829c673fbb53c498bef73a", size = 249941, upload-time = "2025-09-21T20:01:47.296Z" }, + { url = "https://files.pythonhosted.org/packages/e0/fd/ca2650443bfbef5b0e74373aac4df67b08180d2f184b482c41499668e258/coverage-7.10.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:5e1e9802121405ede4b0133aa4340ad8186a1d2526de5b7c3eca519db7bb89fb", size = 249519, upload-time = "2025-09-21T20:01:48.73Z" }, + { url = "https://files.pythonhosted.org/packages/24/79/f692f125fb4299b6f963b0745124998ebb8e73ecdfce4ceceb06a8c6bec5/coverage-7.10.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d41213ea25a86f69efd1575073d34ea11aabe075604ddf3d148ecfec9e1e96a1", size = 251375, upload-time = "2025-09-21T20:01:50.529Z" }, + { url = "https://files.pythonhosted.org/packages/5e/75/61b9bbd6c7d24d896bfeec57acba78e0f8deac68e6baf2d4804f7aae1f88/coverage-7.10.7-cp312-cp312-win32.whl", hash = "sha256:77eb4c747061a6af8d0f7bdb31f1e108d172762ef579166ec84542f711d90256", size = 220699, upload-time = "2025-09-21T20:01:51.941Z" }, + { url = "https://files.pythonhosted.org/packages/ca/f3/3bf7905288b45b075918d372498f1cf845b5b579b723c8fd17168018d5f5/coverage-7.10.7-cp312-cp312-win_amd64.whl", hash = "sha256:f51328ffe987aecf6d09f3cd9d979face89a617eacdaea43e7b3080777f647ba", size = 221512, upload-time = "2025-09-21T20:01:53.481Z" }, + { url = "https://files.pythonhosted.org/packages/5c/44/3e32dbe933979d05cf2dac5e697c8599cfe038aaf51223ab901e208d5a62/coverage-7.10.7-cp312-cp312-win_arm64.whl", hash = "sha256:bda5e34f8a75721c96085903c6f2197dc398c20ffd98df33f866a9c8fd95f4bf", size = 220147, upload-time = "2025-09-21T20:01:55.2Z" }, + { url = "https://files.pythonhosted.org/packages/9a/94/b765c1abcb613d103b64fcf10395f54d69b0ef8be6a0dd9c524384892cc7/coverage-7.10.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:981a651f543f2854abd3b5fcb3263aac581b18209be49863ba575de6edf4c14d", size = 218320, upload-time = "2025-09-21T20:01:56.629Z" }, + { url = "https://files.pythonhosted.org/packages/72/4f/732fff31c119bb73b35236dd333030f32c4bfe909f445b423e6c7594f9a2/coverage-7.10.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:73ab1601f84dc804f7812dc297e93cd99381162da39c47040a827d4e8dafe63b", size = 218575, upload-time = "2025-09-21T20:01:58.203Z" }, + { url = "https://files.pythonhosted.org/packages/87/02/ae7e0af4b674be47566707777db1aa375474f02a1d64b9323e5813a6cdd5/coverage-7.10.7-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a8b6f03672aa6734e700bbcd65ff050fd19cddfec4b031cc8cf1c6967de5a68e", size = 249568, upload-time = "2025-09-21T20:01:59.748Z" }, + { url = "https://files.pythonhosted.org/packages/a2/77/8c6d22bf61921a59bce5471c2f1f7ac30cd4ac50aadde72b8c48d5727902/coverage-7.10.7-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10b6ba00ab1132a0ce4428ff68cf50a25efd6840a42cdf4239c9b99aad83be8b", size = 252174, upload-time = "2025-09-21T20:02:01.192Z" }, + { url = "https://files.pythonhosted.org/packages/b1/20/b6ea4f69bbb52dac0aebd62157ba6a9dddbfe664f5af8122dac296c3ee15/coverage-7.10.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c79124f70465a150e89340de5963f936ee97097d2ef76c869708c4248c63ca49", size = 253447, upload-time = "2025-09-21T20:02:02.701Z" }, + { url = "https://files.pythonhosted.org/packages/f9/28/4831523ba483a7f90f7b259d2018fef02cb4d5b90bc7c1505d6e5a84883c/coverage-7.10.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:69212fbccdbd5b0e39eac4067e20a4a5256609e209547d86f740d68ad4f04911", size = 249779, upload-time = "2025-09-21T20:02:04.185Z" }, + { url = "https://files.pythonhosted.org/packages/a7/9f/4331142bc98c10ca6436d2d620c3e165f31e6c58d43479985afce6f3191c/coverage-7.10.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7ea7c6c9d0d286d04ed3541747e6597cbe4971f22648b68248f7ddcd329207f0", size = 251604, upload-time = "2025-09-21T20:02:06.034Z" }, + { url = "https://files.pythonhosted.org/packages/ce/60/bda83b96602036b77ecf34e6393a3836365481b69f7ed7079ab85048202b/coverage-7.10.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b9be91986841a75042b3e3243d0b3cb0b2434252b977baaf0cd56e960fe1e46f", size = 249497, upload-time = "2025-09-21T20:02:07.619Z" }, + { url = "https://files.pythonhosted.org/packages/5f/af/152633ff35b2af63977edd835d8e6430f0caef27d171edf2fc76c270ef31/coverage-7.10.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:b281d5eca50189325cfe1f365fafade89b14b4a78d9b40b05ddd1fc7d2a10a9c", size = 249350, upload-time = "2025-09-21T20:02:10.34Z" }, + { url = "https://files.pythonhosted.org/packages/9d/71/d92105d122bd21cebba877228990e1646d862e34a98bb3374d3fece5a794/coverage-7.10.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:99e4aa63097ab1118e75a848a28e40d68b08a5e19ce587891ab7fd04475e780f", size = 251111, upload-time = "2025-09-21T20:02:12.122Z" }, + { url = "https://files.pythonhosted.org/packages/a2/9e/9fdb08f4bf476c912f0c3ca292e019aab6712c93c9344a1653986c3fd305/coverage-7.10.7-cp313-cp313-win32.whl", hash = "sha256:dc7c389dce432500273eaf48f410b37886be9208b2dd5710aaf7c57fd442c698", size = 220746, upload-time = "2025-09-21T20:02:13.919Z" }, + { url = "https://files.pythonhosted.org/packages/b1/b1/a75fd25df44eab52d1931e89980d1ada46824c7a3210be0d3c88a44aaa99/coverage-7.10.7-cp313-cp313-win_amd64.whl", hash = "sha256:cac0fdca17b036af3881a9d2729a850b76553f3f716ccb0360ad4dbc06b3b843", size = 221541, upload-time = "2025-09-21T20:02:15.57Z" }, + { url = "https://files.pythonhosted.org/packages/14/3a/d720d7c989562a6e9a14b2c9f5f2876bdb38e9367126d118495b89c99c37/coverage-7.10.7-cp313-cp313-win_arm64.whl", hash = "sha256:4b6f236edf6e2f9ae8fcd1332da4e791c1b6ba0dc16a2dc94590ceccb482e546", size = 220170, upload-time = "2025-09-21T20:02:17.395Z" }, + { url = "https://files.pythonhosted.org/packages/bb/22/e04514bf2a735d8b0add31d2b4ab636fc02370730787c576bb995390d2d5/coverage-7.10.7-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a0ec07fd264d0745ee396b666d47cef20875f4ff2375d7c4f58235886cc1ef0c", size = 219029, upload-time = "2025-09-21T20:02:18.936Z" }, + { url = "https://files.pythonhosted.org/packages/11/0b/91128e099035ece15da3445d9015e4b4153a6059403452d324cbb0a575fa/coverage-7.10.7-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:dd5e856ebb7bfb7672b0086846db5afb4567a7b9714b8a0ebafd211ec7ce6a15", size = 219259, upload-time = "2025-09-21T20:02:20.44Z" }, + { url = "https://files.pythonhosted.org/packages/8b/51/66420081e72801536a091a0c8f8c1f88a5c4bf7b9b1bdc6222c7afe6dc9b/coverage-7.10.7-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f57b2a3c8353d3e04acf75b3fed57ba41f5c0646bbf1d10c7c282291c97936b4", size = 260592, upload-time = "2025-09-21T20:02:22.313Z" }, + { url = "https://files.pythonhosted.org/packages/5d/22/9b8d458c2881b22df3db5bb3e7369e63d527d986decb6c11a591ba2364f7/coverage-7.10.7-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1ef2319dd15a0b009667301a3f84452a4dc6fddfd06b0c5c53ea472d3989fbf0", size = 262768, upload-time = "2025-09-21T20:02:24.287Z" }, + { url = "https://files.pythonhosted.org/packages/f7/08/16bee2c433e60913c610ea200b276e8eeef084b0d200bdcff69920bd5828/coverage-7.10.7-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:83082a57783239717ceb0ad584de3c69cf581b2a95ed6bf81ea66034f00401c0", size = 264995, upload-time = "2025-09-21T20:02:26.133Z" }, + { url = "https://files.pythonhosted.org/packages/20/9d/e53eb9771d154859b084b90201e5221bca7674ba449a17c101a5031d4054/coverage-7.10.7-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:50aa94fb1fb9a397eaa19c0d5ec15a5edd03a47bf1a3a6111a16b36e190cff65", size = 259546, upload-time = "2025-09-21T20:02:27.716Z" }, + { url = "https://files.pythonhosted.org/packages/ad/b0/69bc7050f8d4e56a89fb550a1577d5d0d1db2278106f6f626464067b3817/coverage-7.10.7-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2120043f147bebb41c85b97ac45dd173595ff14f2a584f2963891cbcc3091541", size = 262544, upload-time = "2025-09-21T20:02:29.216Z" }, + { url = "https://files.pythonhosted.org/packages/ef/4b/2514b060dbd1bc0aaf23b852c14bb5818f244c664cb16517feff6bb3a5ab/coverage-7.10.7-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2fafd773231dd0378fdba66d339f84904a8e57a262f583530f4f156ab83863e6", size = 260308, upload-time = "2025-09-21T20:02:31.226Z" }, + { url = "https://files.pythonhosted.org/packages/54/78/7ba2175007c246d75e496f64c06e94122bdb914790a1285d627a918bd271/coverage-7.10.7-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:0b944ee8459f515f28b851728ad224fa2d068f1513ef6b7ff1efafeb2185f999", size = 258920, upload-time = "2025-09-21T20:02:32.823Z" }, + { url = "https://files.pythonhosted.org/packages/c0/b3/fac9f7abbc841409b9a410309d73bfa6cfb2e51c3fada738cb607ce174f8/coverage-7.10.7-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4b583b97ab2e3efe1b3e75248a9b333bd3f8b0b1b8e5b45578e05e5850dfb2c2", size = 261434, upload-time = "2025-09-21T20:02:34.86Z" }, + { url = "https://files.pythonhosted.org/packages/ee/51/a03bec00d37faaa891b3ff7387192cef20f01604e5283a5fabc95346befa/coverage-7.10.7-cp313-cp313t-win32.whl", hash = "sha256:2a78cd46550081a7909b3329e2266204d584866e8d97b898cd7fb5ac8d888b1a", size = 221403, upload-time = "2025-09-21T20:02:37.034Z" }, + { url = "https://files.pythonhosted.org/packages/53/22/3cf25d614e64bf6d8e59c7c669b20d6d940bb337bdee5900b9ca41c820bb/coverage-7.10.7-cp313-cp313t-win_amd64.whl", hash = "sha256:33a5e6396ab684cb43dc7befa386258acb2d7fae7f67330ebb85ba4ea27938eb", size = 222469, upload-time = "2025-09-21T20:02:39.011Z" }, + { url = "https://files.pythonhosted.org/packages/49/a1/00164f6d30d8a01c3c9c48418a7a5be394de5349b421b9ee019f380df2a0/coverage-7.10.7-cp313-cp313t-win_arm64.whl", hash = "sha256:86b0e7308289ddde73d863b7683f596d8d21c7d8664ce1dee061d0bcf3fbb4bb", size = 220731, upload-time = "2025-09-21T20:02:40.939Z" }, + { url = "https://files.pythonhosted.org/packages/23/9c/5844ab4ca6a4dd97a1850e030a15ec7d292b5c5cb93082979225126e35dd/coverage-7.10.7-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b06f260b16ead11643a5a9f955bd4b5fd76c1a4c6796aeade8520095b75de520", size = 218302, upload-time = "2025-09-21T20:02:42.527Z" }, + { url = "https://files.pythonhosted.org/packages/f0/89/673f6514b0961d1f0e20ddc242e9342f6da21eaba3489901b565c0689f34/coverage-7.10.7-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:212f8f2e0612778f09c55dd4872cb1f64a1f2b074393d139278ce902064d5b32", size = 218578, upload-time = "2025-09-21T20:02:44.468Z" }, + { url = "https://files.pythonhosted.org/packages/05/e8/261cae479e85232828fb17ad536765c88dd818c8470aca690b0ac6feeaa3/coverage-7.10.7-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3445258bcded7d4aa630ab8296dea4d3f15a255588dd535f980c193ab6b95f3f", size = 249629, upload-time = "2025-09-21T20:02:46.503Z" }, + { url = "https://files.pythonhosted.org/packages/82/62/14ed6546d0207e6eda876434e3e8475a3e9adbe32110ce896c9e0c06bb9a/coverage-7.10.7-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bb45474711ba385c46a0bfe696c695a929ae69ac636cda8f532be9e8c93d720a", size = 252162, upload-time = "2025-09-21T20:02:48.689Z" }, + { url = "https://files.pythonhosted.org/packages/ff/49/07f00db9ac6478e4358165a08fb41b469a1b053212e8a00cb02f0d27a05f/coverage-7.10.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:813922f35bd800dca9994c5971883cbc0d291128a5de6b167c7aa697fcf59360", size = 253517, upload-time = "2025-09-21T20:02:50.31Z" }, + { url = "https://files.pythonhosted.org/packages/a2/59/c5201c62dbf165dfbc91460f6dbbaa85a8b82cfa6131ac45d6c1bfb52deb/coverage-7.10.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:93c1b03552081b2a4423091d6fb3787265b8f86af404cff98d1b5342713bdd69", size = 249632, upload-time = "2025-09-21T20:02:51.971Z" }, + { url = "https://files.pythonhosted.org/packages/07/ae/5920097195291a51fb00b3a70b9bbd2edbfe3c84876a1762bd1ef1565ebc/coverage-7.10.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:cc87dd1b6eaf0b848eebb1c86469b9f72a1891cb42ac7adcfbce75eadb13dd14", size = 251520, upload-time = "2025-09-21T20:02:53.858Z" }, + { url = "https://files.pythonhosted.org/packages/b9/3c/a815dde77a2981f5743a60b63df31cb322c944843e57dbd579326625a413/coverage-7.10.7-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:39508ffda4f343c35f3236fe8d1a6634a51f4581226a1262769d7f970e73bffe", size = 249455, upload-time = "2025-09-21T20:02:55.807Z" }, + { url = "https://files.pythonhosted.org/packages/aa/99/f5cdd8421ea656abefb6c0ce92556709db2265c41e8f9fc6c8ae0f7824c9/coverage-7.10.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:925a1edf3d810537c5a3abe78ec5530160c5f9a26b1f4270b40e62cc79304a1e", size = 249287, upload-time = "2025-09-21T20:02:57.784Z" }, + { url = "https://files.pythonhosted.org/packages/c3/7a/e9a2da6a1fc5d007dd51fca083a663ab930a8c4d149c087732a5dbaa0029/coverage-7.10.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2c8b9a0636f94c43cd3576811e05b89aa9bc2d0a85137affc544ae5cb0e4bfbd", size = 250946, upload-time = "2025-09-21T20:02:59.431Z" }, + { url = "https://files.pythonhosted.org/packages/ef/5b/0b5799aa30380a949005a353715095d6d1da81927d6dbed5def2200a4e25/coverage-7.10.7-cp314-cp314-win32.whl", hash = "sha256:b7b8288eb7cdd268b0304632da8cb0bb93fadcfec2fe5712f7b9cc8f4d487be2", size = 221009, upload-time = "2025-09-21T20:03:01.324Z" }, + { url = "https://files.pythonhosted.org/packages/da/b0/e802fbb6eb746de006490abc9bb554b708918b6774b722bb3a0e6aa1b7de/coverage-7.10.7-cp314-cp314-win_amd64.whl", hash = "sha256:1ca6db7c8807fb9e755d0379ccc39017ce0a84dcd26d14b5a03b78563776f681", size = 221804, upload-time = "2025-09-21T20:03:03.4Z" }, + { url = "https://files.pythonhosted.org/packages/9e/e8/71d0c8e374e31f39e3389bb0bd19e527d46f00ea8571ec7ec8fd261d8b44/coverage-7.10.7-cp314-cp314-win_arm64.whl", hash = "sha256:097c1591f5af4496226d5783d036bf6fd6cd0cbc132e071b33861de756efb880", size = 220384, upload-time = "2025-09-21T20:03:05.111Z" }, + { url = "https://files.pythonhosted.org/packages/62/09/9a5608d319fa3eba7a2019addeacb8c746fb50872b57a724c9f79f146969/coverage-7.10.7-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:a62c6ef0d50e6de320c270ff91d9dd0a05e7250cac2a800b7784bae474506e63", size = 219047, upload-time = "2025-09-21T20:03:06.795Z" }, + { url = "https://files.pythonhosted.org/packages/f5/6f/f58d46f33db9f2e3647b2d0764704548c184e6f5e014bef528b7f979ef84/coverage-7.10.7-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9fa6e4dd51fe15d8738708a973470f67a855ca50002294852e9571cdbd9433f2", size = 219266, upload-time = "2025-09-21T20:03:08.495Z" }, + { url = "https://files.pythonhosted.org/packages/74/5c/183ffc817ba68e0b443b8c934c8795553eb0c14573813415bd59941ee165/coverage-7.10.7-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8fb190658865565c549b6b4706856d6a7b09302c797eb2cf8e7fe9dabb043f0d", size = 260767, upload-time = "2025-09-21T20:03:10.172Z" }, + { url = "https://files.pythonhosted.org/packages/0f/48/71a8abe9c1ad7e97548835e3cc1adbf361e743e9d60310c5f75c9e7bf847/coverage-7.10.7-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:affef7c76a9ef259187ef31599a9260330e0335a3011732c4b9effa01e1cd6e0", size = 262931, upload-time = "2025-09-21T20:03:11.861Z" }, + { url = "https://files.pythonhosted.org/packages/84/fd/193a8fb132acfc0a901f72020e54be5e48021e1575bb327d8ee1097a28fd/coverage-7.10.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e16e07d85ca0cf8bafe5f5d23a0b850064e8e945d5677492b06bbe6f09cc699", size = 265186, upload-time = "2025-09-21T20:03:13.539Z" }, + { url = "https://files.pythonhosted.org/packages/b1/8f/74ecc30607dd95ad50e3034221113ccb1c6d4e8085cc761134782995daae/coverage-7.10.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:03ffc58aacdf65d2a82bbeb1ffe4d01ead4017a21bfd0454983b88ca73af94b9", size = 259470, upload-time = "2025-09-21T20:03:15.584Z" }, + { url = "https://files.pythonhosted.org/packages/0f/55/79ff53a769f20d71b07023ea115c9167c0bb56f281320520cf64c5298a96/coverage-7.10.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1b4fd784344d4e52647fd7857b2af5b3fbe6c239b0b5fa63e94eb67320770e0f", size = 262626, upload-time = "2025-09-21T20:03:17.673Z" }, + { url = "https://files.pythonhosted.org/packages/88/e2/dac66c140009b61ac3fc13af673a574b00c16efdf04f9b5c740703e953c0/coverage-7.10.7-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:0ebbaddb2c19b71912c6f2518e791aa8b9f054985a0769bdb3a53ebbc765c6a1", size = 260386, upload-time = "2025-09-21T20:03:19.36Z" }, + { url = "https://files.pythonhosted.org/packages/a2/f1/f48f645e3f33bb9ca8a496bc4a9671b52f2f353146233ebd7c1df6160440/coverage-7.10.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:a2d9a3b260cc1d1dbdb1c582e63ddcf5363426a1a68faa0f5da28d8ee3c722a0", size = 258852, upload-time = "2025-09-21T20:03:21.007Z" }, + { url = "https://files.pythonhosted.org/packages/bb/3b/8442618972c51a7affeead957995cfa8323c0c9bcf8fa5a027421f720ff4/coverage-7.10.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a3cc8638b2480865eaa3926d192e64ce6c51e3d29c849e09d5b4ad95efae5399", size = 261534, upload-time = "2025-09-21T20:03:23.12Z" }, + { url = "https://files.pythonhosted.org/packages/b2/dc/101f3fa3a45146db0cb03f5b4376e24c0aac818309da23e2de0c75295a91/coverage-7.10.7-cp314-cp314t-win32.whl", hash = "sha256:67f8c5cbcd3deb7a60b3345dffc89a961a484ed0af1f6f73de91705cc6e31235", size = 221784, upload-time = "2025-09-21T20:03:24.769Z" }, + { url = "https://files.pythonhosted.org/packages/4c/a1/74c51803fc70a8a40d7346660379e144be772bab4ac7bb6e6b905152345c/coverage-7.10.7-cp314-cp314t-win_amd64.whl", hash = "sha256:e1ed71194ef6dea7ed2d5cb5f7243d4bcd334bfb63e59878519be558078f848d", size = 222905, upload-time = "2025-09-21T20:03:26.93Z" }, + { url = "https://files.pythonhosted.org/packages/12/65/f116a6d2127df30bcafbceef0302d8a64ba87488bf6f73a6d8eebf060873/coverage-7.10.7-cp314-cp314t-win_arm64.whl", hash = "sha256:7fe650342addd8524ca63d77b2362b02345e5f1a093266787d210c70a50b471a", size = 220922, upload-time = "2025-09-21T20:03:28.672Z" }, + { url = "https://files.pythonhosted.org/packages/a3/ad/d1c25053764b4c42eb294aae92ab617d2e4f803397f9c7c8295caa77a260/coverage-7.10.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fff7b9c3f19957020cac546c70025331113d2e61537f6e2441bc7657913de7d3", size = 217978, upload-time = "2025-09-21T20:03:30.362Z" }, + { url = "https://files.pythonhosted.org/packages/52/2f/b9f9daa39b80ece0b9548bbb723381e29bc664822d9a12c2135f8922c22b/coverage-7.10.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bc91b314cef27742da486d6839b677b3f2793dfe52b51bbbb7cf736d5c29281c", size = 218370, upload-time = "2025-09-21T20:03:32.147Z" }, + { url = "https://files.pythonhosted.org/packages/dd/6e/30d006c3b469e58449650642383dddf1c8fb63d44fdf92994bfd46570695/coverage-7.10.7-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:567f5c155eda8df1d3d439d40a45a6a5f029b429b06648235f1e7e51b522b396", size = 244802, upload-time = "2025-09-21T20:03:33.919Z" }, + { url = "https://files.pythonhosted.org/packages/b0/49/8a070782ce7e6b94ff6a0b6d7c65ba6bc3091d92a92cef4cd4eb0767965c/coverage-7.10.7-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2af88deffcc8a4d5974cf2d502251bc3b2db8461f0b66d80a449c33757aa9f40", size = 246625, upload-time = "2025-09-21T20:03:36.09Z" }, + { url = "https://files.pythonhosted.org/packages/6a/92/1c1c5a9e8677ce56d42b97bdaca337b2d4d9ebe703d8c174ede52dbabd5f/coverage-7.10.7-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7315339eae3b24c2d2fa1ed7d7a38654cba34a13ef19fbcb9425da46d3dc594", size = 248399, upload-time = "2025-09-21T20:03:38.342Z" }, + { url = "https://files.pythonhosted.org/packages/c0/54/b140edee7257e815de7426d5d9846b58505dffc29795fff2dfb7f8a1c5a0/coverage-7.10.7-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:912e6ebc7a6e4adfdbb1aec371ad04c68854cd3bf3608b3514e7ff9062931d8a", size = 245142, upload-time = "2025-09-21T20:03:40.591Z" }, + { url = "https://files.pythonhosted.org/packages/e4/9e/6d6b8295940b118e8b7083b29226c71f6154f7ff41e9ca431f03de2eac0d/coverage-7.10.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f49a05acd3dfe1ce9715b657e28d138578bc40126760efb962322c56e9ca344b", size = 246284, upload-time = "2025-09-21T20:03:42.355Z" }, + { url = "https://files.pythonhosted.org/packages/db/e5/5e957ca747d43dbe4d9714358375c7546cb3cb533007b6813fc20fce37ad/coverage-7.10.7-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:cce2109b6219f22ece99db7644b9622f54a4e915dad65660ec435e89a3ea7cc3", size = 244353, upload-time = "2025-09-21T20:03:44.218Z" }, + { url = "https://files.pythonhosted.org/packages/9a/45/540fc5cc92536a1b783b7ef99450bd55a4b3af234aae35a18a339973ce30/coverage-7.10.7-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:f3c887f96407cea3916294046fc7dab611c2552beadbed4ea901cbc6a40cc7a0", size = 244430, upload-time = "2025-09-21T20:03:46.065Z" }, + { url = "https://files.pythonhosted.org/packages/75/0b/8287b2e5b38c8fe15d7e3398849bb58d382aedc0864ea0fa1820e8630491/coverage-7.10.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:635adb9a4507c9fd2ed65f39693fa31c9a3ee3a8e6dc64df033e8fdf52a7003f", size = 245311, upload-time = "2025-09-21T20:03:48.19Z" }, + { url = "https://files.pythonhosted.org/packages/0c/1d/29724999984740f0c86d03e6420b942439bf5bd7f54d4382cae386a9d1e9/coverage-7.10.7-cp39-cp39-win32.whl", hash = "sha256:5a02d5a850e2979b0a014c412573953995174743a3f7fa4ea5a6e9a3c5617431", size = 220500, upload-time = "2025-09-21T20:03:50.024Z" }, + { url = "https://files.pythonhosted.org/packages/43/11/4b1e6b129943f905ca54c339f343877b55b365ae2558806c1be4f7476ed5/coverage-7.10.7-cp39-cp39-win_amd64.whl", hash = "sha256:c134869d5ffe34547d14e174c866fd8fe2254918cc0a95e99052903bc1543e07", size = 221408, upload-time = "2025-09-21T20:03:51.803Z" }, + { url = "https://files.pythonhosted.org/packages/ec/16/114df1c291c22cac3b0c127a73e0af5c12ed7bbb6558d310429a0ae24023/coverage-7.10.7-py3-none-any.whl", hash = "sha256:f7941f6f2fe6dd6807a1208737b8a0cbcf1cc6d7b07d24998ad2d63590868260", size = 209952, upload-time = "2025-09-21T20:03:53.918Z" }, +] + +[package.optional-dependencies] +toml = [ + { name = "tomli", marker = "python_full_version == '3.9.*'" }, +] + +[[package]] +name = "coverage" +version = "7.12.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/89/26/4a96807b193b011588099c3b5c89fbb05294e5b90e71018e065465f34eb6/coverage-7.12.0.tar.gz", hash = "sha256:fc11e0a4e372cb5f282f16ef90d4a585034050ccda536451901abfb19a57f40c", size = 819341, upload-time = "2025-11-18T13:34:20.766Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/4a/0dc3de1c172d35abe512332cfdcc43211b6ebce629e4cc42e6cd25ed8f4d/coverage-7.12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:32b75c2ba3f324ee37af3ccee5b30458038c50b349ad9b88cee85096132a575b", size = 217409, upload-time = "2025-11-18T13:31:53.122Z" }, + { url = "https://files.pythonhosted.org/packages/01/c3/086198b98db0109ad4f84241e8e9ea7e5fb2db8c8ffb787162d40c26cc76/coverage-7.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cb2a1b6ab9fe833714a483a915de350abc624a37149649297624c8d57add089c", size = 217927, upload-time = "2025-11-18T13:31:54.458Z" }, + { url = "https://files.pythonhosted.org/packages/5d/5f/34614dbf5ce0420828fc6c6f915126a0fcb01e25d16cf141bf5361e6aea6/coverage-7.12.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5734b5d913c3755e72f70bf6cc37a0518d4f4745cde760c5d8e12005e62f9832", size = 244678, upload-time = "2025-11-18T13:31:55.805Z" }, + { url = "https://files.pythonhosted.org/packages/55/7b/6b26fb32e8e4a6989ac1d40c4e132b14556131493b1d06bc0f2be169c357/coverage-7.12.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b527a08cdf15753279b7afb2339a12073620b761d79b81cbe2cdebdb43d90daa", size = 246507, upload-time = "2025-11-18T13:31:57.05Z" }, + { url = "https://files.pythonhosted.org/packages/06/42/7d70e6603d3260199b90fb48b537ca29ac183d524a65cc31366b2e905fad/coverage-7.12.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9bb44c889fb68004e94cab71f6a021ec83eac9aeabdbb5a5a88821ec46e1da73", size = 248366, upload-time = "2025-11-18T13:31:58.362Z" }, + { url = "https://files.pythonhosted.org/packages/2d/4a/d86b837923878424c72458c5b25e899a3c5ca73e663082a915f5b3c4d749/coverage-7.12.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4b59b501455535e2e5dde5881739897967b272ba25988c89145c12d772810ccb", size = 245366, upload-time = "2025-11-18T13:31:59.572Z" }, + { url = "https://files.pythonhosted.org/packages/e6/c2/2adec557e0aa9721875f06ced19730fdb7fc58e31b02b5aa56f2ebe4944d/coverage-7.12.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d8842f17095b9868a05837b7b1b73495293091bed870e099521ada176aa3e00e", size = 246408, upload-time = "2025-11-18T13:32:00.784Z" }, + { url = "https://files.pythonhosted.org/packages/5a/4b/8bd1f1148260df11c618e535fdccd1e5aaf646e55b50759006a4f41d8a26/coverage-7.12.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:c5a6f20bf48b8866095c6820641e7ffbe23f2ac84a2efc218d91235e404c7777", size = 244416, upload-time = "2025-11-18T13:32:01.963Z" }, + { url = "https://files.pythonhosted.org/packages/0e/13/3a248dd6a83df90414c54a4e121fd081fb20602ca43955fbe1d60e2312a9/coverage-7.12.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:5f3738279524e988d9da2893f307c2093815c623f8d05a8f79e3eff3a7a9e553", size = 244681, upload-time = "2025-11-18T13:32:03.408Z" }, + { url = "https://files.pythonhosted.org/packages/76/30/aa833827465a5e8c938935f5d91ba055f70516941078a703740aaf1aa41f/coverage-7.12.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e0d68c1f7eabbc8abe582d11fa393ea483caf4f44b0af86881174769f185c94d", size = 245300, upload-time = "2025-11-18T13:32:04.686Z" }, + { url = "https://files.pythonhosted.org/packages/38/24/f85b3843af1370fb3739fa7571819b71243daa311289b31214fe3e8c9d68/coverage-7.12.0-cp310-cp310-win32.whl", hash = "sha256:7670d860e18b1e3ee5930b17a7d55ae6287ec6e55d9799982aa103a2cc1fa2ef", size = 220008, upload-time = "2025-11-18T13:32:05.806Z" }, + { url = "https://files.pythonhosted.org/packages/3a/a2/c7da5b9566f7164db9eefa133d17761ecb2c2fde9385d754e5b5c80f710d/coverage-7.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:f999813dddeb2a56aab5841e687b68169da0d3f6fc78ccf50952fa2463746022", size = 220943, upload-time = "2025-11-18T13:32:07.166Z" }, + { url = "https://files.pythonhosted.org/packages/5a/0c/0dfe7f0487477d96432e4815537263363fb6dd7289743a796e8e51eabdf2/coverage-7.12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aa124a3683d2af98bd9d9c2bfa7a5076ca7e5ab09fdb96b81fa7d89376ae928f", size = 217535, upload-time = "2025-11-18T13:32:08.812Z" }, + { url = "https://files.pythonhosted.org/packages/9b/f5/f9a4a053a5bbff023d3bec259faac8f11a1e5a6479c2ccf586f910d8dac7/coverage-7.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d93fbf446c31c0140208dcd07c5d882029832e8ed7891a39d6d44bd65f2316c3", size = 218044, upload-time = "2025-11-18T13:32:10.329Z" }, + { url = "https://files.pythonhosted.org/packages/95/c5/84fc3697c1fa10cd8571919bf9693f693b7373278daaf3b73e328d502bc8/coverage-7.12.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:52ca620260bd8cd6027317bdd8b8ba929be1d741764ee765b42c4d79a408601e", size = 248440, upload-time = "2025-11-18T13:32:12.536Z" }, + { url = "https://files.pythonhosted.org/packages/f4/36/2d93fbf6a04670f3874aed397d5a5371948a076e3249244a9e84fb0e02d6/coverage-7.12.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f3433ffd541380f3a0e423cff0f4926d55b0cc8c1d160fdc3be24a4c03aa65f7", size = 250361, upload-time = "2025-11-18T13:32:13.852Z" }, + { url = "https://files.pythonhosted.org/packages/5d/49/66dc65cc456a6bfc41ea3d0758c4afeaa4068a2b2931bf83be6894cf1058/coverage-7.12.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f7bbb321d4adc9f65e402c677cd1c8e4c2d0105d3ce285b51b4d87f1d5db5245", size = 252472, upload-time = "2025-11-18T13:32:15.068Z" }, + { url = "https://files.pythonhosted.org/packages/35/1f/ebb8a18dffd406db9fcd4b3ae42254aedcaf612470e8712f12041325930f/coverage-7.12.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:22a7aade354a72dff3b59c577bfd18d6945c61f97393bc5fb7bd293a4237024b", size = 248592, upload-time = "2025-11-18T13:32:16.328Z" }, + { url = "https://files.pythonhosted.org/packages/da/a8/67f213c06e5ea3b3d4980df7dc344d7fea88240b5fe878a5dcbdfe0e2315/coverage-7.12.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3ff651dcd36d2fea66877cd4a82de478004c59b849945446acb5baf9379a1b64", size = 250167, upload-time = "2025-11-18T13:32:17.687Z" }, + { url = "https://files.pythonhosted.org/packages/f0/00/e52aef68154164ea40cc8389c120c314c747fe63a04b013a5782e989b77f/coverage-7.12.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:31b8b2e38391a56e3cea39d22a23faaa7c3fc911751756ef6d2621d2a9daf742", size = 248238, upload-time = "2025-11-18T13:32:19.2Z" }, + { url = "https://files.pythonhosted.org/packages/1f/a4/4d88750bcf9d6d66f77865e5a05a20e14db44074c25fd22519777cb69025/coverage-7.12.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:297bc2da28440f5ae51c845a47c8175a4db0553a53827886e4fb25c66633000c", size = 247964, upload-time = "2025-11-18T13:32:21.027Z" }, + { url = "https://files.pythonhosted.org/packages/a7/6b/b74693158899d5b47b0bf6238d2c6722e20ba749f86b74454fac0696bb00/coverage-7.12.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6ff7651cc01a246908eac162a6a86fc0dbab6de1ad165dfb9a1e2ec660b44984", size = 248862, upload-time = "2025-11-18T13:32:22.304Z" }, + { url = "https://files.pythonhosted.org/packages/18/de/6af6730227ce0e8ade307b1cc4a08e7f51b419a78d02083a86c04ccceb29/coverage-7.12.0-cp311-cp311-win32.whl", hash = "sha256:313672140638b6ddb2c6455ddeda41c6a0b208298034544cfca138978c6baed6", size = 220033, upload-time = "2025-11-18T13:32:23.714Z" }, + { url = "https://files.pythonhosted.org/packages/e2/a1/e7f63021a7c4fe20994359fcdeae43cbef4a4d0ca36a5a1639feeea5d9e1/coverage-7.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:a1783ed5bd0d5938d4435014626568dc7f93e3cb99bc59188cc18857c47aa3c4", size = 220966, upload-time = "2025-11-18T13:32:25.599Z" }, + { url = "https://files.pythonhosted.org/packages/77/e8/deae26453f37c20c3aa0c4433a1e32cdc169bf415cce223a693117aa3ddd/coverage-7.12.0-cp311-cp311-win_arm64.whl", hash = "sha256:4648158fd8dd9381b5847622df1c90ff314efbfc1df4550092ab6013c238a5fc", size = 219637, upload-time = "2025-11-18T13:32:27.265Z" }, + { url = "https://files.pythonhosted.org/packages/02/bf/638c0427c0f0d47638242e2438127f3c8ee3cfc06c7fdeb16778ed47f836/coverage-7.12.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:29644c928772c78512b48e14156b81255000dcfd4817574ff69def189bcb3647", size = 217704, upload-time = "2025-11-18T13:32:28.906Z" }, + { url = "https://files.pythonhosted.org/packages/08/e1/706fae6692a66c2d6b871a608bbde0da6281903fa0e9f53a39ed441da36a/coverage-7.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8638cbb002eaa5d7c8d04da667813ce1067080b9a91099801a0053086e52b736", size = 218064, upload-time = "2025-11-18T13:32:30.161Z" }, + { url = "https://files.pythonhosted.org/packages/a9/8b/eb0231d0540f8af3ffda39720ff43cb91926489d01524e68f60e961366e4/coverage-7.12.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:083631eeff5eb9992c923e14b810a179798bb598e6a0dd60586819fc23be6e60", size = 249560, upload-time = "2025-11-18T13:32:31.835Z" }, + { url = "https://files.pythonhosted.org/packages/e9/a1/67fb52af642e974d159b5b379e4d4c59d0ebe1288677fbd04bbffe665a82/coverage-7.12.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:99d5415c73ca12d558e07776bd957c4222c687b9f1d26fa0e1b57e3598bdcde8", size = 252318, upload-time = "2025-11-18T13:32:33.178Z" }, + { url = "https://files.pythonhosted.org/packages/41/e5/38228f31b2c7665ebf9bdfdddd7a184d56450755c7e43ac721c11a4b8dab/coverage-7.12.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e949ebf60c717c3df63adb4a1a366c096c8d7fd8472608cd09359e1bd48ef59f", size = 253403, upload-time = "2025-11-18T13:32:34.45Z" }, + { url = "https://files.pythonhosted.org/packages/ec/4b/df78e4c8188f9960684267c5a4897836f3f0f20a20c51606ee778a1d9749/coverage-7.12.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6d907ddccbca819afa2cd014bc69983b146cca2735a0b1e6259b2a6c10be1e70", size = 249984, upload-time = "2025-11-18T13:32:35.747Z" }, + { url = "https://files.pythonhosted.org/packages/ba/51/bb163933d195a345c6f63eab9e55743413d064c291b6220df754075c2769/coverage-7.12.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b1518ecbad4e6173f4c6e6c4a46e49555ea5679bf3feda5edb1b935c7c44e8a0", size = 251339, upload-time = "2025-11-18T13:32:37.352Z" }, + { url = "https://files.pythonhosted.org/packages/15/40/c9b29cdb8412c837cdcbc2cfa054547dd83affe6cbbd4ce4fdb92b6ba7d1/coverage-7.12.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:51777647a749abdf6f6fd8c7cffab12de68ab93aab15efc72fbbb83036c2a068", size = 249489, upload-time = "2025-11-18T13:32:39.212Z" }, + { url = "https://files.pythonhosted.org/packages/c8/da/b3131e20ba07a0de4437a50ef3b47840dfabf9293675b0cd5c2c7f66dd61/coverage-7.12.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:42435d46d6461a3b305cdfcad7cdd3248787771f53fe18305548cba474e6523b", size = 249070, upload-time = "2025-11-18T13:32:40.598Z" }, + { url = "https://files.pythonhosted.org/packages/70/81/b653329b5f6302c08d683ceff6785bc60a34be9ae92a5c7b63ee7ee7acec/coverage-7.12.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5bcead88c8423e1855e64b8057d0544e33e4080b95b240c2a355334bb7ced937", size = 250929, upload-time = "2025-11-18T13:32:42.915Z" }, + { url = "https://files.pythonhosted.org/packages/a3/00/250ac3bca9f252a5fb1338b5ad01331ebb7b40223f72bef5b1b2cb03aa64/coverage-7.12.0-cp312-cp312-win32.whl", hash = "sha256:dcbb630ab034e86d2a0f79aefd2be07e583202f41e037602d438c80044957baa", size = 220241, upload-time = "2025-11-18T13:32:44.665Z" }, + { url = "https://files.pythonhosted.org/packages/64/1c/77e79e76d37ce83302f6c21980b45e09f8aa4551965213a10e62d71ce0ab/coverage-7.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:2fd8354ed5d69775ac42986a691fbf68b4084278710cee9d7c3eaa0c28fa982a", size = 221051, upload-time = "2025-11-18T13:32:46.008Z" }, + { url = "https://files.pythonhosted.org/packages/31/f5/641b8a25baae564f9e52cac0e2667b123de961985709a004e287ee7663cc/coverage-7.12.0-cp312-cp312-win_arm64.whl", hash = "sha256:737c3814903be30695b2de20d22bcc5428fdae305c61ba44cdc8b3252984c49c", size = 219692, upload-time = "2025-11-18T13:32:47.372Z" }, + { url = "https://files.pythonhosted.org/packages/b8/14/771700b4048774e48d2c54ed0c674273702713c9ee7acdfede40c2666747/coverage-7.12.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:47324fffca8d8eae7e185b5bb20c14645f23350f870c1649003618ea91a78941", size = 217725, upload-time = "2025-11-18T13:32:49.22Z" }, + { url = "https://files.pythonhosted.org/packages/17/a7/3aa4144d3bcb719bf67b22d2d51c2d577bf801498c13cb08f64173e80497/coverage-7.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ccf3b2ede91decd2fb53ec73c1f949c3e034129d1e0b07798ff1d02ea0c8fa4a", size = 218098, upload-time = "2025-11-18T13:32:50.78Z" }, + { url = "https://files.pythonhosted.org/packages/fc/9c/b846bbc774ff81091a12a10203e70562c91ae71badda00c5ae5b613527b1/coverage-7.12.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b365adc70a6936c6b0582dc38746b33b2454148c02349345412c6e743efb646d", size = 249093, upload-time = "2025-11-18T13:32:52.554Z" }, + { url = "https://files.pythonhosted.org/packages/76/b6/67d7c0e1f400b32c883e9342de4a8c2ae7c1a0b57c5de87622b7262e2309/coverage-7.12.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bc13baf85cd8a4cfcf4a35c7bc9d795837ad809775f782f697bf630b7e200211", size = 251686, upload-time = "2025-11-18T13:32:54.862Z" }, + { url = "https://files.pythonhosted.org/packages/cc/75/b095bd4b39d49c3be4bffbb3135fea18a99a431c52dd7513637c0762fecb/coverage-7.12.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:099d11698385d572ceafb3288a5b80fe1fc58bf665b3f9d362389de488361d3d", size = 252930, upload-time = "2025-11-18T13:32:56.417Z" }, + { url = "https://files.pythonhosted.org/packages/6e/f3/466f63015c7c80550bead3093aacabf5380c1220a2a93c35d374cae8f762/coverage-7.12.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:473dc45d69694069adb7680c405fb1e81f60b2aff42c81e2f2c3feaf544d878c", size = 249296, upload-time = "2025-11-18T13:32:58.074Z" }, + { url = "https://files.pythonhosted.org/packages/27/86/eba2209bf2b7e28c68698fc13437519a295b2d228ba9e0ec91673e09fa92/coverage-7.12.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:583f9adbefd278e9de33c33d6846aa8f5d164fa49b47144180a0e037f0688bb9", size = 251068, upload-time = "2025-11-18T13:32:59.646Z" }, + { url = "https://files.pythonhosted.org/packages/ec/55/ca8ae7dbba962a3351f18940b359b94c6bafdd7757945fdc79ec9e452dc7/coverage-7.12.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b2089cc445f2dc0af6f801f0d1355c025b76c24481935303cf1af28f636688f0", size = 249034, upload-time = "2025-11-18T13:33:01.481Z" }, + { url = "https://files.pythonhosted.org/packages/7a/d7/39136149325cad92d420b023b5fd900dabdd1c3a0d1d5f148ef4a8cedef5/coverage-7.12.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:950411f1eb5d579999c5f66c62a40961f126fc71e5e14419f004471957b51508", size = 248853, upload-time = "2025-11-18T13:33:02.935Z" }, + { url = "https://files.pythonhosted.org/packages/fe/b6/76e1add8b87ef60e00643b0b7f8f7bb73d4bf5249a3be19ebefc5793dd25/coverage-7.12.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b1aab7302a87bafebfe76b12af681b56ff446dc6f32ed178ff9c092ca776e6bc", size = 250619, upload-time = "2025-11-18T13:33:04.336Z" }, + { url = "https://files.pythonhosted.org/packages/95/87/924c6dc64f9203f7a3c1832a6a0eee5a8335dbe5f1bdadcc278d6f1b4d74/coverage-7.12.0-cp313-cp313-win32.whl", hash = "sha256:d7e0d0303c13b54db495eb636bc2465b2fb8475d4c8bcec8fe4b5ca454dfbae8", size = 220261, upload-time = "2025-11-18T13:33:06.493Z" }, + { url = "https://files.pythonhosted.org/packages/91/77/dd4aff9af16ff776bf355a24d87eeb48fc6acde54c907cc1ea89b14a8804/coverage-7.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:ce61969812d6a98a981d147d9ac583a36ac7db7766f2e64a9d4d059c2fe29d07", size = 221072, upload-time = "2025-11-18T13:33:07.926Z" }, + { url = "https://files.pythonhosted.org/packages/70/49/5c9dc46205fef31b1b226a6e16513193715290584317fd4df91cdaf28b22/coverage-7.12.0-cp313-cp313-win_arm64.whl", hash = "sha256:bcec6f47e4cb8a4c2dc91ce507f6eefc6a1b10f58df32cdc61dff65455031dfc", size = 219702, upload-time = "2025-11-18T13:33:09.631Z" }, + { url = "https://files.pythonhosted.org/packages/9b/62/f87922641c7198667994dd472a91e1d9b829c95d6c29529ceb52132436ad/coverage-7.12.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:459443346509476170d553035e4a3eed7b860f4fe5242f02de1010501956ce87", size = 218420, upload-time = "2025-11-18T13:33:11.153Z" }, + { url = "https://files.pythonhosted.org/packages/85/dd/1cc13b2395ef15dbb27d7370a2509b4aee77890a464fb35d72d428f84871/coverage-7.12.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:04a79245ab2b7a61688958f7a855275997134bc84f4a03bc240cf64ff132abf6", size = 218773, upload-time = "2025-11-18T13:33:12.569Z" }, + { url = "https://files.pythonhosted.org/packages/74/40/35773cc4bb1e9d4658d4fb669eb4195b3151bef3bbd6f866aba5cd5dac82/coverage-7.12.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:09a86acaaa8455f13d6a99221d9654df249b33937b4e212b4e5a822065f12aa7", size = 260078, upload-time = "2025-11-18T13:33:14.037Z" }, + { url = "https://files.pythonhosted.org/packages/ec/ee/231bb1a6ffc2905e396557585ebc6bdc559e7c66708376d245a1f1d330fc/coverage-7.12.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:907e0df1b71ba77463687a74149c6122c3f6aac56c2510a5d906b2f368208560", size = 262144, upload-time = "2025-11-18T13:33:15.601Z" }, + { url = "https://files.pythonhosted.org/packages/28/be/32f4aa9f3bf0b56f3971001b56508352c7753915345d45fab4296a986f01/coverage-7.12.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9b57e2d0ddd5f0582bae5437c04ee71c46cd908e7bc5d4d0391f9a41e812dd12", size = 264574, upload-time = "2025-11-18T13:33:17.354Z" }, + { url = "https://files.pythonhosted.org/packages/68/7c/00489fcbc2245d13ab12189b977e0cf06ff3351cb98bc6beba8bd68c5902/coverage-7.12.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:58c1c6aa677f3a1411fe6fb28ec3a942e4f665df036a3608816e0847fad23296", size = 259298, upload-time = "2025-11-18T13:33:18.958Z" }, + { url = "https://files.pythonhosted.org/packages/96/b4/f0760d65d56c3bea95b449e02570d4abd2549dc784bf39a2d4721a2d8ceb/coverage-7.12.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4c589361263ab2953e3c4cd2a94db94c4ad4a8e572776ecfbad2389c626e4507", size = 262150, upload-time = "2025-11-18T13:33:20.644Z" }, + { url = "https://files.pythonhosted.org/packages/c5/71/9a9314df00f9326d78c1e5a910f520d599205907432d90d1c1b7a97aa4b1/coverage-7.12.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:91b810a163ccad2e43b1faa11d70d3cf4b6f3d83f9fd5f2df82a32d47b648e0d", size = 259763, upload-time = "2025-11-18T13:33:22.189Z" }, + { url = "https://files.pythonhosted.org/packages/10/34/01a0aceed13fbdf925876b9a15d50862eb8845454301fe3cdd1df08b2182/coverage-7.12.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:40c867af715f22592e0d0fb533a33a71ec9e0f73a6945f722a0c85c8c1cbe3a2", size = 258653, upload-time = "2025-11-18T13:33:24.239Z" }, + { url = "https://files.pythonhosted.org/packages/8d/04/81d8fd64928acf1574bbb0181f66901c6c1c6279c8ccf5f84259d2c68ae9/coverage-7.12.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:68b0d0a2d84f333de875666259dadf28cc67858bc8fd8b3f1eae84d3c2bec455", size = 260856, upload-time = "2025-11-18T13:33:26.365Z" }, + { url = "https://files.pythonhosted.org/packages/f2/76/fa2a37bfaeaf1f766a2d2360a25a5297d4fb567098112f6517475eee120b/coverage-7.12.0-cp313-cp313t-win32.whl", hash = "sha256:73f9e7fbd51a221818fd11b7090eaa835a353ddd59c236c57b2199486b116c6d", size = 220936, upload-time = "2025-11-18T13:33:28.165Z" }, + { url = "https://files.pythonhosted.org/packages/f9/52/60f64d932d555102611c366afb0eb434b34266b1d9266fc2fe18ab641c47/coverage-7.12.0-cp313-cp313t-win_amd64.whl", hash = "sha256:24cff9d1f5743f67db7ba46ff284018a6e9aeb649b67aa1e70c396aa1b7cb23c", size = 222001, upload-time = "2025-11-18T13:33:29.656Z" }, + { url = "https://files.pythonhosted.org/packages/77/df/c303164154a5a3aea7472bf323b7c857fed93b26618ed9fc5c2955566bb0/coverage-7.12.0-cp313-cp313t-win_arm64.whl", hash = "sha256:c87395744f5c77c866d0f5a43d97cc39e17c7f1cb0115e54a2fe67ca75c5d14d", size = 220273, upload-time = "2025-11-18T13:33:31.415Z" }, + { url = "https://files.pythonhosted.org/packages/bf/2e/fc12db0883478d6e12bbd62d481210f0c8daf036102aa11434a0c5755825/coverage-7.12.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a1c59b7dc169809a88b21a936eccf71c3895a78f5592051b1af8f4d59c2b4f92", size = 217777, upload-time = "2025-11-18T13:33:32.86Z" }, + { url = "https://files.pythonhosted.org/packages/1f/c1/ce3e525d223350c6ec16b9be8a057623f54226ef7f4c2fee361ebb6a02b8/coverage-7.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8787b0f982e020adb732b9f051f3e49dd5054cebbc3f3432061278512a2b1360", size = 218100, upload-time = "2025-11-18T13:33:34.532Z" }, + { url = "https://files.pythonhosted.org/packages/15/87/113757441504aee3808cb422990ed7c8bcc2d53a6779c66c5adef0942939/coverage-7.12.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5ea5a9f7dc8877455b13dd1effd3202e0bca72f6f3ab09f9036b1bcf728f69ac", size = 249151, upload-time = "2025-11-18T13:33:36.135Z" }, + { url = "https://files.pythonhosted.org/packages/d9/1d/9529d9bd44049b6b05bb319c03a3a7e4b0a8a802d28fa348ad407e10706d/coverage-7.12.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fdba9f15849534594f60b47c9a30bc70409b54947319a7c4fd0e8e3d8d2f355d", size = 251667, upload-time = "2025-11-18T13:33:37.996Z" }, + { url = "https://files.pythonhosted.org/packages/11/bb/567e751c41e9c03dc29d3ce74b8c89a1e3396313e34f255a2a2e8b9ebb56/coverage-7.12.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a00594770eb715854fb1c57e0dea08cce6720cfbc531accdb9850d7c7770396c", size = 253003, upload-time = "2025-11-18T13:33:39.553Z" }, + { url = "https://files.pythonhosted.org/packages/e4/b3/c2cce2d8526a02fb9e9ca14a263ca6fc074449b33a6afa4892838c903528/coverage-7.12.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5560c7e0d82b42eb1951e4f68f071f8017c824ebfd5a6ebe42c60ac16c6c2434", size = 249185, upload-time = "2025-11-18T13:33:42.086Z" }, + { url = "https://files.pythonhosted.org/packages/0e/a7/967f93bb66e82c9113c66a8d0b65ecf72fc865adfba5a145f50c7af7e58d/coverage-7.12.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d6c2e26b481c9159c2773a37947a9718cfdc58893029cdfb177531793e375cfc", size = 251025, upload-time = "2025-11-18T13:33:43.634Z" }, + { url = "https://files.pythonhosted.org/packages/b9/b2/f2f6f56337bc1af465d5b2dc1ee7ee2141b8b9272f3bf6213fcbc309a836/coverage-7.12.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:6e1a8c066dabcde56d5d9fed6a66bc19a2883a3fe051f0c397a41fc42aedd4cc", size = 248979, upload-time = "2025-11-18T13:33:46.04Z" }, + { url = "https://files.pythonhosted.org/packages/f4/7a/bf4209f45a4aec09d10a01a57313a46c0e0e8f4c55ff2965467d41a92036/coverage-7.12.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:f7ba9da4726e446d8dd8aae5a6cd872511184a5d861de80a86ef970b5dacce3e", size = 248800, upload-time = "2025-11-18T13:33:47.546Z" }, + { url = "https://files.pythonhosted.org/packages/b8/b7/1e01b8696fb0521810f60c5bbebf699100d6754183e6cc0679bf2ed76531/coverage-7.12.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e0f483ab4f749039894abaf80c2f9e7ed77bbf3c737517fb88c8e8e305896a17", size = 250460, upload-time = "2025-11-18T13:33:49.537Z" }, + { url = "https://files.pythonhosted.org/packages/71/ae/84324fb9cb46c024760e706353d9b771a81b398d117d8c1fe010391c186f/coverage-7.12.0-cp314-cp314-win32.whl", hash = "sha256:76336c19a9ef4a94b2f8dc79f8ac2da3f193f625bb5d6f51a328cd19bfc19933", size = 220533, upload-time = "2025-11-18T13:33:51.16Z" }, + { url = "https://files.pythonhosted.org/packages/e2/71/1033629deb8460a8f97f83e6ac4ca3b93952e2b6f826056684df8275e015/coverage-7.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:7c1059b600aec6ef090721f8f633f60ed70afaffe8ecab85b59df748f24b31fe", size = 221348, upload-time = "2025-11-18T13:33:52.776Z" }, + { url = "https://files.pythonhosted.org/packages/0a/5f/ac8107a902f623b0c251abdb749be282dc2ab61854a8a4fcf49e276fce2f/coverage-7.12.0-cp314-cp314-win_arm64.whl", hash = "sha256:172cf3a34bfef42611963e2b661302a8931f44df31629e5b1050567d6b90287d", size = 219922, upload-time = "2025-11-18T13:33:54.316Z" }, + { url = "https://files.pythonhosted.org/packages/79/6e/f27af2d4da367f16077d21ef6fe796c874408219fa6dd3f3efe7751bd910/coverage-7.12.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:aa7d48520a32cb21c7a9b31f81799e8eaec7239db36c3b670be0fa2403828d1d", size = 218511, upload-time = "2025-11-18T13:33:56.343Z" }, + { url = "https://files.pythonhosted.org/packages/67/dd/65fd874aa460c30da78f9d259400d8e6a4ef457d61ab052fd248f0050558/coverage-7.12.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:90d58ac63bc85e0fb919f14d09d6caa63f35a5512a2205284b7816cafd21bb03", size = 218771, upload-time = "2025-11-18T13:33:57.966Z" }, + { url = "https://files.pythonhosted.org/packages/55/e0/7c6b71d327d8068cb79c05f8f45bf1b6145f7a0de23bbebe63578fe5240a/coverage-7.12.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ca8ecfa283764fdda3eae1bdb6afe58bf78c2c3ec2b2edcb05a671f0bba7b3f9", size = 260151, upload-time = "2025-11-18T13:33:59.597Z" }, + { url = "https://files.pythonhosted.org/packages/49/ce/4697457d58285b7200de6b46d606ea71066c6e674571a946a6ea908fb588/coverage-7.12.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:874fe69a0785d96bd066059cd4368022cebbec1a8958f224f0016979183916e6", size = 262257, upload-time = "2025-11-18T13:34:01.166Z" }, + { url = "https://files.pythonhosted.org/packages/2f/33/acbc6e447aee4ceba88c15528dbe04a35fb4d67b59d393d2e0d6f1e242c1/coverage-7.12.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5b3c889c0b8b283a24d721a9eabc8ccafcfc3aebf167e4cd0d0e23bf8ec4e339", size = 264671, upload-time = "2025-11-18T13:34:02.795Z" }, + { url = "https://files.pythonhosted.org/packages/87/ec/e2822a795c1ed44d569980097be839c5e734d4c0c1119ef8e0a073496a30/coverage-7.12.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8bb5b894b3ec09dcd6d3743229dc7f2c42ef7787dc40596ae04c0edda487371e", size = 259231, upload-time = "2025-11-18T13:34:04.397Z" }, + { url = "https://files.pythonhosted.org/packages/72/c5/a7ec5395bb4a49c9b7ad97e63f0c92f6bf4a9e006b1393555a02dae75f16/coverage-7.12.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:79a44421cd5fba96aa57b5e3b5a4d3274c449d4c622e8f76882d76635501fd13", size = 262137, upload-time = "2025-11-18T13:34:06.068Z" }, + { url = "https://files.pythonhosted.org/packages/67/0c/02c08858b764129f4ecb8e316684272972e60777ae986f3865b10940bdd6/coverage-7.12.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:33baadc0efd5c7294f436a632566ccc1f72c867f82833eb59820ee37dc811c6f", size = 259745, upload-time = "2025-11-18T13:34:08.04Z" }, + { url = "https://files.pythonhosted.org/packages/5a/04/4fd32b7084505f3829a8fe45c1a74a7a728cb251aaadbe3bec04abcef06d/coverage-7.12.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:c406a71f544800ef7e9e0000af706b88465f3573ae8b8de37e5f96c59f689ad1", size = 258570, upload-time = "2025-11-18T13:34:09.676Z" }, + { url = "https://files.pythonhosted.org/packages/48/35/2365e37c90df4f5342c4fa202223744119fe31264ee2924f09f074ea9b6d/coverage-7.12.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e71bba6a40883b00c6d571599b4627f50c360b3d0d02bfc658168936be74027b", size = 260899, upload-time = "2025-11-18T13:34:11.259Z" }, + { url = "https://files.pythonhosted.org/packages/05/56/26ab0464ca733fa325e8e71455c58c1c374ce30f7c04cebb88eabb037b18/coverage-7.12.0-cp314-cp314t-win32.whl", hash = "sha256:9157a5e233c40ce6613dead4c131a006adfda70e557b6856b97aceed01b0e27a", size = 221313, upload-time = "2025-11-18T13:34:12.863Z" }, + { url = "https://files.pythonhosted.org/packages/da/1c/017a3e1113ed34d998b27d2c6dba08a9e7cb97d362f0ec988fcd873dcf81/coverage-7.12.0-cp314-cp314t-win_amd64.whl", hash = "sha256:e84da3a0fd233aeec797b981c51af1cabac74f9bd67be42458365b30d11b5291", size = 222423, upload-time = "2025-11-18T13:34:15.14Z" }, + { url = "https://files.pythonhosted.org/packages/4c/36/bcc504fdd5169301b52568802bb1b9cdde2e27a01d39fbb3b4b508ab7c2c/coverage-7.12.0-cp314-cp314t-win_arm64.whl", hash = "sha256:01d24af36fedda51c2b1aca56e4330a3710f83b02a5ff3743a6b015ffa7c9384", size = 220459, upload-time = "2025-11-18T13:34:17.222Z" }, + { url = "https://files.pythonhosted.org/packages/ce/a3/43b749004e3c09452e39bb56347a008f0a0668aad37324a99b5c8ca91d9e/coverage-7.12.0-py3-none-any.whl", hash = "sha256:159d50c0b12e060b15ed3d39f87ed43d4f7f7ad40b8a534f4dd331adbb51104a", size = 209503, upload-time = "2025-11-18T13:34:18.892Z" }, +] + +[package.optional-dependencies] +toml = [ + { name = "tomli", marker = "python_full_version >= '3.10' and python_full_version <= '3.11'" }, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", version = "4.13.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "typing-extensions", version = "4.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9' and python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.9.*'", + "python_full_version < '3.9'", +] +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "jsonpath-python" +version = "1.0.6" +source = { editable = "." } + +[package.optional-dependencies] +dev = [ + { name = "pytest", version = "8.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "pytest", version = "9.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pytest-cov", version = "5.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "pytest-cov", version = "7.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, + { name = "ruff" }, +] + +[package.metadata] +requires-dist = [ + { name = "pytest", marker = "extra == 'dev'", specifier = ">=8.0" }, + { name = "pytest-cov", marker = "extra == 'dev'", specifier = ">=5.0" }, + { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.3" }, +] +provides-extras = ["dev"] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, +] + +[[package]] +name = "pluggy" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.9'", +] +sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955, upload-time = "2024-04-20T21:34:42.531Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556, upload-time = "2024-04-20T21:34:40.434Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", + "python_full_version == '3.9.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pytest" +version = "8.3.5" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.9'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version < '3.9' and sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.9'" }, + { name = "iniconfig", version = "2.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "packaging", marker = "python_full_version < '3.9'" }, + { name = "pluggy", version = "1.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "tomli", marker = "python_full_version < '3.9'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891, upload-time = "2025-03-02T12:54:54.503Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634, upload-time = "2025-03-02T12:54:52.069Z" }, +] + +[[package]] +name = "pytest" +version = "8.4.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.9.*'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version == '3.9.*' and sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version == '3.9.*'" }, + { name = "iniconfig", version = "2.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "packaging", marker = "python_full_version == '3.9.*'" }, + { name = "pluggy", version = "1.6.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "pygments", marker = "python_full_version == '3.9.*'" }, + { name = "tomli", marker = "python_full_version == '3.9.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" }, +] + +[[package]] +name = "pytest" +version = "9.0.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version == '3.10.*'" }, + { name = "iniconfig", version = "2.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "packaging", marker = "python_full_version >= '3.10'" }, + { name = "pluggy", version = "1.6.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pygments", marker = "python_full_version >= '3.10'" }, + { name = "tomli", marker = "python_full_version == '3.10.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/07/56/f013048ac4bc4c1d9be45afd4ab209ea62822fb1598f40687e6bf45dcea4/pytest-9.0.1.tar.gz", hash = "sha256:3e9c069ea73583e255c3b21cf46b8d3c56f6e3a1a8f6da94ccb0fcf57b9d73c8", size = 1564125, upload-time = "2025-11-12T13:05:09.333Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/8b/6300fb80f858cda1c51ffa17075df5d846757081d11ab4aa35cef9e6258b/pytest-9.0.1-py3-none-any.whl", hash = "sha256:67be0030d194df2dfa7b556f2e56fb3c3315bd5c8822c6951162b92b32ce7dad", size = 373668, upload-time = "2025-11-12T13:05:07.379Z" }, +] + +[[package]] +name = "pytest-cov" +version = "5.0.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.9'", +] +dependencies = [ + { name = "coverage", version = "7.6.1", source = { registry = "https://pypi.org/simple" }, extra = ["toml"], marker = "python_full_version < '3.9'" }, + { name = "pytest", version = "8.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/74/67/00efc8d11b630c56f15f4ad9c7f9223f1e5ec275aaae3fa9118c6a223ad2/pytest-cov-5.0.0.tar.gz", hash = "sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857", size = 63042, upload-time = "2024-03-24T20:16:34.856Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/3a/af5b4fa5961d9a1e6237b530eb87dd04aea6eb83da09d2a4073d81b54ccf/pytest_cov-5.0.0-py3-none-any.whl", hash = "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652", size = 21990, upload-time = "2024-03-24T20:16:32.444Z" }, +] + +[[package]] +name = "pytest-cov" +version = "7.0.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", + "python_full_version == '3.9.*'", +] +dependencies = [ + { name = "coverage", version = "7.10.7", source = { registry = "https://pypi.org/simple" }, extra = ["toml"], marker = "python_full_version == '3.9.*'" }, + { name = "coverage", version = "7.12.0", source = { registry = "https://pypi.org/simple" }, extra = ["toml"], marker = "python_full_version >= '3.10'" }, + { name = "pluggy", version = "1.6.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, + { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "pytest", version = "9.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5e/f7/c933acc76f5208b3b00089573cf6a2bc26dc80a8aece8f52bb7d6b1855ca/pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1", size = 54328, upload-time = "2025-09-09T10:57:02.113Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861", size = 22424, upload-time = "2025-09-09T10:57:00.695Z" }, +] + +[[package]] +name = "ruff" +version = "0.14.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/52/f0/62b5a1a723fe183650109407fa56abb433b00aa1c0b9ba555f9c4efec2c6/ruff-0.14.6.tar.gz", hash = "sha256:6f0c742ca6a7783a736b867a263b9a7a80a45ce9bee391eeda296895f1b4e1cc", size = 5669501, upload-time = "2025-11-21T14:26:17.903Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/67/d2/7dd544116d107fffb24a0064d41a5d2ed1c9d6372d142f9ba108c8e39207/ruff-0.14.6-py3-none-linux_armv6l.whl", hash = "sha256:d724ac2f1c240dbd01a2ae98db5d1d9a5e1d9e96eba999d1c48e30062df578a3", size = 13326119, upload-time = "2025-11-21T14:25:24.2Z" }, + { url = "https://files.pythonhosted.org/packages/36/6a/ad66d0a3315d6327ed6b01f759d83df3c4d5f86c30462121024361137b6a/ruff-0.14.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9f7539ea257aa4d07b7ce87aed580e485c40143f2473ff2f2b75aee003186004", size = 13526007, upload-time = "2025-11-21T14:25:26.906Z" }, + { url = "https://files.pythonhosted.org/packages/a3/9d/dae6db96df28e0a15dea8e986ee393af70fc97fd57669808728080529c37/ruff-0.14.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7f6007e55b90a2a7e93083ba48a9f23c3158c433591c33ee2e99a49b889c6332", size = 12676572, upload-time = "2025-11-21T14:25:29.826Z" }, + { url = "https://files.pythonhosted.org/packages/76/a4/f319e87759949062cfee1b26245048e92e2acce900ad3a909285f9db1859/ruff-0.14.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a8e7b9d73d8728b68f632aa8e824ef041d068d231d8dbc7808532d3629a6bef", size = 13140745, upload-time = "2025-11-21T14:25:32.788Z" }, + { url = "https://files.pythonhosted.org/packages/95/d3/248c1efc71a0a8ed4e8e10b4b2266845d7dfc7a0ab64354afe049eaa1310/ruff-0.14.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d50d45d4553a3ebcbd33e7c5e0fe6ca4aafd9a9122492de357205c2c48f00775", size = 13076486, upload-time = "2025-11-21T14:25:35.601Z" }, + { url = "https://files.pythonhosted.org/packages/a5/19/b68d4563fe50eba4b8c92aa842149bb56dd24d198389c0ed12e7faff4f7d/ruff-0.14.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:118548dd121f8a21bfa8ab2c5b80e5b4aed67ead4b7567790962554f38e598ce", size = 13727563, upload-time = "2025-11-21T14:25:38.514Z" }, + { url = "https://files.pythonhosted.org/packages/47/ac/943169436832d4b0e867235abbdb57ce3a82367b47e0280fa7b4eabb7593/ruff-0.14.6-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:57256efafbfefcb8748df9d1d766062f62b20150691021f8ab79e2d919f7c11f", size = 15199755, upload-time = "2025-11-21T14:25:41.516Z" }, + { url = "https://files.pythonhosted.org/packages/c9/b9/288bb2399860a36d4bb0541cb66cce3c0f4156aaff009dc8499be0c24bf2/ruff-0.14.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ff18134841e5c68f8e5df1999a64429a02d5549036b394fafbe410f886e1989d", size = 14850608, upload-time = "2025-11-21T14:25:44.428Z" }, + { url = "https://files.pythonhosted.org/packages/ee/b1/a0d549dd4364e240f37e7d2907e97ee80587480d98c7799d2d8dc7a2f605/ruff-0.14.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:29c4b7ec1e66a105d5c27bd57fa93203637d66a26d10ca9809dc7fc18ec58440", size = 14118754, upload-time = "2025-11-21T14:25:47.214Z" }, + { url = "https://files.pythonhosted.org/packages/13/ac/9b9fe63716af8bdfddfacd0882bc1586f29985d3b988b3c62ddce2e202c3/ruff-0.14.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:167843a6f78680746d7e226f255d920aeed5e4ad9c03258094a2d49d3028b105", size = 13949214, upload-time = "2025-11-21T14:25:50.002Z" }, + { url = "https://files.pythonhosted.org/packages/12/27/4dad6c6a77fede9560b7df6802b1b697e97e49ceabe1f12baf3ea20862e9/ruff-0.14.6-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:16a33af621c9c523b1ae006b1b99b159bf5ac7e4b1f20b85b2572455018e0821", size = 14106112, upload-time = "2025-11-21T14:25:52.841Z" }, + { url = "https://files.pythonhosted.org/packages/6a/db/23e322d7177873eaedea59a7932ca5084ec5b7e20cb30f341ab594130a71/ruff-0.14.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:1432ab6e1ae2dc565a7eea707d3b03a0c234ef401482a6f1621bc1f427c2ff55", size = 13035010, upload-time = "2025-11-21T14:25:55.536Z" }, + { url = "https://files.pythonhosted.org/packages/a8/9c/20e21d4d69dbb35e6a1df7691e02f363423658a20a2afacf2a2c011800dc/ruff-0.14.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:4c55cfbbe7abb61eb914bfd20683d14cdfb38a6d56c6c66efa55ec6570ee4e71", size = 13054082, upload-time = "2025-11-21T14:25:58.625Z" }, + { url = "https://files.pythonhosted.org/packages/66/25/906ee6a0464c3125c8d673c589771a974965c2be1a1e28b5c3b96cb6ef88/ruff-0.14.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:efea3c0f21901a685fff4befda6d61a1bf4cb43de16da87e8226a281d614350b", size = 13303354, upload-time = "2025-11-21T14:26:01.816Z" }, + { url = "https://files.pythonhosted.org/packages/4c/58/60577569e198d56922b7ead07b465f559002b7b11d53f40937e95067ca1c/ruff-0.14.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:344d97172576d75dc6afc0e9243376dbe1668559c72de1864439c4fc95f78185", size = 14054487, upload-time = "2025-11-21T14:26:05.058Z" }, + { url = "https://files.pythonhosted.org/packages/67/0b/8e4e0639e4cc12547f41cb771b0b44ec8225b6b6a93393176d75fe6f7d40/ruff-0.14.6-py3-none-win32.whl", hash = "sha256:00169c0c8b85396516fdd9ce3446c7ca20c2a8f90a77aa945ba6b8f2bfe99e85", size = 13013361, upload-time = "2025-11-21T14:26:08.152Z" }, + { url = "https://files.pythonhosted.org/packages/fb/02/82240553b77fd1341f80ebb3eaae43ba011c7a91b4224a9f317d8e6591af/ruff-0.14.6-py3-none-win_amd64.whl", hash = "sha256:390e6480c5e3659f8a4c8d6a0373027820419ac14fa0d2713bd8e6c3e125b8b9", size = 14432087, upload-time = "2025-11-21T14:26:10.891Z" }, + { url = "https://files.pythonhosted.org/packages/a5/1f/93f9b0fad9470e4c829a5bb678da4012f0c710d09331b860ee555216f4ea/ruff-0.14.6-py3-none-win_arm64.whl", hash = "sha256:d43c81fbeae52cfa8728d8766bbf46ee4298c888072105815b392da70ca836b2", size = 13520930, upload-time = "2025-11-21T14:26:13.951Z" }, +] + +[[package]] +name = "tomli" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/52/ed/3f73f72945444548f33eba9a87fc7a6e969915e7b1acc8260b30e1f76a2f/tomli-2.3.0.tar.gz", hash = "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549", size = 17392, upload-time = "2025-10-08T22:01:47.119Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/2e/299f62b401438d5fe1624119c723f5d877acc86a4c2492da405626665f12/tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45", size = 153236, upload-time = "2025-10-08T22:01:00.137Z" }, + { url = "https://files.pythonhosted.org/packages/86/7f/d8fffe6a7aefdb61bced88fcb5e280cfd71e08939da5894161bd71bea022/tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba", size = 148084, upload-time = "2025-10-08T22:01:01.63Z" }, + { url = "https://files.pythonhosted.org/packages/47/5c/24935fb6a2ee63e86d80e4d3b58b222dafaf438c416752c8b58537c8b89a/tomli-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf", size = 234832, upload-time = "2025-10-08T22:01:02.543Z" }, + { url = "https://files.pythonhosted.org/packages/89/da/75dfd804fc11e6612846758a23f13271b76d577e299592b4371a4ca4cd09/tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441", size = 242052, upload-time = "2025-10-08T22:01:03.836Z" }, + { url = "https://files.pythonhosted.org/packages/70/8c/f48ac899f7b3ca7eb13af73bacbc93aec37f9c954df3c08ad96991c8c373/tomli-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845", size = 239555, upload-time = "2025-10-08T22:01:04.834Z" }, + { url = "https://files.pythonhosted.org/packages/ba/28/72f8afd73f1d0e7829bfc093f4cb98ce0a40ffc0cc997009ee1ed94ba705/tomli-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c", size = 245128, upload-time = "2025-10-08T22:01:05.84Z" }, + { url = "https://files.pythonhosted.org/packages/b6/eb/a7679c8ac85208706d27436e8d421dfa39d4c914dcf5fa8083a9305f58d9/tomli-2.3.0-cp311-cp311-win32.whl", hash = "sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456", size = 96445, upload-time = "2025-10-08T22:01:06.896Z" }, + { url = "https://files.pythonhosted.org/packages/0a/fe/3d3420c4cb1ad9cb462fb52967080575f15898da97e21cb6f1361d505383/tomli-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be", size = 107165, upload-time = "2025-10-08T22:01:08.107Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b7/40f36368fcabc518bb11c8f06379a0fd631985046c038aca08c6d6a43c6e/tomli-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac", size = 154891, upload-time = "2025-10-08T22:01:09.082Z" }, + { url = "https://files.pythonhosted.org/packages/f9/3f/d9dd692199e3b3aab2e4e4dd948abd0f790d9ded8cd10cbaae276a898434/tomli-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22", size = 148796, upload-time = "2025-10-08T22:01:10.266Z" }, + { url = "https://files.pythonhosted.org/packages/60/83/59bff4996c2cf9f9387a0f5a3394629c7efa5ef16142076a23a90f1955fa/tomli-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f", size = 242121, upload-time = "2025-10-08T22:01:11.332Z" }, + { url = "https://files.pythonhosted.org/packages/45/e5/7c5119ff39de8693d6baab6c0b6dcb556d192c165596e9fc231ea1052041/tomli-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52", size = 250070, upload-time = "2025-10-08T22:01:12.498Z" }, + { url = "https://files.pythonhosted.org/packages/45/12/ad5126d3a278f27e6701abde51d342aa78d06e27ce2bb596a01f7709a5a2/tomli-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8", size = 245859, upload-time = "2025-10-08T22:01:13.551Z" }, + { url = "https://files.pythonhosted.org/packages/fb/a1/4d6865da6a71c603cfe6ad0e6556c73c76548557a8d658f9e3b142df245f/tomli-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6", size = 250296, upload-time = "2025-10-08T22:01:14.614Z" }, + { url = "https://files.pythonhosted.org/packages/a0/b7/a7a7042715d55c9ba6e8b196d65d2cb662578b4d8cd17d882d45322b0d78/tomli-2.3.0-cp312-cp312-win32.whl", hash = "sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876", size = 97124, upload-time = "2025-10-08T22:01:15.629Z" }, + { url = "https://files.pythonhosted.org/packages/06/1e/f22f100db15a68b520664eb3328fb0ae4e90530887928558112c8d1f4515/tomli-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878", size = 107698, upload-time = "2025-10-08T22:01:16.51Z" }, + { url = "https://files.pythonhosted.org/packages/89/48/06ee6eabe4fdd9ecd48bf488f4ac783844fd777f547b8d1b61c11939974e/tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b", size = 154819, upload-time = "2025-10-08T22:01:17.964Z" }, + { url = "https://files.pythonhosted.org/packages/f1/01/88793757d54d8937015c75dcdfb673c65471945f6be98e6a0410fba167ed/tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae", size = 148766, upload-time = "2025-10-08T22:01:18.959Z" }, + { url = "https://files.pythonhosted.org/packages/42/17/5e2c956f0144b812e7e107f94f1cc54af734eb17b5191c0bbfb72de5e93e/tomli-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b", size = 240771, upload-time = "2025-10-08T22:01:20.106Z" }, + { url = "https://files.pythonhosted.org/packages/d5/f4/0fbd014909748706c01d16824eadb0307115f9562a15cbb012cd9b3512c5/tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf", size = 248586, upload-time = "2025-10-08T22:01:21.164Z" }, + { url = "https://files.pythonhosted.org/packages/30/77/fed85e114bde5e81ecf9bc5da0cc69f2914b38f4708c80ae67d0c10180c5/tomli-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f", size = 244792, upload-time = "2025-10-08T22:01:22.417Z" }, + { url = "https://files.pythonhosted.org/packages/55/92/afed3d497f7c186dc71e6ee6d4fcb0acfa5f7d0a1a2878f8beae379ae0cc/tomli-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05", size = 248909, upload-time = "2025-10-08T22:01:23.859Z" }, + { url = "https://files.pythonhosted.org/packages/f8/84/ef50c51b5a9472e7265ce1ffc7f24cd4023d289e109f669bdb1553f6a7c2/tomli-2.3.0-cp313-cp313-win32.whl", hash = "sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606", size = 96946, upload-time = "2025-10-08T22:01:24.893Z" }, + { url = "https://files.pythonhosted.org/packages/b2/b7/718cd1da0884f281f95ccfa3a6cc572d30053cba64603f79d431d3c9b61b/tomli-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999", size = 107705, upload-time = "2025-10-08T22:01:26.153Z" }, + { url = "https://files.pythonhosted.org/packages/19/94/aeafa14a52e16163008060506fcb6aa1949d13548d13752171a755c65611/tomli-2.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e", size = 154244, upload-time = "2025-10-08T22:01:27.06Z" }, + { url = "https://files.pythonhosted.org/packages/db/e4/1e58409aa78eefa47ccd19779fc6f36787edbe7d4cd330eeeedb33a4515b/tomli-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3", size = 148637, upload-time = "2025-10-08T22:01:28.059Z" }, + { url = "https://files.pythonhosted.org/packages/26/b6/d1eccb62f665e44359226811064596dd6a366ea1f985839c566cd61525ae/tomli-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc", size = 241925, upload-time = "2025-10-08T22:01:29.066Z" }, + { url = "https://files.pythonhosted.org/packages/70/91/7cdab9a03e6d3d2bb11beae108da5bdc1c34bdeb06e21163482544ddcc90/tomli-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0", size = 249045, upload-time = "2025-10-08T22:01:31.98Z" }, + { url = "https://files.pythonhosted.org/packages/15/1b/8c26874ed1f6e4f1fcfeb868db8a794cbe9f227299402db58cfcc858766c/tomli-2.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879", size = 245835, upload-time = "2025-10-08T22:01:32.989Z" }, + { url = "https://files.pythonhosted.org/packages/fd/42/8e3c6a9a4b1a1360c1a2a39f0b972cef2cc9ebd56025168c4137192a9321/tomli-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005", size = 253109, upload-time = "2025-10-08T22:01:34.052Z" }, + { url = "https://files.pythonhosted.org/packages/22/0c/b4da635000a71b5f80130937eeac12e686eefb376b8dee113b4a582bba42/tomli-2.3.0-cp314-cp314-win32.whl", hash = "sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463", size = 97930, upload-time = "2025-10-08T22:01:35.082Z" }, + { url = "https://files.pythonhosted.org/packages/b9/74/cb1abc870a418ae99cd5c9547d6bce30701a954e0e721821df483ef7223c/tomli-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8", size = 107964, upload-time = "2025-10-08T22:01:36.057Z" }, + { url = "https://files.pythonhosted.org/packages/54/78/5c46fff6432a712af9f792944f4fcd7067d8823157949f4e40c56b8b3c83/tomli-2.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77", size = 163065, upload-time = "2025-10-08T22:01:37.27Z" }, + { url = "https://files.pythonhosted.org/packages/39/67/f85d9bd23182f45eca8939cd2bc7050e1f90c41f4a2ecbbd5963a1d1c486/tomli-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf", size = 159088, upload-time = "2025-10-08T22:01:38.235Z" }, + { url = "https://files.pythonhosted.org/packages/26/5a/4b546a0405b9cc0659b399f12b6adb750757baf04250b148d3c5059fc4eb/tomli-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530", size = 268193, upload-time = "2025-10-08T22:01:39.712Z" }, + { url = "https://files.pythonhosted.org/packages/42/4f/2c12a72ae22cf7b59a7fe75b3465b7aba40ea9145d026ba41cb382075b0e/tomli-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b", size = 275488, upload-time = "2025-10-08T22:01:40.773Z" }, + { url = "https://files.pythonhosted.org/packages/92/04/a038d65dbe160c3aa5a624e93ad98111090f6804027d474ba9c37c8ae186/tomli-2.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67", size = 272669, upload-time = "2025-10-08T22:01:41.824Z" }, + { url = "https://files.pythonhosted.org/packages/be/2f/8b7c60a9d1612a7cbc39ffcca4f21a73bf368a80fc25bccf8253e2563267/tomli-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f", size = 279709, upload-time = "2025-10-08T22:01:43.177Z" }, + { url = "https://files.pythonhosted.org/packages/7e/46/cc36c679f09f27ded940281c38607716c86cf8ba4a518d524e349c8b4874/tomli-2.3.0-cp314-cp314t-win32.whl", hash = "sha256:a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0", size = 107563, upload-time = "2025-10-08T22:01:44.233Z" }, + { url = "https://files.pythonhosted.org/packages/84/ff/426ca8683cf7b753614480484f6437f568fd2fda2edbdf57a2d3d8b27a0b/tomli-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba", size = 119756, upload-time = "2025-10-08T22:01:45.234Z" }, + { url = "https://files.pythonhosted.org/packages/77/b8/0135fadc89e73be292b473cb820b4f5a08197779206b33191e801feeae40/tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b", size = 14408, upload-time = "2025-10-08T22:01:46.04Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.13.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.9'", +] +sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967, upload-time = "2025-04-10T14:19:05.416Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806, upload-time = "2025-04-10T14:19:03.967Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", + "python_full_version == '3.9.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] From 18eab0b6f4e3b5e36c307786104edb14aeeecf54 Mon Sep 17 00:00:00 2001 From: sean2077 <37324769+sean2077@users.noreply.github.com> Date: Sun, 23 Nov 2025 20:27:58 +0800 Subject: [PATCH 02/11] ci: optimize workflows --- .github/workflows/ci.yml | 5 ++-- .github/workflows/docs.yml | 59 -------------------------------------- 2 files changed, 2 insertions(+), 62 deletions(-) delete mode 100644 .github/workflows/docs.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2c0080b..0526cc8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,10 +33,9 @@ jobs: run: uv run ruff check . test: - runs-on: ${{ matrix.os }} + runs-on: ubuntu-latest strategy: matrix: - os: [ubuntu-latest, windows-latest, macos-latest] python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] steps: - name: Checkout code @@ -59,7 +58,7 @@ jobs: run: uv run pytest -v --cov=jsonpath --cov-report=xml - name: Upload coverage - if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.12' + if: matrix.python-version == '3.12' uses: codecov/codecov-action@v5 with: file: ./coverage.xml diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml deleted file mode 100644 index 7e908d1..0000000 --- a/.github/workflows/docs.yml +++ /dev/null @@ -1,59 +0,0 @@ -name: Deploy Documentation - -on: - push: - branches: - - main - - dev - workflow_dispatch: - -permissions: - contents: read - pages: write - id-token: write - -concurrency: - group: "pages" - cancel-in-progress: false - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - with: - fetch-depth: 0 # Fetch all history for git info - - - name: Set up Python - uses: actions/setup-python@v6 - with: - python-version: "3.12" - - - name: Install uv - uses: astral-sh/setup-uv@v7 - with: - version: "latest" - - - name: Install dependencies - run: uv sync --extra docs - - - name: Build documentation - run: uv run mkdocs build --strict - - - name: Upload artifact - uses: actions/upload-pages-artifact@v4 - with: - path: ./site - - deploy: - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - runs-on: ubuntu-latest - needs: build - # Temporarily allow deployment from dev branch for testing - if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev' - steps: - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v4.0.5 From 47bba251233b8f96963dc22e2e83238b417e332a Mon Sep 17 00:00:00 2001 From: sean2077 <37324769+sean2077@users.noreply.github.com> Date: Sun, 23 Nov 2025 20:41:55 +0800 Subject: [PATCH 03/11] fix: resolve issue #17 false positive errors and parsing bugs --- jsonpath/jsonpath.py | 7 ++++--- tests/test_issues.py | 27 +++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 3 deletions(-) create mode 100644 tests/test_issues.py diff --git a/jsonpath/jsonpath.py b/jsonpath/jsonpath.py index 3641dd6..c09c756 100644 --- a/jsonpath/jsonpath.py +++ b/jsonpath/jsonpath.py @@ -66,7 +66,7 @@ class JSONPath: def __init__(self, expr: str): expr = self._parse_expr(expr) - self.segments = expr.split(JSONPath.SEP) + self.segments = [s for s in expr.split(JSONPath.SEP) if s] self.lpath = len(self.segments) logger.debug(f"segments : {self.segments}") @@ -95,6 +95,7 @@ def _parse_expr(self, expr): expr = JSONPath.REP_GET_QUOTE.sub(self._get_quote, expr) expr = JSONPath.REP_GET_BACKQUOTE.sub(self._get_backquote, expr) expr = JSONPath.REP_GET_BRACKET.sub(self._get_bracket, expr) + expr = re.sub(r"\.(\.#B)", r"\1", expr) expr = JSONPath.REP_GET_PAREN.sub(self._get_paren, expr) # split expr = JSONPath.REP_DOUBLEDOT.sub(f"{JSONPath.SEP}..{JSONPath.SEP}", expr) @@ -195,8 +196,8 @@ def _filter(self, obj, i: int, path: str, step: str): r = False try: r = self.eval_func(step, None, {"__obj": obj}) - except Exception as err: - logger.error(err) + except Exception: + pass if r: self._trace(obj, i, path) diff --git a/tests/test_issues.py b/tests/test_issues.py new file mode 100644 index 0000000..b059191 --- /dev/null +++ b/tests/test_issues.py @@ -0,0 +1,27 @@ +from jsonpath import JSONPath + + +def test_issue_17(): + data = [ + {"time": "2023-01-02T20:32:01Z", "user": "user1"}, + {"time": "2023-01-02T20:32:03Z", "user": "user2"}, + {"time": "2023-01-02T20:32:03Z", "user": "user1"}, + ] + user = "user1" + expr = f'$.[?(@.user=="{user}")]' + result = JSONPath(expr).parse(data) + assert len(result) == 2 + assert result[0]["user"] == "user1" + assert result[1]["user"] == "user1" + + +def test_issue_17_bracket_dot_normalization(): + data = {"store": "book"} + # Case 1: Standard bracket notation + assert JSONPath("$['store']").parse(data) == ["book"] + # Case 2: Dot followed by bracket (should be normalized to bracket) + assert JSONPath("$.['store']").parse(data) == ["book"] + # Case 3: Recursive descent with bracket (should NOT be normalized) + data_recursive = {"a": {"store": "book"}, "b": {"store": "paper"}} + # $..['store'] should find all 'store' keys + assert sorted(JSONPath("$..['store']").parse(data_recursive)) == ["book", "paper"] From 6caa9d4e0e9a95363edc6024b41d5b8ea5e785c0 Mon Sep 17 00:00:00 2001 From: sean2077 <37324769+sean2077@users.noreply.github.com> Date: Sun, 23 Nov 2025 20:52:27 +0800 Subject: [PATCH 04/11] fix: resolve issue #16 support quoted keys in filters and paths --- jsonpath/jsonpath.py | 19 +++++++++++++------ tests/test_issues.py | 19 +++++++++++++++++++ 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/jsonpath/jsonpath.py b/jsonpath/jsonpath.py index c09c756..56965b6 100644 --- a/jsonpath/jsonpath.py +++ b/jsonpath/jsonpath.py @@ -118,7 +118,7 @@ def _get_quote(self, m): return f"#Q{n}" def _put_quote(self, m): - return self.subx["#Q"][int(m.group(1))] + return f"'{self.subx['#Q'][int(m.group(1))]}'" def _get_backquote(self, m): n = len(self.subx["#BQ"]) @@ -146,8 +146,11 @@ def _put_paren(self, m): @staticmethod def _gen_obj(m): + content = m.group(1) or m.group(2) # group 2 is for len() ret = "__obj" - for e in m.group(1).split("."): + for e in content.split("."): + if len(e) >= 2 and ((e[0] == "'" and e[-1] == "'") or (e[0] == '"' and e[-1] == '"')): + e = e[1:-1] ret += f'["{e}"]' return ret @@ -239,11 +242,15 @@ def _trace(self, obj, i: int, path): return # get value from dict - if isinstance(obj, dict) and step in obj: - if re.match(r"^\w+$", step): - self._trace(obj[step], i + 1, f"{path}.{step}") + step_key = step + if len(step) >= 2 and step[0] == "'" and step[-1] == "'": + step_key = step[1:-1] + + if isinstance(obj, dict) and step_key in obj: + if re.match(r"^\w+$", step_key): + self._trace(obj[step_key], i + 1, f"{path}.{step_key}") else: - self._trace(obj[step], i + 1, f"{path}['{step}']") + self._trace(obj[step_key], i + 1, f"{path}['{step_key}']") return # slice diff --git a/tests/test_issues.py b/tests/test_issues.py index b059191..d41a5db 100644 --- a/tests/test_issues.py +++ b/tests/test_issues.py @@ -25,3 +25,22 @@ def test_issue_17_bracket_dot_normalization(): data_recursive = {"a": {"store": "book"}, "b": {"store": "paper"}} # $..['store'] should find all 'store' keys assert sorted(JSONPath("$..['store']").parse(data_recursive)) == ["book", "paper"] + + +def test_issue_16_quoted_keys(): + data = {"user-list": [{"city-name": "Austin", "name": "John"}, {"city-name": "New York", "name": "Jane"}]} + + # Case 1: Key with hyphen in the path + assert JSONPath("$.'user-list'").parse(data) == [ + [{"city-name": "Austin", "name": "John"}, {"city-name": "New York", "name": "Jane"}] + ] + + # Case 2: Key with hyphen in a filter (single quotes) + assert JSONPath("$.'user-list'[?(@.'city-name'=='Austin')]").parse(data) == [ + {"city-name": "Austin", "name": "John"} + ] + + # Case 3: Key with hyphen in a filter (double quotes) + assert JSONPath('$.\'user-list\'[?(@."city-name"=="Austin")]').parse(data) == [ + {"city-name": "Austin", "name": "John"} + ] From 100bf6fda10d9802cff7223e443a11fb63f1409a Mon Sep 17 00:00:00 2001 From: sean2077 <37324769+sean2077@users.noreply.github.com> Date: Sun, 23 Nov 2025 21:03:55 +0800 Subject: [PATCH 05/11] fix: resolve issue #15 where filters with bracket notation returned empty results --- jsonpath/jsonpath.py | 23 ++++++++++++++--------- tests/test_issues.py | 20 ++++++++++++++++++++ 2 files changed, 34 insertions(+), 9 deletions(-) diff --git a/jsonpath/jsonpath.py b/jsonpath/jsonpath.py index 56965b6..456ffc2 100644 --- a/jsonpath/jsonpath.py +++ b/jsonpath/jsonpath.py @@ -53,7 +53,7 @@ class JSONPath: # operators REP_SLICE_CONTENT = re.compile(r"^(-?\d*)?:(-?\d*)?(:-?\d*)?$") REP_SELECT_CONTENT = re.compile(r"^([\w.']+)(, ?[\w.']+)+$") - REP_FILTER_CONTENT = re.compile(r"@\.(.*?)(?=<=|>=|==|!=|>|<| in| not| is)|len\(@\.(.*?)\)") + REP_FILTER_CONTENT = re.compile(r"@([.\[].*?)(?=<=|>=|==|!=|>|<| in| not| is)|len\(@([.\[].*?)\)") # annotations f: list @@ -94,15 +94,15 @@ def _parse_expr(self, expr): # pick up special patterns expr = JSONPath.REP_GET_QUOTE.sub(self._get_quote, expr) expr = JSONPath.REP_GET_BACKQUOTE.sub(self._get_backquote, expr) + expr = JSONPath.REP_GET_PAREN.sub(self._get_paren, expr) expr = JSONPath.REP_GET_BRACKET.sub(self._get_bracket, expr) expr = re.sub(r"\.(\.#B)", r"\1", expr) - expr = JSONPath.REP_GET_PAREN.sub(self._get_paren, expr) # split expr = JSONPath.REP_DOUBLEDOT.sub(f"{JSONPath.SEP}..{JSONPath.SEP}", expr) expr = JSONPath.REP_DOT.sub(JSONPath.SEP, expr) # put back - expr = JSONPath.REP_PUT_PAREN.sub(self._put_paren, expr) expr = JSONPath.REP_PUT_BRACKET.sub(self._put_bracket, expr) + expr = JSONPath.REP_PUT_PAREN.sub(self._put_paren, expr) expr = JSONPath.REP_PUT_BACKQUOTE.sub(self._put_backquote, expr) expr = JSONPath.REP_PUT_QUOTE.sub(self._put_quote, expr) if expr.startswith("$;"): @@ -147,12 +147,15 @@ def _put_paren(self, m): @staticmethod def _gen_obj(m): content = m.group(1) or m.group(2) # group 2 is for len() - ret = "__obj" - for e in content.split("."): - if len(e) >= 2 and ((e[0] == "'" and e[-1] == "'") or (e[0] == '"' and e[-1] == '"')): - e = e[1:-1] - ret += f'["{e}"]' - return ret + + def repl(m): + g = m.group(1) + if g[0] in ("'", '"'): + return f"[{g}]" + return f"['{g}']" + + content = re.sub(r"\.(\w+|'[^']*'|\"[^\"]*\")", repl, content) + return "__obj" + content @staticmethod def _traverse(f, obj, i: int, path: str, *args): @@ -275,6 +278,8 @@ def _trace(self, obj, i: int, path): if step.startswith("?(") and step.endswith(")"): step = step[2:-1] step = JSONPath.REP_FILTER_CONTENT.sub(self._gen_obj, step) + if isinstance(obj, dict): + self._filter(obj, i + 1, path, step) self._traverse(self._filter, obj, i + 1, path, step) return diff --git a/tests/test_issues.py b/tests/test_issues.py index d41a5db..5aef09a 100644 --- a/tests/test_issues.py +++ b/tests/test_issues.py @@ -44,3 +44,23 @@ def test_issue_16_quoted_keys(): assert JSONPath('$.\'user-list\'[?(@."city-name"=="Austin")]').parse(data) == [ {"city-name": "Austin", "name": "John"} ] + + +def test_issue_15_bracket_notation_in_filter(): + data = { + "tool": { + "books": [ + {"properties": [{"name": "moq", "value": "x"}, {"name": "url", "value": "1"}]}, + {"properties": [{"name": "url", "value": "3"}]}, + {"properties": [{"name": "moq", "value": "q"}, {"name": "url", "value": "4"}]}, + ] + } + } + + # Case 1: Bracket notation in filter + expr = "$['tool']['books'][*]['properties'][*][?(@['name'] == 'moq')]['value']" + assert JSONPath(expr).parse(data) == ["x", "q"] + + # Case 2: Dot notation in filter (already working, but good to keep) + expr_dot = "$.tool.books.[*].properties.[*].[?(@.name=='moq')].value" + assert JSONPath(expr_dot).parse(data) == ["x", "q"] From 8ac2080c1ff5b520b66a8e5f22a6d0a025d6776f Mon Sep 17 00:00:00 2001 From: sean2077 <37324769+sean2077@users.noreply.github.com> Date: Sun, 23 Nov 2025 21:13:12 +0800 Subject: [PATCH 06/11] fix: resolve issue #10 by raising JSONPathTypeError on mixed type sorting --- jsonpath/__init__.py | 4 ++-- jsonpath/jsonpath.py | 23 +++++++++++++++-------- tests/test_issues.py | 39 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 55 insertions(+), 11 deletions(-) diff --git a/jsonpath/__init__.py b/jsonpath/__init__.py index 3c8271c..891d821 100644 --- a/jsonpath/__init__.py +++ b/jsonpath/__init__.py @@ -8,6 +8,6 @@ __version__ = "1.0.6" __author__ = "sean2077" -from .jsonpath import ExprSyntaxError, JSONPath, compile, search +from .jsonpath import ExprSyntaxError, JSONPath, JSONPathTypeError, compile, search -__all__ = ["JSONPath", "ExprSyntaxError", "compile", "search"] +__all__ = ["JSONPath", "ExprSyntaxError", "JSONPathTypeError", "compile", "search"] diff --git a/jsonpath/jsonpath.py b/jsonpath/jsonpath.py index 456ffc2..2b1a4dc 100644 --- a/jsonpath/jsonpath.py +++ b/jsonpath/jsonpath.py @@ -29,6 +29,10 @@ class ExprSyntaxError(Exception): pass +class JSONPathTypeError(Exception): + pass + + class JSONPath: RESULT_TYPE = { "VALUE": "A list of specific values.", @@ -189,14 +193,17 @@ def _getattr(obj: dict, path: str, *, convert_number_str=False): @staticmethod def _sorter(obj, sortbys): - for sortby in sortbys.split(",")[::-1]: - if sortby.startswith("~"): - obj.sort( - key=lambda t, k=sortby: JSONPath._getattr(t[1], k[1:], convert_number_str=True), - reverse=True, - ) - else: - obj.sort(key=lambda t, k=sortby: JSONPath._getattr(t[1], k, convert_number_str=True)) + try: + for sortby in sortbys.split(",")[::-1]: + if sortby.startswith("~"): + obj.sort( + key=lambda t, k=sortby: JSONPath._getattr(t[1], k[1:], convert_number_str=True), + reverse=True, + ) + else: + obj.sort(key=lambda t, k=sortby: JSONPath._getattr(t[1], k, convert_number_str=True)) + except TypeError as e: + raise JSONPathTypeError(f"not possible to compare str and int when sorting: {e}") from e def _filter(self, obj, i: int, path: str, step: str): r = False diff --git a/tests/test_issues.py b/tests/test_issues.py index 5aef09a..0893d38 100644 --- a/tests/test_issues.py +++ b/tests/test_issues.py @@ -1,4 +1,6 @@ -from jsonpath import JSONPath +import pytest + +from jsonpath import JSONPath, JSONPathTypeError def test_issue_17(): @@ -64,3 +66,38 @@ def test_issue_15_bracket_notation_in_filter(): # Case 2: Dot notation in filter (already working, but good to keep) expr_dot = "$.tool.books.[*].properties.[*].[?(@.name=='moq')].value" assert JSONPath(expr_dot).parse(data) == ["x", "q"] + + +def test_issue_10_mixed_type_sorting(): + # Case 1: Mixed types (int and str) - Should raise JSONPathTypeError + data1 = [{"code": "N"}, {"code": 0}] + with pytest.raises(JSONPathTypeError): + JSONPath("$./(code)").parse(data1) + + # Case 2: Numbers (numerical sort) + data2 = [{"code": 10}, {"code": 2}] + result2 = JSONPath("$./(code)").parse(data2) + assert result2 == [{"code": 2}, {"code": 10}] + + # Case 3: Strings (lexicographical sort) + data3 = [{"code": "b"}, {"code": "a"}] + result3 = JSONPath("$./(code)").parse(data3) + assert result3 == [{"code": "a"}, {"code": "b"}] + + # Case 4: Strings that look like numbers (converted to numbers by _getattr) + data4 = [{"code": "10"}, {"code": "2"}] + result4 = JSONPath("$./(code)").parse(data4) + # "2" -> 2, "10" -> 10. 2 < 10. + assert result4 == [{"code": "2"}, {"code": "10"}] + + # Case 5: Mixed numbers and strings that look like numbers + data5 = [{"code": 10}, {"code": "2"}] + result5 = JSONPath("$./(code)").parse(data5) + # "2" -> 2. 2 < 10. + assert result5 == [{"code": "2"}, {"code": 10}] + + # Case 6: Missing keys (None) + # None vs int comparison in Python 3 raises TypeError + data6 = [{"code": 1}, {"other": 2}] + with pytest.raises(JSONPathTypeError): + JSONPath("$./(code)").parse(data6) From 5e38031611c3623910150320760abd5c852c93aa Mon Sep 17 00:00:00 2001 From: sean2077 <37324769+sean2077@users.noreply.github.com> Date: Sun, 23 Nov 2025 21:20:17 +0800 Subject: [PATCH 07/11] fix: resolve issue #9 by filtering out missing keys in field extractor --- jsonpath/jsonpath.py | 26 ++++++++++++++++++-------- tests/test_issues.py | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 8 deletions(-) diff --git a/jsonpath/jsonpath.py b/jsonpath/jsonpath.py index 2b1a4dc..d795cb3 100644 --- a/jsonpath/jsonpath.py +++ b/jsonpath/jsonpath.py @@ -41,6 +41,7 @@ class JSONPath: # common patterns SEP = ";" + _MISSING = object() REP_DOUBLEDOT = re.compile(r"\.\.") REP_DOT = re.compile(r"(? TypeError in Python 3. So this should raise JSONPathTypeError + data = [{"val": 10}, {"other": 5}] # second item has missing "val" -> None + + with pytest.raises(JSONPathTypeError): + JSONPath("$./(val)").parse(data) + + # Case 2: All missing keys (all None) -> Should raise JSONPathTypeError because None < None is not supported in Python 3 + data2 = [{"other": 1}, {"other": 2}] + with pytest.raises(JSONPathTypeError): + JSONPath("$./(val)").parse(data2) From a5e2973b42f1d6b14d20cae48f250501cb81d341 Mon Sep 17 00:00:00 2001 From: sean2077 <37324769+sean2077@users.noreply.github.com> Date: Sun, 23 Nov 2025 21:34:56 +0800 Subject: [PATCH 08/11] feat: resolve issue #13 by adding support for 'in' operator and regex matching (=~) --- jsonpath/jsonpath.py | 18 ++++++++++++++++-- tests/test_issues.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/jsonpath/jsonpath.py b/jsonpath/jsonpath.py index d795cb3..921361b 100644 --- a/jsonpath/jsonpath.py +++ b/jsonpath/jsonpath.py @@ -58,7 +58,7 @@ class JSONPath: # operators REP_SLICE_CONTENT = re.compile(r"^(-?\d*)?:(-?\d*)?(:-?\d*)?$") REP_SELECT_CONTENT = re.compile(r"^([\w.']+)(, ?[\w.']+)+$") - REP_FILTER_CONTENT = re.compile(r"@([.\[].*?)(?=<=|>=|==|!=|>|<| in| not| is)|len\(@([.\[].*?)\)") + REP_FILTER_CONTENT = re.compile(r"@([.\[].*?)(?=<=|>=|==|!=|>|<| in| not| is|\s|\)|$)|len\(@([.\[].*?)\)") # annotations f: list @@ -216,7 +216,7 @@ def key_func(t, k): def _filter(self, obj, i: int, path: str, step: str): r = False try: - r = self.eval_func(step, None, {"__obj": obj}) + r = self.eval_func(step, None, {"__obj": obj, "RegexPattern": RegexPattern}) except Exception: pass if r: @@ -293,6 +293,10 @@ def _trace(self, obj, i: int, path): if step.startswith("?(") and step.endswith(")"): step = step[2:-1] step = JSONPath.REP_FILTER_CONTENT.sub(self._gen_obj, step) + + if "=~" in step: + step = re.sub(r"=~\s*/(.*?)/", r"@ RegexPattern(r'\1')", step) + if isinstance(obj, dict): self._filter(obj, i + 1, path, step) self._traverse(self._filter, obj, i + 1, path, step) @@ -332,6 +336,16 @@ def _trace(self, obj, i: int, path): return +class RegexPattern: + def __init__(self, pattern): + self.pattern = pattern + + def __rmatmul__(self, other): + if isinstance(other, str): + return bool(re.search(self.pattern, other)) + return False + + def compile(expr): return JSONPath(expr) diff --git a/tests/test_issues.py b/tests/test_issues.py index 3ba09c2..ac9ab64 100644 --- a/tests/test_issues.py +++ b/tests/test_issues.py @@ -143,3 +143,32 @@ def test_sorting_with_missing_keys(): data2 = [{"other": 1}, {"other": 2}] with pytest.raises(JSONPathTypeError): JSONPath("$./(val)").parse(data2) + + +def test_issue_13_contains_and_regex(): + data = { + "products": [ + {"name": "Apple", "tags": ["fruit", "red"]}, + {"name": "Banana", "tags": ["fruit", "yellow"]}, + {"name": "Carrot", "tags": ["vegetable", "orange"]}, + ] + } + + # Case 1: 'in' operator (list contains) + # Check if 'fruit' is in tags list + expr_in_list = "$.products[?('fruit' in @.tags)].name" + assert JSONPath(expr_in_list).parse(data) == ["Apple", "Banana"] + + # Case 2: 'in' operator (string contains) + # Check if 'App' is in name + expr_in_str = "$.products[?('App' in @.name)].name" + assert JSONPath(expr_in_str).parse(data) == ["Apple"] + + # Case 3: Regex operator =~ + # Check if name starts with 'B' + expr_regex = "$.products[?(@.name =~ /B.*/)].name" + assert JSONPath(expr_regex).parse(data) == ["Banana"] + + # Case 4: Case insensitive regex + expr_regex_case = "$.products[?(@.name =~ /(?i)apple/)].name" + assert JSONPath(expr_regex_case).parse(data) == ["Apple"] From d396bc5c2e19d3f081a76eed7e95a893f0d7cd5e Mon Sep 17 00:00:00 2001 From: sean2077 <37324769+sean2077@users.noreply.github.com> Date: Sun, 23 Nov 2025 21:58:28 +0800 Subject: [PATCH 09/11] feat: add update function to JSONPath class (#12) --- jsonpath/jsonpath.py | 47 +++++++++++++++- tests/test_update.py | 126 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 171 insertions(+), 2 deletions(-) create mode 100644 tests/test_update.py diff --git a/jsonpath/jsonpath.py b/jsonpath/jsonpath.py index 921361b..0af5969 100644 --- a/jsonpath/jsonpath.py +++ b/jsonpath/jsonpath.py @@ -3,7 +3,7 @@ import re import sys from collections import defaultdict -from typing import Union +from typing import Any, Callable, Union def create_logger(name: str = None, level: Union[int, str] = logging.INFO): @@ -59,6 +59,7 @@ class JSONPath: REP_SLICE_CONTENT = re.compile(r"^(-?\d*)?:(-?\d*)?(:-?\d*)?$") REP_SELECT_CONTENT = re.compile(r"^([\w.']+)(, ?[\w.']+)+$") REP_FILTER_CONTENT = re.compile(r"@([.\[].*?)(?=<=|>=|==|!=|>|<| in| not| is|\s|\)|$)|len\(@([.\[].*?)\)") + REP_PATH_SEGMENT = re.compile(r"(?:\.|^)(?P\w+)|\[['\"](?P.*?)['\"]\]|\[(?P\d+)\]") # annotations f: list @@ -110,7 +111,9 @@ def _parse_expr(self, expr): expr = JSONPath.REP_PUT_PAREN.sub(self._put_paren, expr) expr = JSONPath.REP_PUT_BACKQUOTE.sub(self._put_backquote, expr) expr = JSONPath.REP_PUT_QUOTE.sub(self._put_quote, expr) - if expr.startswith("$;"): + if expr == "$": + expr = "" + elif expr.startswith("$;"): expr = expr[2:] logger.debug(f"after expr : {expr}") @@ -335,6 +338,46 @@ def _trace(self, obj, i: int, path): return + def update(self, obj: Union[list, dict], value_or_func: Union[Any, Callable[[Any], Any]]) -> Any: + paths = self.parse(obj, result_type="PATH") + for path in paths: + matches = list(JSONPath.REP_PATH_SEGMENT.finditer(path)) + if not matches: + # Root object + if isinstance(value_or_func, Callable): + obj = value_or_func(obj) + else: + obj = value_or_func + continue + + target = obj + # Traverse to parent + for match in matches[:-1]: + group = match.groupdict() + if group["dot"]: + target = target[group["dot"]] + elif group["quote"]: + target = target[group["quote"]] + elif group["int"]: + target = target[int(group["int"])] + + # Update last segment + last_match = matches[-1] + group = last_match.groupdict() + if group["dot"]: + key = group["dot"] + elif group["quote"]: + key = group["quote"] + elif group["int"]: + key = int(group["int"]) + + if isinstance(value_or_func, Callable): + target[key] = value_or_func(target[key]) + else: + target[key] = value_or_func + + return obj + class RegexPattern: def __init__(self, pattern): diff --git a/tests/test_update.py b/tests/test_update.py new file mode 100644 index 0000000..c276ea9 --- /dev/null +++ b/tests/test_update.py @@ -0,0 +1,126 @@ +from jsonpath.jsonpath import JSONPath + + +def test_update_value(): + data = { + "store": { + "book": [ + {"category": "reference", "author": "Nigel Rees", "title": "Sayings of the Century", "price": 8.95} + ] + } + } + jp = JSONPath("$.store.book[0].price") + result = jp.update(data, 10.0) + assert result["store"]["book"][0]["price"] == 10.0 + + +def test_update_function(): + data = {"count": 1} + jp = JSONPath("$.count") + result = jp.update(data, lambda x: x + 1) + assert result["count"] == 2 + + +def test_update_root(): + data = {"a": 1} + jp = JSONPath("$") + result = jp.update(data, {"b": 2}) + assert result == {"b": 2} + + +def test_update_list_index(): + data = [1, 2, 3] + jp = JSONPath("$[1]") + result = jp.update(data, 5) + assert result == [1, 5, 3] + + +def test_update_multiple(): + data = {"items": [{"value": 1}, {"value": 2}, {"value": 3}]} + jp = JSONPath("$.items[*].value") + result = jp.update(data, 0) + for item in result["items"]: + assert item["value"] == 0 + + +def test_update_multiple_func(): + data = {"items": [{"value": 1}, {"value": 2}, {"value": 3}]} + jp = JSONPath("$.items[*].value") + result = jp.update(data, lambda x: x * 2) + assert result["items"][0]["value"] == 2 + assert result["items"][1]["value"] == 4 + assert result["items"][2]["value"] == 6 + + +def test_update_with_filter(): + data = {"books": [{"price": 10, "title": "A"}, {"price": 20, "title": "B"}, {"price": 30, "title": "C"}]} + # Update price where price > 15 + jp = JSONPath("$.books[?(@.price > 15)].price") + result = jp.update(data, 0) + assert result["books"][0]["price"] == 10 # Unchanged + assert result["books"][1]["price"] == 0 # Updated + assert result["books"][2]["price"] == 0 # Updated + + +def test_update_slice(): + data = [0, 1, 2, 3, 4] + jp = JSONPath("$[1:4]") # Indices 1, 2, 3 + result = jp.update(data, 9) + assert result == [0, 9, 9, 9, 4] + + +def test_update_special_keys(): + data = {"complex.key": 1, "key with space": 2, "normal": 3} + # Note: jsonpath-python might handle keys with dots using ['...'] syntax in path output + jp = JSONPath("$['complex.key']") + result = jp.update(data, 10) + assert result["complex.key"] == 10 + + jp = JSONPath("$['key with space']") + result = jp.update(data, 20) + assert result["key with space"] == 20 + + +def test_update_no_match(): + data = {"a": 1} + jp = JSONPath("$.b") + result = jp.update(data, 2) + assert result == {"a": 1} + + +def test_update_nested_structure(): + data = {"a": [{"b": [1, 2]}, {"b": [3, 4]}]} + jp = JSONPath("$.a[*].b[1]") + result = jp.update(data, 99) + assert result["a"][0]["b"][1] == 99 + assert result["a"][1]["b"][1] == 99 + + +def test_update_recursive(): + data = { + "store": { + "book": [{"category": "reference", "price": 8.95}, {"category": "fiction", "price": 12.99}], + "bicycle": {"color": "red", "price": 19.95}, + } + } + jp = JSONPath("$..price") + result = jp.update(data, 10.0) + assert result["store"]["book"][0]["price"] == 10.0 + assert result["store"]["book"][1]["price"] == 10.0 + assert result["store"]["bicycle"]["price"] == 10.0 + + +def test_update_union(): + data = {"a": 1, "b": 2, "c": 3} + jp = JSONPath("$[a,b]") + result = jp.update(data, 0) + assert result["a"] == 0 + assert result["b"] == 0 + assert result["c"] == 3 + + +def test_update_quote_keys(): + data = {'c"d': 1, "normal": 2} + jp = JSONPath("$['c\"d']") + result = jp.update(data, 99) + assert result['c"d'] == 99 From a838f1a8ac1d65ed631c65393384206f7b6a2a1d Mon Sep 17 00:00:00 2001 From: sean2077 <37324769+sean2077@users.noreply.github.com> Date: Sun, 23 Nov 2025 22:12:37 +0800 Subject: [PATCH 10/11] docs: update README with new features and 'Why this' section --- README.md | 47 +++++++++++++++++++++++++++++------------------ 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 3ee4f86..45f1219 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,24 @@ -- [jsonpath-python](#jsonpath-python) - - [Features](#features) - - [JSONPath Syntax](#jsonpath-syntax) - - [Operators](#operators) - - [Examples](#examples) - - [Select Fields](#select-fields) - - [Recursive Descent](#recursive-descent) - - [Slice](#slice) - - [Filter Expression](#filter-expression) - - [Sorter Expression](#sorter-expression) - - [Field-Extractor Expression](#field-extractor-expression) - - [Appendix: Example JSON data:](#appendix-example-json-data) - - [Todo List](#todo-list) - # jsonpath-python -A more powerful JSONPath implementation in modern python. +A lightweight and powerful JSONPath implementation for Python. + +## Why jsonpath-python? + +There are already several JSONPath libraries in Python, so why choose this one? + +1. **Lightweight & Zero Dependency**: Unlike `jsonpath-ng` which relies on complex AST parsing frameworks like `ply`, `jsonpath-python` is implemented with pure Python string parsing. It has **zero third-party dependencies**, making it incredibly easy to integrate into any environment (including restricted ones like AWS Lambda or embedded systems). +2. **Simple & Pythonic**: The implementation is straightforward and linear. If you encounter a bug or need to extend it, the code is easy to read and modify. You can even copy the core file directly into your project as a utility. +3. **Powerful Features**: It supports advanced features like **sorting**, **filtering**, and **updating** JSON data, which are often missing or incomplete in other lightweight implementations. ## Features - [x] **Light. (No need to install third-party dependencies.)** - [x] **Support filter operator, including multi-selection, inverse-selection filtering.** - [x] **Support sorter operator, including sorting by multiple fields, ascending and descending order.** +- [x] **Support updating JSON data using JSONPath expressions.** - [x] Support basic semantics of JSONPath. - [x] Support output modes: VALUE, PATH. -- [ ] Support embedded syntax. -- [ ] Support user-defined function. -- [ ] Support parent operator. +- [x] Support regex filter (`=~`). ## Installation @@ -141,6 +134,8 @@ Support all python comparison operators (`==`, `!=`, `<`, `>`, `>=`, `<=`), pyth ['Moby Dick'] >>> JSONPath('$.book[?(@.author=="Herman Melville" or @.author=="Evelyn Waugh")].author').parse(data) ['Evelyn Waugh', 'Herman Melville'] +>>> JSONPath('$.book[?(@.title =~ /.*Century/)]').parse(data) +[{'category': 'reference', 'author': 'Nigel Rees', 'title': 'Sayings of the Century', 'price': 8.95, 'brand': {'version': 'v1.0.0'}}] ``` `Note`: You must use double quote(`""`) instead of single quote(`''`) to wrap the compared string, because single quote(`''`) has another usage in this JSONPath syntax . @@ -173,6 +168,20 @@ Using `(field1,field2,…,filedn)` after a dict object to extract its fields. [{'title': 'Moby Dick', 'price': 8.99}, {'title': 'Sword of Honour', 'price': 12.99}, {'title': 'The Lord of the Rings', 'price': 22.99}, {'title': 'Sayings of the Century', 'price': 8.95}] ``` +#### Update Data + +Update values in the JSON object using the `update` method. + +```python +# Update with a static value +>>> JSONPath("$.book[*].price").update(data, 100) +# Result: All book prices are set to 100 + +# Update with a function (e.g., apply a discount) +>>> JSONPath("$.book[*].price").update(data, lambda x: x * 0.9) +# Result: All book prices are multiplied by 0.9 +``` + ### Appendix: Example JSON data: ```python @@ -249,6 +258,8 @@ data = { ## Todo List +- Support embedded syntax. +- Support user-defined function. - Syntax and character set (refer to k8s) > The name segment is required and must be 63 characters or less, beginning and ending with an alphanumeric character (`[a-z0-9A-Z]`) with dashes (`-`), underscores (`_`), dots (`.`), and alphanumerics between. From 8f421835ae5299a1f86edbf8635813c36ed41aa0 Mon Sep 17 00:00:00 2001 From: sean2077 <37324769+sean2077@users.noreply.github.com> Date: Sun, 23 Nov 2025 22:21:49 +0800 Subject: [PATCH 11/11] docs: update README to clarify features and remove todo list --- README.md | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 45f1219..db570db 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,9 @@ A lightweight and powerful JSONPath implementation for Python. There are already several JSONPath libraries in Python, so why choose this one? -1. **Lightweight & Zero Dependency**: Unlike `jsonpath-ng` which relies on complex AST parsing frameworks like `ply`, `jsonpath-python` is implemented with pure Python string parsing. It has **zero third-party dependencies**, making it incredibly easy to integrate into any environment (including restricted ones like AWS Lambda or embedded systems). +1. **Lightweight & Zero Dependency**: Unlike `jsonpath-ng` which relies on complex AST parsing frameworks like `ply`, `jsonpath-python` is implemented with pure Python string parsing. It has **zero third-party dependencies**, making it incredibly easy to integrate into any environment. 2. **Simple & Pythonic**: The implementation is straightforward and linear. If you encounter a bug or need to extend it, the code is easy to read and modify. You can even copy the core file directly into your project as a utility. -3. **Powerful Features**: It supports advanced features like **sorting**, **filtering**, and **updating** JSON data, which are often missing or incomplete in other lightweight implementations. +3. **Powerful Features**: It supports advanced features like **sorting**, **filtering**, and **updating** JSON data. If you require strict adherence to the JSONPath standard (RFC 9535), other libraries might be more suitable, but for practical data manipulation, this library offers more power. ## Features @@ -255,11 +255,3 @@ data = { } } ``` - -## Todo List - -- Support embedded syntax. -- Support user-defined function. -- Syntax and character set (refer to k8s) - -> The name segment is required and must be 63 characters or less, beginning and ending with an alphanumeric character (`[a-z0-9A-Z]`) with dashes (`-`), underscores (`_`), dots (`.`), and alphanumerics between.