diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f1f24a5bf..caa908efb 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -27,7 +27,13 @@ jobs: - name: Set up Python uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: - python-version: "3.11" + python-version: "3.12" + + - name: Install the latest version of uv + uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0 + with: + enable-cache: true + cache-dependency-glob: "uv.lock" - uses: j178/prek-action@4e14d07f9231acabce116ccfca13b13dd9755ece # v3.0.1 @@ -41,8 +47,9 @@ jobs: TOXCFG: tox.ini strategy: + fail-fast: false matrix: - python-version: ["3.11", "3.12", "3.13", "3.14"] + python-version: ["3.12", "3.13", "3.14"] steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -108,7 +115,7 @@ jobs: - name: Set up Python uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: - python-version: "3.11" + python-version: "3.12" - name: Install the latest version of uv uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0 @@ -141,7 +148,7 @@ jobs: - name: Set up Python uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: - python-version: "3.11" + python-version: "3.12" - name: Install the latest version of uv uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0 diff --git a/.gitignore b/.gitignore index b8b0b14e3..dee31a23a 100644 --- a/.gitignore +++ b/.gitignore @@ -130,3 +130,6 @@ example/grpc/proto # MyST build outputs _build + +opencode.json +/.opencode/ \ No newline at end of file diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1755fb0b2..f085d269a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -36,5 +36,19 @@ repos: rev: 0.12.5 hooks: - id: uv-lock + - repo: local + hooks: + - id: starlark-docstring-markdown + name: starlark-docstring-markdown + entry: uv + args: + - tool + - run + - git+https://github.com/michaelboulton/starlark-docstring-markdown@df06acc9307e9ad8bd83fe618f012072f288d3d1 + - tavern/tavern/_core/starlark/ + - tavern/docs/source/scripting-api.md + language: system + pass_filenames: false + files: \.star$ exclude: (docs/) diff --git a/AGENTS.md b/AGENTS.md index ecb636adc..252769203 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -42,7 +42,10 @@ uv run tox -c tox-integration.ini -e py3-allure # example/allure, generates pre-PR check. `example/custom_backend` has its own bats-based `run_tests.sh` exercising third-party plugin loading. -Releases: `tbump ` (bumps `tavern/__init__.py`, regenerates CHANGELOG, re-locks) then `flit publish`. +Releases: `./scripts/bump.bash --tag-message "..."` (runs `uv version`, which updates +`[project] version` in `pyproject.toml` and re-locks, then commits, tags and regenerates CHANGELOG) +then `flit publish`. The version literal lives only in `pyproject.toml`; `tavern.__version__` reads it +back from the installed package metadata. ## Architecture diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e97d6562e..20a340418 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -79,7 +79,9 @@ pre-commit run --all-files 1. Setup `~/.pypirc` according to the [official instructions](https://packaging.python.org/en/latest/specifications/pypirc/) -1. Tag and push to git with `tbump --tag-message ""` +1. Bump the version, update the changelog and tag with + `./scripts/bump.bash --tag-message ""`, then push the commit and the + tag as instructed (or pass `--push` to have the script do it) 1. Upload to pypi with `flit publish` diff --git a/docs/source/scripting-api.md b/docs/source/scripting-api.md new file mode 100644 index 000000000..494c739af --- /dev/null +++ b/docs/source/scripting-api.md @@ -0,0 +1,200 @@ +# Modules + +## Table of Contents + +- 🅼 [starlark\.tavern\_helpers](#starlark-tavern_helpers) + + +## 🅼 starlark\.tavern\_helpers + +Tavern helper functions for Starlark scripts\. + +This module provides built-in functions for controlling test execution +in Tavern's Starlark pipeline feature\. + +Usage: + load\("@tavern\_helpers\.star", "run\_stage", "re", "time", "log"\) + +- **Functions:** + - 🅵 [run\_stage](#starlark-tavern_helpers-run_stage) +- **Structs:** + - 🆂 [re](#starlark-tavern_helpers-re) + - 🆂 [time](#starlark-tavern_helpers-time) + +### Functions + + +### 🅵 starlark\.tavern\_helpers\.run\_stage + +```python +def run_stage(name, continue_on_fail = False, extra_vars = None): +``` + +Execute a test stage by its ID and return the response\. + +**Parameters:** + +- **name**: Stage ID to execute \(must have 'id' key in YAML\) +- **continue_on_fail**: If True, return failed response instead of raising +an exception\. Default: False +- **extra_vars**: Optional dict of variables to merge into stage config + +**Returns:** + +- `A struct with properties`: - failed \(bool\): True if stage failed + - success \(bool\): True if stage succeeded + - request\_vars: Variables captured during request execution + - stage\_name: Name of the executed stage + +For HTTP responses, also includes: + - body: Response body \(parsed JSON if Content-Type is application/json\) + - status\_code: HTTP status code + - headers: Response headers + - cookies: Response cookies + +**Examples:** + +```python +# Run a stage by ID +resp = run_stage("get_cookie") +if resp.failed: + fail("Stage failed") + +# Continue on failure +resp = run_stage("try_login", continue_on_fail=True) +if resp.failed: + log("Login failed, using fallback") + run_stage("fallback_login") +``` + +### Structs + + +### 🆂 re + +Regex utilities for pattern matching and text manipulation\. + +Provides Python regex-style operations for use in Starlark scripts\. + +Available methods: + match\(pattern, string\): Match pattern at start of string + search\(pattern, string\): Search for pattern anywhere in string + sub\(pattern, repl, string\): Replace all pattern occurrences + +**Examples:** + +```python +load("@tavern_helpers.star", "re") + +resp = run_stage("get_data") + +# Extract version number +match = re.search("v(\d+)\.", resp.body) +if match == None: + fail("Version not found") +version = match.groups[0] + +# Replace values +new_url = re.sub("OLD", "NEW", original_url) +``` + +**Methods:** + + +#### 🅵 starlark\.tavern\_helpers\.re\.match + +```python +def match(pattern, s): +``` + +Match a regex pattern at the beginning of string\. + +**Parameters:** + +- **pattern**: Regular expression pattern +- **s**: String to match against + +**Returns:** + +- `A struct with match details, or None if no match`: - group0: Full match \(group 0\) +- groups: List of captured groups +- start: Start position of match +- end: End position of match + +#### 🅵 starlark\.tavern\_helpers\.re\.search + +```python +def search(pattern, s): +``` + +Search for a regex pattern anywhere in string\. + +**Parameters:** + +- **pattern**: Regular expression pattern +- **s**: String to search in + +**Returns:** + +- `A struct with match details, or None if no match`: - group0: Full match \(group 0\) +- groups: List of captured groups +- start: Start position of match +- end: End position of match + +#### 🅵 starlark\.tavern\_helpers\.re\.sub + +```python +def sub(pattern, repl, s): +``` + +Substitute occurrences of pattern in string\. + +**Parameters:** + +- **pattern**: Regular expression pattern +- **repl**: Replacement string +- **s**: String to process + +**Returns:** + +- String with all occurrences replaced + +### 🆂 time + +Time utilities for delays and timing operations\. + +Available methods: + sleep\(seconds\): Pause execution for given seconds + +**Examples:** + +```python +load("@tavern_helpers.star", "time") + +for i in range(0, 3): + resp = run_stage("poll", continue_on_fail=True) + if not resp.failed: + break + time.sleep(1) # Wait 1 second before retry +``` + +**Methods:** + + +#### 🅵 starlark\.tavern\_helpers\.time\.sleep + +```python +def sleep(seconds): +``` + +Sleep for specified seconds\. + +**Parameters:** + +- **seconds**: Number of seconds to sleep \(can be float\) + +**Examples:** + +```python +time.sleep(0.5) # Sleep for 500ms +``` diff --git a/docs/source/scripting.md b/docs/source/scripting.md new file mode 100644 index 000000000..b222d2d04 --- /dev/null +++ b/docs/source/scripting.md @@ -0,0 +1,448 @@ +# Scripting Tavern execution with Starlark + +Tavern supports advanced test orchestration through Starlark scripting, enabling complex control flow, dynamic test +logic, and multi-stage workflows beyond simple sequential YAML tests. + +**This should be considered an experimental work in progress feature and some functionality may change without a major +version bump.** + +**This should also only be used when other control flow options are not suitable. Using scripting can make tests harder +to debug, but can be useful for more complex test scenarios.** + +## What problem is this trying to solve? + +In GitHub actions, stage execution is sequential (like Tavern) but stages can be conditionally executed based on +previous stage results using magic string substitutions, eg: + +```yaml +name: basic test + +on: + pull_request: + branches: + - main + +jobs: + simple-checks: + runs-on: ubuntu-24.04 + + steps: + - uses: actions/checkout@v6 + + - name: Do something + id: do-something + uses: do-something-action@v1 + + - name: Do something else + if: ${{ steps.do-something.outputs.success == 'true' }} + uses: do-something-else-action@v2 +``` + +Tavern emulates some of this behaviour already, with +the ['skip' key](./core_concepts/marks.md#skipping-stages-with-simpleeval-expressions), and has some limited support for +retries with the ['max_retries' key](./core_concepts/flow.md#retrying-tests). There are other control flow features like +[adding a delay](./core_concepts/flow.md#adding-a-delay-between-tests), each of which have their own specific syntax for +use. + +To try and combine all of these into one unified test execution model, we need a way to express complex logic +declaratively, in a format that is more readable than interpolated strings in YAML. + +## Starlark Overview + +Starlark is a Python-like language designed for configuration and build systems. It provides: + +- Python-like syntax familiar to most developers +- Deterministic execution (not turing complete) +- Safe, sandboxed environment +- Built-in control flow: `if/elif/else`, `for` +- Basic types: `str`, `int`, `list`, etc. +- Basic built-in functions: `len()`, `max()`, `min()`, `type()`, `sorted()` + +## Enabling Starlark + +Starlark control flow is an experimental feature. Enable it with the pytest flag: + +```bash +pytest --tavern-experimental-starlark-pipeline +``` + +## Basic Usage + +### Inline Control Flow + +Define Starlark scripts directly in your YAML using the `control_flow` key: + +```yaml +--- +test_name: Test control_flow with inline Starlark - basic sequential + +stages: + - name: Get cookie + id: get_cookie + request: + url: "{global_host}/get_cookie" + method: POST + json: + cookie_name: test-cookie + response: + status_code: 200 + cookies: + - test-cookie + + - name: Echo a value + id: echo_value + request: + url: "{global_host}/echo" + method: POST + json: + value: "hello" + response: + status_code: 200 + json: + value: "hello" + +# Inline Starlark script that controls execution order +control_flow: | + # Load the stage runner helper + load("@tavern_helpers.star", "run_stage") + + # First run the get_cookie stage. If this fails, it will fail the test. + resp = run_stage("get_cookie") + + # Then run the echo_value stage + resp = run_stage("echo_value") +``` + +This key being present will _override_ the default sequential test execution. + +Notes about the execution model: + +- All existing Tavern functionality remains the same. Pytest fixtures and marks are applied (including `parametrize`), + tinctures are run between stages, Tavern hooks are called. +- [`finally` stages](./core_concepts/flow.md#finalising-stages) are _not_ run. +- If `run_stage()` is not called, an exception will be raised. This mirrors Pytest's default behaviour, where it will + exit with exit code 1 if no tests were run. + +### Stage Requirements + +Each stage referenced from Starlark must have an `id` key: + +```yaml +stages: + - name: My stage name + id: my_stage_id # Required for Starlark reference + request: + # ... request config +``` + +## Available Functions + +See [the autogenerated scripting API docs](./scripting-api.md) for a complete list of available functions. + +### `run_stage()` + +Execute a test stage by its ID: + +```starlark +load("@tavern_helpers.star", "run_stage") + +# Basic usage +resp = run_stage("stage_id") + +# Continue even if stage fails, fall back to login if necessary +resp = run_stage("try_get_user_data", continue_on_fail=True) +if resp.failed: + log("Login failed") + run_stage("login") + run_stage("try_get_user_data") + +# Pass variables to the stage +resp = run_stage("verify_data", extra_vars={ + "key": "value", + "user_id": extracted_id +}) +``` + +**Parameters:** + +- `name` (string, required): Stage ID to execute +- `continue_on_fail` (bool, optional): If `True`, return a failed response instead of raising an exception. Default: + `False` +- `extra_vars` (dict, optional): Additional variables to merge into the stage's configuration + +**Return value:** A response struct with properties: + +- `.failed` (bool): `True` if the stage failed +- `.success` (bool): `True` if the stage succeeded +- `.request_vars`: Variables captured during request execution +- `.stage_name`: Name of the executed stage + +The struct also has properties specific to the response type, currently only available for HTTP responses: + +- `.body`: Response body (parsed JSON if `Content-Type` is `application/json`, otherwise raw bytes) +- `.status_code`: HTTP status code +- `.headers`: Response headers +- `.cookies`: Response cookies + +### Regex Functions + +Pattern matching and extraction via the `re` module: + +```starlark +load("@tavern_helpers.star", "run_stage", "re") + +# Get data containing text to match +resp = run_stage("get_data") + +# re.search returns a struct with: group0, groups, start, end +version_match = re.search("v(\\d+)\\.", resp.body) +if version_match == None: + fail("Failed to match version pattern") + +# Access captured groups +major_version = version_match.groups[0] + +# Use extracted values in next stage +resp = run_stage("verify", extra_vars={ + "major_version": major_version +}) +``` + +**Available regex functions:** + +- `re.match(pattern, string)`: Match at the beginning of the string +- `re.search(pattern, string)`: Search anywhere in the string +- `re.sub(pattern, replacement, string)`: Substitute pattern matches + +**Return value for match/search:** A struct with: + +- `.group0`: The full match (group 0) +- `.groups`: List of captured groups +- `.start`: Start position of the match +- `.end`: End position of the match + +Returns `None` if no match found. + +### `log()` + +Log messages to stdout at INFO level: + +```starlark +log("Starting pipeline execution") +log("Stage completed with status: " + resp.status_code) +``` + +### `fail()` + +Explicitly fail the test with a message: + +```starlark +if resp.failed: + fail("Stage failed unexpectedly") +``` + +## Working with Included Stages + +Stages defined in included files can be referenced by their IDs: + +```yaml +--- +test_name: Test control_flow with included stages + +includes: + - !include stages.yaml + +# Inline Starlark script using included stage IDs +control_flow: | + load("@tavern_helpers.star", "run_stage") + + # Run stages defined in stages.yaml + resp = run_stage("get-cookie-included") + resp = run_stage("echo-value-included") + + if resp.failed: + fail("Included stage failed") +``` + +Stages defined in global configuration are also available: + +```yaml +# Run with --tavern-global-cfg /path/to/global_cfg.yaml +--- +test_name: Test with global stages + +control_flow: | + load("@tavern_helpers.star", "run_stage") + + # Run a stage defined in global_cfg.yaml + run_stage("finally-nothing-check") +``` + +## Programming Patterns + +### Extracting and Using Response Data + +Use regex to extract values from responses and pass them to subsequent stages: + +```yaml +--- +test_name: Test regex extraction with inline Starlark + +stages: + - name: Get regex test data + id: get_regex_data + request: + url: "{global_host}/regex_data" + method: GET + response: + status_code: 200 + + - name: Verify extracted values + id: verify_extracted + request: + url: "{global_host}/verify_extracted" + method: POST + json: + major_version: "{major_version}" + token_id: "{token_id}" + server_name: "{server_name}" + response: + status_code: 200 + json: + status: "verified" + +control_flow: | + load("@tavern_helpers.star", "run_stage", "re") + + # Get data + resp = run_stage("get_regex_data") + if resp.failed: + fail("get_regex_data stage failed") + + # Extract version: v2.5.1 -> capture major version "2" + version_match = re.search("v(\\d+)\\.", resp.body) + if version_match == None: + fail("Failed to match version pattern") + major_version = version_match.groups[0] + + # Extract token: TKN-a1b2c3d4e5f6 -> capture ID part + token_match = re.search("\"TKN-(.+)\"", resp.body) + if token_match == None: + fail("Failed to match token pattern from " + resp.body) + token_id = token_match.groups[0] + + # Extract server: Server-PROD-01 -> capture "PROD-01" + server_match = re.search("Server-(\\w+-\\w+)\\s", resp.body) + if server_match == None: + fail("Failed to match server pattern") + server_name = server_match.groups[0] + + # Pass extracted values via extra_vars + resp = run_stage("verify_extracted", extra_vars={ + "major_version": major_version, + "token_id": token_id, + "server_name": server_name + }) + if resp.failed: + fail("verify_extracted stage failed") +``` + +### Retry and Polling + +Implement retry logic with `continue_on_fail`: + +```yaml +test_name: test for loop with retry + +stages: + - name: polling + id: polling + request: + url: "{global_host}/poll" + method: GET + response: + status_code: 200 + json: + status: ready + +control_flow: | + load("@tavern_helpers.star", "run_stage", "time") + + succeeded = False + for i in range(0, 3): + resp = run_stage("polling", continue_on_fail=True) + if not resp.failed: + succeeded = True + break + log("polling attempt " + str(i) + " failed") + time.sleep(1) + + if not succeeded: + fail("polling did not succeed after 3 attempts") +``` + +## Current Limitations + +### HTTP-Only Support + +**Important:** Starlark control flow currently only works with HTTP/REST tests. Other protocol backends (MQTT, gRPC, +GraphQL) are not yet supported. + +Attempting to use `run_stage()` with non-HTTP stages will raise a `NotImplementedError`. + +### Error Messages + +Starlark error messages can be unhelpful when debugging failures. Error context may be limited, showing: + +- `"Error evaluating starlark script"` without detailed stack traces +- `"Stage with id '' not found"` without listing available stages +- Python exceptions wrapped without full traceback information + +**Tips for debugging:** + +1. Use `log()` statements to trace execution flow +2. Check stage IDs match exactly (case-sensitive) +3. Verify `control_flow` indentation (YAML multi-line strings) +4. Test regex patterns separately before using in scripts + +### Type Restrictions + +Starlark uses a JSON-serializable subset of Python types. Objects passed between Python and Starlark must be: + +- Primitives: `str`, `int`, `float`, `bool`, `None` +- Collections: `dict` (with string keys), `list`, `tuple` +- Dataclasses (automatically converted to dicts) + +Non-serializable objects (file handles, database connections, custom classes without `to_starlark()` method) can be +passed through to Starlark, but will be opaque and unusable. + +## Starlark Language Reference + +For complete language details, see +the [Starlark specification](https://github.com/bazelbuild/starlark/blob/master/spec.md). + +Key differences from Python: + +| Feature | Python | Starlark | +|----------------|--------------------|-----------------------------------------------------------| +| Classes | Yes | No user-defined classes (use `struct` to emulate classes) | +| Exceptions | `try/except/raise` | No exception handling | +| Comprehensions | Yes | List + dict comprehensions only | +| Lambda | Yes | No | + +## Examples + +See the integration test files in `tests/integration/starlark/` for complete working examples of basic control flow, +includes, regex extraction, retry patterns + +## Possible future improvements + +- Add more library functions. Currently only `re` is available, starlark-go + has [starlib](https://github.com/qri-io/starlib) which exposes a lot of useful functions (math, hashing, base64, + etc). +- Support MQTT, gRPC, GraphQL. This becomes a bit more complicated with the new custom backend functionality. +- Make error messages more helpful. +- Add more helper functions (ensure JWT is valid, sleeping (time module?), etc). + - Make this auto-export functions into either this document with mkdocstrings into + - Let users import their own functions into starlark? +- Add a new CLI/ini flag to say "run 'finally' stages when using starlark script" diff --git a/example/allure/docker-compose.yaml b/example/allure/docker-compose.yaml index 416d55c16..1bb317a35 100644 --- a/example/allure/docker-compose.yaml +++ b/example/allure/docker-compose.yaml @@ -5,7 +5,7 @@ services: context: ../http dockerfile: Dockerfile ports: - - "5000:5000" + - "5004:5000" # Only used to turn the results written by allure-pytest into a report - not # started by 'docker compose up', run it with 'docker compose run --rm allure' diff --git a/example/allure/tests/common.yaml b/example/allure/tests/common.yaml index b12788dff..2a667dffd 100644 --- a/example/allure/tests/common.yaml +++ b/example/allure/tests/common.yaml @@ -3,7 +3,7 @@ name: Allure example includes description: variables and the login stage used by the tests in this folder variables: - service: http://localhost:5000 + service: http://localhost:5004 user: user: test-user pass: correct-password diff --git a/myst.yml b/myst.yml index 06fda1a94..005dec213 100644 --- a/myst.yml +++ b/myst.yml @@ -33,6 +33,8 @@ project: - file: docs/source/plugins/custom.md - file: docs/source/debugging.md - file: docs/source/cookbook.md + children: + - file: docs/source/scripting.md - file: CONTRIBUTING.md - file: CHANGELOG.md diff --git a/pyproject.toml b/pyproject.toml index e26d5c00c..4861a01b2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,9 +9,9 @@ classifiers = [ "Intended Audience :: Developers", "Framework :: Pytest", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "Topic :: Utilities", "Topic :: Software Development :: Testing", "License :: OSI Approved :: MIT License", @@ -21,7 +21,7 @@ keywords = ["testing", "pytest"] name = "tavern" description = "Simple testing of RESTful APIs" -dynamic = ["version"] +version = "4.0.0rc1" dependencies = [ "PyYAML>=6.0.1,<7", @@ -36,7 +36,7 @@ dependencies = [ "stevedore>=4,<6", ] -requires-python = ">=3.11" +requires-python = ">=3.12" [[project.authors]] name = "Michael Boulton" @@ -67,6 +67,10 @@ mqtt = [ "paho-mqtt>=1.3.1,<=1.6.1", ] +scriptable = [ + "starlark-pyo3", +] + graphql = [ "aiohttp", "websockets", @@ -87,9 +91,8 @@ dev = [ "pre-commit", "pytest-cov", "pytest-xdist", - "py", "tox>4.20,<5", - "ruff", + "ruff>=0.15.9", "uv>=0.9.2", "types-PyYAML", # See https://pypi.org/project/protobuf/#history for compatability between these two @@ -104,7 +107,6 @@ dev = [ # for pytest "exceptiongroup", "tomli", - "tbump>=6.10.0", "tox-uv>=1.28.0", "pytest-asyncio>=1.3.0", "hypothesis>=6,<7", @@ -135,7 +137,7 @@ grpc = "tavern._plugins.grpc.tavernhook" gql = "tavern._plugins.graphql.tavernhook" [tool.mypy] -python_version = "3.11" +python_version = "3.12" # See https://mypy.readthedocs.io/en/stable/running_mypy.html#mapping-file-paths-to-modules explicit_package_bases = true @@ -160,8 +162,8 @@ source = ["tavern"] [tool.coverage.paths] tavern = [ "tavern/", - ".tox/py311-generic/lib/python3.11/site-packages/tavern/", - ".tox/py311-mqtt/lib/python3.11/site-packages/tavern", + ".tox/py312-generic/lib/python3.12/site-packages/tavern/", + ".tox/py312-mqtt/lib/python3.12/site-packages/tavern", ] [tool.pytest.ini_options] @@ -173,7 +175,7 @@ addopts = [ "--strict-markers", "--tb=short", "--color=yes", - "-m", "not do_not_run" + "-m", "(not do_not_run) and (not starlark_control_flow)" ] norecursedirs = [ ".git", @@ -183,15 +185,17 @@ norecursedirs = [ "node_modules", "dist", "docs", + ".hypothesis" ] markers = [ "slow: A test marker to check if markers work with custom markers", "xdist_group('group1'): A test marker to check if markers work with args", "do_not_run: Test marker for tests that should not be run", + "starlark_control_flow: starlark control flow tests", ] [tool.ruff] -target-version = "py311" +target-version = "py312" extend-exclude = [ "tests/unit/tavern_grpc/test_services_pb2*", "example/grpc/helloworld_v1_precompiled_pb2*" @@ -223,42 +227,11 @@ known-first-party = ["tavern"] exclude = ["*_pb2.py", "*_pb2_grpc.py", "*_pb2.pyi"] docstring-code-format = true -[tool.tbump.version] -current = "3.6.2" - -regex = ''' - (?P\d+) - \. - (?P\d+) - \. - (?P\d+) - ((?P[a-zA-Z]+)(?P\d+))? - ''' - -[tool.tbump.git] -message_template = "Bump to {new_version}" -tag_template = "{new_version}" - -[[tool.tbump.file]] -src = "tavern/__init__.py" - -[[tool.tbump.before_commit]] -name = "Update changelog" -cmd = "./scripts/update_changelog.py" -[[tool.tbump.before_commit]] -name = "Update lock" -cmd = "uv lock" - -# TODO: enable -# [[tool.tbump.after_push]] -# name = "publish" -# cmd = "./scripts/release.sh" - [tool.tox] runner = "uv-venv-lock-runner" skip_missing_interpreters = true isolated_build = true -base_python = "3.11" +base_python = "3.12" [tool.uv] constraint-dependencies = ["ruamel-yaml<0.19.0"] diff --git a/scripts/bump.bash b/scripts/bump.bash new file mode 100755 index 000000000..b135bf0bb --- /dev/null +++ b/scripts/bump.bash @@ -0,0 +1,94 @@ +#!/usr/bin/env bash + +# Bump the project version, update the changelog, and create the release commit +# and annotated tag. Replaces tbump - the version lives only in the 'version' +# key of pyproject.toml, and 'uv version' is what updates it (and re-locks). +# +# Usage: +# ./scripts/bump.bash --tag-message "" [--push] +# +# is anything 'uv version' accepts as a value, eg '4.0.0rc1'. +# The tag message is required: CHANGELOG.md is generated from the messages of +# annotated tags by scripts/update_changelog.py. + +set -eu + +cd "$(dirname "$0")/.." + +usage() { + echo "usage: $0 --tag-message \"\" [--push]" >&2 + exit 2 +} + +new_version="" +tag_message="" +push=0 + +while [ $# -gt 0 ]; do + case "$1" in + --tag-message) + [ $# -ge 2 ] || usage + tag_message="$2" + shift 2 + ;; + --tag-message=*) + tag_message="${1#--tag-message=}" + shift + ;; + --push) + push=1 + shift + ;; + -h | --help) + usage + ;; + -*) + echo "unknown option: $1" >&2 + usage + ;; + *) + [ -z "$new_version" ] || usage + new_version="$1" + shift + ;; + esac +done + +[ -n "$new_version" ] || usage +[ -n "$tag_message" ] || usage + +if [ -n "$(git status --porcelain)" ]; then + echo "working tree is not clean, refusing to bump" >&2 + exit 1 +fi + +if git rev-parse -q --verify "refs/tags/$new_version" >/dev/null; then + echo "tag $new_version already exists" >&2 + exit 1 +fi + +set -x + +# Updates 'version' in pyproject.toml and re-locks +uv version "$new_version" +version=$(uv version --short) + +git commit --all --message "Bump to $version" +git tag --annotate "$version" --message "$tag_message" + +# The changelog is built from the tags, so it can only be generated once the new +# tag exists - regenerate it and fold it into the release commit +./scripts/update_changelog.py +git commit --all --amend --no-edit +git tag --force --annotate "$version" --message "$tag_message" + +set +x + +if [ "$push" -eq 1 ]; then + git push + git push origin "refs/tags/$version" +else + echo + echo "Created release commit and tag $version. To push:" + echo " git push && git push origin refs/tags/$version" +fi diff --git a/scripts/coverage.sh b/scripts/coverage.sh index eefabb125..85facbca6 100755 --- a/scripts/coverage.sh +++ b/scripts/coverage.sh @@ -2,9 +2,9 @@ set -ex -tox -c tox-integration.ini -e py311-generic -tox -c tox-integration.ini -e py311-mqtt -tox -e py311 +tox -c tox-integration.ini -e py312-generic +tox -c tox-integration.ini -e py312-mqtt +tox -e py312 coverage combine --append .coverage tests/integration/.coverage example/mqtt/.coverage coverage report -m diff --git a/scripts/release.sh b/scripts/release.sh index f6beda9c4..261061ff0 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # Releasing: -# 1. tbump --tag-message "" +# 1. ./scripts/bump.bash --tag-message "" # 2. run this script rm -rf build/ dist/ ./*.egg-info diff --git a/tavern/__init__.py b/tavern/__init__.py index 490dad5f9..28bcc67a3 100644 --- a/tavern/__init__.py +++ b/tavern/__init__.py @@ -3,4 +3,6 @@ Stop pytest warning about module already imported: PYTEST_DONT_REWRITE """ -__version__ = "3.6.2" +import importlib.metadata + +__version__ = importlib.metadata.version("tavern") diff --git a/tavern/_core/dict_util.py b/tavern/_core/dict_util.py index d1ba91157..ca961800d 100644 --- a/tavern/_core/dict_util.py +++ b/tavern/_core/dict_util.py @@ -140,10 +140,7 @@ def _attempt_find_include(to_format: str, box_vars: box.Box) -> str | None: return formatter.convert_field(would_replace, conversion) -T = typing.TypeVar("T", str, dict, list, tuple) - - -def format_keys( +def format_keys[T: (str, dict, list, tuple)]( val: T, variables: Mapping | Box, *, @@ -338,7 +335,7 @@ def yield_keyvals(block: Union[list, dict]) -> Iterator[tuple[list, str, str]]: Checked = typing.TypeVar("Checked", dict, Collection, str) -def check_keys_match_recursive( +def check_keys_match_recursive[Checked: (dict, Collection, str)]( expected_val: Checked, actual_val: Checked, keys: list[Union[str, int]], diff --git a/tavern/_core/exceptions.py b/tavern/_core/exceptions.py index 9dd2eb30a..2c99f6a72 100644 --- a/tavern/_core/exceptions.py +++ b/tavern/_core/exceptions.py @@ -184,5 +184,13 @@ class UnexpectedExceptionError(TavernException): else""" +class StarlarkError(TavernException): + """Exception when running a starlark stage, should only ever wrap another TavernException.""" + + +class DependencyMissingError(TavernException): + """Tried to use some functionality for which the 'extra' had not been installed.""" + + class TinctureError(TavernException): """Badly specified tincture.""" diff --git a/tavern/_core/files.py b/tavern/_core/files.py index ed44e4086..eda7f1008 100644 --- a/tavern/_core/files.py +++ b/tavern/_core/files.py @@ -133,7 +133,14 @@ def _parse_filespec(filespec: str | dict) -> _Filespec: class FileSendSpec(NamedTuple): - """A description of a file to send as part of a multipart/form-data upload to requests""" + """A description of a file to send as part of a multipart/form-data upload to requests + + Attributes: + filename: The name of the file to send + file_obj: The file object to send + content_type: The content type of the file + content_encoding: The content encoding, or the content encoding _headers_ for the file + """ filename: str file_obj: IOBase @@ -152,13 +159,13 @@ def guess_filespec( stack: exit stack to add open files context to Returns: - A tuple of either length 2 (filename and file object), 3 (as before, with content type), + A tuple of: + 1. Either length 2 (filename and file object), 3 (as before, with content type), or 4 (as before, with with content encoding). If a group name for the multipart upload - was specified, this is also returned. - - Notes: - If a 4-tuple is returned, the last element is a dictionary of headers to send to requests, - _not_ the raw encoding value. + was specified, this is also returned. The last element is a dictionary of headers to + pass directly to the requests 'files' argument, _not_ the raw encoding value. + 2. The form field name for the file + 3. The absolute path to the file """ if not mimetypes.inited: mimetypes.init() diff --git a/tavern/_core/loader.py b/tavern/_core/loader.py index 3ee5d7a92..6e910de52 100644 --- a/tavern/_core/loader.py +++ b/tavern/_core/loader.py @@ -7,6 +7,7 @@ import typing import uuid from abc import abstractmethod +from collections.abc import Iterable from itertools import chain from typing import Optional @@ -126,9 +127,26 @@ def __init__(self, stream): env_var_name = "TAVERN_INCLUDE" -def _get_include_dirs(loader): - loader_list = [loader._root] +def get_include_dirs(loader_list: list[os.PathLike]) -> Iterable[str]: + """Get the list of directories to search for include files. + Initializes and retrieves the combined list of include directories from both + the provided loader list and the environment variable TAVERN_INCLUDE. The + environment variable paths are lazily loaded on first access and cached for + subsequent calls. + + Args: + loader_list: A list of directory paths to search for include files. + + Returns: + An iterable containing the combined loader_list and + environment-based include paths. + + Note: + The TAVERN_INCLUDE environment variable should contain colon-separated + directory paths. Environment variables in the paths will be expanded + using os.path.expandvars(). + """ if IncludeLoader.env_path_list is None: if IncludeLoader.env_var_name in os.environ: IncludeLoader.env_path_list = [ @@ -143,7 +161,7 @@ def _get_include_dirs(loader): def find_include(loader, node) -> str: """Locate an include file and return the abs path.""" - for directory in _get_include_dirs(loader): + for directory in get_include_dirs([loader._root]): filename = os.path.abspath( os.path.join(directory, loader.construct_scalar(node)) ) @@ -151,7 +169,7 @@ def find_include(loader, node) -> str: return filename raise BadSchemaError( - f"{loader.construct_scalar(node)} not found in include path: {[str(d) for d in _get_include_dirs(loader)]}" + f"{loader.construct_scalar(node)} not found in include path: {[str(d) for d in get_include_dirs([loader._root])]}" ) @@ -166,6 +184,8 @@ def construct_include(loader, node: yaml.ScalarNode): return load_single_document_yaml(filename) elif extension == "graphql": return resolved_path.read_text(encoding="utf-8") + elif extension == "star": + return resolved_path.read_text(encoding="utf-8") raise BadSchemaError( f"Unknown filetype '{filename}' (included files must be in YAML, JSON, or GraphQL format with extensions .yaml, .yml, .json, or .graphql)" diff --git a/tavern/_core/plugins.py b/tavern/_core/plugins.py index 010a321db..b4b25414f 100644 --- a/tavern/_core/plugins.py +++ b/tavern/_core/plugins.py @@ -213,12 +213,19 @@ def is_plugin_backend_enabled( load_plugins = _PluginCache() -def get_extra_sessions(test_spec: Mapping, test_block_config: TestConfig) -> dict: +def get_extra_sessions( + test_spec: Mapping, + test_block_config: TestConfig, + force_plugins: list[str] | None = None, +) -> dict: """Get extra 'sessions' for any extra test types Args: test_spec: Spec for the test block test_block_config: available config for test + force_plugins: Optional list of plugin names to always load regardless of + whether their request/response blocks are found in stages. This is useful + when stages are loaded from external includes (e.g., Starlark control_flow). Returns: mapping of name to session. Session should be a context manager. @@ -228,11 +235,17 @@ def get_extra_sessions(test_spec: Mapping, test_block_config: TestConfig) -> dic plugins: list[_Plugin] = load_plugins(test_block_config) + logger.debug("Available plugins: %s", [p.name for p in plugins]) + for p in plugins: - if any( + # Check if plugin should be loaded based on stages or force_plugins list + in_stages = any( (p.plugin.request_block_name in i or p.plugin.response_block_name in i) for i in test_spec["stages"] - ): + ) + is_forced = force_plugins and p.name in force_plugins + + if in_stages or is_forced: logger.debug( "Initialising session for %s (%s)", p.name, p.plugin.session_type ) @@ -290,7 +303,13 @@ def get_request_type( except KeyError: pass else: - session = sessions[p.name] + try: + session = sessions[p.name] + except KeyError as e: + raise exceptions.MissingSettingsError( + f"expected session {p.name} but none found" + ) from e + request_class = p.plugin.request_type logger.debug( "Initialising request class for %s (%s)", p.name, request_class diff --git a/tavern/_core/pytest/config.py b/tavern/_core/pytest/config.py index f7445d067..8bc764638 100644 --- a/tavern/_core/pytest/config.py +++ b/tavern/_core/pytest/config.py @@ -29,6 +29,7 @@ class TestConfig: variables: variables available for use in the stage strict: Strictness for test/stage stages: Any extra stages imported from other config files + experimental_starlark_pipeline: Whether experimental starlark control_flow support is enabled (inline 'control_flow' block in test files) test_file_path: Optional path to the test file being run (used for resolving relative paths) tavern_internal: Internal config that should be used only by tavern tinctures: Global tinctures to apply to all test stages @@ -38,6 +39,7 @@ class TestConfig: strict: StrictLevel follow_redirects: bool stages: list + experimental_starlark_pipeline: bool | None tavern_internal: TavernInternalConfig tinctures: list | dict | None = None test_file_path: str | None = None @@ -75,6 +77,19 @@ def backends() -> list[str]: return available_backends + def to_starlark(self) -> dict[str, Any]: + import starlark + + dumped = { + "variables": starlark.OpaquePythonObject(self.variables), + "follow_redirects": self.follow_redirects, + "stages": self.stages, + "strict": starlark.OpaquePythonObject(self.strict), + "tavern_internal": starlark.OpaquePythonObject(self.tavern_internal), + "experimental_starlark_pipeline": self.experimental_starlark_pipeline, + } + return dumped + def has_module(module: str) -> bool: try: diff --git a/tavern/_core/pytest/error.py b/tavern/_core/pytest/error.py index 6b50e91a1..0014ae377 100644 --- a/tavern/_core/pytest/error.py +++ b/tavern/_core/pytest/error.py @@ -52,7 +52,13 @@ def _get_available_format_keys(self) -> dict: keys = self.exce._excinfo[1].test_block_config.variables except AttributeError: logger.warning("Unable to read stage variables - error output may be wrong") - keys = self.item.global_cfg.variables + try: + keys = self.item.global_cfg.variables + except AttributeError: + logger.warning( + "Unable to read global variables - error output may be wrong" + ) + keys = {} return keys @@ -218,10 +224,14 @@ def toterminal(self, tw: TerminalWriter) -> None: except AttributeError: stage = None # Fallback, we don't know which stage it is - stages = self.item.spec["stages"] + stages = self.item.spec.get("stages", []) - first_line = start_mark(stages[0]).line - 1 - last_line = end_mark(stages[-1]).line + try: + first_line = start_mark(stages[0]).line - 1 + last_line = end_mark(stages[-1]).line + except IndexError: + first_line = 0 + last_line = 0 line_start = None else: @@ -236,9 +246,12 @@ def toterminal(self, tw: TerminalWriter) -> None: tw.line("") if not stage: - tw.line( - "[Could not determine which stage was running]", red=True, bold=True - ) + if first_line == last_line == 0: + tw.line("[Only included stages were present]", red=True, bold=True) + else: + tw.line( + "[Could not determine which stage was running]", red=True, bold=True + ) elif missing_format_vars: tw.line("Missing format vars for stage", red=True, bold=True) else: diff --git a/tavern/_core/pytest/file.py b/tavern/_core/pytest/file.py index 83fe0a5ee..2372ba22f 100644 --- a/tavern/_core/pytest/file.py +++ b/tavern/_core/pytest/file.py @@ -93,7 +93,7 @@ def _parse_func_mark(fmt_vars: Mapping, m: str) -> pytest.Mark: posargs = [_ast_node_to_literal(arg) for arg in call.args] # Extract keyword arguments as literals - kwargs = { + kwargs: dict = { kw.arg: _ast_node_to_literal(kw.value) for kw in call.keywords if kw.arg is not None @@ -490,7 +490,7 @@ def collect(self) -> Iterator[YamlItem]: f"If this is meant to be defaults for the file, add 'is_defaults: true'. " f"If this is meant to be a test, add both 'test_name' and 'stages'." ) - else: + elif "control_flow" not in test_spec: raise exceptions.BadSchemaError( f"Document {document_idx + 1} in '{self.path}' is missing 'test_name' or 'stages'" ) diff --git a/tavern/_core/pytest/item.py b/tavern/_core/pytest/item.py index 49f44e283..e3ad38ad9 100644 --- a/tavern/_core/pytest/item.py +++ b/tavern/_core/pytest/item.py @@ -111,6 +111,7 @@ def yamlitem_from_parent(cls, name, parent: Node, spec, path: pathlib.Path): return cls.from_parent(parent, name=name, spec=spec, path=path) def initialise_fixture_attrs(self) -> None: + """Initialise fixture attributes for this item.""" # Prevent pytest from inspecting this item to try and find arguments, # which doesn't work because this isn't a Python function self.funcargs = {} # type: ignore @@ -147,7 +148,7 @@ def setup(self) -> None: @property def obj(self): stages = [] - for i, stage in enumerate(self.spec["stages"]): + for i, stage in enumerate(self.spec.get("stages", ())): name = "" if "name" in stage: name = stage["name"] @@ -202,7 +203,8 @@ def add_markers(self, pytest_marks: Iterable[MarkDecorator]) -> None: self.add_marker(pm) - def _load_fixture_values(self): + def _load_fixture_values(self) -> dict: + """Load fixture values from usefixtures and autouse fixtures.""" fixture_markers = self.iter_markers("usefixtures") values = {} @@ -262,7 +264,7 @@ def runtest(self) -> None: verify_tests(self.spec) - for stage in self.spec["stages"]: + for stage in self.spec.get("stages", []): if not stage.get("name"): if not stage.get("id"): # Should never actually reach here, should be caught at schema check time diff --git a/tavern/_core/pytest/util.py b/tavern/_core/pytest/util.py index 6b8a46ab8..1d2e7a90c 100644 --- a/tavern/_core/pytest/util.py +++ b/tavern/_core/pytest/util.py @@ -1,7 +1,7 @@ import logging from functools import lru_cache from pathlib import Path -from typing import Any, Optional, TypeVar, Union +from typing import Any, Optional, Union import pytest @@ -84,6 +84,12 @@ def add_parser_options(parser_addoption, with_defaults: bool = True) -> None: type=str, action="store", ) + parser_addoption( + "--tavern-experimental-starlark-pipeline", + action="store_true", + default=False, + help="Enable experimental starlark control_flow support (inline 'control_flow' block in test files)", + ) def add_ini_options(parser: pytest.Parser) -> None: @@ -153,6 +159,12 @@ def add_ini_options(parser: pytest.Parser) -> None: type="args", default=[], ) + parser.addini( + "tavern-experimental-starlark-pipeline", + help="Enable experimental starlark control_flow support (inline 'control_flow' block in test files)", + type="bool", + default=False, + ) def load_global_cfg(pytest_config: pytest.Config) -> TestConfig: @@ -195,6 +207,9 @@ def _load_global_cfg(pytest_config: pytest.Config) -> TestConfig: variables=variables, strict=_load_global_strictness(pytest_config), follow_redirects=_load_global_follow_redirects(pytest_config), + experimental_starlark_pipeline=get_option_generic( + pytest_config, "tavern-experimental-starlark-pipeline", False + ), tavern_internal=TavernInternalConfig( pytest_hook_caller=pytest_config.hook, backends=_load_global_backends(pytest_config), @@ -254,10 +269,7 @@ def _load_global_follow_redirects(pytest_config: pytest.Config) -> bool: return get_option_generic(pytest_config, "tavern-always-follow-redirects", False) -T = TypeVar("T", bound=Optional[Union[str, list, list[Path], list[str], bool]]) - - -def get_option_generic( +def get_option_generic[T: Optional[Union[str, list, list[Path], list[str], bool]]]( pytest_config: pytest.Config, flag: str, default: T, diff --git a/tavern/_core/run.py b/tavern/_core/run.py index 7bcc11868..4fe57db8c 100644 --- a/tavern/_core/run.py +++ b/tavern/_core/run.py @@ -2,6 +2,7 @@ import dataclasses import functools import logging +import os import pathlib from collections.abc import Mapping, MutableMapping from contextlib import ExitStack @@ -33,6 +34,79 @@ logger: logging.Logger = logging.getLogger(__name__) +def _run_with_starlark_control_flow( + in_file: pathlib.Path, + test_spec: MutableMapping, + global_cfg: TestConfig, + sessions: dict[str, Any], + included_stages: list[dict], +) -> None: + """ + Executes a test using Starlark-based control flow. This function integrates + a control flow script specified in the `test_spec` with the provided + configuration, running it against an instance of `StarlarkPipelineRunner`. + Logs progress and raises any execution errors encountered. + + Args: + in_file: The path to the input test file containing test definitions. + test_spec: A mutable mapping containing details of the test case, + including control flow script and other test-related configurations. + global_cfg: The global test configuration object, which contains shared + settings and variables used across multiple tests. + sessions: A dictionary containing session-related data (e.g., session + state or objects) that may be required during the test execution. + included_stages: A list of stages included in the test using !include + """ + # Local import to avoid circular dependency + try: + import starlark + except ImportError as e: + raise exceptions.DependencyMissingError( + "starlark", "pip install tavern[starlark]" + ) from e + + from tavern._core.starlark.starlark_env import StarlarkPipelineRunner + + # This is parsed here because it could be done at script load time, but this immediately + # fails the entire test run. This lets Tavern raise an exception specific to this test + dialect = starlark.Dialect.extended() + dialect.enable_keyword_only_arguments = True + try: + starlark.parse(os.fspath(in_file), test_spec["control_flow"], dialect=dialect) + except starlark.StarlarkError as e: + raise exceptions.BadSchemaError("Failed to parse starlark script") from e + + test_block_config = global_cfg.copy() + test_block_config.variables["tavern"] = get_tavern_box()["tavern"] + + # These can never be used from starlark so just get rid of them + test_block_config.variables.pop("event_loop_policy", None) + test_block_config.variables.pop("_session_faker", None) + + control_flow_script = test_spec["control_flow"] + test_block_name = test_spec["test_name"] + + logger.info("Running test with Starlark control_flow: %s", test_block_name) + + runner = StarlarkPipelineRunner( + test_path=str(in_file), + stages=test_spec.get("stages", []) + included_stages, + test_config=test_block_config, + sessions=sessions, + ) + + try: + runner.load_and_run(script=control_flow_script) + except Exception as e: + logger.error("Starlark control_flow failed: %s", e) + raise + + if not runner.stage_run: + raise exceptions.StarlarkError( + "No stages were run in Starlark control_flow - invalid script?" + ) + + def _resolve_test_stages( stages: list[Mapping], available_stages: Mapping ) -> list[Mapping]: @@ -174,7 +248,7 @@ def run_test( tavern_box, test_block_config, test_spec, available_stages ) all_stages = {s["id"]: s for s in available_stages + included_stages} - test_spec["stages"] = _resolve_test_stages(test_spec["stages"], all_stages) + test_spec["stages"] = _resolve_test_stages(test_spec.get("stages", []), all_stages) finally_stages = _resolve_test_stages(test_spec.get("finally", []), all_stages) test_block_config.variables["tavern"] = tavern_box["tavern"] @@ -184,12 +258,33 @@ def run_test( logger.info("Running test : %s", test_block_name) with ExitStack() as stack: - sessions = get_extra_sessions(test_spec, test_block_config) + http_plugin = test_block_config.tavern_internal.backends.get("http", "requests") + sessions = get_extra_sessions( + test_spec, + test_block_config, + [http_plugin] if "control_flow" in test_spec else None, + ) for name, session in sessions.items(): logger.debug("Entering context for %s", name) stack.enter_context(session) + if "control_flow" in test_spec: + if not test_block_config.experimental_starlark_pipeline: + # If not enabled, raise an error + raise exceptions.UnexpectedKeysError( + "control_flow requires --tavern-experimental-starlark-pipeline flag to be enabled" + ) + + _run_with_starlark_control_flow( + in_file, + test_spec, + test_block_config, + sessions, + available_stages + included_stages, + ) + return + def getonly(stage): o = stage.get("only") if o is None: @@ -372,13 +467,16 @@ def run_stage(self, idx: int, stage, *, is_final: bool = False) -> None: def wrapped_run_stage( self, stage: dict, stage_config: TestConfig, tinctures: Tinctures - ) -> None: + ) -> Any: """Run one stage from the test Args: stage: specification of stage to be run stage_config: available variables for test tinctures: tinctures for this stage/test + + Returns: + The response from the request. This could be any type of response, depending on the request type. """ stage = copy.deepcopy(stage) name = stage["name"] @@ -418,3 +516,5 @@ def wrapped_run_stage( tavern_box.pop("request_vars") delay(stage, "after", stage_config.variables) + + return response diff --git a/tavern/_core/schema/tests.jsonschema.yaml b/tavern/_core/schema/tests.jsonschema.yaml index 6df65f271..3534ff7e2 100644 --- a/tavern/_core/schema/tests.jsonschema.yaml +++ b/tavern/_core/schema/tests.jsonschema.yaml @@ -224,3 +224,11 @@ properties: oneOf: - $ref: "#/definitions/stage" - $ref: "#/definitions/stage_ref" + + control_flow: + type: string + description: | + Starlark script that controls the execution order of stages. + When present, stages are NOT executed sequentially - instead, + the Starlark script calls run_stage(stage_id) to execute stages. + Stages must have an 'id' field to be callable from Starlark. diff --git a/tavern/_core/starlark/__init__.py b/tavern/_core/starlark/__init__.py new file mode 100644 index 000000000..1853e0238 --- /dev/null +++ b/tavern/_core/starlark/__init__.py @@ -0,0 +1,13 @@ +"""Starlark pipeline support for Tavern.""" + +from .stage_registry import StageRegistry +from .starlark_env import ( + PipelineContext, + StarlarkPipelineRunner, +) + +__all__ = [ + "PipelineContext", + "StageRegistry", + "StarlarkPipelineRunner", +] diff --git a/tavern/_core/starlark/stage_registry.py b/tavern/_core/starlark/stage_registry.py new file mode 100644 index 000000000..a96cd45df --- /dev/null +++ b/tavern/_core/starlark/stage_registry.py @@ -0,0 +1,24 @@ +from typing import Any, Optional + +from tavern._core import exceptions + + +class StageRegistry: + """Stores all stages which are accessible from starlark (ie, which have an id)""" + + def __init__(self, stages: list[dict[str, Any]]): + self._stages: dict[str, dict] = {} + for stage in stages: + stage_id = stage.get("id") + if stage_id: + if stage_id in self._stages: + raise exceptions.DuplicateStageDefinitionError( + f"Duplicate stage id '{stage_id}' found" + ) + self._stages[stage_id] = stage + + def get_stage(self, stage_id: str) -> Optional[dict]: + return self._stages.get(stage_id) + + def get_all_stages(self) -> dict[str, dict]: + return self._stages.copy() diff --git a/tavern/_core/starlark/starlark_env.py b/tavern/_core/starlark/starlark_env.py new file mode 100644 index 000000000..26dac87bf --- /dev/null +++ b/tavern/_core/starlark/starlark_env.py @@ -0,0 +1,410 @@ +"""Starlark environment setup for Tavern pipelines.""" + +import copy +import dataclasses +import functools +import importlib.resources +import logging +import re +import time +from typing import Any, TypedDict + +import requests +import starlark + +from tavern._core import exceptions +from tavern._core.exceptions import TavernException +from tavern._core.pytest.config import TestConfig +from tavern._core.run import _TestRunner +from tavern._core.strict_util import StrictLevel +from tavern._core.tincture import get_stage_tinctures + +from .stage_registry import StageRegistry +from .types import from_starlark, to_starlark + +logger: logging.Logger = logging.getLogger(__name__) + + +def _wrap_callable(fn): + """Decorator that converts all arguments from starlark→Python before + calling *fn*, and converts the return value from Python→starlark.""" + + @functools.wraps(fn) + def wrapper(*args, **kwargs): + converted_args = [from_starlark(a) for a in args] + converted_kwargs = {k: from_starlark(v) for k, v in kwargs.items()} + result = fn(*converted_args, **converted_kwargs) + return to_starlark(result) + + return wrapper + + +class PipelineContext(TypedDict): + """Context object passed between stages in starlark pipelines. + + This object carries the test configuration and sessions from one stage + to the next, allowing users to explicitly manage the pipeline state. + + Attributes: + test_config: The TestConfig with current variables + sessions: Dictionary of session contexts + """ + + test_config: TestConfig + sessions: dict[str, Any] + + +@dataclasses.dataclass +class StageResponse: + """Response from running a stage. + + Attributes: + success: True if all verifications passed + response: The response body/headers/cookies/status_code + request_vars: Any variables captured during the request + stage_name: Name of the stage that was run + """ + + success: bool + response: Any | None + request_vars: dict[str, Any] + stage_name: str + + def to_starlark(self) -> dict: + return { + "success": self.success, + "response": to_starlark(self.response), + "request_vars": to_starlark(self.request_vars), + "stage_name": self.stage_name, + } + + +def _get_starlark_builtins() -> str: + """Load the Starlark builtins from the tavern_helpers.star file. + + Returns: + The Starlark code for built-in helper functions + """ + return ( + importlib.resources.files(__package__) + .joinpath("tavern_helpers.star") + .read_text() + ) + + +class StarlarkPipelineRunner: + """Runner for executing starlark pipeline scripts. + + This class handles loading and executing starlark scripts that can + control the flow of test execution. + """ + + def __init__( + self, + test_path: str, + stages: list[dict], + test_config: TestConfig, + sessions: dict[str, Any], + ): + """Initialize the pipeline runner. + + Args: + test_config: The test configuration with variables + sessions: session contexts to use for the pipeline + test_path: Path to the test file being run (used for error reporting in starlark parsing) + stages: Optional list of stage dictionaries to register + """ + self.test_path = test_path + self.globals = starlark.Globals.standard().extended_by( + [ + starlark.LibraryExtension.StructType, + ] + ) + self._stage_registry = StageRegistry(stages) if stages else StageRegistry([]) + self._test_config: TestConfig = test_config + self._sessions: dict[str, Any] = sessions + self._python_error: BaseException | None = None + self.stage_run = False + + def load_and_run(self, script: str) -> Any: + """Load and run a starlark pipeline script. + + Args: + script: The starlark script content + + Returns: + The return value of the script, if any + """ + # Create the starlark module + module = starlark.Module() + + # Add built-in functions to module + self._setup_builtins(module) + + # Parse the script + dialect = starlark.Dialect.extended() + dialect.enable_keyword_only_arguments = True + + try: + ast = starlark.parse(self.test_path, script, dialect=dialect) + except starlark.StarlarkError as e: + logger.error("Failed to parse starlark script: %s", e) + raise ValueError("Failed to parse starlark script") from e + + def load(filename: str) -> starlark.FrozenModule: + """Implements the 'load' function in starlark. Currently only supports loading tavern helpers.""" + if filename == "@tavern_helpers.star": + ast = starlark.parse( + filename, _get_starlark_builtins(), dialect=dialect + ) + mod = starlark.Module() + self._setup_builtins(mod) + starlark.eval(mod, ast, self.globals) + return mod.freeze() + raise FileNotFoundError(filename) + + # Evaluate the script + try: + starlark.eval(module, ast, self.globals, starlark.FileLoader(load)) # type: ignore[arg-type] + except starlark.StarlarkError as e: + logger.error("Error evaluating starlark script: %s", e) + python_error = self._python_error + if python_error is not None: + exc = exceptions.StarlarkError("Error evaluating starlark script") + exc.stage = python_error.stage # type:ignore + raise python_error from exc + else: + exc = exceptions.StarlarkError("Error evaluating starlark script") # type:ignore + raise exc from e + + return None + + def _run_stage( + self, + stage: dict[str, Any], + continue_on_fail: bool, + extra_vars: dict | None = None, + ) -> StageResponse: + """Run a single stage and return the response. + + Args: + stage: The stage specification dictionary + continue_on_fail: if True, swallow TavernExceptions and return a + failed StageResponse instead of re-raising + extra_vars: Additional variables to merge into test config for this stage + + Returns: + StageResponse with the result of running the stage + """ + + self.stage_run = True + + stage = copy.deepcopy(stage) # Make a deep copy to avoid mutating nested dicts + stage_name = stage.get("name", "unnamed-stage") + + default_strictness = StrictLevel.all_on() + test_spec = {"test_name": "starlark-pipeline", "stages": [stage]} + + if extra_vars: + test_config = self._test_config.with_new_variables() + test_config.variables.update(extra_vars) + else: + test_config = self._test_config + + runner = _TestRunner( + default_global_strictness=default_strictness, + sessions=self._sessions, + test_block_config=test_config, + test_spec=test_spec, + ) + + try: + tinctures = get_stage_tinctures(stage, test_spec) + stage_config = test_config.with_strictness(default_strictness) + response = runner.wrapped_run_stage(stage, stage_config, tinctures) + + return StageResponse( + success=True, + response=response, + request_vars=test_config.variables, + stage_name=stage_name, + ) + + except TavernException as e: + logger.error("Stage '%s' failed: %s", stage_name, str(e), exc_info=True) + if not continue_on_fail: + self._python_error = e + self._python_error.stage = stage + raise + return StageResponse( + success=False, + response=None, + request_vars=test_config.variables, + stage_name=stage_name, + ) + + def _create_response_struct(self, stage_response: StageResponse) -> dict[str, Any]: + """Convert StageResponse to dict that starlark converts to struct.""" + base_dict: dict[str, Any] = { + # Add "failed" so people don't have to do "if not resp.success" when people will almost certainly + # want to do "if resp.failed" most of the time + "failed": not stage_response.success, + "success": stage_response.success, + "request_vars": stage_response.request_vars, + "stage_name": stage_response.stage_name, + } + if stage_response.response is None: + return base_dict + elif isinstance(stage_response.response, requests.Response): + rest_response = stage_response.response + content_type = rest_response.headers.get("Content-Type", "") + + # Try to parse JSON body, fall back to raw content + if "application/json" in content_type: + body = rest_response.json() + else: + body = rest_response.content + + base_dict.update( + { + "status_code": rest_response.status_code, + "body": body, + "headers": rest_response.headers, + "cookies": rest_response.cookies, + } + ) + return base_dict + + raise NotImplementedError( + f"gRPC, MQTT, etc. are not supported yet. Got {type(stage_response.response)}" + ) + + def _setup_builtins(self, module: "starlark.Module") -> None: + """Set up built-in functions available in starlark scripts. + + Only a basic subset of types can be passed into starlark (anything that can be dumped to json). + To create a simple wrapper script, define the function in the _STARLARK_BUILTINS string. + + def add(a, b): + return a + b + + This can then be used easily from a 'control_flow' script: + + load("@tavern_helpers.star", "add") + + result = add(1, 2) + log(result) # logs '3' + + To create a more advanced wrapper, such as a 'library' module: + + 1. create the basic wrapper functions and a global 'struct' in the _STARLARK_BUILTINS string. + + def _re_match(pattern, s): + return __re_match(pattern, s) + + def _re_sub(pattern, repl, s): + return __re_sub(pattern, repl, s) + + re = struct(match=_re_match, sub=_re_sub) + + 2. Add a wrapper function into this function and add it with module.add_callable. + dunder names are used to 'hide' the original function from the user. + + @_wrap_callable + def re_match(pattern, s): + return re.match(pattern, s) + + @_wrap_callable + def re_sub(pattern, repl, s): + return re.sub(pattern, repl, s) + + module.add_callable("__re_match", re_match) + module.add_callable("__re_sub", re_sub) + + 3. Use from starlark by loading as before: + + load("@tavern_helpers.star", "re") + + resp = run_stage("my_stage") + + if not re.match("(one_thing|another_thing)", resp.json["key"]): + fail("No match found") + """ + for stage_id, stage in self._stage_registry.get_all_stages().items(): + module[stage_id] = to_starlark(stage) + + @_wrap_callable + def run_stage_binding( + stage_id: str, continue_on_fail: bool, extra_vars: dict | None + ) -> Any: + stage = self._stage_registry.get_stage(stage_id) + if stage is None: + raise exceptions.StarlarkError( + f"Stage with id '{stage_id}' not found (had {list(self._stage_registry.get_all_stages().keys())}" + ) + + stage_response = self._run_stage(stage, continue_on_fail, extra_vars) + try: + return self._create_response_struct(stage_response) + except Exception as e: + logger.exception("Failed to convert stage response to struct") + self._python_error = e + self._python_error.stage = stage # type:ignore + raise exceptions.StarlarkError( + "Failed to convert stage response to struct" + ) from e + + module.add_callable("__run_stage", run_stage_binding) + + @_wrap_callable + def log(s: str) -> None: + """log a string to stdout.""" + logger.info(s) + + module.add_callable("log", log) + + @_wrap_callable + def re_match(pattern: str, string: str | bytes) -> dict | None: + if isinstance(string, bytes): + string = string.decode("utf-8") + result = re.match(pattern, string) + if result is None: + return None + return { + "group0": result.group(0), + "groups": list(result.groups()), + "start": result.start(), + "end": result.end(), + } + + module.add_callable("__re_match", re_match) + + @_wrap_callable + def re_search(pattern: str, string: str | bytes) -> dict | None: + if isinstance(string, bytes): + string = string.decode("utf-8") + result = re.search(pattern, string) + if result is None: + return None + return { + "group0": result.group(0), + "groups": list(result.groups()), + "start": result.start(), + "end": result.end(), + } + + module.add_callable("__re_search", re_search) + + @_wrap_callable + def re_sub(pattern: str, repl: str, string: str | bytes) -> str: + if isinstance(string, bytes): + return re.sub(pattern, repl, string.decode("utf-8")) + return re.sub(pattern, repl, string) + + module.add_callable("__re_sub", re_sub) + + @_wrap_callable + def time_sleep(seconds: float) -> None: + time.sleep(seconds) + + module.add_callable("__time_sleep", time_sleep) diff --git a/tavern/_core/starlark/tavern_helpers.star b/tavern/_core/starlark/tavern_helpers.star new file mode 100644 index 000000000..effb1137e --- /dev/null +++ b/tavern/_core/starlark/tavern_helpers.star @@ -0,0 +1,155 @@ +"""Tavern helper functions for Starlark scripts. + +This module provides built-in functions for controlling test execution +in Tavern's Starlark pipeline feature. + +Usage: + load("@tavern_helpers.star", "run_stage", "re", "time", "log") +""" + + +def run_stage(name, *, continue_on_fail=False, extra_vars=None): + """Execute a test stage by its ID and return the response. + + Args: + name: Stage ID to execute (must have 'id' key in YAML) + continue_on_fail: If True, return failed response instead of raising + an exception. Default: False + extra_vars: Optional dict of variables to merge into stage config + + Returns: + A struct with properties: + - failed (bool): True if stage failed + - success (bool): True if stage succeeded + - request_vars: Variables captured during request execution + - stage_name: Name of the executed stage + + For HTTP responses, also includes: + - body: Response body (parsed JSON if Content-Type is application/json) + - status_code: HTTP status code + - headers: Response headers + - cookies: Response cookies + + Example: + # Run a stage by ID + resp = run_stage("get_cookie") + if resp.failed: + fail("Stage failed") + + # Continue on failure + resp = run_stage("try_login", continue_on_fail=True) + if resp.failed: + log("Login failed, using fallback") + run_stage("fallback_login") + """ + resp = __run_stage(name, continue_on_fail, extra_vars) + return struct(**resp) + + +def _re_match(pattern, s): + """Match a regex pattern at the beginning of string. + + Args: + pattern: Regular expression pattern + s: String to match against + + Returns: + A struct with match details, or None if no match: + - group0: Full match (group 0) + - groups: List of captured groups + - start: Start position of match + - end: End position of match + """ + m = __re_match(pattern, s) + if m == None: + return None + return struct(group0=m["group0"], groups=m["groups"], start=m["start"], end=m["end"]) + + +def _re_search(pattern, s): + """Search for a regex pattern anywhere in string. + + Args: + pattern: Regular expression pattern + s: String to search in + + Returns: + A struct with match details, or None if no match: + - group0: Full match (group 0) + - groups: List of captured groups + - start: Start position of match + - end: End position of match + """ + m = __re_search(pattern, s) + if m == None: + return None + return struct(group0=m["group0"], groups=m["groups"], start=m["start"], end=m["end"]) + + +def _re_sub(pattern, repl, s): + """Substitute occurrences of pattern in string. + + Args: + pattern: Regular expression pattern + repl: Replacement string + s: String to process + + Returns: + String with all occurrences replaced + """ + return __re_sub(pattern, repl, s) + + +re = struct(match=_re_match, search=_re_search, sub=_re_sub) +"""Regex utilities for pattern matching and text manipulation. + +Provides Python regex-style operations for use in Starlark scripts. + +Available methods: + match(pattern, string): Match pattern at start of string + search(pattern, string): Search for pattern anywhere in string + sub(pattern, repl, string): Replace all pattern occurrences + +Example: + load("@tavern_helpers.star", "re") + + resp = run_stage("get_data") + + # Extract version number + match = re.search("v(\\d+)\\.", resp.body) + if match == None: + fail("Version not found") + version = match.groups[0] + + # Replace values + new_url = re.sub("OLD", "NEW", original_url) +""" + + +def _time_sleep(seconds): + """Sleep for specified seconds. + + Args: + seconds: Number of seconds to sleep (can be float) + + Example: + time.sleep(0.5) # Sleep for 500ms + """ + __time_sleep(seconds) + + +time = struct(sleep=_time_sleep) +"""Time utilities for delays and timing operations. + +Available methods: + sleep(seconds): Pause execution for given seconds + +Example: + load("@tavern_helpers.star", "time") + + for i in range(0, 3): + resp = run_stage("poll", continue_on_fail=True) + if not resp.failed: + break + time.sleep(1) # Wait 1 second before retry +""" \ No newline at end of file diff --git a/tavern/_core/starlark/types.py b/tavern/_core/starlark/types.py new file mode 100644 index 000000000..e30573581 --- /dev/null +++ b/tavern/_core/starlark/types.py @@ -0,0 +1,61 @@ +import dataclasses +import logging +from typing import Any, Protocol, runtime_checkable + +import starlark + +_STARLARK_PRIMITIVES = (str, int, float, bool, type(None)) + +logger = logging.getLogger(__name__) + + +@runtime_checkable +class StarlarkConvertible(Protocol): + """Protocol for objects that know how to convert themselves to Starlark. + + Implemented by anything which requires special handling of values + """ + + def to_starlark(self) -> Any: + """Convert this object to a Starlark-safe value.""" + raise NotImplementedError + + +def to_starlark(obj: Any) -> Any: + """Recursively convert an arbitrary Python object to a Starlark-safe value. + + If it's a StarlarkConvertible then use the specific implementation. + + Primitives, dicts (with string keys) and lists are kept as-is (recursed). + Everything else is wrapped in an ``OpaquePythonObject`` so it can be passed + through Starlark without triggering JSON serialisation. + """ + if isinstance(obj, StarlarkConvertible): + return obj.to_starlark() + if isinstance(obj, _STARLARK_PRIMITIVES): + return obj + if isinstance(obj, starlark.OpaquePythonObject): + return obj # already wrapped + if dataclasses.is_dataclass(obj): + return to_starlark(dataclasses.asdict(obj)) # type:ignore + if isinstance(obj, dict): + return {k: to_starlark(v) for k, v in obj.items()} + if isinstance(obj, list | tuple): + return [to_starlark(item) for item in obj] + return starlark.OpaquePythonObject(obj) + + +def from_starlark(obj: Any) -> Any: + """Recursively convert a Starlark value back to a plain Python object. + + Primitives, dicts and lists are recursed into; anything else is returned + as-is. Values wrapped in an ``OpaquePythonObject`` on the way in are already + unwrapped by the time they arrive back here, so there is nothing to undo. + """ + if isinstance(obj, _STARLARK_PRIMITIVES): + return obj + if isinstance(obj, dict): + return {k: from_starlark(v) for k, v in obj.items()} + if isinstance(obj, list | tuple): + return [from_starlark(item) for item in obj] + return obj diff --git a/tavern/_plugins/rest/request.py b/tavern/_plugins/rest/request.py index 65607b83b..e31a30a11 100644 --- a/tavern/_plugins/rest/request.py +++ b/tavern/_plugins/rest/request.py @@ -177,6 +177,8 @@ def get_header(name): ) elif isinstance(inferred_content_encoding, dict): fspec["headers"].update(inferred_content_encoding) + else: + fspec["headers"]["content-encoding"] = inferred_content_encoding else: logger.debug( "No encoding inferred from file_body for %s", diff --git a/tests/integration/server.py b/tests/integration/server.py index af210c786..315876098 100644 --- a/tests/integration/server.py +++ b/tests/integration/server.py @@ -1,10 +1,12 @@ import base64 +import contextlib import gzip import itertools import json import math import mimetypes import os +import sqlite3 import time import uuid from datetime import datetime, timedelta @@ -13,13 +15,37 @@ import jwt from box import Box -from flask import Flask, Response, jsonify, make_response, redirect, request, session +from flask import Flask, Response, g, jsonify, make_response, redirect, request, session from flask_httpauth import HTTPDigestAuth from itsdangerous import URLSafeTimedSerializer app = Flask(__name__) app.config.update(SECRET_KEY="secret") +DATABASE = "/tmp/tavern_test.db" + + +def get_db(): + db = getattr(g, "_database", None) + if db is None: + db = g._database = sqlite3.connect(DATABASE) + + with db: + with contextlib.suppress(Exception): + db.execute( + "CREATE TABLE entities (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL)" + ) + + return db + + +@app.teardown_appcontext +def close_connection(exception): + db = getattr(g, "_database", None) + if db is not None: + db.close() + + digest_auth = HTTPDigestAuth() @@ -575,3 +601,87 @@ def ascii_table(): def expected_text(): """Echoes back plain text from request body""" return Response(request.get_data(as_text=True), content_type="text/plain") + + +@app.route("/regex_data", methods=["GET"]) +def get_regex_data(): + """Endpoint for testing regex extraction in Starlark. + + Returns data with patterns for regex testing: + - version: version string like "v1.2.3" + - token: alphanumeric token with prefix + - status: status string with code + """ + return ( + """ + server version: v2.5.1 + server status: Server-PROD-01 is running + token "TKN-a1b2c3d4e5f6" + """, + 200, + ) + + +@app.route("/verify_extracted", methods=["POST"]) +def verify_extracted(): + """Verify that extracted values were passed correctly. + + Expects JSON body with: + - major_version: extracted major version number + - token_id: extracted token ID part + - server_name: extracted server name from message + """ + body = request.get_json() + + major = body.get("major_version") + token_id = body.get("token_id") + server_name = body.get("server_name") + + errors = [] + + if major != "2": + errors.append(f"major_version expected '2', got '{major}'") + + if token_id != "a1b2c3d4e5f6": + errors.append(f"token_id expected 'a1b2c3d4e5f6', got '{token_id}'") + + if server_name != "PROD-01": + errors.append(f"server_name expected 'PROD-01', got '{server_name}'") + + if errors: + return jsonify({"errors": errors}), 400 + + return jsonify({"status": "verified"}), 200 + + +@app.route("/entities", methods=["POST"]) +def create_entity(): + body = request.get_json() + name = body.get("name") + + db = get_db() + cursor = db.execute("INSERT INTO entities (name) VALUES (?)", (name,)) + db.commit() + + return jsonify({"id": cursor.lastrowid}), 201 + + +@app.route("/entities/", methods=["DELETE"]) +def delete_entity(entity_id): + db = get_db() + db.execute("DELETE FROM entities WHERE id = ?", (entity_id,)) + db.commit() + + return "", 204 + + +@app.route("/entities/", methods=["GET"]) +def get_entity(entity_id): + db = get_db() + cursor = db.execute("SELECT id FROM entities WHERE id = ?", (entity_id,)) + row = cursor.fetchone() + + if row is None: + return "", 404 + + return "", 200 diff --git a/tests/integration/starlark/README.md b/tests/integration/starlark/README.md new file mode 100644 index 000000000..b58119479 --- /dev/null +++ b/tests/integration/starlark/README.md @@ -0,0 +1,37 @@ +# Starlark Pipeline Integration Tests + +This folder contains integration tests for the scriptable pipelines feature using Starlark. + +## Prerequisites + +1. The test server must be running (see `tests/integration/server.py`) +2. Docker must be available (integration tests run in containers) + +## Running the Tests + +To run the starlark integration tests, you need to enable the experimental flag: + +```bash +# Start the test server (from tavern root directory) +cd tests/integration && docker-compose up -d server + +# Run the starlark tests with the experimental flag +tox -q -c tox-integration.ini -e py312 -- --tavern-experimental-starlark-pipeline tests/integration/starlark/ +``` + +Or using pytest directly: + +```bash +# Starting server (from tavern root directory) +docker-compose -f tests/integration/docker-compose.yml up -d server + +# Run tests (from tavern root directory) +pytest --tavern-experimental-starlark-pipeline tests/integration/starlark/ -v + +# Cleanup +docker-compose -f tests/integration/docker-compose.yml down +``` + +## Test Files + +- `test_control_flow_inline.tavern.yaml` - Basic pipeline test diff --git a/tests/integration/starlark/badsyntax.star b/tests/integration/starlark/badsyntax.star new file mode 100644 index 000000000..5499d5801 --- /dev/null +++ b/tests/integration/starlark/badsyntax.star @@ -0,0 +1 @@ +f ff f : : : @@\\'\'\'' \ No newline at end of file diff --git a/tests/integration/starlark/correct.star b/tests/integration/starlark/correct.star new file mode 100644 index 000000000..02b04ab6c --- /dev/null +++ b/tests/integration/starlark/correct.star @@ -0,0 +1,2 @@ +load("@tavern_helpers.star", "run_stage") +run_stage("finally-nothing-check") \ No newline at end of file diff --git a/tests/integration/starlark/fail.star b/tests/integration/starlark/fail.star new file mode 100644 index 000000000..6ee7c74b4 --- /dev/null +++ b/tests/integration/starlark/fail.star @@ -0,0 +1 @@ +fail("ohnoes") \ No newline at end of file diff --git a/tests/integration/starlark/nothingrun.star b/tests/integration/starlark/nothingrun.star new file mode 100644 index 000000000..58641b93d --- /dev/null +++ b/tests/integration/starlark/nothingrun.star @@ -0,0 +1,2 @@ +def f(x): + return x + 1 \ No newline at end of file diff --git a/tests/integration/starlark/stages.yaml b/tests/integration/starlark/stages.yaml new file mode 100644 index 000000000..630804694 --- /dev/null +++ b/tests/integration/starlark/stages.yaml @@ -0,0 +1,27 @@ +--- +# Sample stages file for starlark pipeline tests + +stages: + - id: get-cookie-included + name: Get tavern-cookie-1 + request: + url: "{global_host}/get_cookie" + method: POST + json: + cookie_name: tavern-cookie-1 + response: + status_code: 200 + cookies: + - tavern-cookie-1 + + - id: echo-value-included + name: Echo a value back + request: + url: "{global_host}/echo" + method: POST + json: + value: "123" + response: + status_code: 200 + json: + value: "123" diff --git a/tests/integration/starlark/test_control_flow_inline.tavern.yaml b/tests/integration/starlark/test_control_flow_inline.tavern.yaml new file mode 100644 index 000000000..8a74f426f --- /dev/null +++ b/tests/integration/starlark/test_control_flow_inline.tavern.yaml @@ -0,0 +1,259 @@ +is_defaults: True +marks: + - starlark_control_flow + +--- +# Test for inline control_flow Starlark script +# This test demonstrates the new YAML+control_flow format + +test_name: Test control_flow with inline Starlark - basic sequential + +stages: + - name: Get cookie + id: get_cookie + request: + url: "{global_host}/get_cookie" + method: POST + json: + cookie_name: test-cookie + response: + status_code: 200 + cookies: + - test-cookie + + - name: Echo a value + id: echo_value + request: + url: "{global_host}/echo" + method: POST + json: + value: "hello" + response: + status_code: 200 + json: + value: "hello" + +# Inline Starlark script that controls execution order +control_flow: | + # Load the stage runner + load("@tavern_helpers.star", "run_stage") + + # First run the get_cookie stage + resp = run_stage("get_cookie") + + # Then run the echo_value stage + resp = run_stage("echo_value") + + # success + if resp.failed: + fail("echoing did not succeed") + +--- +test_name: Test control_flow with inline Starlark - included stages + +includes: + # Contains stages with an 'id' + - !include stages.yaml + +# Inline Starlark script that controls execution order +control_flow: | + # Load the stage runner + load("@tavern_helpers.star", "run_stage") + + # First run the get_cookie stage + resp = run_stage("get-cookie-included") + + # Then run the echo_value stage + resp = run_stage("echo-value-included") + + # success + if resp.failed: + fail("echoing did not succeed") + +--- +test_name: Test control_flow with inline Starlark - globally included stages + +# Inline Starlark script that controls execution order +control_flow: | + # Load the stage runner + load("@tavern_helpers.star", "run_stage") + + # run a stage defined in the global_cfg.yaml file + run_stage("finally-nothing-check") + +--- +test_name: Test regex functions with inline Starlark + +stages: + - name: Get regex test data + id: get_regex_data + request: + url: "{global_host}/regex_data" + method: GET + response: + status_code: 200 + # Don't check the exact value of the response body, just that it matches the regex below + + - name: Verify extracted values + id: verify_extracted + request: + url: "{global_host}/verify_extracted" + method: POST + json: + # These variables will be extracted from the response body using regex + major_version: "{major_version}" + token_id: "{token_id}" + server_name: "{server_name}" + response: + status_code: 200 + json: + status: "verified" + +control_flow: | + load("@tavern_helpers.star", "run_stage", "re") + + # Get regex test data + resp = run_stage("get_regex_data") + if resp.failed: + fail("get_regex_data stage failed") + + # Extract version number using regex: v2.5.1 -> capture major version + version_match = re.search("v(\\d+)\\.", resp.body) + if version_match == None: + fail("Failed to match version pattern") + major_version = version_match.groups[0] + + # Extract token ID: TKN-a1b2c3d4e5f6 -> capture the ID part + token_match = re.search("\"TKN-(.+)\"", resp.body) + if token_match == None: + fail("Failed to match token pattern TKN-+ from " + resp.body) + token_id = token_match.groups[0] + + # Extract server name from message: Server-PROD-01 -> capture PROD-01 + server_match = re.search("Server-(\\w+-\\w+)\\s", resp.body) + if server_match == None: + fail("Failed to match server pattern") + server_name = server_match.groups[0] + + # Use extracted values in next stage via extra_vars + resp = run_stage("verify_extracted", extra_vars={ + "major_version": major_version, + "token_id": token_id, + "server_name": server_name + }) + if resp.failed: + fail("verify_extracted stage failed") + +--- +test_name: test for loop with retry + +stages: + - name: polling + id: polling + request: + url: "{global_host}/poll" + method: GET + response: + status_code: 200 + json: + status: ready + +control_flow: | + load("@tavern_helpers.star", "run_stage") + + succeeded = False + for i in range(0, 3): + resp = run_stage("polling", continue_on_fail=True) + if not resp.failed: + succeeded = True + break + log("polling stage failed") + + if not succeeded: + fail("polling stage failed after 3 attempts") + +--- +test_name: Test including a generic script + +control_flow: !include correct.star + +--- +test_name: Test including a empty script (or one which runs no stages) fails + +_xfail: run + +control_flow: !include nothingrun.star + +--- +test_name: Test including a script that will fail at runtime + +_xfail: run + +control_flow: !include fail.star + +--- +test_name: Test including YAML + +_xfail: verify + +control_flow: !include stages.yaml + +--- +test_name: Test including a completely invalid script + +_xfail: verify + +control_flow: !include badsyntax.star + +--- +test_name: Test for loop with entities - create and delete multiple + +stages: + - name: Create entities stage + id: create_entities + request: + url: "{global_host}/entities" + method: POST + json: + name: "entity-{index}" + response: + status_code: 201 + json: + id: !anyint + + - name: Delete entity by id + id: delete_entity + request: + url: "{global_host}/entities/{entity_id}" + method: DELETE + response: + status_code: 204 + + - name: Verify entity deleted + id: verify_deleted + request: + url: "{global_host}/entities/{entity_id}" + method: GET + response: + status_code: 404 + +control_flow: | + load("@tavern_helpers.star", "run_stage") + + # Create 3 entities using a for loop + entity_ids = [] + for i in range(3): + resp = run_stage("create_entities", extra_vars={"index": str(i)}) + # Extract the id from the response + entity_ids.append(resp.body["id"]) + log("Created entity with id: " + str(resp.body["id"])) + + # Delete all entities using a for loop + for entity_id in entity_ids: + resp = run_stage("delete_entity", extra_vars={"entity_id": str(entity_id)}) + log("Deleted entity with id: " + str(entity_id)) + + # Verify all entities are deleted using a for loop + for entity_id in entity_ids: + resp = run_stage("verify_deleted", extra_vars={"entity_id": str(entity_id)}) + log("Verified entity " + str(entity_id) + " is deleted") diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py index 57ab621d4..596906ad2 100644 --- a/tests/unit/conftest.py +++ b/tests/unit/conftest.py @@ -22,6 +22,7 @@ ), follow_redirects=False, stages=[], + experimental_starlark_pipeline=False, ) diff --git a/tests/unit/plugins/graphql/conftest.py b/tests/unit/plugins/graphql/conftest.py index 073e5c3ea..1344c0992 100644 --- a/tests/unit/plugins/graphql/conftest.py +++ b/tests/unit/plugins/graphql/conftest.py @@ -16,6 +16,7 @@ ), follow_redirects=False, stages=[], + experimental_starlark_pipeline=False, ) diff --git a/tests/unit/starlark/conftest.py b/tests/unit/starlark/conftest.py new file mode 100644 index 000000000..c054f41ac --- /dev/null +++ b/tests/unit/starlark/conftest.py @@ -0,0 +1,43 @@ +from unittest.mock import Mock + +import pytest +import requests + +from tavern._core.pytest.config import TavernInternalConfig, TestConfig +from tavern._core.starlark.starlark_env import StarlarkPipelineRunner +from tavern._core.strict_util import StrictLevel + + +@pytest.fixture +def fix_test_config(): + """Create a real TestConfig object for starlark tests.""" + config = TestConfig( + variables={"base_url": "http://test.example.com", "tavern": Mock()}, + strict=StrictLevel.all_on(), + follow_redirects=False, + stages=[], + tavern_internal=TavernInternalConfig(pytest_hook_caller=Mock(), backends={}), + experimental_starlark_pipeline=True, + ) + return config + + +@pytest.fixture +def mock_response(): + response = Mock(spec=requests.Response) + response.status_code = 200 + response.headers = {"Content-Type": "application/json"} + response.json.return_value = {"result": "success"} + response.cookies = {} + response.content = b'{"result": "success"}' + return response + + +@pytest.fixture +def basic_runner(fix_test_config): + return StarlarkPipelineRunner( + test_path="/test/path.tavern.star", + stages=[], + test_config=fix_test_config, + sessions={}, + ) diff --git a/tests/unit/starlark/test_regex.py b/tests/unit/starlark/test_regex.py new file mode 100644 index 000000000..2fc86706c --- /dev/null +++ b/tests/unit/starlark/test_regex.py @@ -0,0 +1,194 @@ +"""Unit tests for regex functions in the Starlark environment. + +These tests verify the re.match and re.sub implementations for Starlark, +including match result struct behavior and substitution patterns. +""" + + +class TestReMatch: + """Tests for the re.match function.""" + + def test_match_success_returns_struct(self, basic_runner): + """Test that successful match returns a struct with groups.""" + + script = r""" +load("@tavern_helpers.star", "re") +result = re.match("hello (\\w+)", "hello world") +""" + basic_runner.load_and_run(script) + # Script should execute without errors + + def test_match_returns_none_on_failure(self, basic_runner): + """Test that failed match returns None.""" + + script = """ +load("@tavern_helpers.star", "re") +result = re.match("goodbye", "hello world") +if result != None: + fail("Expected None for failed match") +""" + basic_runner.load_and_run(script) + + def test_match_with_capture_groups(self, basic_runner): + """Test that capture groups are accessible in result.""" + + script = r""" +load("@tavern_helpers.star", "re") +result = re.match("(\\w+)@(\\w+)", "user@domain") +if result == None: + fail("Match should succeed") +if result.group0 != "user@domain": + fail("group0 should be full match") +if result.groups[0] != "user": + fail("First group should be 'user'") +if result.groups[1] != "domain": + fail("Second group should be 'domain'") +""" + basic_runner.load_and_run(script) + + def test_match_start_end_positions(self, basic_runner): + """Test that start and end positions are correct.""" + + script = r""" +load("@tavern_helpers.star", "re") +result = re.match("\\d+", "12345abc") +if result == None: + fail("Match should succeed") +if result.start != 0: + fail("Start should be 0") +if result.end != 5: + fail("End should be 5") +""" + basic_runner.load_and_run(script) + + def test_match_only_at_string_start(self, basic_runner): + """Test that match only works at beginning of string (Python re.match behavior).""" + + script = """ +load("@tavern_helpers.star", "re") +# match() only matches at the start, not in the middle +result = re.match("world", "hello world") +if result != None: + fail("match should return None when pattern not at start") +""" + basic_runner.load_and_run(script) + + def test_match_with_empty_string(self, basic_runner): + """Test match with empty string.""" + + script = """ +load("@tavern_helpers.star", "re") +result = re.match(".*", "") +if result == None: + fail("Empty pattern should match empty string") +""" + basic_runner.load_and_run(script) + + +class TestReSub: + """Tests for the re.sub function.""" + + def test_basic_substitution(self, basic_runner): + """Test basic string substitution.""" + + script = """ +load("@tavern_helpers.star", "re") +result = re.sub("world", "universe", "hello world") +if result != "hello universe": + fail("Substitution failed") +""" + basic_runner.load_and_run(script) + + def test_global_replacement(self, basic_runner): + """Test that all occurrences are replaced by default.""" + + script = """ +load("@tavern_helpers.star", "re") +result = re.sub("a", "b", "aaa aaa") +if result != "bbb bbb": + fail("All occurrences should be replaced") +""" + basic_runner.load_and_run(script) + + def test_no_match_returns_unchanged_string(self, basic_runner): + """Test that no match returns the original string.""" + + script = """ +load("@tavern_helpers.star", "re") +result = re.sub("xyz", "abc", "hello world") +if result != "hello world": + fail("String should be unchanged when no match") +""" + basic_runner.load_and_run(script) + + def test_substitution_with_capture_groups(self, basic_runner): + """Test substitution preserves captured content via backreferences.""" + script = r""" +load("@tavern_helpers.star", "re") +result = re.sub("(\\w+)-(\\w+)", "\\2-\\1", "hello-world") +if result != "world-hello": + fail("Capture swap failed - got: " + result) +""" + basic_runner.load_and_run(script) + + def test_substitution_with_digit_pattern(self, basic_runner): + """Test substitution with digit patterns.""" + + script = r""" +load("@tavern_helpers.star", "re") +result = re.sub("\\d+", "NUM", "user123@example456") +if result != "userNUM@exampleNUM": + fail("Digit substitution failed") +""" + basic_runner.load_and_run(script) + + def test_substitution_empty_string(self, basic_runner): + """Test substitution with empty pattern.""" + + script = """ +load("@tavern_helpers.star", "re") +# Empty pattern matches between every character, inserting replacement +result = re.sub("", "-", "abc") +# This results in "-a-b-c-" in Python +if result != "-a-b-c-": + fail("Empty pattern substitution failed") +""" + basic_runner.load_and_run(script) + + def test_match_result_is_truthy(self, basic_runner): + """Test that successful match result is truthy.""" + + script = """ +load("@tavern_helpers.star", "re") +result = re.match("hello", "hello world") +if not result: + fail("Match result should be truthy") +""" + basic_runner.load_and_run(script) + + def test_no_match_result_is_falsy(self, basic_runner): + """Test that failed match result is falsy (None).""" + + script = """ +load("@tavern_helpers.star", "re") +result = re.match("xyz", "hello world") +if result: + fail("Failed match should be falsy") +""" + basic_runner.load_and_run(script) + + def test_match_in_conditional(self, basic_runner): + """Test using match result in conditional expressions.""" + + script = """ +load("@tavern_helpers.star", "re") +# Use match result in conditional +if re.match("hello", "hello world"): + pass # Match succeeded +else: + fail("Match should have succeeded") + +if re.match("xyz", "hello world"): + fail("Match should have failed") +""" + basic_runner.load_and_run(script) diff --git a/tests/unit/starlark/test_response_struct.py b/tests/unit/starlark/test_response_struct.py new file mode 100644 index 000000000..20b3e41df --- /dev/null +++ b/tests/unit/starlark/test_response_struct.py @@ -0,0 +1,119 @@ +from tavern._core.starlark.starlark_env import StageResponse + + +class TestStageResponseStruct: + def test_stage_response_has_status_code_in_response(self): + response = StageResponse( + success=True, + response={"status_code": 200, "body": {"foo": "bar"}}, + request_vars={}, + stage_name="test_stage", + ) + starlark_obj = response.to_starlark() + assert "status_code" in starlark_obj["response"] + + def test_stage_response_has_failed_not_in_response(self): + response = StageResponse( + success=False, + response={"status_code": 500, "error": "server error"}, + request_vars={}, + stage_name="test_stage", + ) + starlark_obj = response.to_starlark() + assert "failed" not in starlark_obj["response"] + + def test_stage_response_has_success_field(self): + response = StageResponse( + success=True, + response={"status_code": 200}, + request_vars={}, + stage_name="test_stage", + ) + starlark_obj = response.to_starlark() + assert "success" in starlark_obj + + def test_stage_response_has_body_in_response(self): + response = StageResponse( + success=True, + response={"status_code": 200, "body": {"data": "test"}}, + request_vars={}, + stage_name="test_stage", + ) + starlark_obj = response.to_starlark() + assert "response" in starlark_obj + + def test_stage_response_has_request_vars(self): + response = StageResponse( + success=True, + response={"status_code": 200}, + request_vars={"var": "value"}, + stage_name="test_stage", + ) + starlark_obj = response.to_starlark() + assert "request_vars" in starlark_obj + + +class TestCreateResponseStruct: + def test_create_response_dict_has_status_code(self, basic_runner, mock_response): + response = StageResponse( + success=True, + response=mock_response, + request_vars={}, + stage_name="test_stage", + ) + result = basic_runner._create_response_struct(response) + assert "status_code" in result + assert result["status_code"] == 200 + + def test_create_response_dict_has_failed(self, basic_runner): + response = StageResponse( + success=False, + response=None, + request_vars={}, + stage_name="test_stage", + ) + result = basic_runner._create_response_struct(response) + assert "failed" in result + assert result["failed"] is True + + def test_create_response_dict_has_success(self, basic_runner): + response = StageResponse( + success=True, + response=None, + request_vars={}, + stage_name="test_stage", + ) + result = basic_runner._create_response_struct(response) + assert "success" in result + assert result["success"] is True + + def test_create_response_dict_has_body(self, basic_runner, mock_response): + response = StageResponse( + success=True, + response=mock_response, + request_vars={}, + stage_name="test_stage", + ) + result = basic_runner._create_response_struct(response) + assert "body" in result + + def test_create_response_dict_has_request_vars(self, basic_runner): + response = StageResponse( + success=True, + response=None, + request_vars={"token": "abc"}, + stage_name="test_stage", + ) + result = basic_runner._create_response_struct(response) + assert "request_vars" in result + + def test_create_response_dict_has_stage_name(self, basic_runner): + response = StageResponse( + success=True, + response=None, + request_vars={}, + stage_name="my_stage", + ) + result = basic_runner._create_response_struct(response) + assert "stage_name" in result + assert result["stage_name"] == "my_stage" diff --git a/tests/unit/starlark/test_stage_registry.py b/tests/unit/starlark/test_stage_registry.py new file mode 100644 index 000000000..322ae8923 --- /dev/null +++ b/tests/unit/starlark/test_stage_registry.py @@ -0,0 +1,55 @@ +from tavern._core.starlark.stage_registry import StageRegistry + + +class TestStageRegistry: + def test_registry_builds_id_to_stage_map(self): + stages = [ + { + "id": "stage1", + "name": "Stage 1", + "request": {"url": "http://example.com"}, + }, + { + "id": "stage2", + "name": "Stage 2", + "request": {"url": "http://example.com"}, + }, + ] + registry = StageRegistry(stages) + assert registry.get_stage("stage1") is not None + assert registry.get_stage("stage2") is not None + + def test_registry_ignores_stages_without_id(self): + stages = [ + {"name": "Stage without ID", "request": {"url": "http://example.com"}}, + { + "id": "stage_with_id", + "name": "Stage with ID", + "request": {"url": "http://example.com"}, + }, + ] + registry = StageRegistry(stages) + assert registry.get_stage("stage_with_id") is not None + assert registry.get_stage("Stage without ID") is None + + def test_registry_returns_none_for_nonexistent_id(self): + stages = [ + { + "id": "stage1", + "name": "Stage 1", + "request": {"url": "http://example.com"}, + }, + ] + registry = StageRegistry(stages) + assert registry.get_stage("nonexistent") is None + + def test_get_all_stages_returns_dict(self): + stages = [ + {"id": "stage1", "name": "Stage 1"}, + {"id": "stage2", "name": "Stage 2"}, + ] + registry = StageRegistry(stages) + all_stages = registry.get_all_stages() + assert isinstance(all_stages, dict) + assert "stage1" in all_stages + assert "stage2" in all_stages diff --git a/tests/unit/starlark/test_starlark_env.py b/tests/unit/starlark/test_starlark_env.py new file mode 100644 index 000000000..f18bf4a8e --- /dev/null +++ b/tests/unit/starlark/test_starlark_env.py @@ -0,0 +1,570 @@ +"""Unit tests for the starlark_env module. + +These tests verify the StarlarkPipelineRunner and related functionality, +including run_stage behavior and extra_vars formatting. +""" + +from unittest.mock import Mock, patch + +import pytest +import requests +import starlark + +from tavern._core import exceptions +from tavern._core.run import _TestRunner +from tavern._core.starlark.stage_registry import StageRegistry +from tavern._core.starlark.starlark_env import ( + StageResponse, + StarlarkPipelineRunner, + _wrap_callable, +) +from tavern._core.tincture import Tinctures + + +@pytest.fixture +def mock_test_runner(mock_response): + runner = Mock(spec=_TestRunner) + runner.wrapped_run_stage = Mock(return_value=mock_response) + return runner + + +@pytest.fixture +def sample_stage(): + return { + "id": "test_stage", + "name": "Test Stage", + "request": {"url": "http://test.example.com/api", "method": "GET"}, + "response": {"status_code": 200}, + } + + +@pytest.fixture +def sample_stages(): + return [ + { + "id": "get_cookie", + "name": "Get Cookie", + "request": {"url": "http://test.example.com/cookie", "method": "POST"}, + "response": {"status_code": 200}, + }, + { + "id": "echo_value", + "name": "Echo Value", + "request": {"url": "http://test.example.com/echo", "method": "POST"}, + "response": {"status_code": 201}, + }, + ] + + +class TestWrapCallable: + """Tests for the _wrap_callable decorator.""" + + def test_wrap_callable_converts_args_to_starlark(self): + """Test that _wrap_callable converts Python args to starlark format.""" + + @_wrap_callable + def add(a, b): + return a + b + + # The decorator wraps the function, converting arguments + result = add(1, 2) + assert result == 3 + + def test_wrap_callable_converts_kwargs_to_starlark(self): + """Test that _wrap_callable converts Python kwargs to starlark format.""" + + @_wrap_callable + def format_url(base, path=""): + return f"{base}{path}" + + result = format_url("http://example.com", path="/api") + assert result == "http://example.com/api" + + def test_wrap_callable_converts_return_to_starlark(self): + """Test that _wrap_callable converts return value to starlark format.""" + + @_wrap_callable + def get_dict(): + return {"key": "value"} + + result = get_dict() + assert result == {"key": "value"} + + def test_wrap_callable_converts_opaque_return_to_starlark(self): + """Test that _wrap_callable converts opaque return value to starlark format.""" + + class _boobllb: + pass + + @_wrap_callable + def get_dict(): + return _boobllb + + result = get_dict() + assert isinstance(result, starlark.OpaquePythonObject) + + +class TestStageResponseToStarlark: + """Tests for StageResponse.to_starlark method.""" + + def test_to_starlark_success_true(self): + """Test to_starlark with success=True.""" + response = StageResponse( + success=True, + response=None, + request_vars={"key": "value"}, + stage_name="test_stage", + ) + result = response.to_starlark() + assert result["success"] is True + assert result["request_vars"] == {"key": "value"} + assert result["stage_name"] == "test_stage" + + def test_to_starlark_success_false(self): + """Test to_starlark with success=False.""" + response = StageResponse( + success=False, + response=None, + request_vars={}, + stage_name="failed_stage", + ) + result = response.to_starlark() + assert result["success"] is False + + +class TestRunStageBinding: + """Tests for the run_stage function exposed to Starlark.""" + + def test_run_stage_binding_success( + self, + basic_runner, + sample_stage, + mock_test_runner, + ): + """Test that run_stage binding returns success response.""" + basic_runner._stage_registry = StageRegistry([sample_stage]) + tinctures = Tinctures([]) + + with ( + patch( + "tavern._core.starlark.starlark_env._TestRunner", + return_value=mock_test_runner, + ), + patch( + "tavern._core.starlark.starlark_env.get_stage_tinctures", + return_value=tinctures, + ), + ): + result = basic_runner._create_response_struct( + StageResponse( + success=True, + response=Mock( + spec=requests.Response, + status_code=200, + headers={}, + cookies={}, + ), + request_vars={}, + stage_name="test_stage", + ) + ) + + assert result["success"] is True + assert result["failed"] is False + + def test_run_stage_binding_stage_not_found( + self, + fix_test_config, + sample_stages, + ): + """Test that requesting nonexistent stage raises StarlarkError.""" + from tavern._core import exceptions + + runner = StarlarkPipelineRunner( + test_path="/test/path.tavern.star", + stages=[], # Empty registry - no stages + test_config=fix_test_config, + sessions={}, + ) + + script = """ +load("@tavern_helpers.star", "run_stage") +resp = run_stage("nonexistent_stage") +""" + + with pytest.raises(exceptions.StarlarkError): + runner.load_and_run(script) + + +class TestCreateResponseStruct: + """Tests for the _create_response_struct method.""" + + def test_create_response_struct_with_success(self, fix_test_config): + """Test response struct creation with successful response.""" + mock_response = Mock(spec=requests.Response) + mock_response.status_code = 200 + mock_response.headers = {"Content-Type": "application/json"} + mock_response.json.return_value = {"data": "test"} + mock_response.cookies = {} + + stage_response = StageResponse( + success=True, + response=mock_response, + request_vars={"key": "value"}, + stage_name="success_stage", + ) + + runner = StarlarkPipelineRunner( + test_path="/test/path.tavern.star", + stages=[], + test_config=fix_test_config, + sessions={}, + ) + + result = runner._create_response_struct(stage_response) + + assert result["success"] is True + assert result["failed"] is False + assert result["status_code"] == 200 + assert result["body"] == {"data": "test"} + assert result["request_vars"] == {"key": "value"} + assert result["stage_name"] == "success_stage" + # Verify json() was called + mock_response.json.assert_called_once() + + def test_create_response_struct_with_failure(self, fix_test_config): + """Test response struct creation with failed response.""" + stage_response = StageResponse( + success=False, + response=None, + request_vars={}, + stage_name="failed_stage", + ) + + runner = StarlarkPipelineRunner( + test_path="/test/path.tavern.star", + stages=[], + test_config=fix_test_config, + sessions={}, + ) + + result = runner._create_response_struct(stage_response) + + assert result["success"] is False + assert result["failed"] is True + assert "stage_name" in result + + def test_create_response_struct_none_response(self, fix_test_config): + """Test response struct creation when response is None.""" + stage_response = StageResponse( + success=True, + response=None, + request_vars={}, + stage_name="no_response_stage", + ) + + runner = StarlarkPipelineRunner( + test_path="/test/path.tavern.star", + stages=[], + test_config=fix_test_config, + sessions={}, + ) + + result = runner._create_response_struct(stage_response) + + assert result["success"] is True + assert result["failed"] is False + # When response is None, status_code should not be in result + assert "status_code" not in result + + def test_create_response_struct_unsupported_type_raises(self, fix_test_config): + """Test that unsupported response types raise NotImplementedError.""" + # Use an object that's not requests.Response + unsupported_response = {"type": "grpc"} + + stage_response = StageResponse( + success=True, + response=unsupported_response, + request_vars={}, + stage_name="grpc_stage", + ) + + runner = StarlarkPipelineRunner( + test_path="/test/path.tavern.star", + stages=[], + test_config=fix_test_config, + sessions={}, + ) + + with pytest.raises(NotImplementedError, match="gRPC, MQTT"): + runner._create_response_struct(stage_response) + + +class TestStarlarkExecution: + """Tests that execute actual Starlark scripts.""" + + def test_load_and_run_parses_valid_script(self, basic_runner): + """Test that a valid Starlark script parses without errors.""" + script = """ +def my_func(): + return 42 +""" + # Should not raise + basic_runner.load_and_run(script) + + def test_load_and_run_invalid_script_raises(self, basic_runner): + """Test that an invalid Starlark script raises ValueError.""" + script = """ +def broken_func( + # Missing closing parenthesis +""" + with pytest.raises(ValueError, match="Failed to parse starlark script"): + basic_runner.load_and_run(script) + + def test_log_function_executes( + self, + basic_runner, + caplog, + ): + """Test that log function writes to logger.""" + script = """ +load("@tavern_helpers.star", "log") +log("Hello from starlark") +""" + with caplog.at_level("INFO"): + basic_runner.load_and_run(script) + + assert "Hello from starlark" in caplog.text + + def test_time_sleep_function_executes(self, basic_runner): + """Test that time.sleep can be called from Starlark script.""" + import time + + script = """ +load("@tavern_helpers.star", "time") +time.sleep(0.01) +""" + start = time.monotonic() + basic_runner.load_and_run(script) + elapsed = time.monotonic() - start + + # Verify that sleep was actually called (should take at least 0.01s) + assert elapsed >= 0.01 + + def test_run_stage_in_script( + self, + fix_test_config, + sample_stages, + mock_test_runner, + ): + """Test that run_stage can be called from Starlark script.""" + runner = StarlarkPipelineRunner( + test_path="/test/path.tavern.star", + stages=sample_stages, + test_config=fix_test_config, + sessions={}, + ) + tinctures = Tinctures([]) + + script = """ +load("@tavern_helpers.star", "run_stage") +resp = run_stage("get_cookie") +""" + + with ( + patch( + "tavern._core.starlark.starlark_env._TestRunner", + return_value=mock_test_runner, + ), + patch( + "tavern._core.starlark.starlark_env.get_stage_tinctures", + return_value=tinctures, + ), + ): + runner.load_and_run(script) + + # Verify wrapped_run_stage was called + mock_test_runner.wrapped_run_stage.assert_called_once() + + def test_run_stage_tavern_exception_raises( + self, + fix_test_config, + sample_stage, + ): + """Test that TavernException is re-raised when continue_on_fail=False.""" + mock_runner = Mock() + exc = exceptions.TavernException("Stage failed") + exc.stage = sample_stage + mock_runner.wrapped_run_stage = Mock(side_effect=exc) + tinctures = Tinctures([]) + + runner = StarlarkPipelineRunner( + test_path="/test/path.tavern.star", + stages=[sample_stage], + test_config=fix_test_config, + sessions={}, + ) + + with ( + patch( + "tavern._core.starlark.starlark_env._TestRunner", + return_value=mock_runner, + ), + patch( + "tavern._core.starlark.starlark_env.get_stage_tinctures", + return_value=tinctures, + ), + ): + with pytest.raises(exceptions.TavernException): + runner._run_stage(sample_stage, continue_on_fail=False) + + def test_run_stage_tavern_exception_returns_failed( + self, + fix_test_config, + sample_stage, + ): + """Test that TavernException returns failed response when continue_on_fail=True.""" + mock_runner = Mock() + exc = exceptions.TavernException("Stage failed") + exc.stage = sample_stage + mock_runner.wrapped_run_stage = Mock(side_effect=exc) + tinctures = Tinctures([]) + + runner = StarlarkPipelineRunner( + test_path="/test/path.tavern.star", + stages=[sample_stage], + test_config=fix_test_config, + sessions={}, + ) + + with ( + patch( + "tavern._core.starlark.starlark_env._TestRunner", + return_value=mock_runner, + ), + patch( + "tavern._core.starlark.starlark_env.get_stage_tinctures", + return_value=tinctures, + ), + ): + response = runner._run_stage(sample_stage, continue_on_fail=True) + + assert response.success is False + assert response.stage_name == sample_stage["name"] + + def test_run_stage_with_extra_vars( + self, + fix_test_config, + sample_stages, + mock_test_runner, + ): + """Test that extra_vars can be passed to run_stage from Starlark.""" + runner = StarlarkPipelineRunner( + test_path="/test/path.tavern.star", + stages=sample_stages, + test_config=fix_test_config, + sessions={}, + ) + tinctures = Tinctures([]) + + script = """ +load("@tavern_helpers.star", "run_stage") +resp = run_stage("get_cookie", extra_vars={"custom_var": "custom_value"}) +""" + + with ( + patch( + "tavern._core.starlark.starlark_env._TestRunner", + return_value=mock_test_runner, + ), + patch( + "tavern._core.starlark.starlark_env.get_stage_tinctures", + return_value=tinctures, + ), + ): + runner.load_and_run(script) + + # Verify extra_vars were passed to stage_config (positional arg at index 1) + call_args = mock_test_runner.wrapped_run_stage.call_args + stage_config = call_args[0][1] # Second positional argument + extra_vars_in_request = stage_config.variables + assert "custom_var" in extra_vars_in_request + assert extra_vars_in_request["custom_var"] == "custom_value" + + def test_run_stage_continue_on_fail( + self, + fix_test_config, + sample_stages, + ): + """Test that continue_on_fail parameter prevents exception propagation.""" + mock_runner = Mock() + exc = exceptions.TavernException("Stage failed") + exc.stage = sample_stages[0] + mock_runner.wrapped_run_stage = Mock(side_effect=exc) + tinctures = Tinctures([]) + + runner = StarlarkPipelineRunner( + test_path="/test/path.tavern.star", + stages=sample_stages, + test_config=fix_test_config, + sessions={}, + ) + + script = """ +load("@tavern_helpers.star", "run_stage") +# continue_on_fail=True should catch the exception +resp = run_stage("get_cookie", continue_on_fail=True) +# Script should continue without raising +""" + + with ( + patch( + "tavern._core.starlark.starlark_env._TestRunner", + return_value=mock_runner, + ), + patch( + "tavern._core.starlark.starlark_env.get_stage_tinctures", + return_value=tinctures, + ), + ): + # Should not raise + runner.load_and_run(script) + + def test_run_stage_failure_propagates( + self, + fix_test_config, + sample_stages, + ): + """Test that failed stage propagates exception when continue_on_fail=False.""" + mock_runner = Mock() + exc = exceptions.TavernException("Stage failed") + exc.stage = sample_stages[0] + mock_runner.wrapped_run_stage = Mock(side_effect=exc) + tinctures = Tinctures([]) + + runner = StarlarkPipelineRunner( + test_path="/test/path.tavern.star", + stages=sample_stages, + test_config=fix_test_config, + sessions={}, + ) + + script = """ +load("@tavern_helpers.star", "run_stage") +# continue_on_fail is False by default +resp = run_stage("get_cookie") +""" + + with ( + patch( + "tavern._core.starlark.starlark_env._TestRunner", + return_value=mock_runner, + ), + patch( + "tavern._core.starlark.starlark_env.get_stage_tinctures", + return_value=tinctures, + ), + ): + # Should raise StarlarkError (wrapping TavernException) + with pytest.raises((exceptions.StarlarkError, exceptions.TavernException)): + runner.load_and_run(script) diff --git a/tests/unit/test_control_flow_guard.py b/tests/unit/test_control_flow_guard.py new file mode 100644 index 000000000..ebca1619f --- /dev/null +++ b/tests/unit/test_control_flow_guard.py @@ -0,0 +1,81 @@ +"""Test that control_flow requires experimental flag""" + +import pathlib +from unittest.mock import Mock, patch + +import pytest + +from tavern._core import exceptions +from tavern._core.pytest.config import TavernInternalConfig, TestConfig +from tavern._core.run import run_test +from tavern._core.starlark import StarlarkPipelineRunner +from tavern._core.strict_util import StrictLevel + + +def test_control_flow_requires_experimental_flag(): + """Test that control_flow raises an error when experimental flag is not enabled""" + # Create a test spec with control_flow + test_spec = { + "test_name": "test_control_flow", + "control_flow": "def main():\n pass", + "stages": [], + } + + # Create a TestConfig with experimental_starlark_pipeline=False + global_cfg = TestConfig( + variables={}, + strict=StrictLevel.all_on(), + follow_redirects=False, + stages=[], + experimental_starlark_pipeline=False, + tavern_internal=TavernInternalConfig( + pytest_hook_caller=Mock(), + backends={}, + ), + ) + + # Should raise BadSchemaError when control_flow is used without the flag + with pytest.raises(exceptions.UnexpectedKeysError) as exc_info: + run_test( + pathlib.Path("/fake/path.tavern.yaml"), + test_spec, + global_cfg, + ) + + assert "control_flow requires --tavern-experimental-starlark-pipeline flag" in str( + exc_info.value + ) + + +def test_control_flow_works_with_experimental_flag(): + """Test that control_flow works when experimental flag is enabled""" + # Create a test spec with control_flow + test_spec = { + "test_name": "test_control_flow", + "control_flow": "def main():\n pass", + "stages": [], + } + + # Create a TestConfig with experimental_starlark_pipeline=True + global_cfg = TestConfig( + variables={}, + strict=StrictLevel.all_on(), + follow_redirects=False, + stages=[], + experimental_starlark_pipeline=True, + tavern_internal=TavernInternalConfig( + pytest_hook_caller=Mock(), + backends={}, + ), + ) + + # Should not raise an error about the flag + with patch( + "tavern._core.starlark.starlark_env.StarlarkPipelineRunner", + Mock(spec=StarlarkPipelineRunner), + ): + run_test( + pathlib.Path("/fake/path.tavern.yaml"), + test_spec, + global_cfg, + ) diff --git a/tests/unit/test_mqtt.py b/tests/unit/test_mqtt.py index b5bccb499..15c8b38e4 100644 --- a/tests/unit/test_mqtt.py +++ b/tests/unit/test_mqtt.py @@ -1,4 +1,3 @@ -import time from unittest.mock import MagicMock, Mock, patch import paho.mqtt.client as paho @@ -21,7 +20,7 @@ def test_host_required(): @pytest.fixture(name="fake_client") def fix_fake_client(): - args = {"connect": {"host": "localhost", "timeout": 0.6}} + args = {"connect": {"host": "localhost", "timeout": 0.01}} mqtt_client = MQTTClient(**args) @@ -54,7 +53,7 @@ def test_message_queued(self, fake_client): def test_context_connection_failure(self, fake_client): """Unable to connect on __enter__ raises MQTTError""" - fake_client._connect_timeout = 0.3 + fake_client._connect_timeout = 0.01 with patch.object(fake_client._client, "loop_start"): with pytest.raises(exceptions.MQTTError): @@ -112,7 +111,7 @@ def test_assert_message_published_delay(self, fake_client): class FakeMessage(paho.MQTTMessageInfo): def wait_for_publish(self, timeout=None): - time.sleep(0.5) + pass def is_published(self): return True diff --git a/tests/unit/test_tinctures.py b/tests/unit/test_tinctures.py index def2aad3f..d461d53a1 100644 --- a/tests/unit/test_tinctures.py +++ b/tests/unit/test_tinctures.py @@ -48,6 +48,7 @@ def make_test_config(tinctures=None, mock_internal_config=None): strict=StrictLevel.all_off(), follow_redirects=False, stages=[], + experimental_starlark_pipeline=False, tavern_internal=mock_internal_config, tinctures=tinctures, ) diff --git a/tox-integration.ini b/tox-integration.ini index ab0792892..76ff7fd0d 100644 --- a/tox-integration.ini +++ b/tox-integration.ini @@ -34,6 +34,7 @@ extras = ; regression test for https://github.com/taverntesting/tavern/issues/1016 http: mqtt graphql: graphql + generic: scriptable commands = ; docker compose stop ; docker compose build @@ -46,6 +47,7 @@ commands = allure: python -m pytest --alluredir=allure-results {posargs} allure: docker compose run --rm allure generate /allure-results --clean --single-file -o /allure-report + generic: py.test --tavern-global-cfg={toxinidir}/tests/integration/global_cfg.yaml --tavern-experimental-starlark-pipeline -m starlark_control_flow generic: py.test --tavern-global-cfg={toxinidir}/tests/integration/global_cfg.yaml -n 3 generic: tavern-ci --stdout . --tavern-global-cfg={toxinidir}/tests/integration/global_cfg.yaml generic: python -c "from tavern.core import run; exit(run('.', '{toxinidir}/tests/integration/global_cfg.yaml', pytest_args=[ ]))" diff --git a/tox.ini b/tox.ini index 28fbee17a..9304ba65c 100644 --- a/tox.ini +++ b/tox.ini @@ -7,6 +7,7 @@ extras = grpc mqtt graphql + scriptable dependency_groups = dev commands = @@ -14,6 +15,6 @@ commands = [testenv:py3check] allowlist_externals = - pre-commit + uv commands = - pre-commit run --all-files + uv tool run prek run --all-files diff --git a/uv.lock b/uv.lock index f174da972..956ea3df6 100644 --- a/uv.lock +++ b/uv.lock @@ -1,11 +1,9 @@ version = 1 revision = 3 -requires-python = ">=3.11" +requires-python = ">=3.12" resolution-markers = [ "python_full_version >= '3.14'", - "python_full_version == '3.13.*'", - "python_full_version == '3.12.*'", - "python_full_version < '3.12'", + "python_full_version < '3.14'", ] [manifest] @@ -43,24 +41,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/58/d9/22ce5786ac0c1653ae8b6c23bded02c1686d11f0dbb45b31ce128e0df985/aiohttp-3.14.3.tar.gz", hash = "sha256:9491196535a88924a60afd5b5f434b5b203b6cc616250878dbdb223a8f7844bc", size = 7971213, upload-time = "2026-07-23T01:57:27.037Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f8/5c/b3e4ff8ad43a8afef9602c5e90285936da1beaea8b029016b793891f03c3/aiohttp-3.14.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e568e14940c09955aa51f4e645b6daa18a581c5dcfcd73744dcc86a856e3ced3", size = 764250, upload-time = "2026-07-23T01:52:48.525Z" }, - { url = "https://files.pythonhosted.org/packages/0e/da/f1b384465e51449d844056b75070461da03a9a23e6c1747003695bf4172a/aiohttp-3.14.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:54cfcdee2770dac994417cbb0ee1f3eb0e7cb6b30c79bf44f2c02ff79ec5124a", size = 516281, upload-time = "2026-07-23T01:52:51.047Z" }, - { url = "https://files.pythonhosted.org/packages/b9/3f/01264f820ee2e3712a827892b1cd6ff80f3300c1fcbffbb45714a915d47a/aiohttp-3.14.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:21c016079415ed3fd676963e9793700a566d85dbbd6bfc564b9b2d209147dcc8", size = 514742, upload-time = "2026-07-23T01:52:53.779Z" }, - { url = "https://files.pythonhosted.org/packages/9e/8d/a71c6f2db52ac1ed142b133f7feddaa6b70539c3f4de24d7e226c95b794c/aiohttp-3.14.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d6088ec9894113802bddb3c09e974929aed2c7b3a8c456219b8aab4481f1a239", size = 1780613, upload-time = "2026-07-23T01:52:56.948Z" }, - { url = "https://files.pythonhosted.org/packages/a5/11/3dd9b3fb3a170f6ec9011b5291d876a6fab4086714c9e158600edf01b4fd/aiohttp-3.14.3-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:16ea7e24c309fb7c0bbd505d149abe4fe4dccfb8db911db7dbec0921bc889a6f", size = 1737688, upload-time = "2026-07-23T01:52:59.294Z" }, - { url = "https://files.pythonhosted.org/packages/6d/3e/834c26918be7d88068822b40e0db30fca50b5f4fe79104aa16a93f1d74e6/aiohttp-3.14.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:56f355e79f71aef2a85c80305cc915f894b170dba76de5fe84f6351939b83c06", size = 1845742, upload-time = "2026-07-23T01:53:01.641Z" }, - { url = "https://files.pythonhosted.org/packages/cc/c9/49ab8572df7d66bc13d11e31f781292badb04180dd87ba98733066c6aed7/aiohttp-3.14.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:18c441d0a8fca6de8d1f546849b9f0ab20d435993e2c5b59562b2fae6be2f929", size = 1928412, upload-time = "2026-07-23T01:53:04.018Z" }, - { url = "https://files.pythonhosted.org/packages/a5/b9/2b8f0c0ce09c87a1daf80fd483431b56b1435d3f62789bc86f572e1245de/aiohttp-3.14.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:53e7b4ce82b54a8bcc71b3b67a5cbd177ca1d7f592cbc92cd38b7349f73482db", size = 1786220, upload-time = "2026-07-23T01:53:06.481Z" }, - { url = "https://files.pythonhosted.org/packages/85/00/9c45f81de11710460edfa1dc81317b6e882703b160926c879a9d20da9fcc/aiohttp-3.14.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f55119f7bf25f49ed210f6096090715da24f2943c62102448915fde3c62877ce", size = 1637231, upload-time = "2026-07-23T01:53:10.258Z" }, - { url = "https://files.pythonhosted.org/packages/19/ce/967d628e910756f3539c6107cb7844a1b69440dcb3029a5ee7871b09ab63/aiohttp-3.14.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9aa6e61fdf20105c4144e755bd586008ff450791d67b1c8146fdc15959c4d51c", size = 1753161, upload-time = "2026-07-23T01:53:13.817Z" }, - { url = "https://files.pythonhosted.org/packages/11/b2/0c3d4114f0aee4f580f5b3b4eb71b24d7a23b834ea506a4dfebe76513f35/aiohttp-3.14.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:ccd4893707b3e2a13e39c90d43cf80edf2e4d0457935bcc103bf2346214c3f15", size = 1756356, upload-time = "2026-07-23T01:53:16.211Z" }, - { url = "https://files.pythonhosted.org/packages/63/5d/99e7d91c82f1399d1ae2a854e080bd1493fbc31e5e959dbc4ec33dac3bec/aiohttp-3.14.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b2466434105a4e03113c36ec775cc2ebe6676b62eae326fa670bb607ef788c1c", size = 1819846, upload-time = "2026-07-23T01:53:18.289Z" }, - { url = "https://files.pythonhosted.org/packages/ad/05/d5e1cb6480eeffd3f901d40a2c5e2d1e7effdc797837da3b490272699f13/aiohttp-3.14.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:ba59d59aba08ac02fc03b0c8983ccd5ee39a199d0552ce9e6d2b4845b34d59ae", size = 1628531, upload-time = "2026-07-23T01:53:23.86Z" }, - { url = "https://files.pythonhosted.org/packages/c9/90/b934682bcaefae18a9e04f3dff5b68522ba810906358ae5029b68110ea3b/aiohttp-3.14.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:ed099d105449c4f9e84f24af203cd131349d4761d8813fa7e02c32e7128cd910", size = 1832712, upload-time = "2026-07-23T01:53:27.551Z" }, - { url = "https://files.pythonhosted.org/packages/21/df/6061679faaf81fac746e7307c7adb71e858071a5d34c27583afefc64f543/aiohttp-3.14.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:152516815ef926786a0b6ae2b8f1fd2e0c71582dee0b435636865316fd4891b7", size = 1775014, upload-time = "2026-07-23T01:53:30.223Z" }, - { url = "https://files.pythonhosted.org/packages/8a/1d/f854878bbc69b88faefe924b619a34a6f59ec05fd387c77690667eaa75eb/aiohttp-3.14.3-cp311-cp311-win32.whl", hash = "sha256:a4af35c443e0b1a1bd6a8af3f3485d7fda15c142751a00f3ff8090f0b93346fa", size = 456006, upload-time = "2026-07-23T01:53:34.97Z" }, - { url = "https://files.pythonhosted.org/packages/73/0c/2af9d1674baccd1dbd47282a93d660a22e57ef6167c856deb24b4214fbab/aiohttp-3.14.3-cp311-cp311-win_amd64.whl", hash = "sha256:e1e74298bab6ee0d6e749ed4fd1901c7e604bdda32c03d787a2cc71c46d0433d", size = 481069, upload-time = "2026-07-23T01:53:39.673Z" }, - { url = "https://files.pythonhosted.org/packages/8e/76/88401ff3fc95e85c5fc38d588f36f55e61ecb64343b2bc8d69326f453cc0/aiohttp-3.14.3-cp311-cp311-win_arm64.whl", hash = "sha256:03cd2bde3d7f085b64e549c985f4bb928cad7e8ecf5323bfca320db548d81b39", size = 453021, upload-time = "2026-07-23T01:53:43.749Z" }, { url = "https://files.pythonhosted.org/packages/18/d4/eb96299230e20acf2efae207cb8d69051f1f68e357e5ea5e479bf6fb097a/aiohttp-3.14.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:39aded8c7f3b935b54aab1d8d73c70ec0ee2d3ec3b943e0e86611bc150ba47f5", size = 754690, upload-time = "2026-07-23T01:53:47.332Z" }, { url = "https://files.pythonhosted.org/packages/88/11/e7a70a209eb9a067c0d3212b518a0134e3484f5178c7533878b6b514d469/aiohttp-3.14.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5bcb6ff3fdab1258a192679ff1a05d44f59626430aa05cd1a9d2447423599228", size = 509484, upload-time = "2026-07-23T01:53:51.159Z" }, { url = "https://files.pythonhosted.org/packages/30/07/4bbc222cc8dbe31d4c3e8a5baad2286e4d42026ac0c570027b89afce6344/aiohttp-3.14.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:617105e2c3018ee38d0c8ce5ee3c84f621a6d8b9f723202aacaff28449ca91ee", size = 511949, upload-time = "2026-07-23T01:53:55.083Z" }, @@ -269,19 +249,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/9e/ef/008a1939e372c06329a3fce4279c02f328488f3526744906eeec3da7ad5f/cffi-2.1.1.tar.gz", hash = "sha256:dd31f52ea1086513bb9df30f8fcee9b8918323ae067a3d5b78bc826a000712be", size = 530807, upload-time = "2026-08-03T21:21:18.939Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/70/d2/16d99a0c4948febc0ebd133a13b2f688ff7f8cb04da971e1128872ce0c03/cffi-2.1.1-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:c8d2c9fd1f2d16f780d15127abb050d13d1a76c03a4bd87d7e4980e45e511e12", size = 183838, upload-time = "2026-08-03T21:19:29.637Z" }, - { url = "https://files.pythonhosted.org/packages/cd/95/31b535a9f0220ae9f357de4a08d57ce89cb417653c2fd9f075f50822a388/cffi-2.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:398aff33cee2767e3e781d2554c54bd0dff386bb437581e0d8011fde1a942ec1", size = 184168, upload-time = "2026-08-03T21:19:30.764Z" }, - { url = "https://files.pythonhosted.org/packages/ad/5a/4707a0dc1f203f5dde5a907b0d4e3c25d71120241048bd5bc6f1bb9d4e71/cffi-2.1.1-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:154852545011f779917b11c78db2358d095da62a9a172b78ad0a583ee5adc0d0", size = 211805, upload-time = "2026-08-03T21:19:31.867Z" }, - { url = "https://files.pythonhosted.org/packages/ad/66/c19feabb28485b6e0bbaaafa90837a1ef5d302e90f2178bd33f17a49879b/cffi-2.1.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3311ed60d36f83378794e1009ac6258bafbf81f7888b4caa7b35a521e3f95813", size = 218716, upload-time = "2026-08-03T21:19:32.896Z" }, - { url = "https://files.pythonhosted.org/packages/a7/92/500760486c8baab49a7a8a58ba7fc3355ec3974b454b8a09e528efde9e1d/cffi-2.1.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6e192623c49c94421616a5778fba35cf0d5a8d000650c1967ef4448ee5cdd990", size = 205569, upload-time = "2026-08-03T21:19:34.142Z" }, - { url = "https://files.pythonhosted.org/packages/a5/a7/a67c733254d6e7373f7822f8082d8d6beade791e0cf12a7611f376fa61c7/cffi-2.1.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:a6e721d4b0e45d5b65e87534470e67b18dcd092c83f68fba09f152b9cbc061af", size = 204907, upload-time = "2026-08-03T21:19:35.174Z" }, - { url = "https://files.pythonhosted.org/packages/f7/a4/4399daaf8f7dfee9d7c3327fdb0426ee041cc63edc358b93911ceb2bfc7a/cffi-2.1.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:34e261f78cb6ceaaa36f42f2613f4380d94d9c759a9c73c769ee6e0247364632", size = 217807, upload-time = "2026-08-03T21:19:36.286Z" }, - { url = "https://files.pythonhosted.org/packages/28/f7/dabe6da2466ecbd82dc62e7342dc6b1065dad990c06f00f0ede9ebf2a0ed/cffi-2.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7225e4514edb64eb6740324353e0da0711954fd8d7da4576755b1c6e09b697cd", size = 221252, upload-time = "2026-08-03T21:19:37.416Z" }, - { url = "https://files.pythonhosted.org/packages/ce/87/616202d8e51342c07d2534c510111c4cc37201775ce8f60802c9335d1edd/cffi-2.1.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:df913725b79db7bcf03448f36b7bf8815363417d5b58deecf9305e3e30f0f21a", size = 214214, upload-time = "2026-08-03T21:19:38.507Z" }, - { url = "https://files.pythonhosted.org/packages/b4/c6/ab025d75d2c26c19b087c0124e75ee31cb65032f4fe345d356d8c507ab97/cffi-2.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f5cfbc5fe74540d335175b656c725d74d90e3730c626d92575eea35029d9afaa", size = 219408, upload-time = "2026-08-03T21:19:39.809Z" }, - { url = "https://files.pythonhosted.org/packages/db/e2/7e8109f65445bdc673a7b54f02c677de462db75674220fd1335efc8eb598/cffi-2.1.1-cp311-cp311-win32.whl", hash = "sha256:f8ec5e643a9a937f64e1999eb9f75d072263751912dc5cd06d3c85f8f44be7c3", size = 174470, upload-time = "2026-08-03T21:19:41.246Z" }, - { url = "https://files.pythonhosted.org/packages/73/c0/77ba02423c2f7d7091143c45cd49e0e6575c4c1967394bb542bd923a9b74/cffi-2.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:42f6930c31dc7f50732c9ae793c2786c7b6b044195967bbdde40bb9be81c4cc0", size = 185096, upload-time = "2026-08-03T21:19:42.615Z" }, - { url = "https://files.pythonhosted.org/packages/7c/47/9f1f85f9672ceda4984dc6c4f8824e8558992a2972c3d3c81fb8eb28d4ba/cffi-2.1.1-cp311-cp311-win_arm64.whl", hash = "sha256:c7659f22557c5a0bc4855cd635f55edec690cc008a40768527762cb9fb263455", size = 179941, upload-time = "2026-08-03T21:19:43.747Z" }, { url = "https://files.pythonhosted.org/packages/10/69/43965eccfdead3b9220015fd1320e117be8c6ed01a62ffab76eeb752f5d5/cffi-2.1.1-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:c8c69575568085ba0b1b10c0249d779a214aea6f6522e949a0fc9fb0fcb449d0", size = 184821, upload-time = "2026-08-03T21:19:44.887Z" }, { url = "https://files.pythonhosted.org/packages/54/7d/16e5a096677b5e313ca80cd5e5170efa3ea44624a82bb111925522da64b1/cffi-2.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f81b3b8f3d4e343550fa4baa0e479bba9f2d29ce9c2e9b51d1ce1718d7442fcf", size = 184719, upload-time = "2026-08-03T21:19:46.129Z" }, { url = "https://files.pythonhosted.org/packages/56/e6/8941622732edec876dd17d0453dce07317ae96db34f2ec1436c9d3785986/cffi-2.1.1-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:811bd1e21d32de12efca32393a0ab3f5133b54fce9bd44b8bd77ab07da14bf6a", size = 214799, upload-time = "2026-08-03T21:19:47.218Z" }, @@ -373,22 +340,6 @@ version = "3.5.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/e5/3f/143b048436775b0f76ac3eec145c019e8173ccc2885c8f20319b996d5e83/charset_normalizer-3.5.1.tar.gz", hash = "sha256:6117b84ea48435e5356dc737f5121485c30920ba43375fa7b434fd753df0eac3", size = 171764, upload-time = "2026-08-15T08:20:44.807Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/b6/034f6802e9c3f6418966cfabb7db8c9252cc2429c5098f41cc43af804149/charset_normalizer-3.5.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:eda059b6bc8bc0812d626fd91a7ce01bf583df0a61296eff390fd94141a34e30", size = 363585, upload-time = "2026-08-15T08:16:46.646Z" }, - { url = "https://files.pythonhosted.org/packages/d5/fa/6a7e2a7c4b5451912b8c417732df79574354443592a88d616de03da66ae5/charset_normalizer-3.5.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa2bb0b37202dca27175591f761108b5d34096ade1191ffe4808bdf6b1571488", size = 251189, upload-time = "2026-08-15T08:16:48.287Z" }, - { url = "https://files.pythonhosted.org/packages/a4/c8/ab42b07cfd82e919f427fcfaa7c41abae8242833ad1aad66d42bae40b669/charset_normalizer-3.5.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0b2b1b3fa5670c127b246df1d0c059defd41f689a868a3b9d79df9b1cac42d22", size = 239724, upload-time = "2026-08-15T08:16:49.67Z" }, - { url = "https://files.pythonhosted.org/packages/e7/80/b9348b5d3041209f98b4cdad7655766369233f1d533f4f4f7558e9717bec/charset_normalizer-3.5.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6e5e4d73d588ca5ed09df1b7dcd1b203d1df3c542e3f50d126c947d432b10731", size = 280078, upload-time = "2026-08-15T08:16:51.228Z" }, - { url = "https://files.pythonhosted.org/packages/82/38/083a24028304bc85bb9e376fed801178423dcbb67495f73b6ea0624e1894/charset_normalizer-3.5.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b54e7e13267d49ffbfe68e25b3cbd774dab38fa37238f71265e91b36146eb21c", size = 276650, upload-time = "2026-08-15T08:16:52.625Z" }, - { url = "https://files.pythonhosted.org/packages/0d/35/731ac04aa0a097fc1c97f0994c375bdb230c6c96619db794208fe664e9ce/charset_normalizer-3.5.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c7b742bf31c88566b4bb6335a7f393bb322e580b6bb98df7bd0c25e6e3519ce8", size = 262325, upload-time = "2026-08-15T08:16:54.085Z" }, - { url = "https://files.pythonhosted.org/packages/f5/28/c2028e7021fb89c6e56868ed0e387b8e9aa811abdd2ab3208d6578d2c930/charset_normalizer-3.5.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6ba32c4d2abf1d2fe7cf27d280f4cca5664233b0f885549c7761719eb977f486", size = 261140, upload-time = "2026-08-15T08:16:55.604Z" }, - { url = "https://files.pythonhosted.org/packages/28/f0/0c0ceec6d98b7daa62e361e418135d59685811d79ba11529aad5cdf15e84/charset_normalizer-3.5.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0722590aabf9dc6a6c0343d523c05458fa2b5047dbe6302fd526bb570600753f", size = 252791, upload-time = "2026-08-15T08:16:57.103Z" }, - { url = "https://files.pythonhosted.org/packages/f0/3e/48f4cd187b1c33189d86039e9cbe4f92c05454175504b44ff81806d4d1bf/charset_normalizer-3.5.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:aa1099b956fb795e686d073568f6dc002a0bb89765ea6d5b055dd7d9bf1b116c", size = 240730, upload-time = "2026-08-15T08:16:58.418Z" }, - { url = "https://files.pythonhosted.org/packages/42/85/f9e22af69af67c54cce42be9455d9c81294f918b4ccc454db01f66efcac2/charset_normalizer-3.5.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:bd6c173f04743d483881bffa1478d5a4624475b8cd1d2194956a75548e191c18", size = 280791, upload-time = "2026-08-15T08:16:59.918Z" }, - { url = "https://files.pythonhosted.org/packages/fd/4c/9044135f42127630b6fa742feb51256353f6ab87a78f2fdd1de3de955a7f/charset_normalizer-3.5.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:f298e218441525d3794428b4c8b8fb8662c6d3ea79925d4807ee6b9a96a3bca5", size = 259598, upload-time = "2026-08-15T08:17:01.421Z" }, - { url = "https://files.pythonhosted.org/packages/ba/ed/1dd7cfebb4e75812934c49ca3b79757d11948053f7937ab7070c151f3c55/charset_normalizer-3.5.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:6e2912d4babbc65196ac13c2f53468dc57fb8b9c25ef913e8c59ddf7c6dc0e1b", size = 278217, upload-time = "2026-08-15T08:17:02.782Z" }, - { url = "https://files.pythonhosted.org/packages/bf/eb/239c84503cc9e3ba6eb34686a24bc66e84f3924efdd7e38e751a19f6bc10/charset_normalizer-3.5.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3d27167433c0d5f18dc850f07d0b3816221984fecdc405d6c157a6f0b8f8e9e6", size = 263417, upload-time = "2026-08-15T08:17:04.216Z" }, - { url = "https://files.pythonhosted.org/packages/37/ab/4e4510e1e288478e2c8333131d1c1382382ba8cd2165053c79e39d1da961/charset_normalizer-3.5.1-cp311-cp311-win32.whl", hash = "sha256:ac00177c4831ffa650f8609e4bdddd5fe09c03b1c0c47acece7e6ea20421598b", size = 181774, upload-time = "2026-08-15T08:17:05.58Z" }, - { url = "https://files.pythonhosted.org/packages/e3/57/32f0ccea59e8612057c61d6fd22ef2cb63cca93c9fe594094919696ac170/charset_normalizer-3.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:f9b1e28d0e8dbfa858abdba91d6b547beaf2df1a59bec6da6faae7b96a4991a9", size = 206653, upload-time = "2026-08-15T08:17:07.075Z" }, - { url = "https://files.pythonhosted.org/packages/17/d4/b65c433fc521e58b5f54293982a5e51c05cb5f2dd3f1c7a6acb65b75324e/charset_normalizer-3.5.1-cp311-cp311-win_arm64.whl", hash = "sha256:ae31a1a1db2ee6cc2942fccaf695c934bc7f3db9f2133a3fef1f367cf1a4ab10", size = 185630, upload-time = "2026-08-15T08:17:08.502Z" }, { url = "https://files.pythonhosted.org/packages/30/27/78873dc8b6a56357517b74b6bb9568b80450e7bb4f6ef7e3fa9d22aa0bd7/charset_normalizer-3.5.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:5b6d1386bf0096d26d3a863dc0a487a5b4eb9aa93cf5ba69683d29dde6b9d60f", size = 344456, upload-time = "2026-08-15T08:17:10.072Z" }, { url = "https://files.pythonhosted.org/packages/9a/4c/be49ada26b1f0232d57aa89bbebf997a5cc2332a5616b6eca26ff680044d/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4582c27e8c889d64811987b5967fbd3ae0c823fe1fd933b543d55ac20bb475fa", size = 238530, upload-time = "2026-08-15T08:17:11.563Z" }, { url = "https://files.pythonhosted.org/packages/76/84/6f1290fa07ae6978d3960caa3eb1b8019bf9284ab7c2297b00c099ef4250/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:1d1c7a53a6c2103925cdd6d7229f8c567379f211c869793df679f2e9f738c369", size = 230200, upload-time = "2026-08-15T08:17:12.919Z" }, @@ -514,20 +465,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cc/61/d01fc49b8dea277640b55a9e15960dbca9fdc8c9fde18e572d39c59f4019/charset_normalizer-3.5.1-py3-none-any.whl", hash = "sha256:6df0ec430f9a831772c23ca5a224cba36517a58a84bb32c32bb59a9fa67c47f6", size = 68658, upload-time = "2026-08-15T08:20:43.306Z" }, ] -[[package]] -name = "cli-ui" -version = "0.19.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama" }, - { name = "tabulate" }, - { name = "unidecode" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/21/63/70d8fefa7b4140367c45287b94fb5df535b6ba6f77464087b18fdae2bb47/cli_ui-0.19.0.tar.gz", hash = "sha256:59cdab0c6a2a6703c61b31cb75a1943076888907f015fffe15c5a8eb41a933aa", size = 12808, upload-time = "2025-03-29T20:24:37.486Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c6/a9/09591cff626f71bd11f88d8eedb81619c17422b4a5e778614a91e83c4b6a/cli_ui-0.19.0-py3-none-any.whl", hash = "sha256:1cf1b93328f7377730db29507e10bcb29ccc1427ceef45714b522d1f2055e7cd", size = 13492, upload-time = "2025-03-29T20:24:35.841Z" }, -] - [[package]] name = "click" version = "8.4.2" @@ -567,21 +504,6 @@ version = "7.15.4" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/be/c3/4f2195f512fb172aa425a8803a874b2baa9ba7f80ff7b6080998761fc701/coverage-7.15.4.tar.gz", hash = "sha256:0548198fff07ccf4faf469520bce1c2eceb1ce3e62891921138dec10907f9d00", size = 936952, upload-time = "2026-08-06T13:50:24.442Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/66/edcec7d7a0b524aa8923e22925fde6fe50ce005a113dca13ae1581455c4c/coverage-7.15.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bbac5abad70df71019988f83f26ac7092ff2642975def4429e98dc7585ef3490", size = 222367, upload-time = "2026-08-06T13:47:15.578Z" }, - { url = "https://files.pythonhosted.org/packages/e6/c6/ab8de429e2e8548faf58ec7e1674a4ce00414b4113942d3fe87109cf0f68/coverage-7.15.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:357a173465c7ce028d07a95cc2b63b5bf59f50ecdd5ad75c5cbb78ada984048e", size = 222874, upload-time = "2026-08-06T13:47:16.961Z" }, - { url = "https://files.pythonhosted.org/packages/be/c4/3b7b49587e8a6b9af79b3eb468d443d6042b6d65b47aa26586846a0d6566/coverage-7.15.4-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:21b803935e2efc3acebe9697197a294fccf5dc4e5382bd6369542ff7a7d2a1d7", size = 253287, upload-time = "2026-08-06T13:47:18.291Z" }, - { url = "https://files.pythonhosted.org/packages/fb/65/ec03b743a2a229c72cc1eff3e57be9d3564e9c6b4d5aba2d70744a3fc0d8/coverage-7.15.4-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7a2b580774a4786c1053157c0165e04476e03ff293993d7c148eee784a94bae6", size = 255199, upload-time = "2026-08-06T13:47:19.765Z" }, - { url = "https://files.pythonhosted.org/packages/41/4b/5163729e4b6582d61975cfd3ccab45b4ec53e21cf156d9941cb025188468/coverage-7.15.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a9464451c4efffe8d47ace5a540b10b0dc10e879066290f8600872b7f54a419d", size = 257308, upload-time = "2026-08-06T13:47:21.206Z" }, - { url = "https://files.pythonhosted.org/packages/86/08/2167a0f08fb87d702fa423a48578a32865464b7c9e1db3911ad7812ab414/coverage-7.15.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:de602f34123c2f4af1c1869c6dbbbd60da6d5983bf01937367295d135cccbfce", size = 259268, upload-time = "2026-08-06T13:47:22.503Z" }, - { url = "https://files.pythonhosted.org/packages/1e/e5/68eebae3053dbd48508edea559c21b23fbdf3460784f91370c83a86a6acd/coverage-7.15.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6879ded16a27f3eeca19b900c147e81616e7054db451471a611b2755ee5249f7", size = 253392, upload-time = "2026-08-06T13:47:23.88Z" }, - { url = "https://files.pythonhosted.org/packages/1a/46/fd4ced40a2b691c774e515c9b69500bfa64c7960b67fcee4b2f6fad97fc3/coverage-7.15.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:986be58c3ab54aae8d3496a6225eea74f760fdbe739b38bd442c7e8d133aa53b", size = 255001, upload-time = "2026-08-06T13:47:25.469Z" }, - { url = "https://files.pythonhosted.org/packages/53/25/ae2e5fa710bb6957a9aadeb9e3598d3b3e4af6587ce857ad42e8639a3f30/coverage-7.15.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:c6103639613fe6c1e989082948419bc77a2d26b6c825c99d7fad25f7d3d87afc", size = 253061, upload-time = "2026-08-06T13:47:26.845Z" }, - { url = "https://files.pythonhosted.org/packages/d7/31/67ddc0365db2c6e93ac8580bc4bbc50f65273262f973f63ebcdbc15c0495/coverage-7.15.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d3af93dddb5659276c63bc16ac6466ac2033a70ca816097bbc06345b8ccdf571", size = 256831, upload-time = "2026-08-06T13:47:28.217Z" }, - { url = "https://files.pythonhosted.org/packages/f6/78/82b8fd18f57fb13f12d98fe874995bb2c4f9f17be8aff762c426323fdb96/coverage-7.15.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:b10075e5421d04265766a6d1dac809bbeb8a946fbb23c8f82c227409b2190719", size = 252781, upload-time = "2026-08-06T13:47:29.712Z" }, - { url = "https://files.pythonhosted.org/packages/0a/eb/6c74ef4dd12b252e573c49bdef9e2ac265bf3dbb79b8d7feb3266e084e9e/coverage-7.15.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a67a9f78b2942d87ba8ce3059c642164d2aedd65337377fb52fe9803656bc5c7", size = 253692, upload-time = "2026-08-06T13:47:31.192Z" }, - { url = "https://files.pythonhosted.org/packages/5a/66/eb9aed1c3fd2d36ee00eb173f434b14fa607fc056739c9a89ff4244010ea/coverage-7.15.4-cp311-cp311-win32.whl", hash = "sha256:69484d1aca26e322e1c3ce03f09341e84524ababad2d7202161738d83cc9f82e", size = 224461, upload-time = "2026-08-06T13:47:32.572Z" }, - { url = "https://files.pythonhosted.org/packages/e2/6d/81fa4161dfb3ed9d74e40d58647eff83a56b7612e78352581280fce2f477/coverage-7.15.4-cp311-cp311-win_amd64.whl", hash = "sha256:63fd6fcd1dd6e158f7eb78606e72933b3f6d01e7b747f99c6c12d764307a0fdc", size = 224937, upload-time = "2026-08-06T13:47:34.205Z" }, - { url = "https://files.pythonhosted.org/packages/5b/c1/d8dacf683c6cad3cf85ce68fd3774a6774ec402128822fdfaed920f11e6a/coverage-7.15.4-cp311-cp311-win_arm64.whl", hash = "sha256:ea82116c9893fa89e929b7f197ee5a1950a76e91cc5c85ba503fc02379d04890", size = 224479, upload-time = "2026-08-06T13:47:36.118Z" }, { url = "https://files.pythonhosted.org/packages/1d/48/bc8d4ba7b37551a767bd863f15b3f80182b271c2f55975356f5f7dbe94c2/coverage-7.15.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d4fedd1f7f428f9fe83b1ead5e7cc87a43427be31aadafbac3ac0636dc7abb22", size = 222543, upload-time = "2026-08-06T13:47:37.562Z" }, { url = "https://files.pythonhosted.org/packages/20/dd/88d6f83f1fffc974a3691a34a97951c5b12df7512a6782c5963883cbc058/coverage-7.15.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:37e2f0cdf58e2e1fed4e4d5a8f8786ae2f7eb80b478016876667dc4a01d60a97", size = 222905, upload-time = "2026-08-06T13:47:38.927Z" }, { url = "https://files.pythonhosted.org/packages/bd/5c/54ee0d4748585bb0acab9891cd8d92f2d3593165b4e59fc9de113bfb3140/coverage-7.15.4-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:fb55d0e70bb15f2e81477613627286581414693d74ac7963c93a790dd453ca9d", size = 254407, upload-time = "2026-08-06T13:47:40.488Z" }, @@ -675,11 +597,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b4/d9/e70c286c979378f061d8266e279b686ab0b0b688e1fe0af864684f23a77d/coverage-7.15.4-py3-none-any.whl", hash = "sha256:964730a1e9de9c0cf11be6a1a3c79ce419c34882842abd256086ba4698705e84", size = 214332, upload-time = "2026-08-06T13:50:22.192Z" }, ] -[package.optional-dependencies] -toml = [ - { name = "tomli", marker = "python_full_version <= '3.11'" }, -] - [[package]] name = "cross-web" version = "0.7.0" @@ -740,12 +657,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/64/a2/4615c8f7d81a00b1d6e6afe19f694e1543582349fb5f4076f6cb5dc36485/cryptography-50.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c87f62a3d3b9888ed0fdde100ec06aa61ca9cd44bad9057d1dff9a516b5f5bb9", size = 4878208, upload-time = "2026-07-31T14:24:45.522Z" }, { url = "https://files.pythonhosted.org/packages/d2/1a/efcfb02f91407149a0dacffffab791f7e19bf6385f63b3666dc8b5e5c9c8/cryptography-50.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:65c2c3add92b45fd0709db8594536aea39c2a67af0e27ffcf049c498501140b7", size = 5037050, upload-time = "2026-07-31T14:24:47.697Z" }, { url = "https://files.pythonhosted.org/packages/57/30/4a22984d4f1bdfb8c054f07a92bc176b97a3134cc1d6c4b3bffb1f3688b4/cryptography-50.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:d24fead1d4d076e1bfb006dcec392074a3cd8d7b4fc8a595aa64073b2b7a96ba", size = 3874135, upload-time = "2026-07-31T14:24:50.085Z" }, - { url = "https://files.pythonhosted.org/packages/9d/3e/e54cde8c01631a5a8226ccd617eab9e57fd5cfdad90f1a9e6bb570794631/cryptography-50.0.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:5e34edd123674534acd70147f0ca331eaa2c74e6325fb2028c886aa26ba0b68c", size = 3963170, upload-time = "2026-07-31T14:24:51.968Z" }, - { url = "https://files.pythonhosted.org/packages/01/b6/0b9e125e90f3d2dcf599a218a899cda7326a3158cfa258723f0b398b08f6/cryptography-50.0.0-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:8eb5e1172eb569ea8a872796576e6a67c276351728b6455d5beb01242b027c6a", size = 4692441, upload-time = "2026-07-31T14:24:53.743Z" }, - { url = "https://files.pythonhosted.org/packages/53/c9/a5151588710785a96d7bc4de27d4cd62f263bbbcb203cfe29df537eb6505/cryptography-50.0.0-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:910d11e1a385c654bf738bf3e6b8e6ed5de0f5610fcae2be9e5b398d8081d20e", size = 4699810, upload-time = "2026-07-31T14:24:55.746Z" }, - { url = "https://files.pythonhosted.org/packages/c7/1a/15b92b25eb6ce3089cd49377ae990a0f3ad485a510f968aed1f19dbdcdf2/cryptography-50.0.0-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:62598a8a57f815db4c6259a4e97d857dab56697e7de8e8ab02352ab74da1995d", size = 4691924, upload-time = "2026-07-31T14:24:58.082Z" }, - { url = "https://files.pythonhosted.org/packages/62/15/219075012ab13e8905f3cd572204f4acb4b111df787104346b9bc0cea789/cryptography-50.0.0-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:07479a1cb08219ab719147e742e76090c9c773321959bb94946fffdd397a6437", size = 4699593, upload-time = "2026-07-31T14:24:59.951Z" }, - { url = "https://files.pythonhosted.org/packages/8e/b5/c2c5fce26f0ee40d21bafe7f191d29a34b35a65ac4fe8a1191d1983612e9/cryptography-50.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c99c003e088647b8a5b7c145d6f78c335f6348332b62e142d411c4b63d1460b9", size = 3813796, upload-time = "2026-07-31T14:25:02.298Z" }, ] [[package]] @@ -823,11 +734,11 @@ wheels = [ [[package]] name = "filelock" -version = "3.32.3" +version = "3.32.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7d/64/a02e6765de08964ed371eca577870593245afc9dfac16d037de7c10d18e6/filelock-3.32.3.tar.gz", hash = "sha256:0ffa185a3540854c95caa7fa76b76cb219d907415e2c5dc9af25fd970563487f", size = 218135, upload-time = "2026-08-13T16:00:05.577Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6d/30/03b03951873a1a0ffc7e8ca0e10c15597b59e8d0e39260704cd2ea087bc4/filelock-3.32.4.tar.gz", hash = "sha256:2bde2e4cf732e0153406d8a7bc80620ecf5e621fe0d25e41143c4e3b4733ff30", size = 222126, upload-time = "2026-08-23T17:37:55.363Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a7/8e/50f46a9c0ce8d2861a394c1347caae037ea0431d2f67d7feb151cbc4649a/filelock-3.32.3-py3-none-any.whl", hash = "sha256:7f0ca4bcc0e181c60dbbd8aa9ab5b120ebb99e4e064e83636340056f833a1f09", size = 98901, upload-time = "2026-08-13T16:00:03.974Z" }, + { url = "https://files.pythonhosted.org/packages/01/a4/9b63d595d748e3aff8812b65eacc1a2c4bd90b7c2012e08e72373b4835eb/filelock-3.32.4-py3-none-any.whl", hash = "sha256:22e58ca3b1ae3b98993b762d7338367ae64fe50252bf78d59da3bfebcdf1cedd", size = 99864, upload-time = "2026-08-23T17:37:53.913Z" }, ] [[package]] @@ -902,22 +813,6 @@ version = "1.8.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/2d/f5/c831fac6cc817d26fd54c7eaccd04ef7e0288806943f7cc5bbf69f3ac1f0/frozenlist-1.8.0.tar.gz", hash = "sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad", size = 45875, upload-time = "2025-10-06T05:38:17.865Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bc/03/077f869d540370db12165c0aa51640a873fb661d8b315d1d4d67b284d7ac/frozenlist-1.8.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:09474e9831bc2b2199fad6da3c14c7b0fbdd377cce9d3d77131be28906cb7d84", size = 86912, upload-time = "2025-10-06T05:35:45.98Z" }, - { url = "https://files.pythonhosted.org/packages/df/b5/7610b6bd13e4ae77b96ba85abea1c8cb249683217ef09ac9e0ae93f25a91/frozenlist-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:17c883ab0ab67200b5f964d2b9ed6b00971917d5d8a92df149dc2c9779208ee9", size = 50046, upload-time = "2025-10-06T05:35:47.009Z" }, - { url = "https://files.pythonhosted.org/packages/6e/ef/0e8f1fe32f8a53dd26bdd1f9347efe0778b0fddf62789ea683f4cc7d787d/frozenlist-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fa47e444b8ba08fffd1c18e8cdb9a75db1b6a27f17507522834ad13ed5922b93", size = 50119, upload-time = "2025-10-06T05:35:48.38Z" }, - { url = "https://files.pythonhosted.org/packages/11/b1/71a477adc7c36e5fb628245dfbdea2166feae310757dea848d02bd0689fd/frozenlist-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2552f44204b744fba866e573be4c1f9048d6a324dfe14475103fd51613eb1d1f", size = 231067, upload-time = "2025-10-06T05:35:49.97Z" }, - { url = "https://files.pythonhosted.org/packages/45/7e/afe40eca3a2dc19b9904c0f5d7edfe82b5304cb831391edec0ac04af94c2/frozenlist-1.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:957e7c38f250991e48a9a73e6423db1bb9dd14e722a10f6b8bb8e16a0f55f695", size = 233160, upload-time = "2025-10-06T05:35:51.729Z" }, - { url = "https://files.pythonhosted.org/packages/a6/aa/7416eac95603ce428679d273255ffc7c998d4132cfae200103f164b108aa/frozenlist-1.8.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8585e3bb2cdea02fc88ffa245069c36555557ad3609e83be0ec71f54fd4abb52", size = 228544, upload-time = "2025-10-06T05:35:53.246Z" }, - { url = "https://files.pythonhosted.org/packages/8b/3d/2a2d1f683d55ac7e3875e4263d28410063e738384d3adc294f5ff3d7105e/frozenlist-1.8.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:edee74874ce20a373d62dc28b0b18b93f645633c2943fd90ee9d898550770581", size = 243797, upload-time = "2025-10-06T05:35:54.497Z" }, - { url = "https://files.pythonhosted.org/packages/78/1e/2d5565b589e580c296d3bb54da08d206e797d941a83a6fdea42af23be79c/frozenlist-1.8.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c9a63152fe95756b85f31186bddf42e4c02c6321207fd6601a1c89ebac4fe567", size = 247923, upload-time = "2025-10-06T05:35:55.861Z" }, - { url = "https://files.pythonhosted.org/packages/aa/c3/65872fcf1d326a7f101ad4d86285c403c87be7d832b7470b77f6d2ed5ddc/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b6db2185db9be0a04fecf2f241c70b63b1a242e2805be291855078f2b404dd6b", size = 230886, upload-time = "2025-10-06T05:35:57.399Z" }, - { url = "https://files.pythonhosted.org/packages/a0/76/ac9ced601d62f6956f03cc794f9e04c81719509f85255abf96e2510f4265/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:f4be2e3d8bc8aabd566f8d5b8ba7ecc09249d74ba3c9ed52e54dc23a293f0b92", size = 245731, upload-time = "2025-10-06T05:35:58.563Z" }, - { url = "https://files.pythonhosted.org/packages/b9/49/ecccb5f2598daf0b4a1415497eba4c33c1e8ce07495eb07d2860c731b8d5/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c8d1634419f39ea6f5c427ea2f90ca85126b54b50837f31497f3bf38266e853d", size = 241544, upload-time = "2025-10-06T05:35:59.719Z" }, - { url = "https://files.pythonhosted.org/packages/53/4b/ddf24113323c0bbcc54cb38c8b8916f1da7165e07b8e24a717b4a12cbf10/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:1a7fa382a4a223773ed64242dbe1c9c326ec09457e6b8428efb4118c685c3dfd", size = 241806, upload-time = "2025-10-06T05:36:00.959Z" }, - { url = "https://files.pythonhosted.org/packages/a7/fb/9b9a084d73c67175484ba2789a59f8eebebd0827d186a8102005ce41e1ba/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:11847b53d722050808926e785df837353bd4d75f1d494377e59b23594d834967", size = 229382, upload-time = "2025-10-06T05:36:02.22Z" }, - { url = "https://files.pythonhosted.org/packages/95/a3/c8fb25aac55bf5e12dae5c5aa6a98f85d436c1dc658f21c3ac73f9fa95e5/frozenlist-1.8.0-cp311-cp311-win32.whl", hash = "sha256:27c6e8077956cf73eadd514be8fb04d77fc946a7fe9f7fe167648b0b9085cc25", size = 39647, upload-time = "2025-10-06T05:36:03.409Z" }, - { url = "https://files.pythonhosted.org/packages/0a/f5/603d0d6a02cfd4c8f2a095a54672b3cf967ad688a60fb9faf04fc4887f65/frozenlist-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:ac913f8403b36a2c8610bbfd25b8013488533e71e62b4b4adce9c86c8cea905b", size = 44064, upload-time = "2025-10-06T05:36:04.368Z" }, - { url = "https://files.pythonhosted.org/packages/5d/16/c2c9ab44e181f043a86f9a8f84d5124b62dbcb3a02c0977ec72b9ac1d3e0/frozenlist-1.8.0-cp311-cp311-win_arm64.whl", hash = "sha256:d4d3214a0f8394edfa3e303136d0575eece0745ff2b47bd2cb2e66dd92d4351a", size = 39937, upload-time = "2025-10-06T05:36:05.669Z" }, { url = "https://files.pythonhosted.org/packages/69/29/948b9aa87e75820a38650af445d2ef2b6b8a6fab1a23b6bb9e4ef0be2d59/frozenlist-1.8.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:78f7b9e5d6f2fdb88cdde9440dc147259b62b9d3b019924def9f6478be254ac1", size = 87782, upload-time = "2025-10-06T05:36:06.649Z" }, { url = "https://files.pythonhosted.org/packages/64/80/4f6e318ee2a7c0750ed724fa33a4bdf1eacdc5a39a7a24e818a773cd91af/frozenlist-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:229bf37d2e4acdaf808fd3f06e854a4a7a3661e871b10dc1f8f1896a3b05f18b", size = 50594, upload-time = "2025-10-06T05:36:07.69Z" }, { url = "https://files.pythonhosted.org/packages/2b/94/5c8a2b50a496b11dd519f4a24cb5496cf125681dd99e94c604ccdea9419a/frozenlist-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f833670942247a14eafbb675458b4e61c82e002a148f49e68257b79296e865c4", size = 50448, upload-time = "2025-10-06T05:36:08.78Z" }, @@ -1003,7 +898,7 @@ wheels = [ [[package]] name = "google-api-core" -version = "2.33.0" +version = "2.35.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "google-auth" }, @@ -1012,9 +907,9 @@ dependencies = [ { name = "protobuf" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/87/62/8fb1fb647d2788c950d69d6a769cd9d55c918ac1fc57be2f90b7e4029787/google_api_core-2.33.0.tar.gz", hash = "sha256:3a36bcc3e319783f4c97da41f6f45ea6ffcaa55848e341de16e09cb70243c2bb", size = 181607, upload-time = "2026-07-22T16:28:28.027Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d2/b1/de2bc1db39979fb7912c0547563fe720e12a11296976ccd6e2e701e22cfe/google_api_core-2.35.0.tar.gz", hash = "sha256:80611ccbd0291919f6cea8827d57b472946fcd9ab519625becce0c002140a7b0", size = 192600, upload-time = "2026-08-25T19:18:22.346Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/89/31/5056a347bb934ea04583c8b27916ef1501729c72638629545bce26ff4223/google_api_core-2.33.0-py3-none-any.whl", hash = "sha256:a2e22a0c1d0f03eafff1858b38cf46f832d5902b0c052235bf0ab8402929fbdc", size = 176462, upload-time = "2026-07-22T16:28:22.447Z" }, + { url = "https://files.pythonhosted.org/packages/d1/a9/fcaef7316fc169ee07aafeed82d3788cee539f0d31d1c4e30cf085dc6bbe/google_api_core-2.35.0-py3-none-any.whl", hash = "sha256:88ce7a11146e1ddd331f7d2fd379787eb7e9015c34c6ed681e4f5fb8af3615af", size = 181423, upload-time = "2026-08-24T21:55:02.781Z" }, ] [[package]] @@ -1035,40 +930,40 @@ wheels = [ [[package]] name = "google-auth" -version = "2.56.3" +version = "2.57.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cryptography" }, { name = "pyasn1-modules" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/db/4c/fa42116a48bab3f7a143cf5042ecff7df9c8b73f8a376203cd534d1dc966/google_auth-2.56.3.tar.gz", hash = "sha256:40e229fc901f0a305b553050e5fce562d509bee0435be053abfa91582b51b90c", size = 367110, upload-time = "2026-08-06T06:24:01.36Z" } +sdist = { url = "https://files.pythonhosted.org/packages/41/64/55f316b729f92a552d26e00aa3b1542b2e149d0a5efe2842afff0cac7af7/google_auth-2.57.0.tar.gz", hash = "sha256:9b4f96d6a1feb5f7201231f47cfb3de08d8f176f8a61f9e461555116e95a8789", size = 370794, upload-time = "2026-08-25T19:18:26.419Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bc/b3/6117b2f24065cd7e2c4f140e9a193e215f089ca8ba314cf91eb9d0b7fe0a/google_auth-2.56.3-py3-none-any.whl", hash = "sha256:8ec438808f813ad034535000261eed1067475d229d05bbf4216e78c3f2362e53", size = 259116, upload-time = "2026-08-06T06:22:51.788Z" }, + { url = "https://files.pythonhosted.org/packages/00/f3/8508a702c094af5f6e89773f4dfdeee74913df0f41a02c21b5e7dc3d75cd/google_auth-2.57.0-py3-none-any.whl", hash = "sha256:180dafe015cfb62193bea26b677500fab5b9fd51a1e825ebf3ad9b182047ae59", size = 259728, upload-time = "2026-08-24T21:55:08.449Z" }, ] [[package]] name = "google-auth-httplib2" -version = "0.4.1" +version = "0.4.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "google-auth" }, { name = "httplib2" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/11/46/79983cb738f0eb14e6ab4f43457aa9652f8d46bc4376b178f676b68c5c37/google_auth_httplib2-0.4.1.tar.gz", hash = "sha256:125b1bb4fcfdd2d97f19b673c1f46f831603d0acaffe415c8a35dadb312552a1", size = 11158, upload-time = "2026-08-06T06:24:00.452Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d4/74/0c8177b73734dfbd89420c162ac8754257fa0f9007fb49569493d83a17db/google_auth_httplib2-0.4.2.tar.gz", hash = "sha256:916225a6367e613c9af44d83f41688a599d3f687777846b8b91bec65085ed1f1", size = 11211, upload-time = "2026-08-25T19:18:24.611Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/68/8a/96410d0c3e02b584d1e7f5d4000b2e1710855650b226cd0601f2b154940a/google_auth_httplib2-0.4.1-py3-none-any.whl", hash = "sha256:67088a8387dc3f66016a690634f1cb9b7a48a31406f8998e088efc64203e7a12", size = 9529, upload-time = "2026-08-06T06:22:50.53Z" }, + { url = "https://files.pythonhosted.org/packages/20/33/e697a69f13bfd448d33a374231b89cbbb203dedfb4aae9d26126be6f6c72/google_auth_httplib2-0.4.2-py3-none-any.whl", hash = "sha256:4298dd6b1415972d0c464b7177e6a69a595e7aec5b972222ecdca342f6009647", size = 9529, upload-time = "2026-08-24T21:55:05.843Z" }, ] [[package]] name = "googleapis-common-protos" -version = "1.75.0" +version = "1.75.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b5/c8/f439cffde755cffa462bfbb156278fa6f9d09119719af9814b858fd4f81f/googleapis_common_protos-1.75.0.tar.gz", hash = "sha256:53a062ff3c32552fbd62c11fe23768b78e4ddf0494d5e5fd97d3f4689c75fbbd", size = 151035, upload-time = "2026-05-07T08:04:49.423Z" } +sdist = { url = "https://files.pythonhosted.org/packages/72/73/74bcab964c9a7a61f2bb71e8179b0f13e6fa98f7ce00fd168aab291e4a2e/googleapis_common_protos-1.75.1.tar.gz", hash = "sha256:d3042c6c5a2d4e67113104d6b6818b59b6bd92a197f2a91508e801fe815cf071", size = 150967, upload-time = "2026-08-06T06:24:51.972Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e7/c8/e2645aa8ed02fd4c7a2f59d68783b65b1f3cbdfe39a6308e156509d1fee8/googleapis_common_protos-1.75.0-py3-none-any.whl", hash = "sha256:961ed60399c457ceb0ee8f285a84c870aabc9c6a832b9d37bb281b5bebde43ed", size = 300631, upload-time = "2026-05-07T08:03:30.345Z" }, + { url = "https://files.pythonhosted.org/packages/9a/51/186c02b8549b69ccda44429cf6ff5081e4b61a602ddfe6a8020d1be31d1b/googleapis_common_protos-1.75.1-py3-none-any.whl", hash = "sha256:28a1934bcd33b9c9da66ac301a0a4227e3367f095a17d0375cb98f0a09d93b79", size = 300626, upload-time = "2026-08-06T06:23:46.696Z" }, ] [[package]] @@ -1101,16 +996,6 @@ version = "3.5.5" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/0b/d8/7cc97c142388aef03f622e001c572c4f84e9252a439549d483f555771970/greenlet-3.5.5.tar.gz", hash = "sha256:adb4bae02e91a8e863e48b177e4014bdcac8a6b5e047ea1df687a61534b85e6c", size = 207585, upload-time = "2026-08-10T15:09:36.136Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4e/a3/07297917485ee2ca85bc3c8dc6ed85ad3fffcf424047fba62671dba68e97/greenlet-3.5.5-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:be63afcbbccfad3dd95a1ba12ada84dab2ef32031973d80b5b92df67fa763a61", size = 294165, upload-time = "2026-08-10T13:25:17.987Z" }, - { url = "https://files.pythonhosted.org/packages/db/51/6f732f9314cda54c5fd48a7620c7160f4f286967e8045ad94b9d66ce80b7/greenlet-3.5.5-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a268024ce2d7d2b04694bf1594058981a9fa663d1df4b762dee499211ed7c1c", size = 613610, upload-time = "2026-08-10T14:14:33.829Z" }, - { url = "https://files.pythonhosted.org/packages/d8/c0/b27589e25d220289edcd4d582b2b17b83058d1a56d53d971b6ea1a34f10d/greenlet-3.5.5-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:35cbb8bf55ace57fbccb4fb8622c4521713acd8691e77f4696d416ea7ca527da", size = 625481, upload-time = "2026-08-10T14:27:23.647Z" }, - { url = "https://files.pythonhosted.org/packages/39/82/5c873dbb4fb001d22fbbd50e80d4c1b0181ddae106856132160f84b94e88/greenlet-3.5.5-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:abc8bc8d9f935cd685457545b6a53863a877fdc12c2c0f5ee9beee18d9db139c", size = 633329, upload-time = "2026-08-10T14:30:06.062Z" }, - { url = "https://files.pythonhosted.org/packages/51/2d/f2c928218ac52f26d7a2c188c171d1b7e728b23782cb3347e7b4fce1493a/greenlet-3.5.5-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74cc6df89ec5302337adc9cf096221cbed2510fd444b0e0f1586cf0470740864", size = 624562, upload-time = "2026-08-10T13:40:48.064Z" }, - { url = "https://files.pythonhosted.org/packages/70/12/f7df98e72a8eb4a7edfec5a08d6d1a4ab53a52c95ba0b2ea6c10b8dd9bd0/greenlet-3.5.5-cp311-cp311-manylinux_2_39_riscv64.whl", hash = "sha256:3134291427bb0f3526e9d90311988caf336eb43730e95244997a4fb15f45144f", size = 428145, upload-time = "2026-08-10T14:30:01.071Z" }, - { url = "https://files.pythonhosted.org/packages/3e/4a/92fc51d5d35912f4f06eec037ba347985defd0be47463a010a325634d9d2/greenlet-3.5.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6d9b454c5fc48aeaa7c4337813dbf513a6870468e426438a04d922c6d0fe63db", size = 1584909, upload-time = "2026-08-10T14:15:04.343Z" }, - { url = "https://files.pythonhosted.org/packages/ac/58/ed98b80ac5738c149a5258544843c45601ade1fd70f61740cdaead6351b3/greenlet-3.5.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:03551ed792cb1b4fc0277a0c60dfd8c343894a0ba06fe60dcd22f568b433da39", size = 1651184, upload-time = "2026-08-10T13:40:28.879Z" }, - { url = "https://files.pythonhosted.org/packages/d8/be/b582ceb80cefdf9d8da34078714e4b12b3d16f509dee0f65e40a5cc8fc7d/greenlet-3.5.5-cp311-cp311-win_amd64.whl", hash = "sha256:ab3df3dffb58bf70564e93a5cec7941e4d9faa5a36cc4234a10d3131afe04f53", size = 323280, upload-time = "2026-08-10T13:26:07.495Z" }, - { url = "https://files.pythonhosted.org/packages/4d/18/5313c4c58598c38b0373c013e4ff2b3e6d258aaaa338f373335ebecdaddd/greenlet-3.5.5-cp311-cp311-win_arm64.whl", hash = "sha256:2b70a766135540c472ac1393d57c2e1b4a2eb85bf526a1e41e6d096173a8cee5", size = 307785, upload-time = "2026-08-10T13:28:34.874Z" }, { url = "https://files.pythonhosted.org/packages/2e/7e/9ecd0285e3153532ae07aeb88063c43c72b4221cf0d4d123b02f3682e3ff/greenlet-3.5.5-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:49520f0c95a48b42cf55414b8e8479beb274ea70431afc33e3f79903c71f4380", size = 295809, upload-time = "2026-08-10T13:25:34.023Z" }, { url = "https://files.pythonhosted.org/packages/35/73/60e4bbcc89252037b18087f2ec16405d5b2d5be42dde191bbf3667e96102/greenlet-3.5.5-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55272212cbc5f43d1d723725ab931f1939969b7e9523882ca58b55061769d053", size = 611910, upload-time = "2026-08-10T14:14:35.18Z" }, { url = "https://files.pythonhosted.org/packages/a4/17/cd5134be659cd4a443e7a61ae670dabec165a814c51162916d637b6dd38e/greenlet-3.5.5-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:655bca754a2ef4efcb0eb48a94d3f4593536d0f3d48f8ed44343c01d16a92f95", size = 624198, upload-time = "2026-08-10T14:27:25.229Z" }, @@ -1193,16 +1078,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/0c/98/304898ac4e04e2d5e4e4c2eadc178b1f2a16d5f4bc2f91306c87d64680b9/grpcio-1.83.0.tar.gz", hash = "sha256:7674587248fbbb2ac6e4eecf83a8a0f3d91a928f941de571acfd3a2f007fbc24", size = 13428824, upload-time = "2026-07-23T15:20:37.759Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d3/f6/3b781cd07a715ea5f5125ae264226e7fc4d87603d6d3955022cabfdc5da2/grpcio-1.83.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:8ff0b8767ddd62704e0d9571c1890af08d84a3a689ebba1807e62519d0b3277f", size = 6338720, upload-time = "2026-07-23T15:19:13.177Z" }, - { url = "https://files.pythonhosted.org/packages/21/cc/d14833d15d5984e366f1b027fa78bd038c9b028c66880bffb0f5a4d25ee2/grpcio-1.83.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:4772402f43517b4824980be4b3b2274a81eec0004a70009473c31b340d43e223", size = 12178773, upload-time = "2026-07-23T15:19:15.401Z" }, - { url = "https://files.pythonhosted.org/packages/6b/98/8acbb416544e7871132d8e42a07ed70c802d70e6a16c6009e505a34d32a4/grpcio-1.83.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f4cee5fc86e84a0cf7ad1574b454c3320e087c07f55b7df5dc0ac6a873fb90c0", size = 6921203, upload-time = "2026-07-23T15:19:17.824Z" }, - { url = "https://files.pythonhosted.org/packages/45/9c/0fdbfaf4fc54e5c88f6bce4008a065092fe7fbc4460eb5617ae8b20fd505/grpcio-1.83.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:f5e822a7e7d03282f6ad225e710493c48b9057a353358344a5f7c42b2b37618d", size = 7648508, upload-time = "2026-07-23T15:19:19.685Z" }, - { url = "https://files.pythonhosted.org/packages/f3/ea/107b9dbb2ed3ad14dd774fd3dde7d29ff9938a6c198654becb2c3a0e9a6a/grpcio-1.83.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f5f410d7c2903eabb34789dfd6342eef04af1ad459943936b7e09a9f5bd417b9", size = 7079466, upload-time = "2026-07-23T15:19:21.478Z" }, - { url = "https://files.pythonhosted.org/packages/3b/06/9fa9941089e6fae83b060b6ce61c1e81053e52decae43197245f45e07d36/grpcio-1.83.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ee94a4016fdf8699fb1fd8a38652475ff677f1c72074cee44deeeb9a7e95e745", size = 7605583, upload-time = "2026-07-23T15:19:23.74Z" }, - { url = "https://files.pythonhosted.org/packages/a8/2f/f10fb56062dc2771c630827a82d9ad0ecd05cad572ea3b08d49f6631680a/grpcio-1.83.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:c6444666317338e903093c7c756e6cc88eee59f798cb8dd41e87725bf54e1617", size = 8637810, upload-time = "2026-07-23T15:19:25.536Z" }, - { url = "https://files.pythonhosted.org/packages/99/55/f84927258f6a1b6ea6dea661fdc6de859b35e560c96f3012d15ccd39f85e/grpcio-1.83.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:aa074041231f03959cb097dd5517b0677b8ea49215bae01d5710a7b69dd59969", size = 8008021, upload-time = "2026-07-23T15:19:27.863Z" }, - { url = "https://files.pythonhosted.org/packages/c9/6b/cdf72161397ccd29d4ca2192f641524536c9cf54ad948c9dd0e0e01138fa/grpcio-1.83.0-cp311-cp311-win32.whl", hash = "sha256:cb056f6e171c42639a50460b2929c82241fda51f71cf3dcdd68090fe45095a45", size = 4404376, upload-time = "2026-07-23T15:19:30.137Z" }, - { url = "https://files.pythonhosted.org/packages/df/ed/e0ffeb4c848699c194dc9fb6a29ab29bcb2b6aac8c416bf18c51bfe8242c/grpcio-1.83.0-cp311-cp311-win_amd64.whl", hash = "sha256:7416952ca770477990257206276999056f8316d79196f2f25942393e58a20b49", size = 5164469, upload-time = "2026-07-23T15:19:31.941Z" }, { url = "https://files.pythonhosted.org/packages/15/2b/51e32514a4e9b715375c99721aadff0f24164cc2049b8269eda4de82a814/grpcio-1.83.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:28f6c35ac8fcf10e4594f138e468f194360089dde40d126a7033e863fc479930", size = 6303167, upload-time = "2026-07-23T15:19:33.78Z" }, { url = "https://files.pythonhosted.org/packages/39/33/b5b50fc2c6fbe350e04814047bb2d409feec7b36ef8b170254c050e06bc0/grpcio-1.83.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:33898e6a28e4ae598f1577cb1c4fec2a15c033d0ec52b9b45a09610dd045b9da", size = 12160538, upload-time = "2026-07-23T15:19:35.958Z" }, { url = "https://files.pythonhosted.org/packages/7b/5f/734e72e7b9f79bcf0b2c270b8d3bca0e4ebb97a27a50d06240b145f6d41e/grpcio-1.83.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6fb8a1dd0c6f0f931e69e9d0dc6d1c406ed2a44fa963414eafba07b7fb685d16", size = 6869310, upload-time = "2026-07-23T15:19:38.607Z" }, @@ -1273,16 +1148,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/c1/b1/50b17b3a2ba8970dbb00b2035a4df218bc6ec88d2d77fc7da4e42e1c7b19/grpcio_tools-1.83.0.tar.gz", hash = "sha256:515907265d14fa9975d0c7723f95a9da01463d7ac607546a03f8741f86a1bb07", size = 6400437, upload-time = "2026-07-23T15:22:18.313Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b9/f0/d55c59b3af39d2ca975a2bafc5be1a60e0460a1b507bdbcf7bb8a17567db/grpcio_tools-1.83.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:72471a4a46909f1d798836c0a0aa2f568e10f6404585d7b22ac7330dd6a7bc74", size = 2652835, upload-time = "2026-07-23T15:21:06.072Z" }, - { url = "https://files.pythonhosted.org/packages/82/97/59bdf27c5f99848087b3b268c90b3fcc557d400c8d0224cf49c7ac573e60/grpcio_tools-1.83.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:7da547dd1e0b1fe3d6d5677e9c1848969ecdbd51a92c342cf82d885c5935de7d", size = 5967887, upload-time = "2026-07-23T15:21:08.19Z" }, - { url = "https://files.pythonhosted.org/packages/f6/c4/117d6688c4cb40240ce48da1ffac005e8da8bf63785634bdbb9143ca367e/grpcio_tools-1.83.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:33350bd94c4913f8eaf6ca79ce69cc673bb46ca5f800e6474b1d6899ed321dad", size = 2704869, upload-time = "2026-07-23T15:21:09.857Z" }, - { url = "https://files.pythonhosted.org/packages/1a/51/e4d1a89f69072bb93b975548c40fe9524219ab5777cce113c1e2183b1f2d/grpcio_tools-1.83.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:0f27da6dc58c910f31dcee4fcbfd05659c10c14b9e132f4f17da48d867e75eb4", size = 3032318, upload-time = "2026-07-23T15:21:11.55Z" }, - { url = "https://files.pythonhosted.org/packages/ae/26/f9a082b79ae7f7dac7050047614000721dcf3d1c6d14bffa881b1f7a9774/grpcio_tools-1.83.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b3a5000e8540efb80f74d9c760f36ed9408294d76edb0fa4b87fd287b0a8a258", size = 2774113, upload-time = "2026-07-23T15:21:13.037Z" }, - { url = "https://files.pythonhosted.org/packages/de/59/b715d431218f0e8382490db7b1d01b3d32392c359c1886eee7e2551edaa8/grpcio_tools-1.83.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f710032264ed114c9d3b52f4c5cf71d68ccf70f25c4cb5776fe60388374bc01b", size = 3226715, upload-time = "2026-07-23T15:21:14.88Z" }, - { url = "https://files.pythonhosted.org/packages/5f/99/c4e91a2116062f7b42ab99459eeda15ec59a07daad01f4f38753fb1584ac/grpcio_tools-1.83.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ad00de87334154901e9af50a7f3f95261a0133502c6c0cea1f4e6107245154c3", size = 3799000, upload-time = "2026-07-23T15:21:16.352Z" }, - { url = "https://files.pythonhosted.org/packages/b8/f7/0bb5e45a1f51822c0bcf5a355a06132c43b9dbe5c283edc8d0b63bec7626/grpcio_tools-1.83.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:de2b0c645363f7c4b005f145e06e790870dfb21bb7e162dec6fee2f054de9291", size = 3457774, upload-time = "2026-07-23T15:21:17.953Z" }, - { url = "https://files.pythonhosted.org/packages/e7/51/52aec42a3059bad4263a7a82c715a0fc08220f7533be41f0b9177b05468a/grpcio_tools-1.83.0-cp311-cp311-win32.whl", hash = "sha256:6d1a1c9e62689d04b63b227558b759a55dce8fb81a3934d3b5f95d1ee26a2b45", size = 1022798, upload-time = "2026-07-23T15:21:19.854Z" }, - { url = "https://files.pythonhosted.org/packages/46/22/034d6daa5b2fae4e4c1718111c1b791589c3743d7d8fb07984e0b59c7f2c/grpcio_tools-1.83.0-cp311-cp311-win_amd64.whl", hash = "sha256:a6ac3cc2c2d77f869a96dfaf2b1315852878babddfe2dc49b9fd47afbf502865", size = 1192474, upload-time = "2026-07-23T15:21:21.467Z" }, { url = "https://files.pythonhosted.org/packages/27/4d/02e4d809d880785a613697e4c0f8134a436ec0142dc3c11989ad1a1c787a/grpcio_tools-1.83.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:bd93bbe18c4424805fd2e39854f75d76f80655175621254dc43cb45ec8e91e85", size = 2653283, upload-time = "2026-07-23T15:21:22.973Z" }, { url = "https://files.pythonhosted.org/packages/01/c9/23e8423ac54c3858a5cfbca8a954fa292cab7b8a9a1ee9dc3259b906b763/grpcio_tools-1.83.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:dc2d370563ee1ee6c1769e49df35ef3f6e75cea8f25acf4ac6e54b335e6f788f", size = 5965938, upload-time = "2026-07-23T15:21:24.686Z" }, { url = "https://files.pythonhosted.org/packages/bf/a1/6eb17cf322bfbb76a9b9a8a5ca4a6b27f0af84821145969fc464feedaa0e/grpcio_tools-1.83.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8350e236470700b02bc4ba7f27a8796559630170e6527dda41c0344fbe988e56", size = 2705429, upload-time = "2026-07-23T15:21:26.519Z" }, @@ -1317,11 +1182,11 @@ wheels = [ [[package]] name = "gunicorn" -version = "26.1.0" +version = "26.2.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/38/b8/ec4ba3f6cace4091c34e27478b576bb80f2f06fab80fd42c0ecc785b308f/gunicorn-26.1.0.tar.gz", hash = "sha256:1413d777bf99d31ebeb08acd354b01f1ecc44db0aa7b811ae7b86c669232e4f7", size = 755923, upload-time = "2026-08-18T11:49:39.438Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d9/8a/e4ef6ee11701b6cd64702848415ffb69eeff85cb388a3c6c7fe86f22f3f8/gunicorn-26.2.0.tar.gz", hash = "sha256:62b864895d9ebff0b2f9867ba04fe811c93121596540830c9c916d0769668447", size = 787921, upload-time = "2026-08-24T15:05:59.3Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/19/dc/7a55fc605543fd5cb11c003fbbb21a1911d5e88a582cce6c5e063bf5c176/gunicorn-26.1.0-py3-none-any.whl", hash = "sha256:9f45bcddec5e9dc7a25a3bdccb0c6832f11fd5d4739b1ee36c8d2fec25f1dc86", size = 216237, upload-time = "2026-08-18T11:49:38.001Z" }, + { url = "https://files.pythonhosted.org/packages/fe/85/7522a52e5e2f42faf1a129113ab63e548c42e103e9af395b7bfe65e403e2/gunicorn-26.2.0-py3-none-any.whl", hash = "sha256:bd249d0b3f7972f7432f0a6b6ff3b3ee2d129f70cd1ff6c09a9dd9e29a2b88e3", size = 228389, upload-time = "2026-08-24T15:05:57.67Z" }, ] [[package]] @@ -1351,13 +1216,6 @@ version = "0.8.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/43/e5/d471fcb0e14523fe1c3f4ba58ca52480e7bd70ad7109a3846bc75892f7fb/httptools-0.8.0.tar.gz", hash = "sha256:6b2a32f18d97e16e90827d7a819ffa8dbd8cc245fc4e1fa9d1095b54ef4bd999", size = 271342, upload-time = "2026-05-25T22:17:48.841Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f8/d2/c3eedaef57de65c3cc5f8dc244cf12d09c84ad258a479055aad6db23206c/httptools-0.8.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ed377e64805bdba4943c82717333f8f8603a13b09aff9cead2717c6c817fb168", size = 208428, upload-time = "2026-05-25T22:16:59.717Z" }, - { url = "https://files.pythonhosted.org/packages/f1/94/dfe435d90d0ef61ec0f2cc3d480eef78c59727c6c2ce039f433882f6131a/httptools-0.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9518c406d7b310f05adb1a37f80acabac40504a575d7c0da6d3e365c695ac20d", size = 113366, upload-time = "2026-05-25T22:17:00.795Z" }, - { url = "https://files.pythonhosted.org/packages/cc/d4/13025f1a56e615dcb331e0bbe2d9a1143212b58c263385fc5d2e558f5bac/httptools-0.8.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:57278e6fa0424c42a8a3e454828ab4f0aff27b40cddf9679579b98c6dce6a376", size = 464676, upload-time = "2026-05-25T22:17:02.014Z" }, - { url = "https://files.pythonhosted.org/packages/bf/95/4c1c26c0b985f8a3331682d802598f14e32dc41bf7509266eb2c04ad4801/httptools-0.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bbb8caadb2b742d293169d2b458b5c001ef70e3158704aa3d3ef9597624c5d1d", size = 464235, upload-time = "2026-05-25T22:17:03.109Z" }, - { url = "https://files.pythonhosted.org/packages/a2/82/6735be2b0ca527718c431cdb8e5f70c3862c0844a687df0f572c51e11497/httptools-0.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:52dd695b865fe96d9d2b16b64a895f3f57bf3cb064e8383cd3b5713a069e8085", size = 449809, upload-time = "2026-05-25T22:17:04.443Z" }, - { url = "https://files.pythonhosted.org/packages/b5/f9/5811c74f37a758c8a4aa3dc430375119d335947e883efc4664d8f3559a41/httptools-0.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:20b4aac66ff65f7db06a375808b78f42a94970aa22e826b3cb2b43eb09174124", size = 452174, upload-time = "2026-05-25T22:17:05.476Z" }, - { url = "https://files.pythonhosted.org/packages/cc/94/97b75870dea07b71e3ec535cebe525b08d723152e4c7d13fa887e51f4de2/httptools-0.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:a1b4c8e7a489a0d750d91894e9a8cdc295838f1924c0ca903ae993456fddec07", size = 90991, upload-time = "2026-05-25T22:17:06.75Z" }, { url = "https://files.pythonhosted.org/packages/14/88/1d21a36da8f5cb0fa49eafd4b169eba5608d57e75bbcf61845cbc6243216/httptools-0.8.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:880490234c10f70a9830743097e8958d6e4b9f5a0ffc24515023afeef984054d", size = 208247, upload-time = "2026-05-25T22:17:07.843Z" }, { url = "https://files.pythonhosted.org/packages/a5/42/cc4feea2945cb3051038f090c9b36bd5b8a9d7f5a894a506a8983e33fd1c/httptools-0.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5931891fb7b441b8a3853cf1b85c82c903defce084dd5f6771ca46e31bf862c5", size = 113064, upload-time = "2026-05-25T22:17:09.136Z" }, { url = "https://files.pythonhosted.org/packages/e3/a6/febbb8b8db0f58b38e44ad6cb946e6a255ae49b55f2e8543408fb7501ccd/httptools-0.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b15fc622b0f869d19207c4089a501d9bcc63ca5e071ffdd2f03f922df882dcb2", size = 523851, upload-time = "2026-05-25T22:17:10.106Z" }, @@ -1413,13 +1271,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6f/99/27450763853a034bca1574d3e0a315164b33ff49c3862df6872dda45e25e/hypothesis-6.165.10-cp310-abi3-win32.whl", hash = "sha256:b33dc30170a7402e03c180f2c5ef69dc077152f35b91621e9cebcde9c7d71746", size = 669039, upload-time = "2026-08-16T22:55:11.962Z" }, { url = "https://files.pythonhosted.org/packages/2c/fc/ff2988b72b5705ad9ca500444bf3f43e3c2f41edfa034bbfeb23b215791a/hypothesis-6.165.10-cp310-abi3-win_amd64.whl", hash = "sha256:e9f924aa610c0618445e1e8738c822c3190ce2a2699a0cb48ec3a351a96761f2", size = 675213, upload-time = "2026-08-16T22:55:01.697Z" }, { url = "https://files.pythonhosted.org/packages/c5/8b/821810d36f78d9d9421cd2c5d9d36983b45bb3575c3086276cc5c76f9f73/hypothesis-6.165.10-cp310-abi3-win_arm64.whl", hash = "sha256:1d305448e9bd8e2f4f3cea0eafd809efdaab4e998a0019bc615650c8463e42f1", size = 673537, upload-time = "2026-08-16T22:54:47.898Z" }, - { url = "https://files.pythonhosted.org/packages/ed/c2/b9546ace11f241c9c02d389f258cb80c14447a8c885771c9f1f0bc1d85ca/hypothesis-6.165.10-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:592107a0faf6c9c3a63a8dbf13dfb1cbda1cf599b0bc11c953221b00204b9ce1", size = 783716, upload-time = "2026-08-16T22:55:36.624Z" }, - { url = "https://files.pythonhosted.org/packages/37/10/27c2fdd574fd798caf5e91eb51f7834b098f5d840ce733efb3fba79ef86e/hypothesis-6.165.10-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f9180c362bde06fd05380298ded4e234fbc0d6ede0a864835bfd91c1e24283d5", size = 779507, upload-time = "2026-08-16T22:55:07.633Z" }, - { url = "https://files.pythonhosted.org/packages/5e/b6/70bc23695f3783c4b0486b6cad47b08a20f791db4a3c1b25250add9659fa/hypothesis-6.165.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d623801ae3dcd97b77b983400ef3d48bf976648e4efff19929175322eaae074d", size = 1108406, upload-time = "2026-08-16T22:55:39.653Z" }, - { url = "https://files.pythonhosted.org/packages/71/4c/32e200bd7a352af4b7f4e3729aaa4cd002cb5fe8c4c6aef5599d0019f152/hypothesis-6.165.10-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20f6236cfb90b7817bb1a6a087589ca4aa46d73170f0dd62963952ed5dadc589", size = 1157850, upload-time = "2026-08-16T22:55:24.394Z" }, - { url = "https://files.pythonhosted.org/packages/03/a5/8efc2a9a484822efc0d0da466f50094e0f2c068187faaf33831fc905873e/hypothesis-6.165.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ad0764730e8e3421601c2cc7e1f054a9206c60ea0917165d8d9193dc453f34f1", size = 1283704, upload-time = "2026-08-16T22:54:27.279Z" }, - { url = "https://files.pythonhosted.org/packages/46/2a/90cc8d7463929c04786f29600de45f3227c12fa9bed1d5b7ce319b05e1c9/hypothesis-6.165.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:10d9a650a4666b0914831f769703d36140ed8039fd19bf9b71f615b8541eccf2", size = 1325077, upload-time = "2026-08-16T22:55:16.561Z" }, - { url = "https://files.pythonhosted.org/packages/82/ac/bc16faba4b42883e3d290bfaceff51e258b63fbbdf789bf9fe88df1ce537/hypothesis-6.165.10-cp311-cp311-win_amd64.whl", hash = "sha256:5671d2b2bf83bd4b6f02e55b32d432506eff5358c82f39b460a849ce19a2666e", size = 674920, upload-time = "2026-08-16T22:55:42.613Z" }, { url = "https://files.pythonhosted.org/packages/e9/45/cde4f78afe2b9e29caecf38319eedc1deb76aebcacbdd128e03cbb2511c3/hypothesis-6.165.10-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:637445c1593a2a9d1024fda50082f07bb56baedda78d90a25f64b8111727ef94", size = 784835, upload-time = "2026-08-16T22:54:45.429Z" }, { url = "https://files.pythonhosted.org/packages/7f/81/847f30b81cbfd07607296b3ce43067cf4f80799bd9244167f587de9c8081/hypothesis-6.165.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:713f4ce4e82c26b53031f139de959bc9e8b54d3995aa824b89bbdf8229df2a45", size = 776419, upload-time = "2026-08-16T22:55:33.633Z" }, { url = "https://files.pythonhosted.org/packages/04/66/4c71c5be7a49d84b8c3a9278c1807c4c81181ab5474beb27df9d4c40dc0e/hypothesis-6.165.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9ff356e97e3ab09db07c8b675efa67340103874a0bae7465acb83dad7a35f7f", size = 1106830, upload-time = "2026-08-16T22:55:10.389Z" }, @@ -1465,11 +1316,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/55/63/ad5cc153dcc72ae5e7905fb9b3585f3e48ce892a2d6366f90163e867a69d/hypothesis-6.165.10-cp315-abi3.abi3t-win32.whl", hash = "sha256:c6559380469295c4009215fe1cab561301591a3bee2e2fb3f4f96d2273a3affc", size = 666038, upload-time = "2026-08-16T22:56:11.797Z" }, { url = "https://files.pythonhosted.org/packages/80/32/b62307b73fbc99f0a4381d6f9456df76fbcbb7a27ef7256e26f0376f48ea/hypothesis-6.165.10-cp315-abi3.abi3t-win_amd64.whl", hash = "sha256:30797f20ca45e57f526d2df872f63ba453cb4e1091ad542184a7a951af8da79d", size = 671941, upload-time = "2026-08-16T22:55:00.235Z" }, { url = "https://files.pythonhosted.org/packages/c2/dd/e0f98add0548ef73ea7afac45da1fb8efc854d7f9931db568754d0f963f3/hypothesis-6.165.10-cp315-abi3.abi3t-win_arm64.whl", hash = "sha256:c53e9b1c36350df9965ec44d6c0d4e0bbbb38f720dd2b0e1256dc6524d411015", size = 669931, upload-time = "2026-08-16T22:55:50.205Z" }, - { url = "https://files.pythonhosted.org/packages/0b/6a/880d6eeed5c451fb40a66733dadec4a5d498628a4a7f6a8a5f633f4c6dcb/hypothesis-6.165.10-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:34ee6402df6f31274d89119f1561b5f7489c97866afc5b7a3ed3a13d7e762802", size = 784644, upload-time = "2026-08-16T22:54:20.127Z" }, - { url = "https://files.pythonhosted.org/packages/27/e0/9e942bd3c3cf5ea0d5c0fd0905893bbfb6cefb7284c70fcc8033f8fdec38/hypothesis-6.165.10-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:277f41801e88dad2eba082f91a75632b7584ff64044ba2cf9dadf511b0d19cd0", size = 780515, upload-time = "2026-08-16T22:55:04.676Z" }, - { url = "https://files.pythonhosted.org/packages/19/32/f11a618415dc5fa9cdde41fea56c489f0814759527ae1ecd11a75a4558b9/hypothesis-6.165.10-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:72df95fb1db41755b155c5f02106e0036a339250555c8d351d488704fd112cf9", size = 1109374, upload-time = "2026-08-16T22:56:00.241Z" }, - { url = "https://files.pythonhosted.org/packages/5e/6f/db49b719842297c2b71e0d81e5b8967d31215fb7389421abcb465ce7ed3f/hypothesis-6.165.10-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e20a02775eb3cf0ffb4f0219b6d7c1f240336663d4e5d7028675ec247c790c4", size = 1159092, upload-time = "2026-08-16T22:55:58.57Z" }, - { url = "https://files.pythonhosted.org/packages/b2/2a/bf0bae84ba1cb3923d295973f1fe38ee867eaf90119e0d559116083be300/hypothesis-6.165.10-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:1ec53f08732e3cfd0342cbbd75dbd1b193c8f19390660466e536a748bb81f757", size = 676045, upload-time = "2026-08-16T22:55:45.514Z" }, ] [[package]] @@ -1562,17 +1408,6 @@ version = "3.0.3" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631, upload-time = "2025-09-27T18:36:18.185Z" }, - { url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058, upload-time = "2025-09-27T18:36:19.444Z" }, - { url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287, upload-time = "2025-09-27T18:36:20.768Z" }, - { url = "https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", size = 22940, upload-time = "2025-09-27T18:36:22.249Z" }, - { url = "https://files.pythonhosted.org/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", size = 21887, upload-time = "2025-09-27T18:36:23.535Z" }, - { url = "https://files.pythonhosted.org/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", size = 23692, upload-time = "2025-09-27T18:36:24.823Z" }, - { url = "https://files.pythonhosted.org/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", size = 21471, upload-time = "2025-09-27T18:36:25.95Z" }, - { url = "https://files.pythonhosted.org/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", size = 22923, upload-time = "2025-09-27T18:36:27.109Z" }, - { url = "https://files.pythonhosted.org/packages/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", size = 14572, upload-time = "2025-09-27T18:36:28.045Z" }, - { url = "https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", size = 15077, upload-time = "2025-09-27T18:36:29.025Z" }, - { url = "https://files.pythonhosted.org/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", size = 13876, upload-time = "2025-09-27T18:36:29.954Z" }, { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, @@ -1636,17 +1471,6 @@ version = "1.2.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/31/f9/c0a1c127f9049db9155afc316952ea571720dd01833ff5e4d7e8e6352dbb/msgpack-1.2.1.tar.gz", hash = "sha256:04c721c2c7448767e9e3f2520a475663d8ee0f09c31890f6d2bd70fd636a9647", size = 183960, upload-time = "2026-06-18T16:13:52.594Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/6b/e9b1cdc042c4458801d2545ed782a95f3d6ba8e270cce8745b8603c7f748/msgpack-1.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:29a3f6e9667868429d8240dfd063ea5ffdc1321c13d783aa23827a38de0dcb22", size = 82812, upload-time = "2026-06-18T16:12:45.022Z" }, - { url = "https://files.pythonhosted.org/packages/0c/3a/dd518a1bf78ed1e9ad8afe57307c079a00eafe4b3068932a27ca1ea56b4f/msgpack-1.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:aded5bdf32609dc7987a49bbbd15a8ef096193f96dd8bbeb791de729e650acf5", size = 82739, upload-time = "2026-06-18T16:12:46.025Z" }, - { url = "https://files.pythonhosted.org/packages/70/e0/7ba9e1542bf0771a27b8b37c1316e3f95ae9d748fd765284655c476ad4ef/msgpack-1.2.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:146ee4e9ce80b365c6d4c47073da9da7bcec473e58194ceee5dd7620ace77e06", size = 414233, upload-time = "2026-06-18T16:12:47.029Z" }, - { url = "https://files.pythonhosted.org/packages/03/8d/671d81534ea0e2b0e8a121be100020da09eb78861fe3aa8f3ef7dcd3bed1/msgpack-1.2.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a28d076ca7c82b9c8728ad90b7147489449557038bed50e4241eb832395169b4", size = 423843, upload-time = "2026-06-18T16:12:48.19Z" }, - { url = "https://files.pythonhosted.org/packages/d2/b6/e5c737515ed1f166664b87601b532f58cbb73d8aa6a90b99f7c2c5037e8e/msgpack-1.2.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7d31c0ac0c640f877804c67cb2bc9f4e23dc2db97e96c2e67fa27d38283b41f8", size = 390772, upload-time = "2026-06-18T16:12:49.624Z" }, - { url = "https://files.pythonhosted.org/packages/a8/46/62ed8c2e87d7021eab19921594d961ef3aa3794eec76c716dc30f3bfd433/msgpack-1.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8ff92d7feeaf5bc26c51495b69e2f99ed97ab79346fb6555f44be7dd2ac6503b", size = 409559, upload-time = "2026-06-18T16:12:50.936Z" }, - { url = "https://files.pythonhosted.org/packages/70/ff/59aa3887b860bbf43532835e192b1c388a17590d6068ae4f8b2bc74c906e/msgpack-1.2.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:779197a6513bab3c3632265e3d0f7cb3227e62510841a6f34f1eaa37efbb345e", size = 387838, upload-time = "2026-06-18T16:12:52.161Z" }, - { url = "https://files.pythonhosted.org/packages/09/11/f8563e471093420cf6478cb3271a0175d8402b82d879783d4035d2d03360/msgpack-1.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:67f6dd22fa72a93752643f07889796d62739a13415ee630169a8ce764f86cf9f", size = 421732, upload-time = "2026-06-18T16:12:53.556Z" }, - { url = "https://files.pythonhosted.org/packages/57/cf/e673683c4c6c90c1022b24c65af4b03eda72b182a1176ef6449069d66acc/msgpack-1.2.1-cp311-cp311-win32.whl", hash = "sha256:91054a783328e0ea7954b8771095705c8d2243b814743fbaadf14552c9c52c5d", size = 64091, upload-time = "2026-06-18T16:12:54.821Z" }, - { url = "https://files.pythonhosted.org/packages/3f/07/ca212739d179f9083bff2c7c08c24101c3555a334fadc2b876b18768a3ae/msgpack-1.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2eda0b7ebb1283a98d3e4492ac933c8af6aff59fd3df1c3ed024f536af4b1dc8", size = 70462, upload-time = "2026-06-18T16:12:55.898Z" }, - { url = "https://files.pythonhosted.org/packages/6d/be/6798347b425e26f35db82e69dd83c09716c856a3714e7bffc4c0860fd830/msgpack-1.2.1-cp311-cp311-win_arm64.whl", hash = "sha256:6ee967f7c7e1df2890c671ff2ee51a28ded0efc95da3e507176dee881ce36c66", size = 65059, upload-time = "2026-06-18T16:12:57.053Z" }, { url = "https://files.pythonhosted.org/packages/bc/dd/9e8cbd8f5582ca4b590336f2b91ee5662f6a6ca562b565abaf696a0f81ff/msgpack-1.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2ef59c659f289eddf8aa6623823f19fa2f40a4029266889eac7a2505dd210c35", size = 83531, upload-time = "2026-06-18T16:12:58.249Z" }, { url = "https://files.pythonhosted.org/packages/50/2e/ebdb85a8da151397a2790363676b7ed7c125924fe618e4c6d8befb0cc62c/msgpack-1.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d3567748a5107cb40cdf66a275430c2f87c07777698f4bfd25c35f44d533258c", size = 82657, upload-time = "2026-06-18T16:12:59.396Z" }, { url = "https://files.pythonhosted.org/packages/26/aa/753ad8b007b464e1d8aa0c8e650b9c5f4f725e658fc5ac8a7635c55b7f6e/msgpack-1.2.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:60926b75d00c8e816ef98f3034f484a8bc64242d66839cef4cf7e503142316a0", size = 410634, upload-time = "2026-06-18T16:13:00.383Z" }, @@ -1699,24 +1523,6 @@ version = "6.7.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/1a/c2/c2d94cbe6ac1753f3fc980da97b3d930efe1da3af3c9f5125354436c073d/multidict-6.7.1.tar.gz", hash = "sha256:ec6652a1bee61c53a3e5776b6049172c53b6aaba34f18c9ad04f82712bac623d", size = 102010, upload-time = "2026-01-26T02:46:45.979Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ce/f1/a90635c4f88fb913fbf4ce660b83b7445b7a02615bda034b2f8eb38fd597/multidict-6.7.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7ff981b266af91d7b4b3793ca3382e53229088d193a85dfad6f5f4c27fc73e5d", size = 76626, upload-time = "2026-01-26T02:43:26.485Z" }, - { url = "https://files.pythonhosted.org/packages/a6/9b/267e64eaf6fc637a15b35f5de31a566634a2740f97d8d094a69d34f524a4/multidict-6.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:844c5bca0b5444adb44a623fb0a1310c2f4cd41f402126bb269cd44c9b3f3e1e", size = 44706, upload-time = "2026-01-26T02:43:27.607Z" }, - { url = "https://files.pythonhosted.org/packages/dd/a4/d45caf2b97b035c57267791ecfaafbd59c68212004b3842830954bb4b02e/multidict-6.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f2a0a924d4c2e9afcd7ec64f9de35fcd96915149b2216e1cb2c10a56df483855", size = 44356, upload-time = "2026-01-26T02:43:28.661Z" }, - { url = "https://files.pythonhosted.org/packages/fd/d2/0a36c8473f0cbaeadd5db6c8b72d15bbceeec275807772bfcd059bef487d/multidict-6.7.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8be1802715a8e892c784c0197c2ace276ea52702a0ede98b6310c8f255a5afb3", size = 244355, upload-time = "2026-01-26T02:43:31.165Z" }, - { url = "https://files.pythonhosted.org/packages/5d/16/8c65be997fd7dd311b7d39c7b6e71a0cb449bad093761481eccbbe4b42a2/multidict-6.7.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2e2d2ed645ea29f31c4c7ea1552fcfd7cb7ba656e1eafd4134a6620c9f5fdd9e", size = 246433, upload-time = "2026-01-26T02:43:32.581Z" }, - { url = "https://files.pythonhosted.org/packages/01/fb/4dbd7e848d2799c6a026ec88ad39cf2b8416aa167fcc903baa55ecaa045c/multidict-6.7.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:95922cee9a778659e91db6497596435777bd25ed116701a4c034f8e46544955a", size = 225376, upload-time = "2026-01-26T02:43:34.417Z" }, - { url = "https://files.pythonhosted.org/packages/b6/8a/4a3a6341eac3830f6053062f8fbc9a9e54407c80755b3f05bc427295c2d0/multidict-6.7.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6b83cabdc375ffaaa15edd97eb7c0c672ad788e2687004990074d7d6c9b140c8", size = 257365, upload-time = "2026-01-26T02:43:35.741Z" }, - { url = "https://files.pythonhosted.org/packages/f7/a2/dd575a69c1aa206e12d27d0770cdf9b92434b48a9ef0cd0d1afdecaa93c4/multidict-6.7.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:38fb49540705369bab8484db0689d86c0a33a0a9f2c1b197f506b71b4b6c19b0", size = 254747, upload-time = "2026-01-26T02:43:36.976Z" }, - { url = "https://files.pythonhosted.org/packages/5a/56/21b27c560c13822ed93133f08aa6372c53a8e067f11fbed37b4adcdac922/multidict-6.7.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:439cbebd499f92e9aa6793016a8acaa161dfa749ae86d20960189f5398a19144", size = 246293, upload-time = "2026-01-26T02:43:38.258Z" }, - { url = "https://files.pythonhosted.org/packages/5a/a4/23466059dc3854763423d0ad6c0f3683a379d97673b1b89ec33826e46728/multidict-6.7.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6d3bc717b6fe763b8be3f2bee2701d3c8eb1b2a8ae9f60910f1b2860c82b6c49", size = 242962, upload-time = "2026-01-26T02:43:40.034Z" }, - { url = "https://files.pythonhosted.org/packages/1f/67/51dd754a3524d685958001e8fa20a0f5f90a6a856e0a9dcabff69be3dbb7/multidict-6.7.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:619e5a1ac57986dbfec9f0b301d865dddf763696435e2962f6d9cf2fdff2bb71", size = 237360, upload-time = "2026-01-26T02:43:41.752Z" }, - { url = "https://files.pythonhosted.org/packages/64/3f/036dfc8c174934d4b55d86ff4f978e558b0e585cef70cfc1ad01adc6bf18/multidict-6.7.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0b38ebffd9be37c1170d33bc0f36f4f262e0a09bc1aac1c34c7aa51a7293f0b3", size = 245940, upload-time = "2026-01-26T02:43:43.042Z" }, - { url = "https://files.pythonhosted.org/packages/3d/20/6214d3c105928ebc353a1c644a6ef1408bc5794fcb4f170bb524a3c16311/multidict-6.7.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:10ae39c9cfe6adedcdb764f5e8411d4a92b055e35573a2eaa88d3323289ef93c", size = 253502, upload-time = "2026-01-26T02:43:44.371Z" }, - { url = "https://files.pythonhosted.org/packages/b1/e2/c653bc4ae1be70a0f836b82172d643fcf1dade042ba2676ab08ec08bff0f/multidict-6.7.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:25167cc263257660290fba06b9318d2026e3c910be240a146e1f66dd114af2b0", size = 247065, upload-time = "2026-01-26T02:43:45.745Z" }, - { url = "https://files.pythonhosted.org/packages/c8/11/a854b4154cd3bd8b1fd375e8a8ca9d73be37610c361543d56f764109509b/multidict-6.7.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:128441d052254f42989ef98b7b6a6ecb1e6f708aa962c7984235316db59f50fa", size = 241870, upload-time = "2026-01-26T02:43:47.054Z" }, - { url = "https://files.pythonhosted.org/packages/13/bf/9676c0392309b5fdae322333d22a829715b570edb9baa8016a517b55b558/multidict-6.7.1-cp311-cp311-win32.whl", hash = "sha256:d62b7f64ffde3b99d06b707a280db04fb3855b55f5a06df387236051d0668f4a", size = 41302, upload-time = "2026-01-26T02:43:48.753Z" }, - { url = "https://files.pythonhosted.org/packages/c9/68/f16a3a8ba6f7b6dc92a1f19669c0810bd2c43fc5a02da13b1cbf8e253845/multidict-6.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:bdbf9f3b332abd0cdb306e7c2113818ab1e922dc84b8f8fd06ec89ed2a19ab8b", size = 45981, upload-time = "2026-01-26T02:43:49.921Z" }, - { url = "https://files.pythonhosted.org/packages/ac/ad/9dd5305253fa00cd3c7555dbef69d5bf4133debc53b87ab8d6a44d411665/multidict-6.7.1-cp311-cp311-win_arm64.whl", hash = "sha256:b8c990b037d2fff2f4e33d3f21b9b531c5745b33a49a7d6dbe7a177266af44f6", size = 43159, upload-time = "2026-01-26T02:43:51.635Z" }, { url = "https://files.pythonhosted.org/packages/8d/9c/f20e0e2cf80e4b2e4b1c365bf5fe104ee633c751a724246262db8f1a0b13/multidict-6.7.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a90f75c956e32891a4eda3639ce6dd86e87105271f43d43442a3aedf3cddf172", size = 76893, upload-time = "2026-01-26T02:43:52.754Z" }, { url = "https://files.pythonhosted.org/packages/fe/cf/18ef143a81610136d3da8193da9d80bfe1cb548a1e2d1c775f26b23d024a/multidict-6.7.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fccb473e87eaa1382689053e4a4618e7ba7b9b9b8d6adf2027ee474597128cd", size = 45456, upload-time = "2026-01-26T02:43:53.893Z" }, { url = "https://files.pythonhosted.org/packages/a9/65/1caac9d4cd32e8433908683446eebc953e82d22b03d10d41a5f0fefe991b/multidict-6.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0fa96985700739c4c7853a43c0b3e169360d6855780021bfc6d0f1ce7c123e7", size = 43872, upload-time = "2026-01-26T02:43:55.041Z" }, @@ -1854,11 +1660,11 @@ wheels = [ [[package]] name = "platformdirs" -version = "4.11.3" +version = "4.11.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b8/d7/e7bfbc86e9f99ff7807e24de7703f032e9c9ba80bb355cf26e0e9bc5a75e/platformdirs-4.11.3.tar.gz", hash = "sha256:66a73d38a849810252df809a3d8bcbda8e26f6c189920e7535ad608a48dbb5ab", size = 33050, upload-time = "2026-08-13T22:43:27.52Z" } +sdist = { url = "https://files.pythonhosted.org/packages/50/bb/ebc6636e1ae41314f796ebb7215fd28febb45f9aac72f2b04cb74b5071dc/platformdirs-4.11.4.tar.gz", hash = "sha256:f3373be828247211d0febabea97e238c3dfde8a60b3c90c32756fb52cb21556d", size = 34079, upload-time = "2026-08-24T14:53:49.676Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/19/a9/c34aebedd3a4c9afe5101b1b8713710b3fec18087c8a36c35d2f909861bd/platformdirs-4.11.3-py3-none-any.whl", hash = "sha256:5ed065d443751de711da036041a7a214122efc4a4de393b3f4137ba5576540e7", size = 23491, upload-time = "2026-08-13T22:43:26.121Z" }, + { url = "https://files.pythonhosted.org/packages/28/be/0ff05fcd2938fb58ad9219bd54135968342d214737e012d62d43f06a2dd6/platformdirs-4.11.4-py3-none-any.whl", hash = "sha256:e34ff91a24bcddc6d939b878bdf3f5c437c9c46fe9e212b1bf455fdf1ee57586", size = 23741, upload-time = "2026-08-24T14:53:48.406Z" }, ] [[package]] @@ -1892,23 +1698,6 @@ version = "0.5.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/ec/44/c87281c333769159c50594f22610f77398a47ccbfbbf23074e744e86f87c/propcache-0.5.2.tar.gz", hash = "sha256:01c4fc7480cd0598bb4b57022df55b9ca296da7fc5a8760bd8451a7e63a7d427", size = 50208, upload-time = "2026-05-08T21:02:12.199Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e7/f1/8a8cc1c2c7e7934ab77e0163414f736fadbc0f5e8dd9673b952355ac175b/propcache-0.5.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:74b70780220e2dd89175ca24b81b68b67c83db499ae611e7f2313cb329801c78", size = 90744, upload-time = "2026-05-08T20:59:45.799Z" }, - { url = "https://files.pythonhosted.org/packages/c2/f4/651b1225e976bd1a2ba5cfba0c29d096581c2636b437e3a9a7ab6276270a/propcache-0.5.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a4840ab0ae0216d952f4b53dc6d0b992bfc2bedbfe360bdd9b548bc184c08959", size = 52033, upload-time = "2026-05-08T20:59:47.408Z" }, - { url = "https://files.pythonhosted.org/packages/15/a8/8ede85d6aa1f79fc7dc2f8fd2c8d65920b8272c3892903c8a1affde48cfb/propcache-0.5.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c6844ba6364fb12f403928a82cfd295ab103a2b315c77c747b2dbe4a41894ea7", size = 52754, upload-time = "2026-05-08T20:59:49.202Z" }, - { url = "https://files.pythonhosted.org/packages/7d/fe/b3551b41bbc2f5b5bb088fc6920567cd43101253e68fbaa261339eb96fe1/propcache-0.5.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2293949b855ce597f2826452d17c2d545fb5622379c4ea6fdf525e9b8e8a2511", size = 57573, upload-time = "2026-05-08T20:59:50.778Z" }, - { url = "https://files.pythonhosted.org/packages/83/27/ab851ebd1b7172e3e161f5f8d39e315d54a91bea246f01f4d872d3376aef/propcache-0.5.2-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0fd59b5af35f74da48d905dcbad55449ba13be91823cb05a9bd590bbf5b61660", size = 60645, upload-time = "2026-05-08T20:59:52.227Z" }, - { url = "https://files.pythonhosted.org/packages/95/7d/466b3d18022e9897cbda9c735c493c5bd747d7a4c6f5ea1480b4cec434b6/propcache-0.5.2-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29f9309a2e42b0d273be006fdb4be2d6c39a47f6f57d8fb1cf9f81481df81b66", size = 61563, upload-time = "2026-05-08T20:59:53.866Z" }, - { url = "https://files.pythonhosted.org/packages/27/1b/16ab7f2cf2041da2f60d156ba64c2484eadf9168075b4ff43c3ef60045af/propcache-0.5.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5aaa2b923c1944ac8febd6609cb373540a5563e7cbcb0fd770f75dace2eb817b", size = 58888, upload-time = "2026-05-08T20:59:55.457Z" }, - { url = "https://files.pythonhosted.org/packages/0a/67/bb777ffd907633563bf35fd859c4ce97b0512c32f4633cf5d1eb7c33512b/propcache-0.5.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:66ea454f095ddf5b6b14f56c064c0941c4788be11e18d2464cf643bf7203ff67", size = 59253, upload-time = "2026-05-08T20:59:57.075Z" }, - { url = "https://files.pythonhosted.org/packages/b9/42/64f8d90b73fd9cdc1499b48057ff6d9cd2a98a25734c9bb62ecf07e87061/propcache-0.5.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:95f1e3f4760d404b13c9976c0229b2b49a3c8e2c62a9ce92efdd2b11ada75e3f", size = 57558, upload-time = "2026-05-08T20:59:58.602Z" }, - { url = "https://files.pythonhosted.org/packages/eb/02/dba5bc03c9041f2092ea55a449caf5dfe68352c6654511b29ba0654ddb69/propcache-0.5.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:85341b12b9d55bad0bded24cac341bb34289469e03a11f3f583ea1cc1db0326c", size = 55007, upload-time = "2026-05-08T20:59:59.837Z" }, - { url = "https://files.pythonhosted.org/packages/14/c0/43f649c7aa2a77a3b100d84e9dea3a483120ecb608bfe36ce49eaff517fe/propcache-0.5.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:26a4dca084132874e639895c3135dfad5eb20bae209f62d1aeb31b03e601c3c0", size = 60355, upload-time = "2026-05-08T21:00:01.144Z" }, - { url = "https://files.pythonhosted.org/packages/83/c0/435dafd27f1cb4a495381dae60e25883ccfe4020bb72818e8184c1678092/propcache-0.5.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:3b199b9b2b3d6a7edf3183ba8a9a137a22b97f7df525feb5ae1eccf026d2a9c6", size = 59057, upload-time = "2026-05-08T21:00:02.401Z" }, - { url = "https://files.pythonhosted.org/packages/53/ae/6e292df9135d659944e96cb3389258e4a663e5b2b5f6c217ef0ddc8d2f73/propcache-0.5.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e59bc9e66329185b93dab73f210f1a37f81cb40f321501db8017c9aea15dba27", size = 61938, upload-time = "2026-05-08T21:00:03.638Z" }, - { url = "https://files.pythonhosted.org/packages/0b/42/314ebc50d8159055411fd6b0bda322ff510e4b1f7d2e4927940ad0f6af20/propcache-0.5.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:552ffadf6ad409844bc5919c42a0a83d88314cedddaea0e41e80a8b8fffe881f", size = 59731, upload-time = "2026-05-08T21:00:04.881Z" }, - { url = "https://files.pythonhosted.org/packages/b8/9b/2da6dee38871c3c8772fabc2758325a5c9077d6d18c597737dc04dd884cd/propcache-0.5.2-cp311-cp311-win32.whl", hash = "sha256:cd416c1de191973c52ff1a12a57446bfc7642797b282d7caf2162d7d1b8aa9a0", size = 38966, upload-time = "2026-05-08T21:00:06.511Z" }, - { url = "https://files.pythonhosted.org/packages/42/4e/f17363fb58c0afe05b067361cb6d86ed2d29de6506779a27547c4d183075/propcache-0.5.2-cp311-cp311-win_amd64.whl", hash = "sha256:44e488ef40dbb452700b2b1f8188934121f6648f52c295055662d2191959ff82", size = 42135, upload-time = "2026-05-08T21:00:08.088Z" }, - { url = "https://files.pythonhosted.org/packages/c6/eb/6af6685077d22e8b33358d3c548e3282706a0b3cd85044ffba4e5dd08e3b/propcache-0.5.2-cp311-cp311-win_arm64.whl", hash = "sha256:54adaa85a22078d1e306304a40984dc5be99d599bf3dc0a24dc98f7daeab89ab", size = 38381, upload-time = "2026-05-08T21:00:09.692Z" }, { url = "https://files.pythonhosted.org/packages/4a/cb/e27bc2b2737a0bb49962b275efa051e8f1c35a936df7d5139b6b658b7dc9/propcache-0.5.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:806719138ecd720339a12410fb9614ac9b2b2d3a5fdf8235d56981c36f4039ba", size = 95887, upload-time = "2026-05-08T21:00:11.277Z" }, { url = "https://files.pythonhosted.org/packages/e6/13/b8ae04c59392f8d11c6cd9fb4011d1dc7c86b81225c770280300e259ffe1/propcache-0.5.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:db2b80ea58eab4f86b2beec3cc8b39e8ff9276ac20e96b7cce43c8ae84cd6b5a", size = 54654, upload-time = "2026-05-08T21:00:12.604Z" }, { url = "https://files.pythonhosted.org/packages/2c/7d/49777a3e20b55863d4794384a38acd460c04157b0a00f8602b0d508b8431/propcache-0.5.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e5cbfac9f61484f7e9f3597775500cd3ebe8274e9b050c38f9525c77c97520bf", size = 55190, upload-time = "2026-05-08T21:00:13.935Z" }, @@ -2011,17 +1800,17 @@ wheels = [ [[package]] name = "protobuf" -version = "7.35.1" +version = "7.36.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/da/01/9ef0afd7999eb9badb3a768b4aedd78c86d4c65cfaf1958ab276199e76b4/protobuf-7.35.1.tar.gz", hash = "sha256:ce115a26fe0c39a2c29973d914d327e516a6455464489fe3cd1e51a1b354f81a", size = 458717, upload-time = "2026-06-11T21:55:40.257Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a7/e7/0553e21d25ca4d9f573135775348a372c3ec34a93a71d5f297c3bac38341/protobuf-7.36.0.tar.gz", hash = "sha256:e8e09cb0d794c6687926fa558a8a6e72aa10edb997d5ca61da0765f12a3e00ea", size = 510034, upload-time = "2026-08-20T16:34:01.071Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/10/03/8aeeb7458d22546bf64b5250ca1daeb5ff757d900e8e4a7476c6f0db843e/protobuf-7.35.1-cp310-abi3-macosx_10_9_universal2.whl", hash = "sha256:24f857477359a85c0c235261b8ba905fd51b2562f4a64ca1df5473f29850cbf6", size = 433226, upload-time = "2026-06-11T21:55:31.719Z" }, - { url = "https://files.pythonhosted.org/packages/37/4b/dfb89eb0e652a1ff073c39a59fb5e3a83cfe9b57a2c83fa6d78270101767/protobuf-7.35.1-cp310-abi3-manylinux2014_aarch64.whl", hash = "sha256:11d6b0ec246892d85215b0a13ca6e0233cf5284b68f0ac02646427f4ff88a799", size = 328847, upload-time = "2026-06-11T21:55:34.035Z" }, - { url = "https://files.pythonhosted.org/packages/0f/58/dc12f2cd484951524af6e3382c785869b9b3fb5e52ee95ae23add53ee8f9/protobuf-7.35.1-cp310-abi3-manylinux2014_s390x.whl", hash = "sha256:b73f9489a4b8b1c9cb1f8ed951c736392592edb24b9d6819f36d2e10b171d5b4", size = 344030, upload-time = "2026-06-11T21:55:34.941Z" }, - { url = "https://files.pythonhosted.org/packages/e4/be/5b3cfe508bfab6761414ff944e3366eb13be4fd71efcd69450f89ba39f43/protobuf-7.35.1-cp310-abi3-manylinux2014_x86_64.whl", hash = "sha256:74758715c53d7158fb76caf4f0cfdacc5329a4b1bb994f865d6cf302d413a1c4", size = 327130, upload-time = "2026-06-11T21:55:35.921Z" }, - { url = "https://files.pythonhosted.org/packages/d8/bc/6d6c7ba8709c85f8f2c390b2b118d6fb08a783676a572271851bf45a7d22/protobuf-7.35.1-cp310-abi3-win32.whl", hash = "sha256:353652e4efd0bca5b5fc2656abf8307ef351f0cf938c9eba09f0e09c20a25c30", size = 428945, upload-time = "2026-06-11T21:55:37.034Z" }, - { url = "https://files.pythonhosted.org/packages/0a/19/8d0cb6f20a1ef7b18f1c8986ad5783f22f84cce39c6ce9a6e645ea55192e/protobuf-7.35.1-cp310-abi3-win_amd64.whl", hash = "sha256:230a75ddfc2de4806e56696ce9640c1cdfdb6543b7cfce98d42a4c0a0e7bdb87", size = 439996, upload-time = "2026-06-11T21:55:38.123Z" }, - { url = "https://files.pythonhosted.org/packages/19/c7/5f7c636ec43e0c545e28d1f1db71990108306f7bdcb89f069ba97e428e7f/protobuf-7.35.1-py3-none-any.whl", hash = "sha256:4bc97768d8fe4ad6743c8a19403e314511ed9f6d13205b687e52421c023ac1b9", size = 171659, upload-time = "2026-06-11T21:55:39.155Z" }, + { url = "https://files.pythonhosted.org/packages/8f/ae/58e3ca96cb2e118cc546b677359b3c6659f79a140935c08dec94c7998585/protobuf-7.36.0-cp310-abi3-macosx_10_9_universal2.whl", hash = "sha256:9103532dffd80c6fab7e50c65a31007680a06eb57537d437bb1b35812c138a37", size = 453256, upload-time = "2026-08-20T16:33:53.945Z" }, + { url = "https://files.pythonhosted.org/packages/f0/15/5162230af4912697f0fe406f6800f80760945babcff0e2c2fe6c84ef2d5d/protobuf-7.36.0-cp310-abi3-manylinux2014_aarch64.whl", hash = "sha256:bf94a5917c71058262de683669bc0a797a7669d3de71f0b36d058e3194f47b44", size = 341436, upload-time = "2026-08-20T16:33:55.134Z" }, + { url = "https://files.pythonhosted.org/packages/d7/09/1670b2bfc9a45e807e520c3e9be36524db9ccc7dc05ea17af7681cabdc61/protobuf-7.36.0-cp310-abi3-manylinux2014_s390x.whl", hash = "sha256:3297e60abdff301e5f74393d87f6cc59dacab5f024a89548a6e8de1d26576b16", size = 354440, upload-time = "2026-08-20T16:33:56.077Z" }, + { url = "https://files.pythonhosted.org/packages/c7/f8/bd5804695ba400e423c33fd4d9f58c28d86633d5ba1945c36ff3967d98cb/protobuf-7.36.0-cp310-abi3-manylinux2014_x86_64.whl", hash = "sha256:70f5ec8eb0da81a44360c0dc0beac99a0d78071d21956a7076bae8bd2051841b", size = 340439, upload-time = "2026-08-20T16:33:56.992Z" }, + { url = "https://files.pythonhosted.org/packages/ef/9f/acd02338235a3e7d03168c4303478347b7624fc8189ff4e7f0d2654bbe86/protobuf-7.36.0-cp310-abi3-win32.whl", hash = "sha256:7326fd717bdc419162a735938d89d4032332bcc3408804012b24ff3a37086071", size = 440216, upload-time = "2026-08-20T16:33:57.99Z" }, + { url = "https://files.pythonhosted.org/packages/0e/4e/12cb93270967a2affff5b3f720694700d4d87712a67afd05c8cb3f6fa52c/protobuf-7.36.0-cp310-abi3-win_amd64.whl", hash = "sha256:1781cc1de61249b750848029bca452c0a8b7e990080316b9bbc2518b2117b488", size = 453731, upload-time = "2026-08-20T16:33:58.951Z" }, + { url = "https://files.pythonhosted.org/packages/01/c3/629999e78d46c1115c11886d51c6bd68c17ce4a944f1ea3e153a91316a33/protobuf-7.36.0-py3-none-any.whl", hash = "sha256:53374d53fc29a67f7dbbf0ade47d7526a0f0137bf0f9c90e48d8a60790ef748c", size = 177024, upload-time = "2026-08-20T16:34:00.053Z" }, ] [[package]] @@ -2035,15 +1824,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/81/1d/ccd670cdf55a4d49ae97c20035fb19e5c359a535998373e5c69a1c964f10/protobuf_protoc_bin-29.5-py2.py3-none-win_amd64.whl", hash = "sha256:7f8f26e63dc1cf56fe2ab339f0c7533b07b6f010b822c45135b543785562ca78", size = 3193605, upload-time = "2025-05-29T05:55:24.511Z" }, ] -[[package]] -name = "py" -version = "1.11.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/98/ff/fec109ceb715d2a6b4c4a85a61af3b40c723a961e8828319fbcb15b868dc/py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719", size = 207796, upload-time = "2021-11-04T17:17:01.377Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f6/f0/10642828a8dfb741e5f3fbaac830550a518a775c7fff6f04a007259b0548/py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378", size = 98708, upload-time = "2021-11-04T17:17:00.152Z" }, -] - [[package]] name = "pyasn1" version = "0.6.4" @@ -2098,21 +1878,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/9d/56/921726b776ace8d8f5db44c4ef961006580d91dc52b803c489fafd1aa249/pydantic_core-2.46.4.tar.gz", hash = "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1", size = 471464, upload-time = "2026-05-06T13:37:06.98Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5c/fa/6d7708d2cfc1a832acb6aeb0cd16e801902df8a0f583bb3b4b527fde022e/pydantic_core-2.46.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:0e96592440881c74a213e5ad528e2b24d3d4f940de2766bed9010ab1d9e51594", size = 2111872, upload-time = "2026-05-06T13:40:27.596Z" }, - { url = "https://files.pythonhosted.org/packages/ae/6f/aa064a3e74b5745afbdf250594f38e7ead05e2d651bcb35994b9417a0d4d/pydantic_core-2.46.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0d65b8c354be7fb5f720c3caa8bc940bc2d20ce749c8e06135f07f8ed95dd7c", size = 1948255, upload-time = "2026-05-06T13:39:12.574Z" }, - { url = "https://files.pythonhosted.org/packages/43/3a/41114a9f7569b84b4d84e7a018c57c56347dac30c0d4a872946ec4e36c46/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bfb192b3f4b9e8a89b6277b6ce787564f62cfd272055f6e685726b111dc7826", size = 1972827, upload-time = "2026-05-06T13:38:19.841Z" }, - { url = "https://files.pythonhosted.org/packages/ef/25/1ab42e8048fe551934d9884e8d64daa7e990ad386f310a15981aeb6a5b08/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9037063db01f09b09e237c282b6792bd4da634b5402c4e7f0c61effed7701a04", size = 2041051, upload-time = "2026-05-06T13:38:10.447Z" }, - { url = "https://files.pythonhosted.org/packages/94/c2/1a934597ddf08da410385b3b7aae91956a5a76c635effef456074fad7e88/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc010ab034c8c7452522748bf937df58020d256ccae0874463d1f4d01758af8e", size = 2221314, upload-time = "2026-05-06T13:40:13.089Z" }, - { url = "https://files.pythonhosted.org/packages/02/6d/9e8ad178c9c4df27ad3c8f25d1fe2a7ab0d2ba0559fad4aee5d3d1f16771/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c5dac79fa1614d1e06ca695109c6105923bd9c7d1d6c918d4e637b7e6b32fd3", size = 2285146, upload-time = "2026-05-06T13:38:59.224Z" }, - { url = "https://files.pythonhosted.org/packages/80/50/540cd3aeefc041beb111125c4bff779831a2111fc6b15a9138cda277d32c/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9fa868638bf362d3d138ea55829cefb3d5f4b0d7f142234382a15e2485dbec4", size = 2089685, upload-time = "2026-05-06T13:38:17.762Z" }, - { url = "https://files.pythonhosted.org/packages/6b/a4/b440ad35f05f6a38f89fa0f149accb3f0e02be94ca5e15f3c449a61b4bc9/pydantic_core-2.46.4-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:17299feefe090f2caa5b8e37222bb5f663e4935a8bfa6931d4102e5df1a9f398", size = 2115420, upload-time = "2026-05-06T13:37:58.195Z" }, - { url = "https://files.pythonhosted.org/packages/99/61/de4f55db8dfd57bfdfa9a12ec90fe1b57c4f41062f7ca86f08586b3e0ac0/pydantic_core-2.46.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4c63ebc82684aa89d9a3bcbd13d515b3be44250dc68dd3bd81526c1cb31286c3", size = 2165122, upload-time = "2026-05-06T13:37:01.167Z" }, - { url = "https://files.pythonhosted.org/packages/f7/52/7c529d7bdb2d1068bd52f51fe32572c8301f9a4febf1948f10639f1436f5/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:aaa2a54443eff1950ba5ddc6b6ccda0d9c84a364276a62f969bdf2a390650848", size = 2182573, upload-time = "2026-05-06T13:38:45.04Z" }, - { url = "https://files.pythonhosted.org/packages/37/b3/7c40325848ba78247f2812dcf9c7274e38cd801820ca6dd9fe63bcfb0eb4/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:18e5ceec2ab67e6d5f1a9085e5a24c9c4e2ac4545730bfe668680bca05e555f3", size = 2317139, upload-time = "2026-05-06T13:37:15.539Z" }, - { url = "https://files.pythonhosted.org/packages/d9/37/f913f81a657c865b75da6c0dbed79876073c2a43b5bd9edbe8da785e4d49/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a0f62d0a58f4e7da165457e995725421e0064f2255d8eccebc49f41bbc23b109", size = 2360433, upload-time = "2026-05-06T13:37:30.099Z" }, - { url = "https://files.pythonhosted.org/packages/c4/67/6acaa1be2567f9256b056d8477158cac7240813956ce86e49deae8e173b4/pydantic_core-2.46.4-cp311-cp311-win32.whl", hash = "sha256:041bde0a48fd37cf71cab1c9d56d3e8625a3793fef1f7dd232b3ff37e978ecda", size = 1985513, upload-time = "2026-05-06T13:38:15.669Z" }, - { url = "https://files.pythonhosted.org/packages/aa/e6/c505f83dfeda9a2e5c995cfd872949e4d05e12f7feb3dca72f633daefa94/pydantic_core-2.46.4-cp311-cp311-win_amd64.whl", hash = "sha256:6f2eeda33a839975441c86a4119e1383c50b47faf0cbb5176985565c6bb02c33", size = 2071114, upload-time = "2026-05-06T13:40:35.416Z" }, - { url = "https://files.pythonhosted.org/packages/0f/da/7a263a96d965d9d0df5e8de8a475f33495451117035b09acb110288c381f/pydantic_core-2.46.4-cp311-cp311-win_arm64.whl", hash = "sha256:14f4c5d6db102bd796a627bbb3a17b4cf4574b9ae861d8b7c9a9661c6dd3362d", size = 2044298, upload-time = "2026-05-06T13:38:29.754Z" }, { url = "https://files.pythonhosted.org/packages/ce/8c/af022f0af448d7747c5154288d46b5f2bc5f17366eaa0e23e9aa04d59f3b/pydantic_core-2.46.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3245406455a5d98187ec35530fd772b1d799b26667980872c8d4614991e2c4a2", size = 2106158, upload-time = "2026-05-06T13:38:57.215Z" }, { url = "https://files.pythonhosted.org/packages/19/95/6195171e385007300f0f5574592e467c568becce2d937a0b6804f218bc49/pydantic_core-2.46.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:962ccbab7b642487b1d8b7df90ef677e03134cf1fd8880bf698649b22a69371f", size = 1951724, upload-time = "2026-05-06T13:37:02.697Z" }, { url = "https://files.pythonhosted.org/packages/8e/bc/f47d1ff9cbb1620e1b5b697eef06010035735f07820180e74178226b27b3/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8233f2947cf85404441fd7e0085f53b10c93e0ee78611099b5c7237e36aacbf7", size = 1975742, upload-time = "2026-05-06T13:37:09.448Z" }, @@ -2173,22 +1938,10 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/30/a6/9f9f380dbb301f67023bf8f707aaa75daadf84f7152d95c410fd7e81d994/pydantic_core-2.46.4-cp314-cp314t-win32.whl", hash = "sha256:e846ae7835bf0703ae43f534ab79a867146dadd59dc9ca5c8b53d5c8f7c9ef02", size = 1955575, upload-time = "2026-05-06T13:38:51.116Z" }, { url = "https://files.pythonhosted.org/packages/40/1f/f1eb9eb350e795d1af8586289746f5c5677d16043040d63710e22abc43c9/pydantic_core-2.46.4-cp314-cp314t-win_amd64.whl", hash = "sha256:2108ba5c1c1eca18030634489dc544844144ee36357f2f9f780b93e7ddbb44b5", size = 2051624, upload-time = "2026-05-06T13:38:21.672Z" }, { url = "https://files.pythonhosted.org/packages/f6/d2/42dd53d0a85c27606f316d3aa5d2869c4e8470a5ed6dec30e4a1abe19192/pydantic_core-2.46.4-cp314-cp314t-win_arm64.whl", hash = "sha256:4fcbe087dbc2068af7eda3aa87634eba216dbda64d1ae73c8684b621d33f6596", size = 2017325, upload-time = "2026-05-06T13:40:52.723Z" }, - { url = "https://files.pythonhosted.org/packages/ee/a4/73995fd4ebbb46ba0ee51e6fa049b8f02c40daebb762208feda8a6b7894d/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:14d4edf427bdcf950a8a02d7cb44a08614388dd6e1bdcbf4f67504fa7887da9c", size = 2111589, upload-time = "2026-05-06T13:37:10.817Z" }, - { url = "https://files.pythonhosted.org/packages/fb/7f/f37d3a5e8bfcc2e403f5c57a730f2d815693fb42119e8ea48b3789335af1/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:0ce40cd7b21210e99342afafbd4d0f76d784eb5b1d60f3bdc566be4983c6c73b", size = 1944552, upload-time = "2026-05-06T13:36:56.717Z" }, - { url = "https://files.pythonhosted.org/packages/15/3c/d7eb777b3ff43e8433a4efb39a17aa8fd98a4ee8561a24a67ef5db07b2d6/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90884113d8b48f760e9587002789ddd741e76ab9f89518cd1e43b1f1a52ec44b", size = 1982984, upload-time = "2026-05-06T13:39:06.207Z" }, - { url = "https://files.pythonhosted.org/packages/63/87/70b9f40170a81afd55ca26c9b2acb25c20d64bcfbf888fafecb3ba077d4c/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66ce7632c22d837c95301830e111ad0128a32b8207533b60896a96c4915192ea", size = 2138417, upload-time = "2026-05-06T13:39:45.476Z" }, { url = "https://files.pythonhosted.org/packages/9d/1d/8987ad40f65ae1432753072f214fb5c74fe47ffbd0698bb9cbbb585664f8/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:1d8ba486450b14f3b1d63bc521d410ec7565e52f887b9fb671791886436a42f7", size = 2095527, upload-time = "2026-05-06T13:39:52.283Z" }, { url = "https://files.pythonhosted.org/packages/64/d3/84c282a7eee1d3ac4c0377546ef5a1ea436ce26840d9ac3b7ed54a377507/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:3009f12e4e90b7f88b4f9adb1b0c4a3d58fe7820f3238c190047209d148026df", size = 1936024, upload-time = "2026-05-06T13:40:15.671Z" }, { url = "https://files.pythonhosted.org/packages/d7/ca/eac61596cdeb4d7e174d3dc0bd8a6238f14f75f97a24e7b7db4c7e7340a0/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad785e92e6dc634c21555edc8bd6b64957ab844541bcb96a1366c202951ae526", size = 1990696, upload-time = "2026-05-06T13:38:34.717Z" }, { url = "https://files.pythonhosted.org/packages/fa/c3/7c8b240552251faf6b3a957db200fcfbbcec36763c050428b601e0c9b83b/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00c603d540afdd6b80eb39f078f33ebd46211f02f33e34a32d9f053bba711de0", size = 2147590, upload-time = "2026-05-06T13:39:29.883Z" }, - { url = "https://files.pythonhosted.org/packages/11/cb/428de0385b6c8d44b716feba566abfacfbd23ee3c4439faa789a1456242f/pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0c563b08bca408dc7f65f700633d8442fffb2421fc47b8101377e9fd65051ff0", size = 2112782, upload-time = "2026-05-06T13:37:04.016Z" }, - { url = "https://files.pythonhosted.org/packages/0b/b5/6a17bdadd0fc1f170adfd05a20d37c832f52b117b4d9131da1f41bb097ce/pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:db06ffe51636ffe9ca531fe9023dd64bdd794be8754cb5df57c5498ae5b518a7", size = 1952146, upload-time = "2026-05-06T13:39:43.092Z" }, - { url = "https://files.pythonhosted.org/packages/2a/dc/03734d80e362cd43ef65428e9de77c730ce7f2f11c60d2b1e1b39f0fbf99/pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:133878133d271ade3d41d1bfb2a45ec38dbdbda40bc065921c6b04e4630127e2", size = 2134492, upload-time = "2026-05-06T13:36:58.124Z" }, - { url = "https://files.pythonhosted.org/packages/de/df/5e5ffc085ed07cc22d298134d3d911c63e91f6a0eb91fe646750a3209910/pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9bc519fbf2b7578398853d815009ae5e4d4603d12f4e3f91da8c06852d3da3e9", size = 2156604, upload-time = "2026-05-06T13:37:49.88Z" }, - { url = "https://files.pythonhosted.org/packages/81/44/6e112a4253e56f5705467cbab7ab5e91ee7398ba3d56d358635958893d3e/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c7a7bd4e39e8e4c12c39cd480356842b6a8a06e41b23a55a5e3e191718838ddf", size = 2183828, upload-time = "2026-05-06T13:37:43.053Z" }, - { url = "https://files.pythonhosted.org/packages/ac/ad/5565071e937d8e752842ac241463944c9eb14c87e2d269f2658a5bd05e98/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:d396ec2b979760aaf3218e76c24e65bd0aca24983298653b3a9d7a45f9e47b30", size = 2310000, upload-time = "2026-05-06T13:37:56.694Z" }, - { url = "https://files.pythonhosted.org/packages/4f/c3/66883a5cec183e7fba4d024b4cbbe61851a63750ef606b0afecc46d1f2bf/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:86e1a4418c6cd97d60c95c71164158eaf7324fae7b0923264016baa993eba6fc", size = 2361286, upload-time = "2026-05-06T13:40:05.667Z" }, - { url = "https://files.pythonhosted.org/packages/4b/2d/69abac8f838090bbecd5df894befb2c2619e7996a98ddb949db9f3b93225/pydantic_core-2.46.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:d51026d73fcfd93610abc7b27789c26b313920fcfb20e27462d74a7f8b06e983", size = 2193071, upload-time = "2026-05-06T13:38:08.682Z" }, ] [[package]] @@ -2278,7 +2031,7 @@ name = "pytest-cov" version = "7.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "coverage", extra = ["toml"] }, + { name = "coverage" }, { name = "pluggy" }, { name = "pytest" }, ] @@ -2306,9 +2059,6 @@ version = "7.4.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/0f/0f/34e7ee0a72f1464b4c7a2e8bafb389f230477256af586bc82bcfad85295a/python_box-7.4.1.tar.gz", hash = "sha256:e412e36c25fca8223560516d53ef6c7993591c3b0ec8bb4ec582bf7defdd79f0", size = 49859, upload-time = "2026-02-21T16:21:16.008Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f8/a8/c8bcd3ff0905ec549273ea3485e6b9f2039f57baab419123fb18f964f829/python_box-7.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3f76dad8be9d57d65a3edc792b952f7afe3991515aa6eba616cf5efb2fbb2e0c", size = 1870869, upload-time = "2026-02-21T16:21:34.16Z" }, - { url = "https://files.pythonhosted.org/packages/0f/bc/9382766d388e258363a18a094e251d2624e3c524614c733d1afa989d9770/python_box-7.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c66582f41a94d46cb0896d468b0efebf9bc4c3a5634cd15373d871767c2e741d", size = 4494287, upload-time = "2026-02-21T16:26:03.131Z" }, - { url = "https://files.pythonhosted.org/packages/8c/cf/b9d1d4550615f69f6f9c72767f026543442739e5c56adf6f844b50d88251/python_box-7.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:43c62f66d694eb6410f51eb2eb5726f9b466e6f685e5dc90b5cd11f7b3047362", size = 1321085, upload-time = "2026-02-21T16:22:01.093Z" }, { url = "https://files.pythonhosted.org/packages/4d/d9/d05f317b38b42253422d8483f5d7dc16d382c99ddc253e426639a0f2f235/python_box-7.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:dfb91effff00d9e23486c4f0db3b19e03d602ebb7c9e20fc6a287c704fad2552", size = 1849441, upload-time = "2026-02-21T16:21:37.314Z" }, { url = "https://files.pythonhosted.org/packages/ba/a3/383eb3d658f36c6e531c8cf1e348ccb4b5031231df4aeb7742bb159a3166/python_box-7.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7f977f00e715b030cee6ffef2322ff8ce100ffbf1dbcc4ef91099c75752d5f8", size = 4485153, upload-time = "2026-02-21T16:26:04.507Z" }, { url = "https://files.pythonhosted.org/packages/65/f9/5de3c18415dd6f5286f00e6539c0ae3cceb1c6aaf28d1d5f17b0b568c97f/python_box-7.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:2ca9a18fd15326bc267e9cc7e0e6e3a0cb78d11507940f43f687adf7e156d882", size = 1295520, upload-time = "2026-02-21T16:22:26.192Z" }, @@ -2334,14 +2084,14 @@ wheels = [ [[package]] name = "python-discovery" -version = "1.5.2" +version = "1.5.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "filelock" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/38/b7/ac44da2cf0e53ada0e419033c2d058219c95dc1403126f163304c9e814b1/python_discovery-1.5.2.tar.gz", hash = "sha256:45fd4f20a4e3f9b7bf2e0817870bc8e3b320a19658da177af800768c82dbf354", size = 82350, upload-time = "2026-08-12T14:05:26.419Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/8f/3c92c45737f654f2488ab3662b7604a55d3d35146d37c9ce80f5c95b95a6/python_discovery-1.5.3.tar.gz", hash = "sha256:e500eb24025fb7c4876c1fdcfbafd9028a10c71b661aee38cb6fb0de594518c1", size = 82477, upload-time = "2026-08-24T14:48:46.396Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/71/45/689603d04b3bb8d7faa00f25c24acef993aab7813b3dbbfc472a459ab0b5/python_discovery-1.5.2-py3-none-any.whl", hash = "sha256:3e338c2d0f15dfaeea57493f4c2c6caebe0e998ea815c30ae8bf8ee21f1112d3", size = 38350, upload-time = "2026-08-12T14:05:25.113Z" }, + { url = "https://files.pythonhosted.org/packages/30/12/823d9a321904ccfd2969a24b84fdfd1e6614c707ec569c62879bf1dbc6c5/python_discovery-1.5.3-py3-none-any.whl", hash = "sha256:8305296358f1aa2ed302a25b84be7df84fef8ca47c7dce2da63cb7325333044e", size = 38290, upload-time = "2026-08-24T14:48:45.305Z" }, ] [[package]] @@ -2368,15 +2118,6 @@ version = "6.0.3" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" }, - { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" }, - { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" }, - { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" }, - { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" }, - { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" }, - { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" }, - { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" }, - { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" }, { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, @@ -2452,21 +2193,6 @@ version = "2026.6.3" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/aa/2a/9618a122aeb2a169a28b03889a2995fe297588964333d4a7d67bdf46e147/rpds_py-2026.6.3.tar.gz", hash = "sha256:1cebd1337c242e4ec2293e541f712b2da849b29f48f0c293684b71c0632625d4", size = 64051, upload-time = "2026-06-30T07:17:53.009Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/94/1f/a2dca5ffdbf1d475ffc4e80e4d5d720ff3a00f691795910116960ee12511/rpds_py-2026.6.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:7b689145a1485c335569bd056464f3243a29af7ed3871c7be31ad624ba239bc7", size = 342174, upload-time = "2026-06-30T07:14:54.821Z" }, - { url = "https://files.pythonhosted.org/packages/4d/dc/323d08583c0832911768663d1944f0107fcd4088704858d84b5e06d105a0/rpds_py-2026.6.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:db08f45aecde626498fb3df07bcf6d2ec040af42e859a4f5040d79c200342911", size = 345513, upload-time = "2026-06-30T07:14:56.515Z" }, - { url = "https://files.pythonhosted.org/packages/0b/2a/e31989834d18d2f26ec1d2774c5b1eb3331df4ea8ada525175294c94b48a/rpds_py-2026.6.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:acc992ab27b15f852c76755eb2ab7dce86585ddadba6fa5946e58556088845b4", size = 373783, upload-time = "2026-06-30T07:14:57.736Z" }, - { url = "https://files.pythonhosted.org/packages/87/fe/e80107ee3639585c9941c17d6a42cd65325022f656c023191fce78c324c8/rpds_py-2026.6.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7f88d653e7b3b779d71ae7454e20dcc9b6bae903f33c269db9f2be41bda3f261", size = 378316, upload-time = "2026-06-30T07:14:59.077Z" }, - { url = "https://files.pythonhosted.org/packages/22/6f/81e3adf81acfb6fa694de2a6e4e7d8863121e3e0799e0a7725e6cf5679c4/rpds_py-2026.6.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e52655eaf81e32593abedaa4bfe33170c8cfedf3365ed9be6e11e07f148f0278", size = 499423, upload-time = "2026-06-30T07:15:00.488Z" }, - { url = "https://files.pythonhosted.org/packages/2d/9a/41263969df0ce3d9af2a96d5005a288200af1989aed3354bfceb5fc0b21f/rpds_py-2026.6.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dfcc8b909769d19db55c7cc9541eb64b9b774b1057ffffb4f1048070475bb9f9", size = 386077, upload-time = "2026-06-30T07:15:01.911Z" }, - { url = "https://files.pythonhosted.org/packages/5e/19/7e98f468bd50346faff5b10e5297374b443bfdddacc8e9fbc65984539597/rpds_py-2026.6.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c1255b302953c86a486b81d330d5ee1d5bd937691ce271b6be0ef0e299eaab7", size = 371315, upload-time = "2026-06-30T07:15:03.317Z" }, - { url = "https://files.pythonhosted.org/packages/99/3c/2b973b4d371906a134b03decfea7f5d9835a2c6d263454392e15b64b5b18/rpds_py-2026.6.3-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:8d2294a31386bfa251d8c8a39472beee17db67d4f1a6eabea665d35c9a4461c3", size = 383502, upload-time = "2026-06-30T07:15:04.627Z" }, - { url = "https://files.pythonhosted.org/packages/98/2a/12e2799500af0a307bca76b63361c51f9fe479223561489c29eea1f2ee41/rpds_py-2026.6.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f8f23ead891a3b762f35ab3b04623da7056545b48aa60d59957e6789914545da", size = 402673, upload-time = "2026-06-30T07:15:05.856Z" }, - { url = "https://files.pythonhosted.org/packages/2d/e3/21e5872d165fe08be4f229e3d5ee9d90019c0bf0e5538de60dbd54009450/rpds_py-2026.6.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:421aba32367055614287a4292b6a17f1939c9452299f7a0209c117e990b646d4", size = 549964, upload-time = "2026-06-30T07:15:07.159Z" }, - { url = "https://files.pythonhosted.org/packages/1a/d0/5ee0fe36844297de8123bee27bc12078c1a7416ad9f1b8a8ca18d6b0c0ac/rpds_py-2026.6.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1e5822dfc2f0d4ab7e745eaa6d85945069329beeccef965af3f3bb26058fcab6", size = 615446, upload-time = "2026-06-30T07:15:08.531Z" }, - { url = "https://files.pythonhosted.org/packages/b1/80/1ea5873cb683f2fbe5f21b23ea1f6d179ead19f3c5b249b7eb5dca568ef2/rpds_py-2026.6.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:83e35b57523816c8613fd0776b40cd8bb9f596b37ddd2692eb4a6bb5ab2f8c93", size = 576975, upload-time = "2026-06-30T07:15:09.97Z" }, - { url = "https://files.pythonhosted.org/packages/c9/e1/90ef639217a5ddb15b7f4f61b1c33911fd044ad03c311bafdd2bcab85582/rpds_py-2026.6.3-cp311-cp311-win32.whl", hash = "sha256:de3eceba0b683bcbb1ab93da016d0270df1f9ae7be716b40214c5dafac6ea45a", size = 204453, upload-time = "2026-06-30T07:15:11.324Z" }, - { url = "https://files.pythonhosted.org/packages/f2/b7/b7a1695d7af36f521fb11e80d6d3adbd744f73b921859bd3c2a2c0dc706f/rpds_py-2026.6.3-cp311-cp311-win_amd64.whl", hash = "sha256:2c54a076ca4d370980ab57bc0e31df57bbe8d41340436a90ef8b1219a3cbb127", size = 223219, upload-time = "2026-06-30T07:15:12.476Z" }, - { url = "https://files.pythonhosted.org/packages/d7/a2/145afacf796e4506062825941176ad9445c2dcf2b3b6a1f13d3030a15e19/rpds_py-2026.6.3-cp311-cp311-win_arm64.whl", hash = "sha256:168c733a7112e071bb7a66460e667edfcff06c017a3c523f7a8a8e08d0140804", size = 219137, upload-time = "2026-06-30T07:15:13.631Z" }, { url = "https://files.pythonhosted.org/packages/5c/be/2e8974163072e7bab7df1a5acd54c4498e75e35d6d18b864d3a9d5dadc92/rpds_py-2026.6.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a0811d33247c3d6128a3001d763f2aa056bb3425204335400ac54f89eec3a0d0", size = 343691, upload-time = "2026-06-30T07:15:14.96Z" }, { url = "https://files.pythonhosted.org/packages/a4/73/319dfa745dd668efe89309141ded489126461fcecd2b8f3a3cda185129b6/rpds_py-2026.6.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:538949e262e46caa31ac01bdb3c1e8f642622922cacbabbae6a8445d9dc33eaf", size = 338542, upload-time = "2026-06-30T07:15:16.267Z" }, { url = "https://files.pythonhosted.org/packages/21/63/4239893be1c4d09b709b1a8f6be4188f0870084ff547f46606b8a75f1b03/rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55927d532399c2c646100ff7feb48eaa940ad70f42cd68e1328f3ded9f81ca24", size = 368180, upload-time = "2026-06-30T07:15:17.62Z" }, @@ -2555,18 +2281,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8b/71/14edf065f04630b1a8472f7653cad03f6c478bcf95ea0e6aed55451e33ea/rpds_py-2026.6.3-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:23a439f31ccbeff1574e24889128821d1f7917470e830cf6544dced1c662262a", size = 576533, upload-time = "2026-06-30T07:17:28.546Z" }, { url = "https://files.pythonhosted.org/packages/ba/76/65002b08596c389105720a8c0d22298b8dc25a4baf89b2ce431343c8b1de/rpds_py-2026.6.3-cp315-cp315t-win32.whl", hash = "sha256:913ca42ccad3f8cc6e292b587ae8ae49c8c823e5dce51a736252fc7c7cdfa577", size = 201204, upload-time = "2026-06-30T07:17:30.193Z" }, { url = "https://files.pythonhosted.org/packages/8c/97/d855d6b3c322d1f27e26f5241c42016b56cf01377ea8ed348285f54652f0/rpds_py-2026.6.3-cp315-cp315t-win_amd64.whl", hash = "sha256:ae3d4fe8c0b9213624fdce7279d70e3b148b682ca20719ebd193a23ebfa47324", size = 220719, upload-time = "2026-06-30T07:17:31.788Z" }, - { url = "https://files.pythonhosted.org/packages/b4/9c/f0d19ac587fd0e4ab6b72cda355e9c5a6166b01ef7e064e437aef8eb9fef/rpds_py-2026.6.3-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:4cf2d36a2357e4d07bb5a4f98801265327b48256867816cfd2ceb001e9754a8f", size = 349791, upload-time = "2026-06-30T07:17:33.315Z" }, - { url = "https://files.pythonhosted.org/packages/38/c7/1d49d204c9fd2ee6c537601dc4c1ba921e03363ca576bfab94a00254ac9a/rpds_py-2026.6.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:30c6dc199b24a5e3e81d50da0f00858c5bbdb2617a750395687f4339c5818171", size = 352842, upload-time = "2026-06-30T07:17:34.897Z" }, - { url = "https://files.pythonhosted.org/packages/ac/e5/c0b5dc93cd0d4c06ce1f438907649514e2ea077bcd911e3154a51e96c38e/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9891e594296ab9dada6551c8e7b387b2721f27a67eecd528412e8906247a7b90", size = 382094, upload-time = "2026-06-30T07:17:36.514Z" }, - { url = "https://files.pythonhosted.org/packages/0d/54/ec0e907b4ca8d541112db352409bd15f871c9b243e0c92c9b5a46ae96f01/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b5c2dc92304aa48a4a60443b548bb12f12e119d4b72f314015e67b9e1be97fca", size = 388662, upload-time = "2026-06-30T07:17:38.235Z" }, - { url = "https://files.pythonhosted.org/packages/d3/f4/921c22a4fd0f1c1ac13a3996ffbf0aa67951e2c8ad0d1d9574938a2932e8/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:127e08c0642d880cf32ca47ec2a4a77b901f7e2dd1ad9762adb13955d72ffcc9", size = 504896, upload-time = "2026-06-30T07:17:39.689Z" }, - { url = "https://files.pythonhosted.org/packages/0b/1b/a114b972cefa1ab1cdb3c7bb177cd3844a12826c507c722d3a73516dbbaf/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8bb68f03f395eb793220b45c097bd4d8c32944393da0fad8b999efac0868fc8c", size = 391545, upload-time = "2026-06-30T07:17:41.336Z" }, - { url = "https://files.pythonhosted.org/packages/4e/98/af9b3db77d47fcbe6c8c1f36e2c2147ec70292819e99c325f871584a1c11/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3450b693fde92133e9f51060568a4c31fcca76d5e53bbd611e689ca446517e9", size = 380059, upload-time = "2026-06-30T07:17:42.857Z" }, - { url = "https://files.pythonhosted.org/packages/c9/ba/0efd8668b97c1d26a61566386c636a7a7a09829e474fdf807caa15a2c844/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:5e8d07bddee435a2ff6f1920e18feff28d0bc4533e42f4bf6927fbd073312c41", size = 393235, upload-time = "2026-06-30T07:17:44.637Z" }, - { url = "https://files.pythonhosted.org/packages/62/90/8c139ee9690f73b0829f32647de6f40d826f8f443af6fa72644f96351aac/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3a83ae6c67b7676b9878378547ca8e93ed77a580037bcbcd1d32f739e1e6089c", size = 413008, upload-time = "2026-06-30T07:17:46.225Z" }, - { url = "https://files.pythonhosted.org/packages/9c/97/0043896fdd7828ce09a1d9a8b06433714d0960fc4ff3fc4aa72b666b764e/rpds_py-2026.6.3-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:2bfd04c19ddbd6640de0b51894d764bd2758854d5b75bd102d2ef10cb9c293a9", size = 558118, upload-time = "2026-06-30T07:17:47.759Z" }, - { url = "https://files.pythonhosted.org/packages/f6/40/02355f0e134f783a8f9814c4680a1bd311d37671577a5964ea838573ff37/rpds_py-2026.6.3-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:ca6546b66be9dc4738b1b043d5ebd5488c66c578c5ff0fd0e8065313fe3afb76", size = 623138, upload-time = "2026-06-30T07:17:49.355Z" }, - { url = "https://files.pythonhosted.org/packages/10/85/48f0abdcef5cce4e034c7a5b0ceeceba0b01bf0d942824f4bb720afe2dec/rpds_py-2026.6.3-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:8e65860d238379ed982fd9ba690579b5e95af2f4840f99c772816dbe573cb826", size = 586486, upload-time = "2026-06-30T07:17:51.141Z" }, ] [[package]] @@ -2587,16 +2301,6 @@ version = "0.2.15" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/ea/97/60fda20e2fb54b83a61ae14648b0817c8f5d84a3821e40bfbdae1437026a/ruamel_yaml_clib-0.2.15.tar.gz", hash = "sha256:46e4cc8c43ef6a94885f72512094e482114a8a706d3c555a34ed4b0d20200600", size = 225794, upload-time = "2025-11-16T16:12:59.761Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/80/8ce7b9af532aa94dd83360f01ce4716264db73de6bc8efd22c32341f6658/ruamel_yaml_clib-0.2.15-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c583229f336682b7212a43d2fa32c30e643d3076178fb9f7a6a14dde85a2d8bd", size = 147998, upload-time = "2025-11-16T16:13:13.241Z" }, - { url = "https://files.pythonhosted.org/packages/53/09/de9d3f6b6701ced5f276d082ad0f980edf08ca67114523d1b9264cd5e2e0/ruamel_yaml_clib-0.2.15-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:56ea19c157ed8c74b6be51b5fa1c3aff6e289a041575f0556f66e5fb848bb137", size = 132743, upload-time = "2025-11-16T16:13:14.265Z" }, - { url = "https://files.pythonhosted.org/packages/0e/f7/73a9b517571e214fe5c246698ff3ed232f1ef863c8ae1667486625ec688a/ruamel_yaml_clib-0.2.15-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5fea0932358e18293407feb921d4f4457db837b67ec1837f87074667449f9401", size = 731459, upload-time = "2025-11-16T20:22:44.338Z" }, - { url = "https://files.pythonhosted.org/packages/9b/a2/0dc0013169800f1c331a6f55b1282c1f4492a6d32660a0cf7b89e6684919/ruamel_yaml_clib-0.2.15-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef71831bd61fbdb7aa0399d5c4da06bea37107ab5c79ff884cc07f2450910262", size = 749289, upload-time = "2025-11-16T16:13:15.633Z" }, - { url = "https://files.pythonhosted.org/packages/aa/ed/3fb20a1a96b8dc645d88c4072df481fe06e0289e4d528ebbdcc044ebc8b3/ruamel_yaml_clib-0.2.15-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:617d35dc765715fa86f8c3ccdae1e4229055832c452d4ec20856136acc75053f", size = 777630, upload-time = "2025-11-16T16:13:16.898Z" }, - { url = "https://files.pythonhosted.org/packages/60/50/6842f4628bc98b7aa4733ab2378346e1441e150935ad3b9f3c3c429d9408/ruamel_yaml_clib-0.2.15-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1b45498cc81a4724a2d42273d6cfc243c0547ad7c6b87b4f774cb7bcc131c98d", size = 744368, upload-time = "2025-11-16T16:13:18.117Z" }, - { url = "https://files.pythonhosted.org/packages/d3/b0/128ae8e19a7d794c2e36130a72b3bb650ce1dd13fb7def6cf10656437dcf/ruamel_yaml_clib-0.2.15-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:def5663361f6771b18646620fca12968aae730132e104688766cf8a3b1d65922", size = 745233, upload-time = "2025-11-16T20:22:45.833Z" }, - { url = "https://files.pythonhosted.org/packages/75/05/91130633602d6ba7ce3e07f8fc865b40d2a09efd4751c740df89eed5caf9/ruamel_yaml_clib-0.2.15-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:014181cdec565c8745b7cbc4de3bf2cc8ced05183d986e6d1200168e5bb59490", size = 770963, upload-time = "2025-11-16T16:13:19.344Z" }, - { url = "https://files.pythonhosted.org/packages/fd/4b/fd4542e7f33d7d1bc64cc9ac9ba574ce8cf145569d21f5f20133336cdc8c/ruamel_yaml_clib-0.2.15-cp311-cp311-win32.whl", hash = "sha256:d290eda8f6ada19e1771b54e5706b8f9807e6bb08e873900d5ba114ced13e02c", size = 102640, upload-time = "2025-11-16T16:13:20.498Z" }, - { url = "https://files.pythonhosted.org/packages/bb/eb/00ff6032c19c7537371e3119287999570867a0eafb0154fccc80e74bf57a/ruamel_yaml_clib-0.2.15-cp311-cp311-win_amd64.whl", hash = "sha256:bdc06ad71173b915167702f55d0f3f027fc61abd975bd308a0968c02db4a4c3e", size = 121996, upload-time = "2025-11-16T16:13:21.855Z" }, { url = "https://files.pythonhosted.org/packages/72/4b/5fde11a0722d676e469d3d6f78c6a17591b9c7e0072ca359801c4bd17eee/ruamel_yaml_clib-0.2.15-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cb15a2e2a90c8475df45c0949793af1ff413acfb0a716b8b94e488ea95ce7cff", size = 149088, upload-time = "2025-11-16T16:13:22.836Z" }, { url = "https://files.pythonhosted.org/packages/85/82/4d08ac65ecf0ef3b046421985e66301a242804eb9a62c93ca3437dc94ee0/ruamel_yaml_clib-0.2.15-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:64da03cbe93c1e91af133f5bec37fd24d0d4ba2418eaf970d7166b0a26a148a2", size = 134553, upload-time = "2025-11-16T16:13:24.151Z" }, { url = "https://files.pythonhosted.org/packages/b9/cb/22366d68b280e281a932403b76da7a988108287adff2bfa5ce881200107a/ruamel_yaml_clib-0.2.15-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f6d3655e95a80325b84c4e14c080b2470fe4f33b6846f288379ce36154993fb1", size = 737468, upload-time = "2025-11-16T20:22:47.335Z" }, @@ -2654,15 +2358,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0e/fe/da8b9e1347696bb22120b77280ec5ce25d500ca5cb39d5ad6e5c18de19c1/ruff-0.16.4-py3-none-win_arm64.whl", hash = "sha256:a3a61621c9b6f6a89573e938a080e648f1695baa3f58570a3a707bc51ff65a21", size = 10451579, upload-time = "2026-08-20T17:43:57.135Z" }, ] -[[package]] -name = "schema" -version = "0.7.8" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fb/2e/8da627b65577a8f130fe9dfa88ce94fcb24b1f8b59e0fc763ee61abef8b8/schema-0.7.8.tar.gz", hash = "sha256:e86cc08edd6fe6e2522648f4e47e3a31920a76e82cce8937535422e310862ab5", size = 45540, upload-time = "2025-10-11T13:15:40.281Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c9/75/aad85817266ac5285c93391711d231ca63e9ae7d42cd3ca37549e24ebe52/schema-0.7.8-py2.py3-none-any.whl", hash = "sha256:00bd977fadc7d9521bf289850cd8a8aa5f4948f575476b8daaa5c1b57af2dce1", size = 19108, upload-time = "2025-10-11T17:13:07.323Z" }, -] - [[package]] name = "sentinel" version = "1.0.0" @@ -2733,13 +2428,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/3b/21/77b4c147963073040dc3c3a5cb7a8c3001a1893c0209432cb77f9df836aa/sqlalchemy-2.0.52.tar.gz", hash = "sha256:5e2d46356ac2ccb7d268ab6c2319ac6a2b42f1b8d5fd8bd3d46855cd82abee97", size = 9945637, upload-time = "2026-08-11T19:07:09.829Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6b/08/cc5f7627b92f1456bc0b5fb7e98af4600248abe422a44da0d17a3fe6a448/sqlalchemy-2.0.52-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0c3ce43907374889f3352bdcc6195c970148a2cb71574cd0237a5071a37fb6c", size = 2172460, upload-time = "2026-08-11T20:58:22.429Z" }, - { url = "https://files.pythonhosted.org/packages/ed/dc/9a2abad8bfc8fdcd38c64adc056aeefab7aaa96ecd32f5e8c140e6375f17/sqlalchemy-2.0.52-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a0d48c4b80717c61385b4e966e087c839a66cfd7b780641dcb428f4dba65608", size = 3355720, upload-time = "2026-08-11T21:00:06.746Z" }, - { url = "https://files.pythonhosted.org/packages/a8/73/e75597b5841043e3c74055d00d4feb53d9a49a5c89ba2450d2d9aab53597/sqlalchemy-2.0.52-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:938325a5373267afc53bfbe72983b20fbd64ca47842aac62433c3da1137ecff1", size = 3354394, upload-time = "2026-08-11T21:05:51.454Z" }, - { url = "https://files.pythonhosted.org/packages/12/25/410fbc6c2f1fa8310f4ef1b6847d47d0ac1c042c7b4e81eaaca063d030a9/sqlalchemy-2.0.52-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5f8438a98d49424acf69d0d53c0a522951dfe49a6f2d86417fbb37ad3066ab43", size = 3306991, upload-time = "2026-08-11T21:00:08.603Z" }, - { url = "https://files.pythonhosted.org/packages/b2/ba/25ffd5c24681ea4b46e62c80ceca8200ce204de1773366321306cf3f608a/sqlalchemy-2.0.52-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4699dbb8d396d199e7e78fd4d525e3ad3d6008a9c8c0160b87e74c606c2c3736", size = 3327454, upload-time = "2026-08-11T21:05:53.368Z" }, - { url = "https://files.pythonhosted.org/packages/c2/f1/0f1b1d4800e51218e736a06ed55a3b2a59c257600bbaca7673bf13d2dbec/sqlalchemy-2.0.52-cp311-cp311-win32.whl", hash = "sha256:cef328349452ae152637df4d11ce5a0919ecdf0a363e16c830c3518ee33bde72", size = 2131248, upload-time = "2026-08-11T21:09:50.765Z" }, - { url = "https://files.pythonhosted.org/packages/7a/f0/04d2ac5ad66f3d31278f37064ed5f5ef3fe653f7bdaa67036663f223d186/sqlalchemy-2.0.52-cp311-cp311-win_amd64.whl", hash = "sha256:f1c850792a3b25a3ad74dade3f05e4f402cdebfea27438bcadafaa1617f77bcc", size = 2156943, upload-time = "2026-08-11T21:09:51.979Z" }, { url = "https://files.pythonhosted.org/packages/e0/d5/1b77a026d161f98a08f11af1a5f6c47b98ee7c7e2648af525a1004826c78/sqlalchemy-2.0.52-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:be8c49131665dfe2cc74c498aa1240ffb548d0fd901325dd11c2c7a18956f727", size = 2170940, upload-time = "2026-08-11T20:58:11.25Z" }, { url = "https://files.pythonhosted.org/packages/54/bd/f444444adb37b5d53753fb1730ee7a421628e2e3b756c4da461af7e6394a/sqlalchemy-2.0.52-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b2d9e507a458832adcfbd8af6e2036ddf069b7710b799448542ebccae2dceee", size = 3383415, upload-time = "2026-08-11T21:02:38.534Z" }, { url = "https://files.pythonhosted.org/packages/be/57/2eadf93a552568c57e8680b7e58bb5e9770d80942a1bdbaf4f2f63f0d7c8/sqlalchemy-2.0.52-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8738008376d22f30f411ea3efecf39b51110b6996d80bb73786f30bcfdd5fd3b", size = 3398577, upload-time = "2026-08-11T21:16:59.092Z" }, @@ -2770,6 +2458,31 @@ asyncio = [ { name = "greenlet" }, ] +[[package]] +name = "starlark-pyo3" +version = "2026.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/90/2b/80f70627e7e417119192814a9101dfdd1219a77f4e6c934792dd66947fea/starlark_pyo3-2026.1.1.tar.gz", hash = "sha256:8d071823c0fe71f04779bd87b1612add3ab3ffc2a7d16705429bccd0fd9af247", size = 48279, upload-time = "2026-08-23T20:43:37.33Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/9b/f22ccb6d8202db3b2bb3abbf72873ef243e10e6a8fc1fdc6724c5b064630/starlark_pyo3-2026.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9265667bdc5ddef57e7599fb0988225f4f613b84e0e57a415e4f3034c34ec199", size = 2951835, upload-time = "2026-08-23T20:43:04.666Z" }, + { url = "https://files.pythonhosted.org/packages/6b/42/5f5777f62b3e4e245733339651b1c83617225af0cfbefa6346be5b7d3208/starlark_pyo3-2026.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18610c46e3cb9190edf8fb58f3c54e042be5e8d6e4a2c122c10ed36c9ea8c36e", size = 3682058, upload-time = "2026-08-23T20:43:06.551Z" }, + { url = "https://files.pythonhosted.org/packages/d5/e5/e562141f476f07080e7c82a40c95ae6b502bfcea36d5bc96a6e81e1aa884/starlark_pyo3-2026.1.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9ba62b9a644ac4cfeb737c0daf7f865dfbdf9337f03b5c8f0dc42a421dbb4c1f", size = 3466260, upload-time = "2026-08-23T20:43:08.337Z" }, + { url = "https://files.pythonhosted.org/packages/ea/e7/0050bc1c68278f53bdc280c110463f5776e5e267adc9e1f5544f247c7efe/starlark_pyo3-2026.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ae6878baf3d63383f30cd18e6e1f567217aa55d2ad6098553f632d60cee2fe7", size = 3443295, upload-time = "2026-08-23T20:43:10.127Z" }, + { url = "https://files.pythonhosted.org/packages/c6/b8/083d6de6ea34335a2238a5a4ab5c041f1e74910a21195e0eb752f5253a1d/starlark_pyo3-2026.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:bbed517f2e23e9737863a2f32e2ab5b232fd43a7151b34fb848bdc196d4f8e7e", size = 2676327, upload-time = "2026-08-23T20:43:11.882Z" }, + { url = "https://files.pythonhosted.org/packages/ac/3d/287392dc29b59bb75b2537fb1f0e41aaaa922d3712db8754a029d791f88f/starlark_pyo3-2026.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fc9de52892352af45819f298596678e2ae67697c65b7a80a7f255b4c70b723b4", size = 2951948, upload-time = "2026-08-23T20:43:13.641Z" }, + { url = "https://files.pythonhosted.org/packages/5e/5d/a0e15c750c07f5b70f5d8bf123ad742d7bbb34ed5bd77095d6c1c87e6f21/starlark_pyo3-2026.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:04ac6f4d49a55e1ce1074459c2ee8346b89c820981b2fa53a499e477e9773e8b", size = 3682285, upload-time = "2026-08-23T20:43:15.451Z" }, + { url = "https://files.pythonhosted.org/packages/80/7f/94139e848b277bd8cce7485a75f5c11a7f10e52d4a8d95dae29b490925dd/starlark_pyo3-2026.1.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3acfaad8688bd314a615ed634781c09b9a40990996d34067b35c94263b17b257", size = 3465908, upload-time = "2026-08-23T20:43:17.268Z" }, + { url = "https://files.pythonhosted.org/packages/57/cf/c161987482d4a575f05ddf48b36ff8ade0e61c9281320a4d6d0d662163d1/starlark_pyo3-2026.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87939a62b454b90a0dc6c8025efe7fafcbdae95b4a5a5514a1824ef6b9c39b79", size = 3442352, upload-time = "2026-08-23T20:43:19.173Z" }, + { url = "https://files.pythonhosted.org/packages/53/00/3c0141084646eba933beab04df60988dc3a18a9311bd5160cab26323d73a/starlark_pyo3-2026.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:e3e6d31043795060c9187425c984ecb296ce1f1465169eecf4ac1a53430467a0", size = 2676144, upload-time = "2026-08-23T20:43:21.07Z" }, + { url = "https://files.pythonhosted.org/packages/4a/22/40130c181b5d65586bceb8e1f5622f1152930b4be2ebeabedd485db859f9/starlark_pyo3-2026.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9b267b146ceffb04d2e0e2040c3c07ce17064ead64ab2b5bc14eec1217b7a6ba", size = 2952693, upload-time = "2026-08-23T20:43:22.823Z" }, + { url = "https://files.pythonhosted.org/packages/cd/99/485e87b5fcd8eceecf8f7fd847d136e2300e9d5e91c3404efc1f030da88d/starlark_pyo3-2026.1.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9584a58eae2c9b248d13f27bff4982350c76ae0243a652453fd856dce815e7c7", size = 3441842, upload-time = "2026-08-23T20:43:24.687Z" }, + { url = "https://files.pythonhosted.org/packages/85/d1/3fd921c77ef56502cfade0b2df592b0d7b687cbf64012dc9910e4d6537bb/starlark_pyo3-2026.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:de17752d978e20ac23e509816caa4df056c36a1a7e8a204ec959457d90f3710f", size = 2675363, upload-time = "2026-08-23T20:43:26.395Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e4/59de78a13fdb463ebe9830cf9b1276d71cc7e02822674a8a29421fa29f38/starlark_pyo3-2026.1.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f68034f1e4ec2ce80be7b8b0f0ba2e173c87c9618258686a0a73fb14728925bc", size = 3439343, upload-time = "2026-08-23T20:43:28.441Z" }, +] + [[package]] name = "starlette" version = "1.6.0" @@ -2785,11 +2498,11 @@ wheels = [ [[package]] name = "stevedore" -version = "5.9.0" +version = "5.9.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d7/dd/04d56c2a5232358df41f3d0f0e31833d378b6c8ed7803a6b1b7867b0eba6/stevedore-5.9.0.tar.gz", hash = "sha256:abbd0af7a38a8bbb1d6adea2e35b17609cf004eaac323e88a8d8963640dd2b3c", size = 514850, upload-time = "2026-07-02T11:38:08.509Z" } +sdist = { url = "https://files.pythonhosted.org/packages/db/a1/3b8ed9c1fc3aa6eebb57732d924ddaa0500ecc3b638d0454816320994383/stevedore-5.9.1.tar.gz", hash = "sha256:e97a2667923efda926e8713fde6a73616df68210a3cbc6f02b48967b676fd8bf", size = 518111, upload-time = "2026-08-20T15:25:14.754Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/62/8d/008761f6e1000600e5303db30d05724bdcf3d2d186cbb59fac79b52e39ed/stevedore-5.9.0-py3-none-any.whl", hash = "sha256:e520945d4c257700eddc1eb1d79df04b2ea578eef185e0e3fa5b442fc848d3f7", size = 54463, upload-time = "2026-07-02T11:38:07.43Z" }, + { url = "https://files.pythonhosted.org/packages/c5/97/bba6e7ec2f5498b9dcb7b1b6400086b80ae5a8ebaff4b25e8c8add75f439/stevedore-5.9.1-py3-none-any.whl", hash = "sha256:5c8ff3a9f336cc1a06ac0f597bc79d11a2f950bfd32e290ca56b5a301fafafbf", size = 54931, upload-time = "2026-08-20T15:25:13.602Z" }, ] [[package]] @@ -2819,7 +2532,7 @@ name = "strawberry-sqlalchemy-mapper" version = "0.8.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "greenlet", marker = "python_full_version >= '3.12'" }, + { name = "greenlet" }, { name = "sentinel" }, { name = "sqlakeyset" }, { name = "sqlalchemy", extra = ["asyncio"] }, @@ -2830,17 +2543,9 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f2/1f/cae88ec6f0b45f983972801c0ba093f7332e4b099ff3b48bbc1c6bb937f8/strawberry_sqlalchemy_mapper-0.8.0-py3-none-any.whl", hash = "sha256:7341327c31898547ccbc3e50c01f8c1dc009af0fb90a5c71aeda857016fb870c", size = 29376, upload-time = "2025-12-01T19:18:08.699Z" }, ] -[[package]] -name = "tabulate" -version = "0.9.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ec/fe/802052aecb21e3797b8f7902564ab6ea0d60ff8ca23952079064155d1ae1/tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c", size = 81090, upload-time = "2022-10-06T17:21:48.54Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f", size = 35252, upload-time = "2022-10-06T17:21:44.262Z" }, -] - [[package]] name = "tavern" +version = "4.0.0rc1" source = { editable = "." } dependencies = [ { name = "jmespath" }, @@ -2872,12 +2577,15 @@ grpc = [ mqtt = [ { name = "paho-mqtt" }, ] +scriptable = [ + { name = "starlark-pyo3" }, +] [package.dev-dependencies] dev = [ { name = "allure-pytest" }, { name = "colorlog" }, - { name = "coverage", extra = ["toml"] }, + { name = "coverage" }, { name = "exceptiongroup" }, { name = "faker" }, { name = "flask" }, @@ -2890,13 +2598,11 @@ dev = [ { name = "itsdangerous" }, { name = "pre-commit" }, { name = "protobuf-protoc-bin" }, - { name = "py" }, { name = "pydantic" }, { name = "pytest-asyncio" }, { name = "pytest-cov" }, { name = "pytest-xdist" }, { name = "ruff" }, - { name = "tbump" }, { name = "tomli" }, { name = "tox" }, { name = "tox-uv" }, @@ -2933,10 +2639,11 @@ requires-dist = [ { name = "pyyaml", specifier = ">=6.0.1,<7" }, { name = "requests", specifier = ">=2.22.0,<3" }, { name = "simpleeval", specifier = ">=1.0.3" }, + { name = "starlark-pyo3", marker = "extra == 'scriptable'" }, { name = "stevedore", specifier = ">=4,<6" }, { name = "websockets", marker = "extra == 'graphql'" }, ] -provides-extras = ["graphql", "grpc", "mqtt"] +provides-extras = ["grpc", "mqtt", "scriptable", "graphql"] [package.metadata.requires-dev] dev = [ @@ -2955,13 +2662,11 @@ dev = [ { name = "itsdangerous" }, { name = "pre-commit" }, { name = "protobuf-protoc-bin", specifier = "==29.5" }, - { name = "py" }, { name = "pydantic" }, { name = "pytest-asyncio", specifier = ">=1.3.0" }, { name = "pytest-cov" }, { name = "pytest-xdist" }, - { name = "ruff" }, - { name = "tbump", specifier = ">=6.10.0" }, + { name = "ruff", specifier = ">=0.15.9" }, { name = "tomli" }, { name = "tox", specifier = ">4.20,<5" }, { name = "tox-uv", specifier = ">=1.28.0" }, @@ -3060,36 +2765,12 @@ requires-dist = [ { name = "tavern", extras = ["mqtt"], editable = "." }, ] -[[package]] -name = "tbump" -version = "6.11.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cli-ui" }, - { name = "docopt" }, - { name = "schema" }, - { name = "tomlkit" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ab/1f/d02379532311192521a20b3597dc0f01bd37596e950a6cb40795ae9acb94/tbump-6.11.0.tar.gz", hash = "sha256:385e710eedf0a8a6ff959cf1e9f3cfd17c873617132fc0ec5f629af0c355c870", size = 28642, upload-time = "2023-09-09T11:22:59.039Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/48/41/c21994a64efe86ed81c1a0935aeec840548839a37a7f3716f74a75e54fc2/tbump-6.11.0-py3-none-any.whl", hash = "sha256:6b181fe6f3ae84ce0b9af8cc2009a8bca41ded34e73f623a7413b9684f1b4526", size = 35607, upload-time = "2023-09-09T11:22:56.581Z" }, -] - [[package]] name = "tomli" version = "2.4.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/22/de/48c59722572767841493b26183a0d1cc411d54fd759c5607c4590b6563a6/tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f", size = 17543, upload-time = "2026-03-25T20:22:03.828Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/11/db3d5885d8528263d8adc260bb2d28ebf1270b96e98f0e0268d32b8d9900/tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30", size = 154704, upload-time = "2026-03-25T20:21:10.473Z" }, - { url = "https://files.pythonhosted.org/packages/6d/f7/675db52c7e46064a9aa928885a9b20f4124ecb9bc2e1ce74c9106648d202/tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a", size = 149454, upload-time = "2026-03-25T20:21:12.036Z" }, - { url = "https://files.pythonhosted.org/packages/61/71/81c50943cf953efa35bce7646caab3cf457a7d8c030b27cfb40d7235f9ee/tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076", size = 237561, upload-time = "2026-03-25T20:21:13.098Z" }, - { url = "https://files.pythonhosted.org/packages/48/c1/f41d9cb618acccca7df82aaf682f9b49013c9397212cb9f53219e3abac37/tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9", size = 243824, upload-time = "2026-03-25T20:21:14.569Z" }, - { url = "https://files.pythonhosted.org/packages/22/e4/5a816ecdd1f8ca51fb756ef684b90f2780afc52fc67f987e3c61d800a46d/tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c", size = 242227, upload-time = "2026-03-25T20:21:15.712Z" }, - { url = "https://files.pythonhosted.org/packages/6b/49/2b2a0ef529aa6eec245d25f0c703e020a73955ad7edf73e7f54ddc608aa5/tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc", size = 247859, upload-time = "2026-03-25T20:21:17.001Z" }, - { url = "https://files.pythonhosted.org/packages/83/bd/6c1a630eaca337e1e78c5903104f831bda934c426f9231429396ce3c3467/tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049", size = 97204, upload-time = "2026-03-25T20:21:18.079Z" }, - { url = "https://files.pythonhosted.org/packages/42/59/71461df1a885647e10b6bb7802d0b8e66480c61f3f43079e0dcd315b3954/tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e", size = 108084, upload-time = "2026-03-25T20:21:18.978Z" }, - { url = "https://files.pythonhosted.org/packages/b8/83/dceca96142499c069475b790e7913b1044c1a4337e700751f48ed723f883/tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece", size = 95285, upload-time = "2026-03-25T20:21:20.309Z" }, { url = "https://files.pythonhosted.org/packages/c1/ba/42f134a3fe2b370f555f44b1d72feebb94debcab01676bf918d0cb70e9aa/tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a", size = 155924, upload-time = "2026-03-25T20:21:21.626Z" }, { url = "https://files.pythonhosted.org/packages/dc/c7/62d7a17c26487ade21c5422b646110f2162f1fcc95980ef7f63e73c68f14/tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085", size = 150018, upload-time = "2026-03-25T20:21:23.002Z" }, { url = "https://files.pythonhosted.org/packages/5c/05/79d13d7c15f13bdef410bdd49a6485b1c37d28968314eabee452c22a7fda/tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9", size = 244948, upload-time = "2026-03-25T20:21:24.04Z" }, @@ -3138,18 +2819,9 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c7/18/c86eb8e0202e32dd3df50d43d7ff9854f8e0603945ff398974c1d91ac1ef/tomli_w-1.2.0-py3-none-any.whl", hash = "sha256:188306098d013b691fcadc011abd66727d3c414c571bb01b1a174ba8c983cf90", size = 6675, upload-time = "2025-01-15T12:07:22.074Z" }, ] -[[package]] -name = "tomlkit" -version = "0.11.8" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/10/37/dd53019ccb72ef7d73fff0bee9e20b16faff9658b47913a35d79e89978af/tomlkit-0.11.8.tar.gz", hash = "sha256:9330fc7faa1db67b541b28e62018c17d20be733177d290a13b24c62d1614e0c3", size = 188825, upload-time = "2023-04-27T10:39:21.201Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/a8/b1c193be753c02e2a94af6e37ee45d3378a74d44fe778c2434a63af92731/tomlkit-0.11.8-py3-none-any.whl", hash = "sha256:8c726c4c202bdb148667835f68d68780b9a003a9ec34167b6c673b38eff2a171", size = 35807, upload-time = "2023-04-27T10:39:19.629Z" }, -] - [[package]] name = "tox" -version = "4.60.0" +version = "4.60.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cachetools" }, @@ -3164,9 +2836,9 @@ dependencies = [ { name = "typing-extensions", marker = "python_full_version < '3.13'" }, { name = "virtualenv" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9f/1f/3acba68301b0081cec87b1769ba4c3a4a0a30f20879a05420673c7e4671d/tox-4.60.0.tar.gz", hash = "sha256:6f93bb580d35e00fc69cfec1116eecbe21155f747753ca5019374f7c69b2eac9", size = 301598, upload-time = "2026-08-13T23:14:26.944Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/ae/05210c695efa7349af65aeb988604b15520eade3b6e2d43bdf44ce69b4b3/tox-4.60.1.tar.gz", hash = "sha256:2611b77ebe06fa571ae2f4176eaf644115d08891befb9c7cb705cb038d5ab38e", size = 303833, upload-time = "2026-08-25T05:39:00.934Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/e6/3a1042add765c0e1e67f76a3b0f2036bfe6b0229ace83c6ff9638a5e391b/tox-4.60.0-py3-none-any.whl", hash = "sha256:175abbc4cdef615d66874c0843be4f44c353c14aab6d89939bb22246f84122bd", size = 226332, upload-time = "2026-08-13T23:14:25.324Z" }, + { url = "https://files.pythonhosted.org/packages/83/d8/5ca808e8b2d98ac55e9141309c63b273caec25eb5bdaf3d7bf6d809903bc/tox-4.60.1-py3-none-any.whl", hash = "sha256:6f23a6417ad761b17c047e14b6e278968c09eb9b1fc00025269f82ec8327bc75", size = 226880, upload-time = "2026-08-25T05:38:59.194Z" }, ] [[package]] @@ -3226,11 +2898,11 @@ wheels = [ [[package]] name = "types-protobuf" -version = "7.34.1.20260816" +version = "7.35.1.20260825" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e0/86/f592845ff8410583687e6dbf985a028e6ec6d378d0bdf548817aa39937b9/types_protobuf-7.34.1.20260816.tar.gz", hash = "sha256:6f43846e7a3cc2621abfa79e2bb03461e6769a74cdae7462efaddf84580fd5c1", size = 69170, upload-time = "2026-08-16T02:50:00.998Z" } +sdist = { url = "https://files.pythonhosted.org/packages/bc/5f/e6daccd9249d8cb21b59cf5638c3be4d86575212f351b8aad33369f5e743/types_protobuf-7.35.1.20260825.tar.gz", hash = "sha256:37997789e1255571db9991fd80d1663cfb50a974820ff3eaa8e51139a9a6d65e", size = 69624, upload-time = "2026-08-25T02:48:32.093Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/14/cc/909e791f11de77c2073007dbcce8b502865ef12c7881bdf36b0ccb206ab4/types_protobuf-7.34.1.20260816-py3-none-any.whl", hash = "sha256:2e3a225b3c21f0022daa34cec155f03858bb9aff9792947b8e71f0e9f1791fdb", size = 86036, upload-time = "2026-08-16T02:49:59.807Z" }, + { url = "https://files.pythonhosted.org/packages/8f/2d/029d9846615bfeb872c3bd2ddaa030caf1e26ddc2aaeb24ca01cc1821761/types_protobuf-7.35.1.20260825-py3-none-any.whl", hash = "sha256:5be41ee0ffcd2bf36e8b66cb90e58631f7bcf7cd31083b75de780f1218c96173", size = 86412, upload-time = "2026-08-25T02:48:30.975Z" }, ] [[package]] @@ -3284,15 +2956,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e5/6d/b53b99a9f2766d095985947a5782f1702cabb129a34f7a802d7197af832f/tzdata-2026.3-py2.py3-none-any.whl", hash = "sha256:dc096730c87af6cab1b171c9d532be840741ff5d459015e7f6947bd7d7e54931", size = 348168, upload-time = "2026-07-10T08:50:36.46Z" }, ] -[[package]] -name = "unidecode" -version = "1.4.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/94/7d/a8a765761bbc0c836e397a2e48d498305a865b70a8600fd7a942e85dcf63/Unidecode-1.4.0.tar.gz", hash = "sha256:ce35985008338b676573023acc382d62c264f307c8f7963733405add37ea2b23", size = 200149, upload-time = "2025-04-24T08:45:03.798Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8f/b7/559f59d57d18b44c6d1250d2eeaa676e028b9c527431f5d0736478a73ba1/Unidecode-1.4.0-py3-none-any.whl", hash = "sha256:c3c7606c27503ad8d501270406e345ddb480a7b5f38827eafe4fa82a137f0021", size = 235837, upload-time = "2025-04-24T08:45:01.609Z" }, -] - [[package]] name = "uritemplate" version = "4.2.0" @@ -3366,12 +3029,6 @@ version = "0.22.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/06/f0/18d39dbd1971d6d62c4629cc7fa67f74821b0dc1f5a77af43719de7936a7/uvloop-0.22.1.tar.gz", hash = "sha256:6c84bae345b9147082b17371e3dd5d42775bddce91f885499017f4607fdaf39f", size = 2443250, upload-time = "2025-10-16T22:17:19.342Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/d5/69900f7883235562f1f50d8184bb7dd84a2fb61e9ec63f3782546fdbd057/uvloop-0.22.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c60ebcd36f7b240b30788554b6f0782454826a0ed765d8430652621b5de674b9", size = 1352420, upload-time = "2025-10-16T22:16:21.187Z" }, - { url = "https://files.pythonhosted.org/packages/a8/73/c4e271b3bce59724e291465cc936c37758886a4868787da0278b3b56b905/uvloop-0.22.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3b7f102bf3cb1995cfeaee9321105e8f5da76fdb104cdad8986f85461a1b7b77", size = 748677, upload-time = "2025-10-16T22:16:22.558Z" }, - { url = "https://files.pythonhosted.org/packages/86/94/9fb7fad2f824d25f8ecac0d70b94d0d48107ad5ece03769a9c543444f78a/uvloop-0.22.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53c85520781d84a4b8b230e24a5af5b0778efdb39142b424990ff1ef7c48ba21", size = 3753819, upload-time = "2025-10-16T22:16:23.903Z" }, - { url = "https://files.pythonhosted.org/packages/74/4f/256aca690709e9b008b7108bc85fba619a2bc37c6d80743d18abad16ee09/uvloop-0.22.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:56a2d1fae65fd82197cb8c53c367310b3eabe1bbb9fb5a04d28e3e3520e4f702", size = 3804529, upload-time = "2025-10-16T22:16:25.246Z" }, - { url = "https://files.pythonhosted.org/packages/7f/74/03c05ae4737e871923d21a76fe28b6aad57f5c03b6e6bfcfa5ad616013e4/uvloop-0.22.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:40631b049d5972c6755b06d0bfe8233b1bd9a8a6392d9d1c45c10b6f9e9b2733", size = 3621267, upload-time = "2025-10-16T22:16:26.819Z" }, - { url = "https://files.pythonhosted.org/packages/75/be/f8e590fe61d18b4a92070905497aec4c0e64ae1761498cad09023f3f4b3e/uvloop-0.22.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:535cc37b3a04f6cd2c1ef65fa1d370c9a35b6695df735fcff5427323f2cd5473", size = 3723105, upload-time = "2025-10-16T22:16:28.252Z" }, { url = "https://files.pythonhosted.org/packages/3d/ff/7f72e8170be527b4977b033239a83a68d5c881cc4775fca255c677f7ac5d/uvloop-0.22.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:fe94b4564e865d968414598eea1a6de60adba0c040ba4ed05ac1300de402cd42", size = 1359936, upload-time = "2025-10-16T22:16:29.436Z" }, { url = "https://files.pythonhosted.org/packages/c3/c6/e5d433f88fd54d81ef4be58b2b7b0cea13c442454a1db703a1eea0db1a59/uvloop-0.22.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:51eb9bd88391483410daad430813d982010f9c9c89512321f5b60e2cddbdddd6", size = 752769, upload-time = "2025-10-16T22:16:30.493Z" }, { url = "https://files.pythonhosted.org/packages/24/68/a6ac446820273e71aa762fa21cdcc09861edd3536ff47c5cd3b7afb10eeb/uvloop-0.22.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:700e674a166ca5778255e0e1dc4e9d79ab2acc57b9171b79e65feba7184b3370", size = 4317413, upload-time = "2025-10-16T22:16:31.644Z" }, @@ -3400,7 +3057,7 @@ wheels = [ [[package]] name = "virtualenv" -version = "21.7.4" +version = "21.7.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "distlib" }, @@ -3408,9 +3065,9 @@ dependencies = [ { name = "platformdirs" }, { name = "python-discovery" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2d/dc/a6eb1ddfa7f1e390fa599b078453c97edb3f6f846b34fb4eac3e8ea16401/virtualenv-21.7.4.tar.gz", hash = "sha256:c9d960c95fa458171e58222a5ccab7465298e4b6559977865e627c4719f1e825", size = 5345511, upload-time = "2026-08-10T22:54:33.316Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/60/fc54e876e34f94dd0cf0185aaecfd4bfa906653f003d9b2fb21428642fca/virtualenv-21.7.5.tar.gz", hash = "sha256:a73c4246fba3c8901ff9717399f466e00eeca5a3834981f1a6ebb4f1e94de2f8", size = 5346743, upload-time = "2026-08-25T05:39:16.14Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/40/4c/eb2f52aeeaf30dbd073d315a251a63ae2b8263171ec4428c135140cb0802/virtualenv-21.7.4-py3-none-any.whl", hash = "sha256:376ec93cd6aab3044fa395d7db226db38043b7b5748948044b2a87168525e843", size = 5324444, upload-time = "2026-08-10T22:54:31.515Z" }, + { url = "https://files.pythonhosted.org/packages/ca/d8/401141bf45637be916c86d325bd821c5838c7eff83294b934cd94e774e4f/virtualenv-21.7.5-py3-none-any.whl", hash = "sha256:e36ca889510ab6cb0b1dca93c59e5431dd4422a3c88f487358d470c90af8c07a", size = 5324697, upload-time = "2026-08-25T05:39:14.229Z" }, ] [[package]] @@ -3422,20 +3079,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/cd/41/5e1a4bb12aac5f1493fa1bdc11154eca3b258ca4eba65d39c473fe19d8e9/watchfiles-1.2.0.tar.gz", hash = "sha256:c995fba777f1ea992f090f9236e9284cf7a5d1a0130dd5a3d82c598cacd76838", size = 108252, upload-time = "2026-05-18T04:32:04.251Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fc/3d/8024c801df84d1587740d0359e7fdd80afeae3d159011f3d5376dd82f18e/watchfiles-1.2.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:704fd259e332e01f9b9c178f4bce9e49027e5587cc2600eeeaf8e76e1c846201", size = 400242, upload-time = "2026-05-18T04:31:19.014Z" }, - { url = "https://files.pythonhosted.org/packages/87/5b/f4dfd45323e949984a3a7f9dc31d1cbb049921e7d98253488dda72ccdaa9/watchfiles-1.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6543cf55d170003296d185c0af981f3e1311564907e1f4e08671fc7693a890a5", size = 394562, upload-time = "2026-05-18T04:30:08.46Z" }, - { url = "https://files.pythonhosted.org/packages/98/d8/19483ef075d601c409bce8bcbb5c0f81a10876fff870400568f08ce484a1/watchfiles-1.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89d8c2394a065ca86f5d2910ff263ae67c127e1376ccc4f9fc35c71db879f80a", size = 456611, upload-time = "2026-05-18T04:30:45.723Z" }, - { url = "https://files.pythonhosted.org/packages/b1/6a/cc81fbe7ee42f2f22e661a6e12def7807e01b14b2f39e0ff83fd373fd307/watchfiles-1.2.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:772b80df316480d894a0e3165fdd19cf77f5d17f9a787f94029465ad0e3529d1", size = 461379, upload-time = "2026-05-18T04:31:29.292Z" }, - { url = "https://files.pythonhosted.org/packages/b1/57/7e669002082c0a0f4fb5113bb70125f7110124b846b0a11bc5ae8e90eac1/watchfiles-1.2.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d158cd89df6053823533e06fb1d73c549133bff5f0396170c0e53d9559340717", size = 493556, upload-time = "2026-05-18T04:30:05.44Z" }, - { url = "https://files.pythonhosted.org/packages/45/7d/f60a2b19807b21fe8281f3a8da4f59eef0d5f96825ac4680ba2d4f2ebf91/watchfiles-1.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d516b3283a758e087841aedb8031549fb41ced08f3db10aa6d2bf32dc042525b", size = 575255, upload-time = "2026-05-18T04:30:40.568Z" }, - { url = "https://files.pythonhosted.org/packages/bd/49/77f5b5e6efbcd57482f74948ebb1b97e5c0046d6b61475042d830c84b3ff/watchfiles-1.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:53b2290c92e0506d102cd448fbc610d87079553f86caa39d67440856a8b8bba5", size = 467052, upload-time = "2026-05-18T04:31:17.942Z" }, - { url = "https://files.pythonhosted.org/packages/ee/5a/73e2959af1b97fd5d556f9a8bdba017be23ceeef731869d5eaa0a753d5a3/watchfiles-1.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a711b51aec4370d0dcda5b6c09463206f133a5759341d7744b953a7b62e1100e", size = 456858, upload-time = "2026-05-18T04:30:30.182Z" }, - { url = "https://files.pythonhosted.org/packages/50/57/1bc8c27fad7e6c19bddee15d276dbb6ab72480ec01c127afff1673aee417/watchfiles-1.2.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:e2ca07fa7d89195ec0865d3d285666286740bfa83d83e5cee204043a31ecc165", size = 467579, upload-time = "2026-05-18T04:32:15.897Z" }, - { url = "https://files.pythonhosted.org/packages/09/6c/3c2e44edba3553c5e3c3b8c8a2a6dee6b9e12ae2cf4bd2378bebf9dc3038/watchfiles-1.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e0618518f282c4ebff60f5e5b1247b6d91bb8b9f4476947563a1e74acc66f3c6", size = 633253, upload-time = "2026-05-18T04:31:37.123Z" }, - { url = "https://files.pythonhosted.org/packages/30/c2/d8c84a882ab39bbefcc4915ab3e91830b7a7e990c5570b0b69075aba3faf/watchfiles-1.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0d191c054d0715c3c95c99df9b8dbf6fd096d8c1e021e8f212e1bd8bc444ccb5", size = 660713, upload-time = "2026-05-18T04:31:24.62Z" }, - { url = "https://files.pythonhosted.org/packages/a9/07/f97736a5fc605364fe67b25e9fa4a6965dfd4840d50c406ada507e9d735f/watchfiles-1.2.0-cp311-cp311-win32.whl", hash = "sha256:9342472aff9b093c5acd4f6d8f70ae0937964ab56542502bcf5579782da69ae8", size = 277222, upload-time = "2026-05-18T04:31:21.131Z" }, - { url = "https://files.pythonhosted.org/packages/cf/99/2b04981977fc2608afd60360d928c6aecf6b950292ca221d98f4005f6694/watchfiles-1.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:dbd6c97045dad81227c8d040173da044c1de08de64a5ea8b555da4aee1d5fa22", size = 290274, upload-time = "2026-05-18T04:31:45.966Z" }, - { url = "https://files.pythonhosted.org/packages/3c/74/f7f58a7075ee9cf612b0cfcddb78b8cd8234f0742d6f0075cf0da2dde1c6/watchfiles-1.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:57a2d9fa4fb4c2ecae57b13dfff2c7ab53e21a2ba674fe9f05506680fcdcc0d7", size = 283460, upload-time = "2026-05-18T04:31:39.126Z" }, { url = "https://files.pythonhosted.org/packages/b8/2f/e42c992d2afda3108ea1c02acecc991b9f31d05c14adc2a7cee9ee211fc4/watchfiles-1.2.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:bc13eb17538be00c874699dc0abe4ee2bc8d50bb1166a6b9e175ef3fd7eb8f26", size = 400115, upload-time = "2026-05-18T04:32:02.06Z" }, { url = "https://files.pythonhosted.org/packages/5f/8f/6af2ea19065c91d8b0ea3516fdfc8c0d349f407e8e9fbf4e5a17360de8ad/watchfiles-1.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2d95ddc1eb6914154253d239089900813f6a767e174b8e6a50e7fdacb7e4236c", size = 393659, upload-time = "2026-05-18T04:30:50.951Z" }, { url = "https://files.pythonhosted.org/packages/13/01/b32a967c56fb3e3e5be3db52c3d3b87fa4513aa367d8ed1ad96d42952e5f/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f70d8b291ef6e88d19b1f297a6905ddb978888d9272b0d05e6f53309856bcfc", size = 453207, upload-time = "2026-05-18T04:31:04.231Z" }, @@ -3511,10 +3154,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/56/fe/cb8ef3d6f929d14158fdaaad9925985b7310abc9384dcd4d82dd0016fb59/watchfiles-1.2.0-cp315-cp315-manylinux_2_31_riscv64.whl", hash = "sha256:cee9d5efd929efdac5f7e58f72b3376f676b64050a91c5b99a7094c5b2317488", size = 465096, upload-time = "2026-05-18T04:31:30.384Z" }, { url = "https://files.pythonhosted.org/packages/25/91/80908e835e100527a9267147b08c0eee1fa6ab0ffec15edc04d1d44885f7/watchfiles-1.2.0-cp315-cp315-musllinux_1_1_aarch64.whl", hash = "sha256:b718bf356bbc15e559bd8ef41782b573b8ae0e3f177ab244b440568d7ea02cfb", size = 630638, upload-time = "2026-05-18T04:30:49.89Z" }, { url = "https://files.pythonhosted.org/packages/46/4b/95ab2f256bb4af3cb2eb23b9317bda984ee6e0f11733a5c004a6c95b06e3/watchfiles-1.2.0-cp315-cp315-musllinux_1_1_x86_64.whl", hash = "sha256:922c0e019fe68b3ae392965a766b02a71ba1168c932cebc3733cd52c5fe5b377", size = 657684, upload-time = "2026-05-18T04:31:32.027Z" }, - { url = "https://files.pythonhosted.org/packages/23/f4/7513ef1e85fc4c6331b59479d6d72661fc391fbe543678052ac72c8b6c19/watchfiles-1.2.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:4674d49eb94706dfe666c069fc0a1b646ffcf920473492e209f6d5f60d3f0cc2", size = 403050, upload-time = "2026-05-18T04:30:36.753Z" }, - { url = "https://files.pythonhosted.org/packages/27/0b/a54103cfd732bb703c7a749222011a0483ef3705948dae3b203158601119/watchfiles-1.2.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:094b9b70103d4e963499bdea001ee3c2697b144cd9ae6218a62c0f89ec9e31db", size = 396629, upload-time = "2026-05-18T04:32:03.268Z" }, - { url = "https://files.pythonhosted.org/packages/5e/2c/73f31a3b893886206c3f54d73e8ad8dee58cdb2f69ad2622e0a8a9e07f4e/watchfiles-1.2.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0ef001f8c25ad0fa9529f914c1600647ecd0f542d11c19b7894768c67b6acb7", size = 457318, upload-time = "2026-05-18T04:31:01.932Z" }, - { url = "https://files.pythonhosted.org/packages/e9/f9/45d021e4a5cc7b9dd567f7cbb06d3b75f751a690063fb6cc7ec60f4e46b7/watchfiles-1.2.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a88fc94e647bc4eec523f1caa540258eb71d14278b9daf72fa1e2658a98df0f0", size = 457771, upload-time = "2026-05-18T04:30:56.331Z" }, ] [[package]] @@ -3523,26 +3162,6 @@ version = "17.0.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/f7/96/e01084f83a64bcb3a27994bd0cb0db68ff29d9c6707fae37ec19b18ba990/websockets-17.0.1.tar.gz", hash = "sha256:5baa9bc0dfbae8c507e51c8cf1b6d4628086f7a87bbd3a9952bd5f035451f1cc", size = 183298, upload-time = "2026-07-31T11:31:27.665Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/54/b2bce5b754b91b727b852e78af6d7193d4fe985e420dc54e6c2abe161c1c/websockets-17.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c38515cb54902f7e97d0239e81ef46c4444f9475f4807fb9bbdb789b4089abcf", size = 212573, upload-time = "2026-07-31T11:29:04.803Z" }, - { url = "https://files.pythonhosted.org/packages/78/1b/eaefeb695b217d8c735fc377ab53c6b00bc9ed64a01a3f6f797096ac0ee8/websockets-17.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:70d438268e49f1a4bd096b6b6f7010f3ab48b5db2574dbf7d8c864c46ce7a06a", size = 210262, upload-time = "2026-07-31T11:29:06.298Z" }, - { url = "https://files.pythonhosted.org/packages/87/2d/68439a174c74969fb51619bbe9af9496826610883b828a2edd2f022c94fc/websockets-17.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0b52c76b8a870b141b7ca0705289452183ce7a523101954ccfe29a25986a673f", size = 210535, upload-time = "2026-07-31T11:29:07.553Z" }, - { url = "https://files.pythonhosted.org/packages/a4/ab/13a856b488dbac7fd3c5473e38098897cda0baa04e3ca3b34ec6c8a32b46/websockets-17.0.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b98860aefbd3d9bc8e3c7f0eefb83b11142b16110739c68cd33d3b4d6e84e536", size = 219602, upload-time = "2026-07-31T11:29:09.227Z" }, - { url = "https://files.pythonhosted.org/packages/45/a1/2b100e71aa1fe283ec8fb73f8b74a8576d855486a49117903b100dd3b78d/websockets-17.0.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d41e9845514754a42d1d83b2fca9d27fee2ca7b3b0bee6843ba5a9bb2b6e25ac", size = 219873, upload-time = "2026-07-31T11:29:10.362Z" }, - { url = "https://files.pythonhosted.org/packages/3b/71/92e6146d3588d145136c0e0e16d106bbb855bb5047e13ec3fdee39cce770/websockets-17.0.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d9aac6081513f02eac3f8caace800dbfc5c608b69e4a7bef69e414eabfc95aa1", size = 221108, upload-time = "2026-07-31T11:29:11.708Z" }, - { url = "https://files.pythonhosted.org/packages/09/8d/357afa2ffd29686536109e7e3cb2a94f2d09e535df4cf3989393dc506a40/websockets-17.0.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b85b960a4507b0714c0a1246d031be9118d908ee974dc085257297a955205f1d", size = 224401, upload-time = "2026-07-31T11:29:12.959Z" }, - { url = "https://files.pythonhosted.org/packages/01/27/9efba1e7a8df48e405d017e67513c6b2a8f0b59c8500b820c47cd5f3dea9/websockets-17.0.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c356dbddab0a529ed7574f78f559d75a223735c321c28f6f587fbf02b11ed301", size = 221670, upload-time = "2026-07-31T11:29:14.199Z" }, - { url = "https://files.pythonhosted.org/packages/01/85/ab27d62103e8a150f3657e043e5fc711ad3e018c8cd8a715093e93d7640c/websockets-17.0.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5661f868ef191d33dfc6a0cc7c5b3d495f0cc8bb3f8b30d87bda8755c61c95f5", size = 220442, upload-time = "2026-07-31T11:29:15.417Z" }, - { url = "https://files.pythonhosted.org/packages/6d/04/289e00b8001b622b0c397a6901fcc4aa8f34a6d2ed42be17f2704f2faae1/websockets-17.0.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:2fa2cb465a131c347ba6717a78c887746e73edb1c131d01c982d6ef0d68b82e0", size = 217760, upload-time = "2026-07-31T11:29:16.772Z" }, - { url = "https://files.pythonhosted.org/packages/2f/70/0fe58cdac988dfc0066786cc07b09dfd72b48b0c01e5de667721192b2e6e/websockets-17.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:55383d8177b3c99fd873ee5db0e0193f4c1dd4a3feaccf1a4a03c1b7cf539cac", size = 220597, upload-time = "2026-07-31T11:29:18.075Z" }, - { url = "https://files.pythonhosted.org/packages/d5/76/d3eaf120710d1a791d1c7a4963f0b50a589df8ba675394fd2a97dfea3746/websockets-17.0.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:038cfad5d5417f8bb09295abe986029a26d22f34bda622ccc79b670efd4dab56", size = 219187, upload-time = "2026-07-31T11:29:19.468Z" }, - { url = "https://files.pythonhosted.org/packages/e6/00/4e9ae886bdb1647537176ca33fca66d79dddb3773d213ac98ddc6bba9ab4/websockets-17.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:15920057a6b723f84734f0641403bca163a4b176e5af809ee4f0c4a1e75e9fed", size = 219955, upload-time = "2026-07-31T11:29:20.641Z" }, - { url = "https://files.pythonhosted.org/packages/7c/67/cd7cc6849a86cf8c9979c0c570b6b6ccee75d2872854238d8d54e77be6ec/websockets-17.0.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:5508f38c98ac29def9e747b87543b008a58b075df6da70b2cf2e0b47073d33bb", size = 221002, upload-time = "2026-07-31T11:29:21.753Z" }, - { url = "https://files.pythonhosted.org/packages/de/5c/4d14eaf7b2f1448d1af24c1641f04eb74c1632a5802952aac4b7e068b8e7/websockets-17.0.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:a68e604c6d1b0338e46652e2688cbce8096ad9c03548b075fda9e2ea19a9b7dd", size = 218579, upload-time = "2026-07-31T11:29:22.888Z" }, - { url = "https://files.pythonhosted.org/packages/15/67/ff8bc4b8a6ec235ed8985de12fecc59ad2cd68cc8fc79b97deaa42e412ac/websockets-17.0.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:a8af570fc29cd998a921c7131c8ac81d9434466d6d25300cb12a690fb56a8a08", size = 219612, upload-time = "2026-07-31T11:29:24.052Z" }, - { url = "https://files.pythonhosted.org/packages/91/eb/103d81d655bab3ffd5c7d5d4b08f92c374499decd1d4be6035ce715b385b/websockets-17.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:246927ae9ae06ca0d42a483a4bdb80d4862e1ee5b4cab37c354a5e1ad8356448", size = 219846, upload-time = "2026-07-31T11:29:25.421Z" }, - { url = "https://files.pythonhosted.org/packages/b8/1e/3495161b1827941258545604fdf72e3e053d03f25bb61752228a784c26a1/websockets-17.0.1-cp311-cp311-win32.whl", hash = "sha256:1d4cf7e8e5b8b1fa40758ac7524843a00237b124ab217e227542cafcfeb7a946", size = 213046, upload-time = "2026-07-31T11:29:27.094Z" }, - { url = "https://files.pythonhosted.org/packages/7b/b1/ba0ce59681db38c320a6d485f95a497ddea20356d9a9e8e70615ddd867b9/websockets-17.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:02f0b037a737d0cb0c33866c97bcd1a0b73170dfbf42d69d8fb86f51002fd5ae", size = 213344, upload-time = "2026-07-31T11:29:28.28Z" }, - { url = "https://files.pythonhosted.org/packages/83/9e/abfdde9cbd57f0b5867a70e3426ac63341e1a21557b273c39bb6d2ccf8b9/websockets-17.0.1-cp311-cp311-win_arm64.whl", hash = "sha256:1bdd8c4be420905dd732e00dcd669852d8128cc723efa585a0c0e51adb00a28a", size = 213277, upload-time = "2026-07-31T11:29:29.504Z" }, { url = "https://files.pythonhosted.org/packages/50/ff/6199a52d864215750af8668d84b0274775011a90052081f5a9495807a92b/websockets-17.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:10f461191125c63902ea7394ae9e752b1b5785641850c1d365bb30b0f88bc53f", size = 212603, upload-time = "2026-07-31T11:29:30.771Z" }, { url = "https://files.pythonhosted.org/packages/54/7e/439a962bcada88dcf586da77a1b2385f91e2d2910e9359540934c827156b/websockets-17.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cffc84ddec6da7f447677266fee2a3c40ecc78172f00752aa1150b8a8d65df1d", size = 210286, upload-time = "2026-07-31T11:29:32.157Z" }, { url = "https://files.pythonhosted.org/packages/d9/82/123660edc759c225626b3b91952c7625f85c77a8362acbc35a4623120f7d/websockets-17.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c23e532c8a2325a1e7486de8763a60dc43e83f01bcaeca07e3ba79652c156db1", size = 210549, upload-time = "2026-07-31T11:29:33.388Z" }, @@ -3623,12 +3242,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/26/93/70f6516d85b9744f7eac224c4b1b9ef4e84133f80b53be02080cb1c3e663/websockets-17.0.1-cp314-cp314t-win32.whl", hash = "sha256:17ac37716c0244e82c9e384c41653c090b1864c6610224ca3857e7f7b58fce10", size = 212755, upload-time = "2026-07-31T11:31:13.883Z" }, { url = "https://files.pythonhosted.org/packages/f1/2c/9d9c1da5a7ea9af307b386d25f64d1dead4729644198d2b92e36db5dfd41/websockets-17.0.1-cp314-cp314t-win_amd64.whl", hash = "sha256:bb31f42ea095ea826463c770829aa188a86c9a5c976b1467cbbf583c811de833", size = 213094, upload-time = "2026-07-31T11:31:15.367Z" }, { url = "https://files.pythonhosted.org/packages/5b/24/a585e7573e128070605d003b5544729bcd58d9756c7e99d550818ca4b916/websockets-17.0.1-cp314-cp314t-win_arm64.whl", hash = "sha256:dbfae8e75b342e31fc6fd1a8bbb393b7cbb91d6cfd581650300a94381e7b7e2b", size = 213009, upload-time = "2026-07-31T11:31:16.776Z" }, - { url = "https://files.pythonhosted.org/packages/51/77/63b4abd29f15107d856f010de6f35434faa7c49ef89151e051d2807a9c40/websockets-17.0.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:49266e4488309b38783257293a38298942b9a03aa106fcb45195377a77c0c1e2", size = 210194, upload-time = "2026-07-31T11:31:18.046Z" }, - { url = "https://files.pythonhosted.org/packages/ef/ab/6160542ee644f72b865af13ddfff23740c95595b237e16312262cafdb641/websockets-17.0.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d69fd559f9f0e8a52d2fce6f04ee143f86e70df0a189cd95164eddac599e810f", size = 210465, upload-time = "2026-07-31T11:31:19.333Z" }, - { url = "https://files.pythonhosted.org/packages/53/1a/d4437d3cb0691eeac2c6064e21c83a9eeda04f6ef0261abfc0dde708590d/websockets-17.0.1-pp311-pypy311_pp73-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a39ce3a7b0e6059be093213d637963101380157bcbad355916738fafb490698d", size = 211417, upload-time = "2026-07-31T11:31:20.687Z" }, - { url = "https://files.pythonhosted.org/packages/88/28/2d671e23a20359a1cc142848384659813b49028d46cb0acdc28e81ac59b5/websockets-17.0.1-pp311-pypy311_pp73-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2437d4ca208cc0f246d3a2297ae7474b4ba18261aaf5b9c79c84c031ecf348e1", size = 211309, upload-time = "2026-07-31T11:31:21.969Z" }, - { url = "https://files.pythonhosted.org/packages/02/52/b85a676b161991e5c0d884252376435f60510789e7740e3da73489d22a6e/websockets-17.0.1-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6740be6d1bab69f08ab52cb15b08f76c143b6fe61c580ba62bd929f3ab7a1d42", size = 212203, upload-time = "2026-07-31T11:31:23.38Z" }, - { url = "https://files.pythonhosted.org/packages/bf/e3/91e297e41381d9131f3142a9c1a50389fd96b98125ce9b81526fa4e14b9f/websockets-17.0.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:afbce6e3f0fac32dc87c2a0d84869d1a706460d64f39f3889386413e6e4d3d26", size = 213434, upload-time = "2026-07-31T11:31:24.707Z" }, { url = "https://files.pythonhosted.org/packages/09/ce/3929538b2b9918f5eee623fbf3346893973191f6df93f19bbda097bd7bb7/websockets-17.0.1-py3-none-any.whl", hash = "sha256:c6be9cba65c65cc76dfa3d4619e359ff02a4476c74e179b215236c11a0b32345", size = 206718, upload-time = "2026-07-31T11:31:26.037Z" }, ] @@ -3667,23 +3280,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/31/33/ebe9e3d1f86c7a0b51094c0a146392045ca1631d2664889539dec8088a33/yarl-1.24.5.tar.gz", hash = "sha256:e81b83143bee16329c23db3c1b2d82b29892fcbcb849186d2f6e98a5abe9a57f", size = 228679, upload-time = "2026-07-20T02:07:45.435Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fe/db/3cb5df059756a45761cc3dee8fd25ec82b83a6585ea3542b969fda850f99/yarl-1.24.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2c1fe720934a16ea8e7146175cba2126f87f54912c8c5435e7f7c7a51ef808d3", size = 135043, upload-time = "2026-07-20T02:04:52.39Z" }, - { url = "https://files.pythonhosted.org/packages/44/f8/767d6bd5a03db63bc467df2fb56d6fafeae9667d74aea92cd6af399f828b/yarl-1.24.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c687ed078e145f5fd53a14854beff320e1d2ab76df03e2009c98f39a0f68f39a", size = 96942, upload-time = "2026-07-20T02:04:54.26Z" }, - { url = "https://files.pythonhosted.org/packages/ce/97/10b939c44d7b28d1dbc389cfc7012306d1ea8dba01eaef44b39fffaee52a/yarl-1.24.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:709f1efed56c4a145793c046cd4939f9959bcd818979a787b77d8e09c57a0840", size = 97046, upload-time = "2026-07-20T02:04:56.638Z" }, - { url = "https://files.pythonhosted.org/packages/5b/7a/b410dbe39b6255c55fb2a2bcee96eb844d0789235ddc381a889a90dc72d6/yarl-1.24.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:874019bd513008b009f58657134e5d0c5e030b3559bd0553976837adf52fe966", size = 110512, upload-time = "2026-07-20T02:04:58.955Z" }, - { url = "https://files.pythonhosted.org/packages/83/c7/da591971f78a5617e1f21f5699858ebccd836fe181a6493788ffc91ba69b/yarl-1.24.5-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a4582acf7ef76482f6f511ebaf1946dae7f2e85ec4728b81a678c01df63bd723", size = 102454, upload-time = "2026-07-20T02:05:00.623Z" }, - { url = "https://files.pythonhosted.org/packages/c4/8e/73b0ed4de47289a78a96045d76d1cfe5e41848bf0da59ce25b2ec87ee05d/yarl-1.24.5-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2cabe6546e41dabe439999a23fcb5246e0c3b595b4315b96ef755252be90caeb", size = 117617, upload-time = "2026-07-20T02:05:02.325Z" }, - { url = "https://files.pythonhosted.org/packages/cf/14/b744747bc4f57a8d55bd744df463457524583e1e9f7538b5ace0346ab92e/yarl-1.24.5-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:17f57620f5475b3c69109376cc87e42a7af5db13c9398e4292772a706ff10780", size = 116135, upload-time = "2026-07-20T02:05:04.05Z" }, - { url = "https://files.pythonhosted.org/packages/66/ca/95aa4d0e5b7ea4f20e4d577c42d001ed9df207569fdb063cc5ed4ebb496b/yarl-1.24.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:570fec8fbd22b032733625f03f10b7ff023bc399213db15e72a7acaef28c2f4e", size = 111935, upload-time = "2026-07-20T02:05:05.738Z" }, - { url = "https://files.pythonhosted.org/packages/72/0d/d2ad8d6b147832d177a4e720ba1962fe686eb0913b74503b3eca094b8bba/yarl-1.24.5-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5fede79c6f73ff2c3ef822864cb1ada23196e62756df53bc6231d351a49516a2", size = 110010, upload-time = "2026-07-20T02:05:07.471Z" }, - { url = "https://files.pythonhosted.org/packages/50/18/eb335e4120903903f4865041355ae46256a2406eb2865bc24827f4f27b61/yarl-1.24.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8ccf9aca873b767977c73df497a85dbedee4ee086ae9ae49dc461333b9b79f58", size = 110058, upload-time = "2026-07-20T02:05:09.246Z" }, - { url = "https://files.pythonhosted.org/packages/44/70/97353add32c62ad6f206d948ac5a5ee84398225e534dc6ed6433d1b335b6/yarl-1.24.5-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:ad5d8201d310b031e6cd839d9bac2d4e5a01533ce5d3d5b50b7de1ef3af1de61", size = 103308, upload-time = "2026-07-20T02:05:11.31Z" }, - { url = "https://files.pythonhosted.org/packages/68/39/5e7398d4b6f6b3c9062823ebc60802df5b272e3fe9e788f9734c6ee46c85/yarl-1.24.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:841f0852f48fefea3b12c9dfec00704dfa3aef5215d0e3ce564bb3d7cd8d57c6", size = 116898, upload-time = "2026-07-20T02:05:13.099Z" }, - { url = "https://files.pythonhosted.org/packages/e4/c9/09e52f2239e8b96357eccca05915382e4ba5405ebfb623b6036040d99654/yarl-1.24.5-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:9baafc71b04f8f4bb0703b21d6fc9f0c30b346c636a532ff16ec8491a5ea4b1f", size = 109400, upload-time = "2026-07-20T02:05:14.821Z" }, - { url = "https://files.pythonhosted.org/packages/4b/6a/e94133d4c2d1a14d2384310bf3e79d9cf32c9d1eae1c6f034fb80d098fa1/yarl-1.24.5-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d897129df1a22b12aeed2c2c98df0785a2e8e6e0bde87b389491d0025c187077", size = 115934, upload-time = "2026-07-20T02:05:17.78Z" }, - { url = "https://files.pythonhosted.org/packages/4e/3c/34955ed967b976fc38edcbb6d538dee79dbda4cb7fc7f72a0907a7c78e0f/yarl-1.24.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dd625535328fd9882374356269227670189adfcc6a2d90284f323c05862eecbd", size = 112178, upload-time = "2026-07-20T02:05:19.675Z" }, - { url = "https://files.pythonhosted.org/packages/f5/46/d7bd3a8859d47dcfaffd7127af7076032a7da278a9a02e17b5f37bfb6712/yarl-1.24.5-cp311-cp311-win_amd64.whl", hash = "sha256:f4239bbec5a3577ddb49e4b50aeb32d8e5792098262ae2f63723f916a29b1a25", size = 97544, upload-time = "2026-07-20T02:05:21.523Z" }, - { url = "https://files.pythonhosted.org/packages/01/69/c1bfd21e32c638974ea2c542a0b8c53ef1fa9eff336020f5d014f9503ff2/yarl-1.24.5-cp311-cp311-win_arm64.whl", hash = "sha256:3ac6aff147deb9c09461b2d4bbdf6256831198f5d8a23f5d37138213090b6d8a", size = 93359, upload-time = "2026-07-20T02:05:23.493Z" }, { url = "https://files.pythonhosted.org/packages/1b/84/71d051c850b5af41d168c679d9eb67eb7c55283ac4ee131673edf134bc4e/yarl-1.24.5-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d693396e5aea78db03decd60aec9ece16c9b40ba00a587f089615ff4e718a81d", size = 136035, upload-time = "2026-07-20T02:05:25.489Z" }, { url = "https://files.pythonhosted.org/packages/03/4d/8ad27f9a1b7e69313cca5d695b925b48efe51208d3490e0844bae97cabc0/yarl-1.24.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3363fcc96e665878946ad7a106b9a13eac0541766a690ef287c0232ac768b6ec", size = 97642, upload-time = "2026-07-20T02:05:27.429Z" }, { url = "https://files.pythonhosted.org/packages/ea/b4/05b4131c407006cd1e410e9c6539f16a0945724677e5364447313c15ea3e/yarl-1.24.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9d399bdcfb4a0f659b9b3788bbc89babe63d9a6a65aacdf4d4e7065ff2e6316c", size = 97323, upload-time = "2026-07-20T02:05:29.441Z" },