diff --git a/.github/workflows/rw-python-tests.yaml b/.github/workflows/rw-python-tests.yaml index 7ed62e817..3bbe39190 100644 --- a/.github/workflows/rw-python-tests.yaml +++ b/.github/workflows/rw-python-tests.yaml @@ -44,6 +44,9 @@ jobs: - name: pep8 and formatting check run: | make format + - name: lint check + run: | + make lint docs-scripts-tests: runs-on: ubuntu-latest if: ${{inputs.changed-python-modules == 'true'}} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index fbca85b87..940e28c65 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,7 +15,7 @@ repos: args: [ '--maxkb=890' ] - id: check-case-conflict - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.15.1 + rev: v0.15.20 hooks: # Run the linter. - id: ruff diff --git a/Dockerfile b/Dockerfile index 5091ae954..9bd721f33 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ # (C) 2021 GoodData Corporation ARG PY_TAG -FROM ghcr.io/astral-sh/uv:0.12 AS uv +FROM ghcr.io/astral-sh/uv:0.12.5 AS uv FROM python:${PY_TAG} ARG PY_TAG @@ -40,16 +40,35 @@ WORKDIR /data COPY pyproject.toml uv.lock ./ # Install tox and tox-uv as system packages so they're available globally. -# NOTE: `uv pip install --group` reads the group's requirements from pyproject.toml but -# resolves them FRESH from the index -- it does NOT read uv.lock. Every version that must -# stay fixed therefore needs an explicit bound in the group itself; in particular `uv`, -# whose console script installs over the binary copied above. +# Via `uv export` and not `uv pip install --group`: the latter re-resolves fresh from the +# index, while export reads uv.lock, so the image gets exactly the pinned versions. +# The group uses tox-uv-bare, so nothing here installs a `uv` console script over the +# binary COPYed above -- that COPY is the image's only uv, hence its exact pin. # Clean up dependency files after installation to reduce image size RUN set -x \ - && uv pip install --system --group tox \ - && rm -f pyproject.toml uv.lock \ + && uv export --frozen --only-group tox -o /tmp/tox-requirements.txt \ + && uv pip install --system -r /tmp/tox-requirements.txt \ + && rm -f pyproject.toml uv.lock /tmp/tox-requirements.txt \ && true +# Any uv command here must not REWRITE the bind-mounted host uv.lock if it thinks it is +# stale -- fail instead. Not UV_FROZEN: tox-uv reads that and downgrades its own --locked +# to --frozen, silently accepting a stale lock. Must be set AFTER the export above, which +# is rejected in combination with UV_LOCKED and has to stay --frozen because only the root +# pyproject.toml and uv.lock exist at that layer for --locked to validate against. +ENV UV_LOCKED=1 + +# Use the lock-pinned tox installed system-wide above rather than project_common.mk's +# default `uv run tox`, which would first sync the whole workspace into a throwaway +# in-container project env (measured: 58 packages, ~7s) just to obtain the same tox. +ENV TOX=tox + +# The repo is bind-mounted at /data, so the default project environment (/data/.venv) is +# the developer's host venv; a `uv run` here would rebuild it against this image's Linux +# interpreter. Redirect it somewhere container-local (/tmp, not a home dir: the runtime +# user is created by entrypoint.sh, so no home exists when this ENV is evaluated). +ENV UV_PROJECT_ENVIRONMENT=/tmp/uv-project-venv + COPY .docker/entrypoint.sh /entrypoint.sh LABEL image_name="GoodData Python SDK test image with python, tox and make" diff --git a/Makefile b/Makefile index 7d5139fab..335453c62 100644 --- a/Makefile +++ b/Makefile @@ -104,13 +104,13 @@ test-staging: clean-staging: @test -n "$(STAGING_ADMIN_TOKEN)" || (echo "ERROR: STAGING_ADMIN_TOKEN is required. Set it in .env or pass on CLI." && exit 1) @test -n "$(STAGING_DS_PASSWORD)" || (echo "ERROR: STAGING_DS_PASSWORD is required. Set it in .env or pass on CLI." && exit 1) - cd packages/tests-support && STAGING=1 TOKEN="$(STAGING_ADMIN_TOKEN)" DS_PASSWORD="$(STAGING_DS_PASSWORD)" python clean_staging.py + cd packages/tests-support && STAGING=1 TOKEN="$(STAGING_ADMIN_TOKEN)" DS_PASSWORD="$(STAGING_DS_PASSWORD)" uv run --locked python clean_staging.py .PHONY: load-staging load-staging: @test -n "$(STAGING_ADMIN_TOKEN)" || (echo "ERROR: STAGING_ADMIN_TOKEN is required. Set it in .env or pass on CLI." && exit 1) @test -n "$(STAGING_DS_PASSWORD)" || (echo "ERROR: STAGING_DS_PASSWORD is required. Set it in .env or pass on CLI." && exit 1) - cd packages/tests-support && STAGING=1 TOKEN="$(STAGING_ADMIN_TOKEN)" DS_PASSWORD="$(STAGING_DS_PASSWORD)" python upload_demo_layout.py + cd packages/tests-support && STAGING=1 TOKEN="$(STAGING_ADMIN_TOKEN)" DS_PASSWORD="$(STAGING_DS_PASSWORD)" uv run --locked python upload_demo_layout.py .PHONY: release release: diff --git a/packages/gooddata-dbt/tox.ini b/packages/gooddata-dbt/tox.ini index e448632ad..60e6f16db 100644 --- a/packages/gooddata-dbt/tox.ini +++ b/packages/gooddata-dbt/tox.ini @@ -5,7 +5,6 @@ envlist = py3{10,11,12,13,14} [testenv] runner = uv-venv-lock-runner package = wheel -wheel_build_env = .pkg dependency_groups = test setenv = diff --git a/packages/gooddata-eval/.gitignore b/packages/gooddata-eval/.gitignore new file mode 100644 index 000000000..53f427c46 --- /dev/null +++ b/packages/gooddata-eval/.gitignore @@ -0,0 +1,8 @@ +# (C) 2026 GoodData Corporation + +# Unit test / coverage reports +.tox/ +.coverage +.coverage.* +coverage.xml +.json-report-*.json diff --git a/packages/gooddata-eval/src/gooddata_eval/core/chat/sse_client.py b/packages/gooddata-eval/src/gooddata_eval/core/chat/sse_client.py index cb18d3804..7d52dad3f 100644 --- a/packages/gooddata-eval/src/gooddata_eval/core/chat/sse_client.py +++ b/packages/gooddata-eval/src/gooddata_eval/core/chat/sse_client.py @@ -89,13 +89,11 @@ def _is_retryable_exc(exc: Exception) -> bool: return True if isinstance(exc, httpx.HTTPStatusError): return exc.response.status_code in _RETRYABLE_STATUS_CODES - if isinstance(exc, httpx.RemoteProtocolError): - # Mid-stream disconnect ("peer closed connection without sending complete - # message body") -- pure network flake, not a real agent/content failure. - # Confirmed live: contaminated ~1-4% of visualization runs with a hard - # fail and zero retry attempts. - return True - return False + # Mid-stream disconnect ("peer closed connection without sending complete + # message body") -- pure network flake, not a real agent/content failure. + # Confirmed live: contaminated ~1-4% of visualization runs with a hard + # fail and zero retry attempts. + return isinstance(exc, httpx.RemoteProtocolError) def _retry_transient(operation: Callable[[], T], *, is_retryable: Callable[[Exception], bool]) -> T: diff --git a/packages/gooddata-eval/tests/test_agentic_runner.py b/packages/gooddata-eval/tests/test_agentic_runner.py index b71ce135c..3d9959eba 100644 --- a/packages/gooddata-eval/tests/test_agentic_runner.py +++ b/packages/gooddata-eval/tests/test_agentic_runner.py @@ -3,7 +3,7 @@ from unittest.mock import patch import pytest -from gooddata_eval.cli.agentic_runner import _dispatch_agentic, run_agentic_items +from gooddata_eval.cli.agentic_runner import AGENTIC_TEST_KINDS, _dispatch_agentic, run_agentic_items from gooddata_eval.core.agentic.alert_skill import AlertSkillAssertionError from gooddata_eval.core.models import AgenticEvalOutcome, DatasetItem @@ -81,8 +81,6 @@ def test_all_agentic_kind_cases_covers_every_registered_kind(): """Guards the two parametrized tests below against silently going stale: a kind added to AGENTIC_TEST_KINDS without a matching case here would otherwise just not get tested, not fail loudly.""" - from gooddata_eval.cli.agentic_runner import AGENTIC_TEST_KINDS - covered = {kind for kind, _, _ in _ALL_AGENTIC_KIND_CASES} assert covered == set(AGENTIC_TEST_KINDS) @@ -194,8 +192,6 @@ def test_dispatch_agentic_returns_a_real_outcome_for_every_kind(kind, expected_o evaluator produced -- not None, not the outcome's reasoning_steps list alone, not any other bare value the old `isinstance(outcome, tuple)`/`isinstance(outcome, AgenticEvalOutcome)` fallback could silently swallow.""" - from gooddata_eval.core.models import AgenticEvalOutcome - item = DatasetItem( id="q1", dataset_name="ds", diff --git a/packages/gooddata-eval/tox.ini b/packages/gooddata-eval/tox.ini index f82a78049..27694bc9e 100644 --- a/packages/gooddata-eval/tox.ini +++ b/packages/gooddata-eval/tox.ini @@ -5,7 +5,6 @@ envlist = py3{10,11,12,13,14} [testenv] runner = uv-venv-lock-runner package = wheel -wheel_build_env = .pkg extras = llm-judge dependency_groups = diff --git a/packages/gooddata-fdw/tox.ini b/packages/gooddata-fdw/tox.ini index f408517d5..254cffb58 100644 --- a/packages/gooddata-fdw/tox.ini +++ b/packages/gooddata-fdw/tox.ini @@ -5,7 +5,6 @@ envlist = py3{10,11,12,13,14} [testenv] runner = uv-venv-lock-runner package = wheel -wheel_build_env = .pkg dependency_groups = test setenv = diff --git a/packages/gooddata-flexconnect/tox.ini b/packages/gooddata-flexconnect/tox.ini index a92cdcbb3..7bad33f51 100644 --- a/packages/gooddata-flexconnect/tox.ini +++ b/packages/gooddata-flexconnect/tox.ini @@ -5,7 +5,6 @@ envlist = py3{10,11,12,13,14} [testenv] runner = uv-venv-lock-runner package = wheel -wheel_build_env = .pkg dependency_groups = test setenv = diff --git a/packages/gooddata-flight-server/tox.ini b/packages/gooddata-flight-server/tox.ini index a92cdcbb3..7bad33f51 100644 --- a/packages/gooddata-flight-server/tox.ini +++ b/packages/gooddata-flight-server/tox.ini @@ -5,7 +5,6 @@ envlist = py3{10,11,12,13,14} [testenv] runner = uv-venv-lock-runner package = wheel -wheel_build_env = .pkg dependency_groups = test setenv = diff --git a/packages/gooddata-pandas/tox.ini b/packages/gooddata-pandas/tox.ini index f408517d5..254cffb58 100644 --- a/packages/gooddata-pandas/tox.ini +++ b/packages/gooddata-pandas/tox.ini @@ -5,7 +5,6 @@ envlist = py3{10,11,12,13,14} [testenv] runner = uv-venv-lock-runner package = wheel -wheel_build_env = .pkg dependency_groups = test setenv = diff --git a/packages/gooddata-pipelines/pyproject.toml b/packages/gooddata-pipelines/pyproject.toml index 897f63eb9..be6cd253a 100644 --- a/packages/gooddata-pipelines/pyproject.toml +++ b/packages/gooddata-pipelines/pyproject.toml @@ -25,12 +25,12 @@ line-length = 80 [dependency-groups] test = [ - "pytest (>=8.3.5,<9.0.0)", + "pytest~=8.3.4", "pytest-cov~=6.0.0", "pytest-json-report==1.5.0", - "pytest-mock (>=3.14.0,<4.0.0)", - "moto (>=5.1.6,<6.0.0)", - "orjson (>=3.11.3,<4.0.0)", + "pytest-mock>=3.14.0,<4.0.0", + "moto>=5.1.6,<6.0.0", + "orjson>=3.11.3,<4.0.0", ] [tool.ty.analysis] diff --git a/packages/gooddata-pipelines/tox.ini b/packages/gooddata-pipelines/tox.ini index f66e67bb5..849f3202a 100644 --- a/packages/gooddata-pipelines/tox.ini +++ b/packages/gooddata-pipelines/tox.ini @@ -5,7 +5,6 @@ envlist = py3{10,11,12,13,14} [testenv] runner = uv-venv-lock-runner package = wheel -wheel_build_env = .pkg dependency_groups = test setenv = diff --git a/packages/gooddata-sdk/tox.ini b/packages/gooddata-sdk/tox.ini index 0313f62ed..986f85943 100644 --- a/packages/gooddata-sdk/tox.ini +++ b/packages/gooddata-sdk/tox.ini @@ -5,7 +5,6 @@ envlist = py3{10,11,12,13,14} [testenv] runner = uv-venv-lock-runner package = wheel -wheel_build_env = .pkg dependency_groups = test pass_env = diff --git a/project_common.mk b/project_common.mk index f74e344b5..2b27aa0f5 100644 --- a/project_common.mk +++ b/project_common.mk @@ -5,6 +5,10 @@ ROOT_DIR = ../.. RUFF = ./.venv/bin/ruff # ty needs uv run (unlike ruff) because it resolves imports from installed packages TY = uv run ty +# `uv run` so a host dev gets tox+tox-uv from the lock without activating the venv. The +# test image overrides this to bare `tox` (it installs the same lock-pinned versions +# system-wide), which skips an entire redundant project sync inside every container. +TOX ?= uv run tox PKG_PATH = packages/$(CURR_DIR_BASE_NAME) TOX_FLAGS = @@ -59,7 +63,7 @@ types: type-check .PHONY: test test: - uv run tox -v $(TOX_FLAGS) $(LOCAL_TEST_ENVS) $(LOCAL_ADD_ARGS) + $(TOX) -v $(TOX_FLAGS) $(LOCAL_TEST_ENVS) $(LOCAL_ADD_ARGS) .PHONY: test-ci test-ci: @@ -69,7 +73,7 @@ test-ci: .PHONY: test-staging test-staging: @test -n "$(TOKEN)" || (echo "ERROR: TOKEN is required." && exit 1) - TOKEN=$(TOKEN) DS_PASSWORD=$(DS_PASSWORD) GD_TEST_ENV=staging uv run tox -v $(TOX_FLAGS) $(LOCAL_TEST_ENVS) $(LOCAL_ADD_ARGS) + TOKEN=$(TOKEN) DS_PASSWORD=$(DS_PASSWORD) GD_TEST_ENV=staging $(TOX) -v $(TOX_FLAGS) $(LOCAL_TEST_ENVS) $(LOCAL_ADD_ARGS) # this is effective for gooddata-sdk only now - it should be part of test fixtures # remove this target once implemented in pytest global fixture diff --git a/pyproject.toml b/pyproject.toml index 41edef734..9848798dc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,6 +19,11 @@ dependencies = [ [tool.uv] required-version = "~=0.12.0" +# The root only aggregates the members; it is never built or published. +package = false +# So a bare `uv sync` can run every make target (ruff, ty, pytest, tox). Not "all": +# `release` would then be installed by every CI `uv sync --group ...` too. +default-groups = ["dev", "lint", "type", "test", "tox"] [tool.uv.sources] gooddata-sdk = { workspace = true } @@ -34,7 +39,20 @@ gooddata-api-client = { path = "gooddata-api-client", editable = true } [tool.uv.workspace] # note gooddata-api-client is not a workspace package, it is a dependency of other packages -members = ["packages/*"] +# Listed explicitly, not as a "packages/*" glob: any stray directory under packages/ would +# match and, lacking a pyproject.toml, break every uv command in the repo. A new package +# needs three entries -- here, [project].dependencies above, and [tool.uv.sources]. +members = [ + "packages/gooddata-dbt", + "packages/gooddata-eval", + "packages/gooddata-fdw", + "packages/gooddata-flexconnect", + "packages/gooddata-flight-server", + "packages/gooddata-pandas", + "packages/gooddata-pipelines", + "packages/gooddata-sdk", + "packages/tests-support", +] [dependency-groups] dev = [ @@ -48,27 +66,29 @@ type = [ "ty~=0.0.55", ] test = [ - # Common test dependencies used across all workspace packages + # Declared only here: consumed by the scripts/docs/ tests (`make test-docs-scripts`). "pytest~=8.3.4", - # Required by scripts/docs/ tests "toml~=0.10.2", "griffe>=1.0", "docstring_parser~=0.15", "jinja2~=3.1", - "pytest-cov~=6.0.0", - "pytest-json-report==1.5.0", - # Additional test dependencies used by multiple packages - "pytest-snapshot==0.9.0", - "pytest-order~=1.3.0", - "vcrpy~=8.2.1", - "urllib3~=2.6.0", - "python-dotenv~=1.0.0", - "deepdiff~=8.5.0", - "pytest-mock>=3.14.0", - # Package-specific dependencies (only needed by some packages) - # but included here for convenience when working on the full workspace - "moto>=5.1.6", - "orjson>=3.11.3", + # Mirror of the members' `test` groups so the root venv can run any package's tests + # (`uv run pytest -k ...`, see scripts/validate_python.sh). Add to it when a member + # adds a test dependency, or that member's tests stop collecting from the root venv. + # Unbounded on purpose: the authoritative bounds live in each member's own group, and + # the single workspace lock resolves one version for everything anyway. + "pytest-cov", + "jsonschema", + "pytest-json-report", + "pytest-snapshot", + "pytest-order", + "pytest-mock", + "vcrpy", + "urllib3", + "python-dotenv", + "deepdiff", + "moto", + "orjson", ] release = [ "tbump~=6.11.0", @@ -76,13 +96,10 @@ release = [ ] tox = [ "tox~=4.56.1", - "tox-uv~=1.35.2", - # tox-uv depends on the uv PyPI package without a version bound, and the Dockerfile - # installs this group with `uv pip install`, which resolves fresh instead of reading - # uv.lock. Without this bound the resolver picks the newest uv, whose console script - # then shadows the pinned binary in the image and trips required-version at runtime. - # Keep in sync with [tool.uv] required-version above. - "uv~=0.12.0", + # tox-uv-bare carries the actual plugin; the `tox-uv` wrapper only adds a dependency on + # the 58MB `uv` PyPI package, which the image already has as a binary and the host has + # on PATH. Requires `uv` to be on PATH, which holds in both places. + "tox-uv-bare~=1.35.2", ] [tool.ruff] diff --git a/uv.lock b/uv.lock index b053de143..f0b31b521 100644 --- a/uv.lock +++ b/uv.lock @@ -1092,7 +1092,7 @@ requires-dist = [ test = [ { name = "moto", specifier = ">=5.1.6,<6.0.0" }, { name = "orjson", specifier = ">=3.11.3,<4.0.0" }, - { name = "pytest", specifier = ">=8.3.5,<9.0.0" }, + { name = "pytest", specifier = "~=8.3.4" }, { name = "pytest-cov", specifier = "~=6.0.0" }, { name = "pytest-json-report", specifier = "==1.5.0" }, { name = "pytest-mock", specifier = ">=3.14.0,<4.0.0" }, @@ -1132,6 +1132,7 @@ test = [ { name = "docstring-parser" }, { name = "griffe" }, { name = "jinja2" }, + { name = "jsonschema" }, { name = "moto" }, { name = "orjson" }, { name = "pytest" }, @@ -1147,8 +1148,7 @@ test = [ ] tox = [ { name = "tox" }, - { name = "tox-uv" }, - { name = "uv" }, + { name = "tox-uv-bare" }, ] type = [ { name = "ty" }, @@ -1179,27 +1179,27 @@ release = [ { name = "tomlkit", specifier = ">=0.11" }, ] test = [ - { name = "deepdiff", specifier = "~=8.5.0" }, + { name = "deepdiff" }, { name = "docstring-parser", specifier = "~=0.15" }, { name = "griffe", specifier = ">=1.0" }, { name = "jinja2", specifier = "~=3.1" }, - { name = "moto", specifier = ">=5.1.6" }, - { name = "orjson", specifier = ">=3.11.3" }, + { name = "jsonschema" }, + { name = "moto" }, + { name = "orjson" }, { name = "pytest", specifier = "~=8.3.4" }, - { name = "pytest-cov", specifier = "~=6.0.0" }, - { name = "pytest-json-report", specifier = "==1.5.0" }, - { name = "pytest-mock", specifier = ">=3.14.0" }, - { name = "pytest-order", specifier = "~=1.3.0" }, - { name = "pytest-snapshot", specifier = "==0.9.0" }, - { name = "python-dotenv", specifier = "~=1.0.0" }, + { name = "pytest-cov" }, + { name = "pytest-json-report" }, + { name = "pytest-mock" }, + { name = "pytest-order" }, + { name = "pytest-snapshot" }, + { name = "python-dotenv" }, { name = "toml", specifier = "~=0.10.2" }, - { name = "urllib3", specifier = "~=2.6.0" }, - { name = "vcrpy", specifier = "~=8.2.1" }, + { name = "urllib3" }, + { name = "vcrpy" }, ] tox = [ { name = "tox", specifier = "~=4.56.1" }, - { name = "tox-uv", specifier = "~=1.35.2" }, - { name = "uv", specifier = "~=0.12.0" }, + { name = "tox-uv-bare", specifier = "~=1.35.2" }, ] type = [{ name = "ty", specifier = "~=0.0.55" }] @@ -2969,18 +2969,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d8/97/560a5dfde154619d9643b1e208119dddc29bbb35a38a4ce4d095c16cf8f0/tox-4.56.1-py3-none-any.whl", hash = "sha256:4d06b925c4dd67872099b39c5a46fba79a2169c5f6e32060f95a8b1181f0ef55", size = 216469, upload-time = "2026-06-25T06:18:35.229Z" }, ] -[[package]] -name = "tox-uv" -version = "1.35.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "tox-uv-bare" }, - { name = "uv" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/ca/dc/6e9994c799bdbb309f829dd6b8d98764dd0757302f3433c380438a3a127b/tox_uv-1.35.2-py3-none-any.whl", hash = "sha256:2d99b0e3c782ba49e7cbe521c8d344758595961b17a3633738d67096641c1bde", size = 6565, upload-time = "2026-05-05T01:34:16.07Z" }, -] - [[package]] name = "tox-uv-bare" version = "1.35.2" @@ -3119,32 +3107,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, ] -[[package]] -name = "uv" -version = "0.12.5" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7c/b0/3085b844fe59aa319a3f94a5cca9938fffecc82705aa9c2762a749f7095c/uv-0.12.5.tar.gz", hash = "sha256:442a21d181faae21742aaaf6d2091a0d27755d3eac344061a9a00c90169b7524", size = 7101936, upload-time = "2026-08-14T19:56:57.693Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b8/4c/6412d4a618230db699118b362ec41c54795f93992b43c53e225bd0213501/uv-0.12.5-py3-none-linux_armv6l.whl", hash = "sha256:2bd62134e56af35b9cf017aaf8ae41a605d6501dd49afc35b70b544a45dd8354", size = 23310055, upload-time = "2026-08-14T19:55:51.357Z" }, - { url = "https://files.pythonhosted.org/packages/bd/ec/d76387b388fa21620088b89b9c67f2596a707add585104e0cb5e8abf55f2/uv-0.12.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:1a06c8bc4d43b5f6c1e3f2ae3d0f6455b07515f762516f95e52e6c0cbccedf15", size = 21401335, upload-time = "2026-08-14T19:55:55.371Z" }, - { url = "https://files.pythonhosted.org/packages/6d/bc/81ab953b7261ae6be40874b1f283a10873871e02eb353d354614dd8da96b/uv-0.12.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d87156bc174d94fae890bb7a261e2867140abb9fe1e9de81a5295e582fb9d0f5", size = 19290641, upload-time = "2026-08-14T19:55:58.998Z" }, - { url = "https://files.pythonhosted.org/packages/7d/13/07585043c10e648820bf826474dac46864ce6691da5dc52fee43c5c7523a/uv-0.12.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:2d65b7b3bc3fd28678f62aa7fb5d90f106ad9782c1354af60b6cecdf9ea9ecd9", size = 22245569, upload-time = "2026-08-14T19:56:02.729Z" }, - { url = "https://files.pythonhosted.org/packages/3e/6d/310f8f56f8d001b4000112a09d7b7de80fb2024a90208fabb9ddc457c123/uv-0.12.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.musllinux_1_1_armv7l.whl", hash = "sha256:712624b62e25c84e5a10fc6aa144d8a81b685fdc067a54a7ca4367d75d2cf791", size = 22745152, upload-time = "2026-08-14T19:56:06.426Z" }, - { url = "https://files.pythonhosted.org/packages/92/da/7922b67eec5ee03e94333c5841b682c335033ee80acac17c3417bd752656/uv-0.12.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f9656ac7a00fd4314980fb0f790df1c1f3fa9cbcf9af9c6f611b19448b9da687", size = 22787947, upload-time = "2026-08-14T19:56:10.149Z" }, - { url = "https://files.pythonhosted.org/packages/62/55/5dbaed832a4b36809ef8a07c8e56e9fee0dedb0aa0454f6d232b6e468f2c/uv-0.12.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:568485b44e848eb3693f85d6b00299ccd8fc4d26902030dbf24f549c276db9ca", size = 23367616, upload-time = "2026-08-14T19:56:13.768Z" }, - { url = "https://files.pythonhosted.org/packages/11/77/baf761d12bb66efb01706e3bbb5926ed0d13cb0a40539a661fcfffd46de4/uv-0.12.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bd08c82831b0033330f8eeeb0d90f938a4d999f25569bee68a975c736142d795", size = 24586263, upload-time = "2026-08-14T19:56:17.57Z" }, - { url = "https://files.pythonhosted.org/packages/c3/a8/76c1031c4834c959bb8a8059c9feabeaa77488ce8b6a3529d6d929ae81cf/uv-0.12.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:edd9ff6154b891146a342c143cd29b330ad97ac6a4b20ff4a99a20a4da84ceca", size = 24160655, upload-time = "2026-08-14T19:56:21.568Z" }, - { url = "https://files.pythonhosted.org/packages/93/22/dacc9a0bc8604187a1ba954a3aef8329e4104eb0af772d2c3c634893bd9b/uv-0.12.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e195ccf1ed60c8bb24a6447ce306441a4181d54b602407e09bc56e963911c15", size = 23657089, upload-time = "2026-08-14T19:56:25.144Z" }, - { url = "https://files.pythonhosted.org/packages/39/98/e8f9c071622f2cb4072d8b587d27b27d23cf0d3ebf8b3687f5af6030f587/uv-0.12.5-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:58abfb0f658b39a834307a11223bc170294ea214263b4c99ecc7663720d43544", size = 22379954, upload-time = "2026-08-14T19:56:28.789Z" }, - { url = "https://files.pythonhosted.org/packages/73/95/4c3f060e95f7cbe9177b4ab361f0cbfc4ae22e5a49b22e73eee9f0d0a6ca/uv-0.12.5-py3-none-manylinux_2_31_riscv64.musllinux_1_1_riscv64.whl", hash = "sha256:6ad2c455f1fe4d2962f6fd7ccb3b1f61c61856681c9d99f40e170b2074353fa3", size = 23318163, upload-time = "2026-08-14T19:56:32.504Z" }, - { url = "https://files.pythonhosted.org/packages/a0/96/ca0497ef8912ef48dbbc9982a8b4212260c34d56bfd0d45fe67b31942121/uv-0.12.5-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:a05b497c2a948c8600f4c831a89852b4d2514b7f561074225cc9edd0cc4811e2", size = 23470437, upload-time = "2026-08-14T19:56:36.525Z" }, - { url = "https://files.pythonhosted.org/packages/60/e7/8bdc37669a6cd2b46a2ec08ccbb58c61395ec84a073e199f5a4a64bb998f/uv-0.12.5-py3-none-musllinux_1_1_i686.whl", hash = "sha256:7817f8e957960f9ddc452ea353f283c0d6393e2e31b400276485adced5b1f371", size = 22545803, upload-time = "2026-08-14T19:56:40.606Z" }, - { url = "https://files.pythonhosted.org/packages/37/cc/01e39e1dbeb838a6b3c26bf97c867d6f366459b22a38bea691af8c6c94c0/uv-0.12.5-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:dc14e4f81a99b585a891350c60d1ff4557d54cb3c3c81fa45fd4e0dd512ba752", size = 23874113, upload-time = "2026-08-14T19:56:44.193Z" }, - { url = "https://files.pythonhosted.org/packages/0a/38/9053599a73a351d1cd34195c7a48c1db4d4d51b57b543607fad7ecf9354c/uv-0.12.5-py3-none-win32.whl", hash = "sha256:39bb102766c95571781a7b4c611675ea213e08df5c680f3936279b3c0d1f6c3c", size = 20744641, upload-time = "2026-08-14T19:56:47.689Z" }, - { url = "https://files.pythonhosted.org/packages/ce/f6/a9af9311c7f5640ca2bfcfdedb7aca37fa6d1d9f5c981fb50c5be02b7477/uv-0.12.5-py3-none-win_amd64.whl", hash = "sha256:455c3e57602e2141e66e2f0bf685898c9c5e5a70377d14c9a71554a3baf3ddbf", size = 21621812, upload-time = "2026-08-14T19:56:51.126Z" }, - { url = "https://files.pythonhosted.org/packages/bc/fb/e1266399f755f97a0783de379f2fed6dae0a2a240db32fe5a2eb976fec8a/uv-0.12.5-py3-none-win_arm64.whl", hash = "sha256:bea86f27a027e0e3af908db4bdd4f1ceef3ca2bd47673b5ccca7f550e325b1b4", size = 20381876, upload-time = "2026-08-14T19:56:54.883Z" }, -] - [[package]] name = "vcrpy" version = "8.2.1"