diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 5fba4e39d..b13a9d436 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -27,7 +27,10 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v6
with:
- python-version: "3.11"
+ python-version: "3.12"
+
+ - name: Install the latest version of uv
+ uses: astral-sh/setup-uv@v7
- uses: pre-commit/action@v3.0.0
@@ -41,8 +44,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@v6
@@ -121,7 +125,7 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v6
with:
- python-version: "3.11"
+ python-version: "3.12"
- name: Install the latest version of uv
uses: astral-sh/setup-uv@v7
@@ -152,10 +156,10 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v6
with:
- python-version: "3.11"
+ python-version: "3.12"
- name: Install the latest version of uv
- uses: astral-sh/setup-uv@v6
+ uses: astral-sh/setup-uv@v7
- name: Run tests
env:
diff --git a/.gitignore b/.gitignore
index 091b4b4e1..9805298bf 100644
--- a/.gitignore
+++ b/.gitignore
@@ -128,3 +128,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 29055695b..71ad7c7b6 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -31,5 +31,19 @@ repos:
rev: 0.11.8
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/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/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 386e614d9..2457a7023 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",
@@ -36,7 +36,7 @@ dependencies = [
"stevedore>=4,<5",
]
-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>=2025.2.5",
+]
+
graphql = [
"aiohttp",
"websockets",
@@ -89,7 +93,7 @@ dev = [
"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
@@ -136,7 +140,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
@@ -161,8 +165,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]
@@ -174,7 +178,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",
@@ -184,15 +188,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*"
@@ -259,7 +265,7 @@ cmd = "uv lock"
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/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/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..326cab033 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,34 @@ 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
+
+ @classmethod
+ def from_starlark(cls, starlark_dict: dict) -> "TestConfig":
+ from tavern._core.starlark.types import from_starlark
+
+ return cls(
+ variables=from_starlark(starlark_dict["variables"]),
+ follow_redirects=starlark_dict["follow_redirects"],
+ stages=from_starlark(starlark_dict["stages"]),
+ strict=from_starlark(starlark_dict["strict"]),
+ tavern_internal=from_starlark(starlark_dict["tavern_internal"]),
+ experimental_starlark_pipeline=starlark_dict[
+ "experimental_starlark_pipeline"
+ ],
+ )
+
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 91549efe8..f2187602b 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
@@ -489,7 +489,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 efd01139c..09ae1219f 100644
--- a/tavern/_core/pytest/item.py
+++ b/tavern/_core/pytest/item.py
@@ -108,6 +108,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
@@ -144,7 +145,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"]
@@ -199,7 +200,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 = {}
@@ -257,7 +259,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..6e4e052f2
--- /dev/null
+++ b/tavern/_core/starlark/starlark_env.py
@@ -0,0 +1,419 @@
+"""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,
+ }
+
+ @classmethod
+ def from_starlark(cls, obj: dict) -> "StageResponse":
+ return cls(
+ success=obj["success"],
+ response=from_starlark(obj["response"]),
+ request_vars=from_starlark(obj["request_vars"]),
+ stage_name=obj["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..50afa0a10
--- /dev/null
+++ b/tavern/_core/starlark/types.py
@@ -0,0 +1,60 @@
+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/from Starlark."""
+
+ def to_starlark(self) -> Any:
+ """Convert this object to a Starlark-safe value."""
+ raise NotImplementedError
+
+ @classmethod
+ def from_starlark(cls, obj: Any) -> "StarlarkConvertible":
+ """Reconstruct an instance from a Starlark value."""
+ raise NotImplementedError
+
+
+def to_starlark(obj: Any) -> Any:
+ """Recursively convert an arbitrary Python object to a Starlark-safe value.
+
+ 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.
+
+ ``OpaquePythonObject`` instances are unwrapped; primitives, dicts and lists
+ are recursed into.
+ """
+ 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..1a57245e5
--- /dev/null
+++ b/tests/unit/starlark/test_starlark_env.py
@@ -0,0 +1,584 @@
+"""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
+
+ def test_from_starlark_roundtrip(self):
+ """Test from_starlark creates equivalent object."""
+ original = StageResponse(
+ success=True,
+ response={"status_code": 200},
+ request_vars={"token": "abc"},
+ stage_name="test",
+ )
+ starlark_dict = original.to_starlark()
+ reconstructed = StageResponse.from_starlark(starlark_dict)
+ assert reconstructed.success == original.success
+ assert reconstructed.request_vars == original.request_vars
+ assert reconstructed.stage_name == original.stage_name
+
+
+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 ee65b222d..9d9a002d4 100644
--- a/tox-integration.ini
+++ b/tox-integration.ini
@@ -32,6 +32,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
@@ -39,6 +40,7 @@ commands =
python -m pytest --collect-only
python -m pytest --tavern-global-cfg={toxinidir}/tests/integration/global_cfg.yaml --cov tavern {posargs} --tavern-setup-init-logging
+ 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 2cf4d7918..d3f371604 100644
--- a/uv.lock
+++ b/uv.lock
@@ -1,10 +1,9 @@
version = 1
revision = 3
-requires-python = ">=3.11"
+requires-python = ">=3.12"
resolution-markers = [
"python_full_version >= '3.13'",
- "python_full_version == '3.12.*'",
- "python_full_version < '3.12'",
+ "python_full_version < '3.13'",
]
[manifest]
@@ -42,24 +41,6 @@ dependencies = [
]
sdist = { url = "https://files.pythonhosted.org/packages/82/78/8ea7308cac6934de8c74a14f3d5f65d1c89287426688be79538d0e5c013d/aiohttp-3.14.1.tar.gz", hash = "sha256:307f2cff90a764d329e77040603fa032db89c5c24fdad50c4c15334cba744035", size = 7955794, upload-time = "2026-06-07T21:09:35.529Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/26/dd/bf526e6f0a1120dd6f2df2e97bacfe4d358f13d17a0ff5847301a1375a51/aiohttp-3.14.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:aa00140699487bd435fde4342d85c94cb256b7cd3a5b9c3396c67f19922afda2", size = 765225, upload-time = "2026-06-07T21:06:07.957Z" },
- { url = "https://files.pythonhosted.org/packages/8f/e1/a2872aa55495a70f61310d411541c6ee23812d9a884e000c716e1bc3edbf/aiohttp-3.14.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1c1af67559445498b502030c35c59db59966f47041ca9de5b4e707f86bd10b5f", size = 518743, upload-time = "2026-06-07T21:06:09.749Z" },
- { url = "https://files.pythonhosted.org/packages/5b/e7/c60c7b209e509cc787de3cea0550a518538cfc08003e1c1e14c1c63fff71/aiohttp-3.14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d44ec478e713ee7f29b439f7eb8dc2b9d4079e11ae114d2c2ac3d5daf30516c8", size = 514139, upload-time = "2026-06-07T21:06:11.26Z" },
- { url = "https://files.pythonhosted.org/packages/5b/8d/614ace2f579702c9840ab1e1447fd8509e35b0b904f7196418fa2f57b25d/aiohttp-3.14.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d3b1a184a9a8f548a6b73f1e26b96b052193e4b3175ed7342aaf1151a1f00a04", size = 1784088, upload-time = "2026-06-07T21:06:12.887Z" },
- { url = "https://files.pythonhosted.org/packages/49/e0/726e90f99542bf292f81a96a12cc4847deb86f3ccf62c6f4014a201f4d33/aiohttp-3.14.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5f2504bc0322437c9a1ff6d3333ca56c7477b727c995f036b976ae17b98372c8", size = 1737835, upload-time = "2026-06-07T21:06:14.564Z" },
- { url = "https://files.pythonhosted.org/packages/0b/4b/d176d5c4db9d33dacf0543102ea59503bc1d528af4cfd0b719949ca49389/aiohttp-3.14.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:73f05ea02013e02512c3bf42714f1208c57168c779cc6fe23516e4543089d0a6", size = 1842801, upload-time = "2026-06-07T21:06:16.228Z" },
- { url = "https://files.pythonhosted.org/packages/dc/d6/5a99b563690ea0cbed912ae94a2ce33993a5709a651a3a4fe761e7dd973a/aiohttp-3.14.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:797457503c2d426bee06eef808d07b31ede30b65e054444e7de64cad0061b7af", size = 1929992, upload-time = "2026-06-07T21:06:17.947Z" },
- { url = "https://files.pythonhosted.org/packages/76/7f/a987b14a3859094b3cea3f4825219c3e5536242564af6e3f9c2f6c994eb2/aiohttp-3.14.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b821a1f7dedf7e37450654e620038ac3b2e81e8fa6ea269337e97101978ec730", size = 1786989, upload-time = "2026-06-07T21:06:19.677Z" },
- { url = "https://files.pythonhosted.org/packages/f1/1a/420e5c85a3e73349372ed22ce0b6af86bfa6ce16a4b20a64a2e94608c781/aiohttp-3.14.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4cd96b5ba05d67ed0cf00b5b405c8cd99586d8e3481e8ee0a831057591af7621", size = 1640129, upload-time = "2026-06-07T21:06:22.558Z" },
- { url = "https://files.pythonhosted.org/packages/a7/80/18a592ed3be0a402cc03670bd72ee1f8563ddbe1d8d5542dbf868f274136/aiohttp-3.14.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d459b98a932296c6f0e94f87511a0b1b90a8a02c30a50e60a297619cd5a58ee", size = 1756576, upload-time = "2026-06-07T21:06:24.8Z" },
- { url = "https://files.pythonhosted.org/packages/ec/0b/8b3d5713373858ff71a617daf6e3b0e81ad63e79d09a3cf2f6b6b983939c/aiohttp-3.14.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:764457a7be60825fb770a644852ff717bcbb5042f189f2bd16df61a81b3f6573", size = 1754668, upload-time = "2026-06-07T21:06:26.528Z" },
- { url = "https://files.pythonhosted.org/packages/9f/49/fd564575cf225821d7ba5a117cb8bc27213d8a7e1811162afb43ae077039/aiohttp-3.14.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f7a16ef45b081454ef844502d87a848876c490c4cb5c650c230f6ec79ed2c1e7", size = 1817019, upload-time = "2026-06-07T21:06:28.297Z" },
- { url = "https://files.pythonhosted.org/packages/ed/1b/e850c9ae6fc91356552ae668bb6c51e93fa29c8aef13398a10b56678557f/aiohttp-3.14.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:2fbc3ed048b3475b9f0cbcb9978e9d2d3511acd91ead203af26ed9f0056004cf", size = 1631638, upload-time = "2026-06-07T21:06:30.242Z" },
- { url = "https://files.pythonhosted.org/packages/eb/94/3c337ba72451a89806ace6f75bddc92bafc5b8d53d90115a512858024b63/aiohttp-3.14.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:bedb0cd073cc2dc035e30aeb99444389d3cd2113afe4ef9fcd23d439f5bade85", size = 1835660, upload-time = "2026-06-07T21:06:31.943Z" },
- { url = "https://files.pythonhosted.org/packages/2b/9c/9c18cf367a0498212d9ba7daf990b504a5e8ae064cda4b504e2647c89c03/aiohttp-3.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b6feea921016eb3d4e04d65fc4e9ca402d1a3801f562aef94989f54694917af3", size = 1775698, upload-time = "2026-06-07T21:06:33.72Z" },
- { url = "https://files.pythonhosted.org/packages/b5/63/a251a9d2a6cb45065b2ddc0bde2b3dd10108740a9a42f632c66405a761a2/aiohttp-3.14.1-cp311-cp311-win32.whl", hash = "sha256:313701e488100074ce99850404ee36e741abf6330179fec908a1944ecf570126", size = 458386, upload-time = "2026-06-07T21:06:35.279Z" },
- { url = "https://files.pythonhosted.org/packages/17/ca/69274c51dcd6e8947d77b2806cf47a4a15f2c846e2cbeb1882547d3da283/aiohttp-3.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:03ab4530fdcb3a543a122ba4b65ac9919da9fe9f78a03d328a6e38ff962f7aa5", size = 483406, upload-time = "2026-06-07T21:06:36.824Z" },
- { url = "https://files.pythonhosted.org/packages/2c/8a/c25904f77690c3688ec140f87591ef11a0cfe36bf3d5c0f1f38056fb62b3/aiohttp-3.14.1-cp311-cp311-win_arm64.whl", hash = "sha256:486f7d16ed54c39c2cbd7ca71fd8ba2b8bb7860df65bd7b6ed640bab96a38a8b", size = 452987, upload-time = "2026-06-07T21:06:38.371Z" },
{ url = "https://files.pythonhosted.org/packages/1d/21/151624b51cd92553d95424daf4bf19f19ce9be9002d19253e7e7ce67197b/aiohttp-3.14.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d35143e27778b4bb0fb189562d7f275bff79c62ab8e98459717c0ea617ff2480", size = 757402, upload-time = "2026-06-07T21:06:40.311Z" },
{ url = "https://files.pythonhosted.org/packages/c2/82/280619e0bd7bf2454987e19282616e84762255dd9c8468f62382e8c191f1/aiohttp-3.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bcfb80a2cc36fba2534e5e5b5264dc7ae6fcd9bf15256da3e53d2f499e6fa29d", size = 512310, upload-time = "2026-06-07T21:06:42.207Z" },
{ url = "https://files.pythonhosted.org/packages/55/b2/2aac325583aaa1353045f96dffa586d8a34e8322e14a7ba49cffeb103ab4/aiohttp-3.14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:27fd7c91e51729b4f7e1577865fa6d34c9adccbc39aabe9000285b48af9f0ec2", size = 512448, upload-time = "2026-06-07T21:06:43.813Z" },
@@ -268,19 +249,6 @@ dependencies = [
]
sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", size = 184344, upload-time = "2025-09-08T23:22:26.456Z" },
- { url = "https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", size = 180560, upload-time = "2025-09-08T23:22:28.197Z" },
- { url = "https://files.pythonhosted.org/packages/b1/b7/1200d354378ef52ec227395d95c2576330fd22a869f7a70e88e1447eb234/cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", size = 209613, upload-time = "2025-09-08T23:22:29.475Z" },
- { url = "https://files.pythonhosted.org/packages/b8/56/6033f5e86e8cc9bb629f0077ba71679508bdf54a9a5e112a3c0b91870332/cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", size = 216476, upload-time = "2025-09-08T23:22:31.063Z" },
- { url = "https://files.pythonhosted.org/packages/dc/7f/55fecd70f7ece178db2f26128ec41430d8720f2d12ca97bf8f0a628207d5/cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", size = 203374, upload-time = "2025-09-08T23:22:32.507Z" },
- { url = "https://files.pythonhosted.org/packages/84/ef/a7b77c8bdc0f77adc3b46888f1ad54be8f3b7821697a7b89126e829e676a/cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", size = 202597, upload-time = "2025-09-08T23:22:34.132Z" },
- { url = "https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", size = 215574, upload-time = "2025-09-08T23:22:35.443Z" },
- { url = "https://files.pythonhosted.org/packages/44/64/58f6255b62b101093d5df22dcb752596066c7e89dd725e0afaed242a61be/cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", size = 218971, upload-time = "2025-09-08T23:22:36.805Z" },
- { url = "https://files.pythonhosted.org/packages/ab/49/fa72cebe2fd8a55fbe14956f9970fe8eb1ac59e5df042f603ef7c8ba0adc/cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", size = 211972, upload-time = "2025-09-08T23:22:38.436Z" },
- { url = "https://files.pythonhosted.org/packages/0b/28/dd0967a76aab36731b6ebfe64dec4e981aff7e0608f60c2d46b46982607d/cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", size = 217078, upload-time = "2025-09-08T23:22:39.776Z" },
- { url = "https://files.pythonhosted.org/packages/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", size = 172076, upload-time = "2025-09-08T23:22:40.95Z" },
- { url = "https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", size = 182820, upload-time = "2025-09-08T23:22:42.463Z" },
- { url = "https://files.pythonhosted.org/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", size = 177635, upload-time = "2025-09-08T23:22:43.623Z" },
{ url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" },
{ url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" },
{ url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" },
@@ -344,22 +312,6 @@ version = "3.4.7"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/e7/a1/67fe25fac3c7642725500a3f6cfe5821ad557c3abb11c9d20d12c7008d3e/charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5", size = 144271, upload-time = "2026-04-02T09:28:39.342Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/c2/d7/b5b7020a0565c2e9fa8c09f4b5fa6232feb326b8c20081ccded47ea368fd/charset_normalizer-3.4.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7641bb8895e77f921102f72833904dcd9901df5d6d72a2ab8f31d04b7e51e4e7", size = 309705, upload-time = "2026-04-02T09:26:02.191Z" },
- { url = "https://files.pythonhosted.org/packages/5a/53/58c29116c340e5456724ecd2fff4196d236b98f3da97b404bc5e51ac3493/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:202389074300232baeb53ae2569a60901f7efadd4245cf3a3bf0617d60b439d7", size = 206419, upload-time = "2026-04-02T09:26:03.583Z" },
- { url = "https://files.pythonhosted.org/packages/b2/02/e8146dc6591a37a00e5144c63f29fb7c97a734ea8a111190783c0e60ab63/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:30b8d1d8c52a48c2c5690e152c169b673487a2a58de1ec7393196753063fcd5e", size = 227901, upload-time = "2026-04-02T09:26:04.738Z" },
- { url = "https://files.pythonhosted.org/packages/fb/73/77486c4cd58f1267bf17db420e930c9afa1b3be3fe8c8b8ebbebc9624359/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:532bc9bf33a68613fd7d65e4b1c71a6a38d7d42604ecf239c77392e9b4e8998c", size = 222742, upload-time = "2026-04-02T09:26:06.36Z" },
- { url = "https://files.pythonhosted.org/packages/a1/fa/f74eb381a7d94ded44739e9d94de18dc5edc9c17fb8c11f0a6890696c0a9/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fe249cb4651fd12605b7288b24751d8bfd46d35f12a20b1ba33dea122e690df", size = 214061, upload-time = "2026-04-02T09:26:08.347Z" },
- { url = "https://files.pythonhosted.org/packages/dc/92/42bd3cefcf7687253fb86694b45f37b733c97f59af3724f356fa92b8c344/charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:65bcd23054beab4d166035cabbc868a09c1a49d1efe458fe8e4361215df40265", size = 199239, upload-time = "2026-04-02T09:26:09.823Z" },
- { url = "https://files.pythonhosted.org/packages/4c/3d/069e7184e2aa3b3cddc700e3dd267413dc259854adc3380421c805c6a17d/charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:08e721811161356f97b4059a9ba7bafb23ea5ee2255402c42881c214e173c6b4", size = 210173, upload-time = "2026-04-02T09:26:10.953Z" },
- { url = "https://files.pythonhosted.org/packages/62/51/9d56feb5f2e7074c46f93e0ebdbe61f0848ee246e2f0d89f8e20b89ebb8f/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e060d01aec0a910bdccb8be71faf34e7799ce36950f8294c8bf612cba65a2c9e", size = 209841, upload-time = "2026-04-02T09:26:12.142Z" },
- { url = "https://files.pythonhosted.org/packages/d2/59/893d8f99cc4c837dda1fe2f1139079703deb9f321aabcb032355de13b6c7/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:38c0109396c4cfc574d502df99742a45c72c08eff0a36158b6f04000043dbf38", size = 200304, upload-time = "2026-04-02T09:26:13.711Z" },
- { url = "https://files.pythonhosted.org/packages/7d/1d/ee6f3be3464247578d1ed5c46de545ccc3d3ff933695395c402c21fa6b77/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:1c2a768fdd44ee4a9339a9b0b130049139b8ce3c01d2ce09f67f5a68048d477c", size = 229455, upload-time = "2026-04-02T09:26:14.941Z" },
- { url = "https://files.pythonhosted.org/packages/54/bb/8fb0a946296ea96a488928bdce8ef99023998c48e4713af533e9bb98ef07/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:1a87ca9d5df6fe460483d9a5bbf2b18f620cbed41b432e2bddb686228282d10b", size = 210036, upload-time = "2026-04-02T09:26:16.478Z" },
- { url = "https://files.pythonhosted.org/packages/9a/bc/015b2387f913749f82afd4fcba07846d05b6d784dd16123cb66860e0237d/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d635aab80466bc95771bb78d5370e74d36d1fe31467b6b29b8b57b2a3cd7d22c", size = 224739, upload-time = "2026-04-02T09:26:17.751Z" },
- { url = "https://files.pythonhosted.org/packages/17/ab/63133691f56baae417493cba6b7c641571a2130eb7bceba6773367ab9ec5/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ae196f021b5e7c78e918242d217db021ed2a6ace2bc6ae94c0fc596221c7f58d", size = 216277, upload-time = "2026-04-02T09:26:18.981Z" },
- { url = "https://files.pythonhosted.org/packages/06/6d/3be70e827977f20db77c12a97e6a9f973631a45b8d186c084527e53e77a4/charset_normalizer-3.4.7-cp311-cp311-win32.whl", hash = "sha256:adb2597b428735679446b46c8badf467b4ca5f5056aae4d51a19f9570301b1ad", size = 147819, upload-time = "2026-04-02T09:26:20.295Z" },
- { url = "https://files.pythonhosted.org/packages/20/d9/5f67790f06b735d7c7637171bbfd89882ad67201891b7275e51116ed8207/charset_normalizer-3.4.7-cp311-cp311-win_amd64.whl", hash = "sha256:8e385e4267ab76874ae30db04c627faaaf0b509e1ccc11a95b3fc3e83f855c00", size = 159281, upload-time = "2026-04-02T09:26:21.74Z" },
- { url = "https://files.pythonhosted.org/packages/ca/83/6413f36c5a34afead88ce6f66684d943d91f233d76dd083798f9602b75ae/charset_normalizer-3.4.7-cp311-cp311-win_arm64.whl", hash = "sha256:d4a48e5b3c2a489fae013b7589308a40146ee081f6f509e047e0e096084ceca1", size = 147843, upload-time = "2026-04-02T09:26:22.901Z" },
{ url = "https://files.pythonhosted.org/packages/0c/eb/4fc8d0a7110eb5fc9cc161723a34a8a6c200ce3b4fbf681bc86feee22308/charset_normalizer-3.4.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46", size = 311328, upload-time = "2026-04-02T09:26:24.331Z" },
{ url = "https://files.pythonhosted.org/packages/f8/e3/0fadc706008ac9d7b9b5be6dc767c05f9d3e5df51744ce4cc9605de7b9f4/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2", size = 208061, upload-time = "2026-04-02T09:26:25.568Z" },
{ url = "https://files.pythonhosted.org/packages/42/f0/3dd1045c47f4a4604df85ec18ad093912ae1344ac706993aff91d38773a2/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1421b502d83040e6d7fb2fb18dff63957f720da3d77b2fbd3187ceb63755d7b", size = 229031, upload-time = "2026-04-02T09:26:26.865Z" },
@@ -480,21 +432,6 @@ version = "7.14.3"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/b4/91/0a7c28934e50d8ac9a7b117712d176f2953c3170bccced5eaacfa3e96175/coverage-7.14.3.tar.gz", hash = "sha256:1a7563a443f3d53fdeb040ec8c9f7466aed7ca3dc5891aa09d3ca3625fa4387f", size = 924398, upload-time = "2026-06-22T23:10:25.584Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/f1/24/efb17eb94018dd3415d0e8a76a4786a866e8964aa9c50f033399d23939c2/coverage-7.14.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e574801e1d643561594aa021206c46d80b257e9853087090ba97bed8b0a509d3", size = 220501, upload-time = "2026-06-22T23:08:02.182Z" },
- { url = "https://files.pythonhosted.org/packages/76/93/32f1bfca6cdd34259c8af42820a034b7a28dfb44969a13ed38c17e0ba5b0/coverage-7.14.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f82b6bb7d75a2613e85d07cefa3a8c973d0544a8993337f6e2728e4a1e94c305", size = 221008, upload-time = "2026-06-22T23:08:03.701Z" },
- { url = "https://files.pythonhosted.org/packages/eb/88/0d0f974855ff905d15a64f7873d00bdc4182e2736267486c6634f4af293c/coverage-7.14.3-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a2335ea5fed26af2e831094964fa3f8fae60b45f7e37fcc2d3b615b2add3ad87", size = 251420, upload-time = "2026-06-22T23:08:05.211Z" },
- { url = "https://files.pythonhosted.org/packages/39/7f/117dd2ec65e4140576f8ef991d88220f9b806769f7a8c20e0550c0f924e2/coverage-7.14.3-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fbb8c3a98e779013786ae01d229662aeacbc77100efbd3f2f245219ace5af700", size = 253331, upload-time = "2026-06-22T23:08:06.672Z" },
- { url = "https://files.pythonhosted.org/packages/87/55/f0bd6d6538e3f16829fb8a44b6c0d2fe9da638bbfdd6a20f8b5da8f4fa81/coverage-7.14.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ac082660de8f429ba0ea363595abb838998570b9a7546777c60f413ab902bbde", size = 255441, upload-time = "2026-06-22T23:08:08.208Z" },
- { url = "https://files.pythonhosted.org/packages/1e/98/aa71f7879019c846a8a9662579ea4484b0202cf1e252ffeed647075e7eca/coverage-7.14.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ac012839ff7e396030f1e94e10553a431d14e4de2ab65cb3acb72bbd5628ca2", size = 257398, upload-time = "2026-06-22T23:08:09.749Z" },
- { url = "https://files.pythonhosted.org/packages/f3/4f/5fd367e59844190f5965015d7bee899e67a89d13eb2760118479bf836f2f/coverage-7.14.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5952f8c1bda2a5347154450379316e6dfa4d934d62ca35f6784451e6f55074fb", size = 251558, upload-time = "2026-06-22T23:08:11.37Z" },
- { url = "https://files.pythonhosted.org/packages/8f/de/5383a6ee5a6376701fe07d980fa8e4a66c0c377fead16712720340d701a3/coverage-7.14.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8cf0f2509acb4619e2471a1951089054dd58ebea7a912066d2ea56dd4c24ca4a", size = 253134, upload-time = "2026-06-22T23:08:13.04Z" },
- { url = "https://files.pythonhosted.org/packages/01/99/09542b1a99f788e3daec7f0fadc288821e71aca9ea298d51bfa1ba79fed5/coverage-7.14.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:2e41fd3aab806770008279a93879b0924b16247e09ab537c043d08bbca53b4ab", size = 251195, upload-time = "2026-06-22T23:08:14.606Z" },
- { url = "https://files.pythonhosted.org/packages/02/9d/722fe8c13f0fbb064491b9e8656e56a606286792e5068c47ca1042e773e8/coverage-7.14.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f0a47095963cfe054e0df178daca95aec21e680d6076da807c3add28dfe920f7", size = 254959, upload-time = "2026-06-22T23:08:16.431Z" },
- { url = "https://files.pythonhosted.org/packages/fb/58/943627179ff1d82da9e54d0a5b0bb907bb19cf19515599ccd921de50b469/coverage-7.14.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:a090cbf9521e78ffdb2fcf448b72902afe9f5923ff6a12d5c0d0120200348af9", size = 250914, upload-time = "2026-06-22T23:08:18.03Z" },
- { url = "https://files.pythonhosted.org/packages/a5/d4/803efcbf9ae5567454a0c71e983589529448e2704ee0da2dc0163d482f18/coverage-7.14.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4d310baf69a4fbe8a098ce727e4808a34866ac718a6f759ae659cbd3221358bc", size = 251824, upload-time = "2026-06-22T23:08:19.704Z" },
- { url = "https://files.pythonhosted.org/packages/32/79/3f78ea9563132746eed5cecb75d2e576f9d8fec45a47242b5ae0950b82a3/coverage-7.14.3-cp311-cp311-win32.whl", hash = "sha256:74fdd718d88fe144f4579b8747873a07ec3f04cb837d5faec5a25d9e22fa31a8", size = 222594, upload-time = "2026-06-22T23:08:21.311Z" },
- { url = "https://files.pythonhosted.org/packages/85/22/9ebbc5a2ab42ac5d0eea1f48648629e1de9bbe41ec243ed6b93d55a5a53f/coverage-7.14.3-cp311-cp311-win_amd64.whl", hash = "sha256:cc96aa922e21d4bc5d5ed3c915cef27dfcbc13686f47d5e378d647fbfba655a2", size = 223073, upload-time = "2026-06-22T23:08:23.318Z" },
- { url = "https://files.pythonhosted.org/packages/71/af/69d5fcc16cb555153f99cec5467922f226be0369f7335a9506856d2a7bd0/coverage-7.14.3-cp311-cp311-win_arm64.whl", hash = "sha256:c66f9f9d4f1e9712eb9b1de5310f881d4e2188cfcba5065e1a8490f38687f2c4", size = 222617, upload-time = "2026-06-22T23:08:25.054Z" },
{ url = "https://files.pythonhosted.org/packages/bd/b0/8a911f6ffe6974dac4df95b468ab9a2899d0e59f0f99a489afeec39f00bc/coverage-7.14.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3d74ff26299c4879ce3a4d826f9d3d4d556fd285fde7bbce3c0ef5a8ab1cec24", size = 220672, upload-time = "2026-06-22T23:08:26.621Z" },
{ url = "https://files.pythonhosted.org/packages/36/16/0fc0cb52538783dbbae0934b834f5a58fd5354380ee6cad4a07b15dc845d/coverage-7.14.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:96150a9cf3468ea20f0bc5d0e21b3df8972c31480ef90fa7614b773cc6429665", size = 221035, upload-time = "2026-06-22T23:08:28.372Z" },
{ url = "https://files.pythonhosted.org/packages/77/e2/421ccfbb48335ac49e93301478cf5d623b0c2bf1c0cadd8e2b2fc6c0c710/coverage-7.14.3-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:27d07a46500ba23515b838dbcf52512026af04090755cf6cc64166d88c9b9a1a", size = 252540, upload-time = "2026-06-22T23:08:30.226Z" },
@@ -558,11 +495,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/eb/e3/a0aa32bfa3a081951f60a23bc0e7b512891ef0eecda1153cf1d8ba36c6b1/coverage-7.14.3-py3-none-any.whl", hash = "sha256:fb7e18afb6e903c1a92401a2f0501ac277dca527bb9ca6fe1f691a8a0026a0e8", size = 212469, upload-time = "2026-06-22T23:10:23.405Z" },
]
-[package.optional-dependencies]
-toml = [
- { name = "tomli", marker = "python_full_version <= '3.11'" },
-]
-
[[package]]
name = "cross-web"
version = "0.7.0"
@@ -623,12 +555,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/6c/a0/db537264e234f7273a73ec020873d6d6b39dfd8a53db78b550ca8320440e/cryptography-49.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:67e1d20ad9ef3a563c59ef22e7a8a0b8210bd26604369ea4a30a7c66aefe504e", size = 4834820, upload-time = "2026-06-12T20:01:51.847Z" },
{ url = "https://files.pythonhosted.org/packages/93/77/8df9eb486495979bccecd1062e2eaf435250e84437040295b57d09048b0b/cryptography-49.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:42b0684e0e40cf26122427802486f6d93aea593612603a94fbf260c7eb1e9c1b", size = 4967968, upload-time = "2026-06-12T20:02:12.524Z" },
{ url = "https://files.pythonhosted.org/packages/c2/e6/f60198ea8d9dfa15fff9ed4ca02ce362f6eadd9ba757dcc50634c4257b63/cryptography-49.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:026ac7423e6fa66872d3bf889be5974507da3944f866f704fa200eadacd00001", size = 3785547, upload-time = "2026-06-12T20:02:26.847Z" },
- { url = "https://files.pythonhosted.org/packages/63/d3/4a83af35d65e3fad632c926fad684c193ea4398569ccb0bbbc7fe8f5dc9a/cryptography-49.0.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:fc1e275c2f1d97b1a6450b8b0ea3ebfa6e087a611c2b26cb2404d48588abab7b", size = 3993685, upload-time = "2026-06-12T20:02:14.883Z" },
- { url = "https://files.pythonhosted.org/packages/d6/a7/f9dac0ab7f80368c56993a7bf638ef9935f825c91902798481fac0898138/cryptography-49.0.0-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c83782480a4a9da4d0feb51950131ba32e12e70813848b3343f6e18c28a66838", size = 4676239, upload-time = "2026-06-12T20:02:28.793Z" },
- { url = "https://files.pythonhosted.org/packages/d7/70/2ba3769dd0ae167e2f33dfa9592d45db6ff9a61d62ca1a5b3d1bdd09068f/cryptography-49.0.0-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b39efa323140595abd3ecca8529d321ae50f55f3aa3ba9cc81ea56a6011953d5", size = 4715584, upload-time = "2026-06-12T20:01:27.495Z" },
- { url = "https://files.pythonhosted.org/packages/94/64/2923570ac1c0bd3a737aa366ac3abbbbde273042308b8cde95e2364a6e6a/cryptography-49.0.0-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:b47db11c2c3525083296069b98ac5221907455e989ae0c2e3008bde851921615", size = 4675885, upload-time = "2026-06-12T20:01:55.49Z" },
- { url = "https://files.pythonhosted.org/packages/ab/f8/614dc7e051418cfe53d55173c1e24c6b0085e89996fe90508c2fdf769aef/cryptography-49.0.0-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:084ef1af862eb07ec46d25f68689f2102a9fc0e05ce7b80f14f5fe51e4eef0f6", size = 4715449, upload-time = "2026-06-12T20:02:05.469Z" },
- { url = "https://files.pythonhosted.org/packages/aa/50/a9caea39ad19c431c1a3f8a31114df65b260cdfe67786b6c7e7c040c4c44/cryptography-49.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:be9fcb48a55f023493482827d4f459bd263cc20efde64f204b97c123201850c6", size = 3783731, upload-time = "2026-06-12T20:02:43.319Z" },
]
[[package]]
@@ -785,22 +711,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" },
@@ -984,16 +894,6 @@ version = "3.5.3"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/e2/f1/fbbfef6af0bad0548f09bc28948ea3c275b4edb19e17fc5ca9900a6a634d/greenlet-3.5.3.tar.gz", hash = "sha256:a61efc018fd3eb317eeca31aba90ee9e7f26f22884a79b6c6ec715bf71bb62f1", size = 200270, upload-time = "2026-06-26T19:28:24.832Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/51/58/5404031044f55afad7aad1aff8be3f22b1bed03e237cfeabbc7e5c8cfde0/greenlet-3.5.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:aca9b4ce85b152b5524ef7d88170efdff80dc0032aa8b75f9aaf7f3479ea95b4", size = 287424, upload-time = "2026-06-26T18:20:31.469Z" },
- { url = "https://files.pythonhosted.org/packages/b4/bf/1c65e9b94a54d547068fa5b5a8a06f221f3316b48908e08668d29c77cb50/greenlet-3.5.3-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f71be4920368fe1fabeeaa53d1e3548337e2b223d9565f8ad5e392a75ba23fc", size = 606523, upload-time = "2026-06-26T19:07:08.859Z" },
- { url = "https://files.pythonhosted.org/packages/b8/c7/b66baacc95775ad511287acb0137b95574a9ce5491902372b7564799d790/greenlet-3.5.3-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4d77e67f65f98449e3fb83f795b5d0a8437aead2f874ca89c96576caf4be3af6", size = 618315, upload-time = "2026-06-26T19:10:06.055Z" },
- { url = "https://files.pythonhosted.org/packages/b0/a0/68afd1ebad40db87dac0a28ffa120726b98bf9c7c40c481b0f63c105d298/greenlet-3.5.3-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e18619ba655ac05d78d80fc83cac4ba892bd6927b99e3b8237aee861aaacc8bb", size = 626155, upload-time = "2026-06-26T19:24:14.44Z" },
- { url = "https://files.pythonhosted.org/packages/78/2b/28ed29463522fdbe4c15b1f63922041626a7478316b34ab4adda3f0a4aba/greenlet-3.5.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8540f1e6205bd13ca0ce685581037219ca54a1b41a0a15d228c6c9b8ad5903d7", size = 617381, upload-time = "2026-06-26T18:32:16.077Z" },
- { url = "https://files.pythonhosted.org/packages/07/7f/e327d912239ec4b3b49999e3967389bcf1ee8722b9ee9194d2752ecd558a/greenlet-3.5.3-cp311-cp311-manylinux_2_39_riscv64.whl", hash = "sha256:d27c0c653a60d9535f690226474a5cc1036a8b0d7b57504d1c4f89c44a07a80c", size = 421083, upload-time = "2026-06-26T19:25:35.804Z" },
- { url = "https://files.pythonhosted.org/packages/2a/7b/ad04e9d1337fc04965dc9fc616b6a72cb65a24b800a014c011ec812f5489/greenlet-3.5.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7ef56fe650f50575bf843acde967b9c567687f3c22340941a899b7bc56e956a8", size = 1577771, upload-time = "2026-06-26T19:09:01.537Z" },
- { url = "https://files.pythonhosted.org/packages/d8/33/6c87ab7ba663f70ca21f3022aad1ffe56d3f3e0521e836c2415e13abcc3c/greenlet-3.5.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5121af01cf911e70056c00d4b46d5e9b5d1415550038573d744138bacb59e6b8", size = 1644048, upload-time = "2026-06-26T18:31:42.996Z" },
- { url = "https://files.pythonhosted.org/packages/1c/35/f0d8ee998b422cf8693b270f098e55d8d4ec8006b061b333f54f177d28d9/greenlet-3.5.3-cp311-cp311-win_amd64.whl", hash = "sha256:0f41e4a05a3c0cb31b17023eff28dd111e1d16bf7d7d00406cd7df23f31398a7", size = 239137, upload-time = "2026-06-26T18:23:21.664Z" },
- { url = "https://files.pythonhosted.org/packages/fb/96/b9820295576ef18c9edc404f10e260ae7215ceaf3781a54b720ed2627862/greenlet-3.5.3-cp311-cp311-win_arm64.whl", hash = "sha256:ec6f1af59f6b5f3fc9678e2ea062d8377d22ac644f7844cb7a292910cf12ff44", size = 237630, upload-time = "2026-06-26T18:24:00.281Z" },
{ url = "https://files.pythonhosted.org/packages/5d/6e/4c37d51a2b7f82d2ff11bb6b5f7d766d9a011726624af255e843727627a3/greenlet-3.5.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:719757059f5a53fd0dde23f78cffeafcdd97b21c850ddb7ca684a3c1a1f122e2", size = 288685, upload-time = "2026-06-26T18:22:08.977Z" },
{ url = "https://files.pythonhosted.org/packages/7a/73/815dd90131c1b71ebdf53dbc7c276cafec2a1173b97559f97aba72724a87/greenlet-3.5.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:efa9f765dd09f9d0cdac651ffdf631ee59ec5dc6ee7a73e0c012ba9c52fbdf5b", size = 604761, upload-time = "2026-06-26T19:07:10.114Z" },
{ url = "https://files.pythonhosted.org/packages/9f/57/079cfe76bcef36b153b25607ee91c6fcb58f17f8b23c86bbbeabe0c88d72/greenlet-3.5.3-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7faba15ac005376e02a0384504e0243be3370ce010296a44a820feb342b505ab", size = 617044, upload-time = "2026-06-26T19:10:07.25Z" },
@@ -1076,16 +976,6 @@ dependencies = [
]
sdist = { url = "https://files.pythonhosted.org/packages/b0/b5/1ff353970a87eda4c98251e34d2dfd214abd4982dc89119c9252a2a482d2/grpcio-1.81.1.tar.gz", hash = "sha256:6fa10a767143a5e82e8eaab53918af0cd8909a57a27f8cb2288b80a613ac671b", size = 13026582, upload-time = "2026-06-11T12:46:51.673Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/52/ea/1c2fa386b718ff493225e61cfc052ef400b4d6ffc54cbe261026432624b5/grpcio-1.81.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:d71d30f2d92f67d944631c523713934fee37292469e182ebcd2c1dd8a64ce53f", size = 6093112, upload-time = "2026-06-11T12:44:52.131Z" },
- { url = "https://files.pythonhosted.org/packages/2b/18/acf45fa8bd1bc5d7b0c2fd3dc4c209379fbd5bb396b440b68a83342226b7/grpcio-1.81.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:b137f4bf3ada9dc44d411478decc6ff09a79ed30b306cd2abaa98408c3588137", size = 12074277, upload-time = "2026-06-11T12:44:55.354Z" },
- { url = "https://files.pythonhosted.org/packages/48/d7/ee86a60699b7db039f772a2c4a7e4facc7138984ff42c0130933a0063884/grpcio-1.81.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a3acb384427816dd5d470f47e62137b87f74da694faa8a50147012cf40df276a", size = 6640348, upload-time = "2026-06-11T12:44:59.223Z" },
- { url = "https://files.pythonhosted.org/packages/26/ee/d2de5e47378ffc207d476c230fea3be4d2601edbce9995f4fe45535d4896/grpcio-1.81.1-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:f9a0ebbe45c29b5e5866593c12b78bd9035f0f0f0d4bc8361680cd580d99db49", size = 7331842, upload-time = "2026-06-11T12:45:02.001Z" },
- { url = "https://files.pythonhosted.org/packages/23/d6/abeda5c2b896a0b341584fe5ac411bbf72e197a9a374c355fb90965e08d2/grpcio-1.81.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0a37165cc80b1a368384b383e63a4c38116a10467ae44c904d2d7468c4470ec2", size = 6842229, upload-time = "2026-06-11T12:45:04.76Z" },
- { url = "https://files.pythonhosted.org/packages/10/1c/1f0da7d590b4aeee006826ba568d0e419ca14b23e18f901a3da3e9fba613/grpcio-1.81.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6282caffb41ec326d4cb67ca9cf53b739d1b2f975a2acb498c7418e9f7d9a416", size = 7446096, upload-time = "2026-06-11T12:45:07.499Z" },
- { url = "https://files.pythonhosted.org/packages/6a/81/5c505d508f7c887aa7982d21443a4126597c80d34b0bcf40f9cec576d7f3/grpcio-1.81.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a35009284d0d3d5c2c9601c164a911b8b4331608d98a9a66d47d97bb2f522b70", size = 8445238, upload-time = "2026-06-11T12:45:10.243Z" },
- { url = "https://files.pythonhosted.org/packages/f7/b2/524847365122ee509ca17bcc4e092198b700e94af7bfd5bb5e6dd9f3ee66/grpcio-1.81.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1b22c80559854b789a01fd89e8929b3798a156c0829b5282a8939f33ad4115ad", size = 7873989, upload-time = "2026-06-11T12:45:13.102Z" },
- { url = "https://files.pythonhosted.org/packages/18/fa/07c037c50b006909d1d13a5848774f8aa7b242f70dc03a035c64eea0e6db/grpcio-1.81.1-cp311-cp311-win32.whl", hash = "sha256:428bec0161b48d8cf583c068591bc0016d0d9cfff52462b72b3884861ea768c5", size = 4202223, upload-time = "2026-06-11T12:45:16.166Z" },
- { url = "https://files.pythonhosted.org/packages/41/ed/6bff15376920942fac6b95b9802752b837437172c9e8fc2d3170546b89cc/grpcio-1.81.1-cp311-cp311-win_amd64.whl", hash = "sha256:30e825f6848d9f18bba350ed6c75c1b02a0b5184474a31db9a32b1fa66fd8c79", size = 4941303, upload-time = "2026-06-11T12:45:18.724Z" },
{ url = "https://files.pythonhosted.org/packages/85/07/9a979c81738863a738dc23d65177056e71fbb2db817740ed870b33434e7a/grpcio-1.81.1-cp312-cp312-linux_armv7l.whl", hash = "sha256:8b39472beafc0bdcafc4c8c73ad082ebfdb449d566897a61e7acb4fa88089115", size = 6053264, upload-time = "2026-06-11T12:45:21.017Z" },
{ url = "https://files.pythonhosted.org/packages/75/95/539706ca0d3bd40dbad583dc56fd883da941f37556b629132da5762781b9/grpcio-1.81.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:12b7524c88d4026d3dcb7b0ebe16b6714f3b4af402ddd0f0639ab064a00c87c3", size = 12052560, upload-time = "2026-06-11T12:45:23.652Z" },
{ url = "https://files.pythonhosted.org/packages/e0/44/f257b7e0bd69c93b06c6cb8ac8d1b901ccb42bedabd83c1a4c77a71f8810/grpcio-1.81.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1e123f9b37edb8375fd74130d1f69c944bbf0a7b06761ae7211154b8759e94d2", size = 6595983, upload-time = "2026-06-11T12:45:26.963Z" },
@@ -1156,16 +1046,6 @@ dependencies = [
]
sdist = { url = "https://files.pythonhosted.org/packages/ad/9a/edfefb47f11ef6b0f39eea4d8f022c5bb05ac1d14fcc7058e84a51305b73/grpcio_tools-1.71.2.tar.gz", hash = "sha256:b5304d65c7569b21270b568e404a5a843cf027c66552a6a0978b23f137679c09", size = 5330655, upload-time = "2025-06-28T04:22:00.308Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/17/e4/0568d38b8da6237ea8ea15abb960fb7ab83eb7bb51e0ea5926dab3d865b1/grpcio_tools-1.71.2-cp311-cp311-linux_armv7l.whl", hash = "sha256:0acb8151ea866be5b35233877fbee6445c36644c0aa77e230c9d1b46bf34b18b", size = 2385557, upload-time = "2025-06-28T04:20:54.323Z" },
- { url = "https://files.pythonhosted.org/packages/76/fb/700d46f72b0f636cf0e625f3c18a4f74543ff127471377e49a071f64f1e7/grpcio_tools-1.71.2-cp311-cp311-macosx_10_14_universal2.whl", hash = "sha256:b28f8606f4123edb4e6da281547465d6e449e89f0c943c376d1732dc65e6d8b3", size = 5447590, upload-time = "2025-06-28T04:20:55.836Z" },
- { url = "https://files.pythonhosted.org/packages/12/69/d9bb2aec3de305162b23c5c884b9f79b1a195d42b1e6dabcc084cc9d0804/grpcio_tools-1.71.2-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:cbae6f849ad2d1f5e26cd55448b9828e678cb947fa32c8729d01998238266a6a", size = 2348495, upload-time = "2025-06-28T04:20:57.33Z" },
- { url = "https://files.pythonhosted.org/packages/d5/83/f840aba1690461b65330efbca96170893ee02fae66651bcc75f28b33a46c/grpcio_tools-1.71.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e4d1027615cfb1e9b1f31f2f384251c847d68c2f3e025697e5f5c72e26ed1316", size = 2742333, upload-time = "2025-06-28T04:20:59.051Z" },
- { url = "https://files.pythonhosted.org/packages/30/34/c02cd9b37de26045190ba665ee6ab8597d47f033d098968f812d253bbf8c/grpcio_tools-1.71.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9bac95662dc69338edb9eb727cc3dd92342131b84b12b3e8ec6abe973d4cbf1b", size = 2473490, upload-time = "2025-06-28T04:21:00.614Z" },
- { url = "https://files.pythonhosted.org/packages/4d/c7/375718ae091c8f5776828ce97bdcb014ca26244296f8b7f70af1a803ed2f/grpcio_tools-1.71.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c50250c7248055040f89eb29ecad39d3a260a4b6d3696af1575945f7a8d5dcdc", size = 2850333, upload-time = "2025-06-28T04:21:01.95Z" },
- { url = "https://files.pythonhosted.org/packages/19/37/efc69345bd92a73b2bc80f4f9e53d42dfdc234b2491ae58c87da20ca0ea5/grpcio_tools-1.71.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6ab1ad955e69027ef12ace4d700c5fc36341bdc2f420e87881e9d6d02af3d7b8", size = 3300748, upload-time = "2025-06-28T04:21:03.451Z" },
- { url = "https://files.pythonhosted.org/packages/d2/1f/15f787eb25ae42086f55ed3e4260e85f385921c788debf0f7583b34446e3/grpcio_tools-1.71.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dd75dde575781262b6b96cc6d0b2ac6002b2f50882bf5e06713f1bf364ee6e09", size = 2913178, upload-time = "2025-06-28T04:21:04.879Z" },
- { url = "https://files.pythonhosted.org/packages/12/aa/69cb3a9dff7d143a05e4021c3c9b5cde07aacb8eb1c892b7c5b9fb4973e3/grpcio_tools-1.71.2-cp311-cp311-win32.whl", hash = "sha256:9a3cb244d2bfe0d187f858c5408d17cb0e76ca60ec9a274c8fd94cc81457c7fc", size = 946256, upload-time = "2025-06-28T04:21:06.518Z" },
- { url = "https://files.pythonhosted.org/packages/1e/df/fb951c5c87eadb507a832243942e56e67d50d7667b0e5324616ffd51b845/grpcio_tools-1.71.2-cp311-cp311-win_amd64.whl", hash = "sha256:00eb909997fd359a39b789342b476cbe291f4dd9c01ae9887a474f35972a257e", size = 1117661, upload-time = "2025-06-28T04:21:08.18Z" },
{ url = "https://files.pythonhosted.org/packages/9c/d3/3ed30a9c5b2424627b4b8411e2cd6a1a3f997d3812dbc6a8630a78bcfe26/grpcio_tools-1.71.2-cp312-cp312-linux_armv7l.whl", hash = "sha256:bfc0b5d289e383bc7d317f0e64c9dfb59dc4bef078ecd23afa1a816358fb1473", size = 2385479, upload-time = "2025-06-28T04:21:10.413Z" },
{ url = "https://files.pythonhosted.org/packages/54/61/e0b7295456c7e21ef777eae60403c06835160c8d0e1e58ebfc7d024c51d3/grpcio_tools-1.71.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:b4669827716355fa913b1376b1b985855d5cfdb63443f8d18faf210180199006", size = 5431521, upload-time = "2025-06-28T04:21:12.261Z" },
{ url = "https://files.pythonhosted.org/packages/75/d7/7bcad6bcc5f5b7fab53e6bce5db87041f38ef3e740b1ec2d8c49534fa286/grpcio_tools-1.71.2-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:d4071f9b44564e3f75cdf0f05b10b3e8c7ea0ca5220acbf4dc50b148552eef2f", size = 2350289, upload-time = "2025-06-28T04:21:13.625Z" },
@@ -1227,13 +1107,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" },
@@ -1366,17 +1239,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" },
@@ -1440,17 +1302,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" },
@@ -1503,24 +1354,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" },
@@ -1699,23 +1532,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" },
@@ -1904,21 +1720,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" },
@@ -1979,22 +1780,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]]
@@ -2084,7 +1873,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" },
]
@@ -2112,9 +1901,6 @@ version = "6.1.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/9a/85/b02b80d74bdb95bfe491d49ad1627e9833c73d331edbe6eed0bdfe170361/python-box-6.1.0.tar.gz", hash = "sha256:6e7c243b356cb36e2c0f0e5ed7850969fede6aa812a7f501de7768996c7744d7", size = 41443, upload-time = "2022-10-29T22:30:45.515Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/d4/16/48bcaacf750fa2cc78882a53eef953c28a42e4a84f5e0b27e05d7188a92a/python_box-6.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ac44b3b85714a4575cc273b5dbd39ef739f938ef6c522d6757704a29e7797d16", size = 1571634, upload-time = "2022-10-29T22:32:40.118Z" },
- { url = "https://files.pythonhosted.org/packages/8b/b4/ae3736cfc3970fe6ee348620780811c016fe4c01d2d0ff4a3a19f4eff5f7/python_box-6.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f0036f91e13958d2b37d2bc74c1197aa36ffd66755342eb64910f63d8a2990f", size = 3546030, upload-time = "2022-10-29T22:35:05.688Z" },
- { url = "https://files.pythonhosted.org/packages/f3/7d/5cc1f3145792b803ee6debc82d1faf791659baa15c2de7b1d9318adbcd68/python_box-6.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:af6bcee7e1abe9251e9a41ca9ab677e1f679f6059321cfbae7e78a3831e0b736", size = 957417, upload-time = "2022-10-29T22:33:41.542Z" },
{ url = "https://files.pythonhosted.org/packages/88/c6/6d1e368710cb6c458ed692d179d7e101ebce80a3e640b2e74cc7ae886d6f/python_box-6.1.0-py3-none-any.whl", hash = "sha256:bdec0a5f5a17b01fc538d292602a077aa8c641fb121e1900dff0591791af80e8", size = 27277, upload-time = "2022-10-29T22:30:43.645Z" },
]
@@ -2167,15 +1953,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" },
@@ -2251,21 +2028,6 @@ version = "2026.5.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/2e/43/25a8dcd3feedd735039a8f0b5b7e3b118232b5eae288c4fd9ab200d41094/rpds_py-2026.5.1.tar.gz", hash = "sha256:07b24fea40541e28570e5b795a4a38fbdcd12550c06bd0748005ecc8116ca256", size = 64459, upload-time = "2026-05-28T12:02:13.232Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/4f/a0/acf8b6fc20bfdcd3a45bd3f57680fb198e157b7e997b9123b10763798bd2/rpds_py-2026.5.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:3397a5ed7174dc2786bb214030232fc36fe8e5584fec43a9952cc542b1a12036", size = 355609, upload-time = "2026-05-28T11:58:50.78Z" },
- { url = "https://files.pythonhosted.org/packages/b6/95/f8203fd997484b1690a6869cd0e503b6c3c6be55b0ecc36d1a491fe742f0/rpds_py-2026.5.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:99ab6ba7bfa2cb0f96a04e3652355bf04e3f51aceb1e943b8541dab7ba4828cc", size = 348460, upload-time = "2026-05-28T11:58:52.374Z" },
- { url = "https://files.pythonhosted.org/packages/33/8c/b47326ad2f0be545a5e5c1a55937a12afaea7d392ba2837bb9680f57e6c9/rpds_py-2026.5.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0efbe45632665e53e3db8fe1e5692db58fc5cb9bab4459d570b83efefe11164", size = 381031, upload-time = "2026-05-28T11:58:53.775Z" },
- { url = "https://files.pythonhosted.org/packages/22/0b/e83bbd97ffac6f6389b605cd4e1c8ac5761dc7e977769c9255d8c5adb7bd/rpds_py-2026.5.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:01d17b29c0c23d82b1f4751147ec49cf451f1fc2554eb9ef5f957e55d2656ead", size = 387121, upload-time = "2026-05-28T11:58:55.243Z" },
- { url = "https://files.pythonhosted.org/packages/fd/0e/d285d1bc8864245919c61e1ca82263e4a66d337759c3a4cef72766ff9afc/rpds_py-2026.5.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7559f72b94ae52659086c595dfa017cde03155f7832071d30959049052cb3ece", size = 501026, upload-time = "2026-05-28T11:58:56.788Z" },
- { url = "https://files.pythonhosted.org/packages/86/06/ccb2109a1e543437b5e43816f2b43b9554cc6783145528a4e3711e05c011/rpds_py-2026.5.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e25b7088f9ccbfc0dfcaa52bf969300ca229e10ecf758974ebcbb080a4b37bb", size = 391865, upload-time = "2026-05-28T11:58:58.298Z" },
- { url = "https://files.pythonhosted.org/packages/3d/33/237173db1cfef10105b3839a24de00eb8d2a523711add4632447cdf0aedd/rpds_py-2026.5.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:613fc4ee9eaef26dc5840666214dd6fbcebcf32f46e76f4abc473059f4e13dda", size = 378012, upload-time = "2026-05-28T11:58:59.589Z" },
- { url = "https://files.pythonhosted.org/packages/97/64/1eae54e34d5161f9969295e80bd6b62a55f2b6ac5f2a5b60d02c2140e758/rpds_py-2026.5.1-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:85264a90ff4c05c1568dd65f5921c837614b67c60358fb4c17df3b7f2e90690a", size = 391111, upload-time = "2026-05-28T11:59:01.104Z" },
- { url = "https://files.pythonhosted.org/packages/d8/34/5bb334a5a0f65d77869217c4654f34c78a7d11b93938a3c076a2edeafc52/rpds_py-2026.5.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe71bca7d547acb17027c7fd1624ff8aae623499c498d3e7011182c4de5c25e0", size = 409225, upload-time = "2026-05-28T11:59:02.433Z" },
- { url = "https://files.pythonhosted.org/packages/16/0f/007ec21283b5b040b4ec3bd95e0402591e22bfa7d5c93dfe01c465c2d2d7/rpds_py-2026.5.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05fa4f41f37ec97c9c260441a940450a192f78d774d2b097eee1379f1e1246a", size = 556487, upload-time = "2026-05-28T11:59:04.012Z" },
- { url = "https://files.pythonhosted.org/packages/ff/10/5437c94508169b6b22d8418fef7a66e9ffb5f3b9e9c94460f2eedafe06ff/rpds_py-2026.5.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:df1d2a1996755b24b9ecee92cb4d36c28f86f464a6a173349c26bab41e94b8c2", size = 620798, upload-time = "2026-05-28T11:59:05.485Z" },
- { url = "https://files.pythonhosted.org/packages/e0/d5/9937dce4d6bda74157b954e7d1460db05a22f5929dccfeeba1ed27a93df0/rpds_py-2026.5.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8895840ac4809e5f60c88fd07617cd71326e73d6e5a8aa783c5c0f7c24985de2", size = 584053, upload-time = "2026-05-28T11:59:06.837Z" },
- { url = "https://files.pythonhosted.org/packages/6c/31/750617dd0ae1752471bf43f9e41d263398fae7cde7849d23b8574a70e617/rpds_py-2026.5.1-cp311-cp311-win32.whl", hash = "sha256:3684a59b158a7683aaeb8e25352e9a9dd2122cec78f2d8530266e4f91b4c7b3f", size = 214390, upload-time = "2026-05-28T11:59:08.402Z" },
- { url = "https://files.pythonhosted.org/packages/3c/bb/3dcab0e1d9516303f2eb672a5d6f62eca5a69e2886301e9c8c54b520c39b/rpds_py-2026.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:7bd530e6a530bb3ea892f194fafa455f3516ac25ecf7143fd33c09be62b0470a", size = 231097, upload-time = "2026-05-28T11:59:09.786Z" },
- { url = "https://files.pythonhosted.org/packages/49/d6/c6bbf5cb1cf12b9732df8074b57f6ef8341ba884c95d40632ae8bddb44e4/rpds_py-2026.5.1-cp311-cp311-win_arm64.whl", hash = "sha256:0a5ae4dbe43c1076983b72616496919872ae7bbe7a1e21cc48336bc3154d130b", size = 226361, upload-time = "2026-05-28T11:59:11.079Z" },
{ url = "https://files.pythonhosted.org/packages/d4/e7/a78582dc57caa592dcc7d4fb69b61390561e908eb3d2f5df5928a8e354c0/rpds_py-2026.5.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3abe24a66e57adcfa645d718063a5fa5103ecc71ddbf26d78af8f9368018ff1d", size = 353040, upload-time = "2026-05-28T11:59:12.531Z" },
{ url = "https://files.pythonhosted.org/packages/a3/43/35e3f136343aef451e545ce8c38d36c2f93c0ed88703db8b64ba2b205c68/rpds_py-2026.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:58b1d94308ddf0b1982f61f2eb54bf92997c9ece8a8093ef014250f4a517906c", size = 345775, upload-time = "2026-05-28T11:59:13.827Z" },
{ url = "https://files.pythonhosted.org/packages/20/e1/0f2160c5982d3157734d5cb3ed63d8b2d583a73c9864f77b666449f32cf8/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fa92420128dadce7f54bd73ba1825a273e9268fe9e35dbf7e6362890efa4e08", size = 376329, upload-time = "2026-05-28T11:59:15.271Z" },
@@ -2368,18 +2130,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/77/65/38ab2f90df44c2febfb63cc10ced40763d9b4bc94d173e734528663fe7f5/rpds_py-2026.5.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:b1be5c35683684d5331b93600c210e8367c254683d8a6df6bd21bd2da3a334fb", size = 581839, upload-time = "2026-05-28T12:01:48.109Z" },
{ url = "https://files.pythonhosted.org/packages/15/2d/ce1f605fe036aadd460e5822e578c6c7ec3a860936cca37d6e0f299daa77/rpds_py-2026.5.1-cp315-cp315t-win32.whl", hash = "sha256:75808f6c38ce7749bb68cc2770161aae5045e6c6f6781a9782e74b93304399df", size = 207866, upload-time = "2026-05-28T12:01:49.648Z" },
{ url = "https://files.pythonhosted.org/packages/79/cb/966040123eb102371559746908ef2c9471f4d43e17ec9a645a2258dab64b/rpds_py-2026.5.1-cp315-cp315t-win_amd64.whl", hash = "sha256:90bd6630002a1c7f09e7843dd79f0d24f3d2897cc25a753480917865d14f15b3", size = 225441, upload-time = "2026-05-28T12:01:51.408Z" },
- { url = "https://files.pythonhosted.org/packages/42/56/3fe0fb34820ff667be791b3a3c22b85e8bcba54e9c832f47438c191fa7be/rpds_py-2026.5.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:edf2765d84e42447f112ad877af8fe1db0089aaec5b28e88d6eab45e7fe99cea", size = 357151, upload-time = "2026-05-28T12:01:53.43Z" },
- { url = "https://files.pythonhosted.org/packages/8b/f2/3eb9ccdb9f143b8c9b003978898cb497f942a324c077401e6b8834238e63/rpds_py-2026.5.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ad3773236e95f7f33991eb125224b7da66f206504d032a253a02da7e134519fb", size = 350195, upload-time = "2026-05-28T12:01:54.901Z" },
- { url = "https://files.pythonhosted.org/packages/a7/24/dbda232bc4f3ed732120692ab0d2c8402cb020516556d8bee622dcef2413/rpds_py-2026.5.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a04df86b3f0fade39ec8fd0e0aab089b1da9fbd2b48df778a57ef96f5e7d38df", size = 381850, upload-time = "2026-05-28T12:01:56.601Z" },
- { url = "https://files.pythonhosted.org/packages/40/30/32e769839a358f78810c234f160f2cc21d1e4e47e1c0e0e0d535be5a0219/rpds_py-2026.5.1-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6142dbd80c4df62a5d899f0d616d417f84e0bc8d32526c8e5589019d75d028a7", size = 387899, upload-time = "2026-05-28T12:01:58.212Z" },
- { url = "https://files.pythonhosted.org/packages/ab/86/ec84d243aadb3b34b71dd26a010d0930b2d284ff5fc9a69fec53810ee6fd/rpds_py-2026.5.1-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0b35217adefe87f2fe4db7e9766cabe84744bfe9616d9667be18988928c7f2dc", size = 501618, upload-time = "2026-05-28T12:01:59.888Z" },
- { url = "https://files.pythonhosted.org/packages/74/25/b60e52686bbff777a64f9e4f4d3dd57980dc846913777177a2c92e4937aa/rpds_py-2026.5.1-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b95d5e11fc712b752081183a55a244c03cd00570489edd7014d8899f8ceb8162", size = 394003, upload-time = "2026-05-28T12:02:01.482Z" },
- { url = "https://files.pythonhosted.org/packages/9b/c7/b3a6a588cc2219510ef3f42e207483a93950bedd1e3a0fd4015c95cff9e5/rpds_py-2026.5.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:141c9498daf2ace9eda35d2b0e376f9ea8b058d84f2aef4f96fccfd449a2f251", size = 379778, upload-time = "2026-05-28T12:02:03.197Z" },
- { url = "https://files.pythonhosted.org/packages/31/00/c7dba3fc8a3da8cb3f6db1eb3386be4d79c2e97c6890d20eb9ac66ae8c43/rpds_py-2026.5.1-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:6f249f8b860a200ad35193af961183ebe9132710484e6f6ce0cf89fd83c63a9a", size = 392359, upload-time = "2026-05-28T12:02:04.817Z" },
- { url = "https://files.pythonhosted.org/packages/93/dd/472ba494c70753f93745992c99855bee0636daf74e6984e5e003f150316f/rpds_py-2026.5.1-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e4abbf391a70be864920858bf360f4fb380577c9a0f732438a1996726e2c195b", size = 412820, upload-time = "2026-05-28T12:02:06.401Z" },
- { url = "https://files.pythonhosted.org/packages/1d/6f/93831a3bfe789542ed0c1d0d74b78b440f055d6dc3ea4640eba2d95e6e23/rpds_py-2026.5.1-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:c74005a7bb87752acf351c93897ec63ad77a07a0da7ecad9c050e32e7286ba34", size = 557243, upload-time = "2026-05-28T12:02:08.013Z" },
- { url = "https://files.pythonhosted.org/packages/1f/ff/0b3d604614ffc77522c6b288fdbce68957eb583da1002aa65ba38ac0ee40/rpds_py-2026.5.1-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:8213afbe8a3a906fb9acb2014423fe3359ee783d0bf90995f70623a3217bfa6c", size = 623541, upload-time = "2026-05-28T12:02:09.661Z" },
- { url = "https://files.pythonhosted.org/packages/ea/ea/e7b0251441da9adfeaebcf29601d10f2a1455fcf0772fae9e7e19032bd96/rpds_py-2026.5.1-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:8c43a8a973270fd173bf48cdf80bbe66312421cba68d40845034f174f2389049", size = 586326, upload-time = "2026-05-28T12:02:11.47Z" },
]
[[package]]
@@ -2400,16 +2150,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" },
@@ -2546,13 +2286,6 @@ dependencies = [
]
sdist = { url = "https://files.pythonhosted.org/packages/02/f1/a7a892f18d4d224e6b26f706531eafccc41e37594d37d304786969ee13cb/sqlalchemy-2.0.51.tar.gz", hash = "sha256:804dccd8a4a6242c4e30ad961e540e18a588f6527202f2d6791b01845d59fdc9", size = 9912201, upload-time = "2026-06-15T15:41:20.012Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/3a/69/a67c69e5f28fc9c99d6f7bd60bd50e91f2fed2423e3b30fb228fa00e51f3/sqlalchemy-2.0.51-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1aa10c0daee6705294d181daadaa793221e1a59ed55000a3fab1d42b088ce4ba", size = 2161838, upload-time = "2026-06-15T16:05:17.144Z" },
- { url = "https://files.pythonhosted.org/packages/9a/a4/c8c22b8438bddc0a030157c6ec0f6ef97b3c38effa444bdab2a27af04090/sqlalchemy-2.0.51-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a5b2ed6d828f1f09bd812861f4f59ca3bc3803f9df871f4555187f0faf018604", size = 3319402, upload-time = "2026-06-15T16:10:40.002Z" },
- { url = "https://files.pythonhosted.org/packages/90/54/44012d32fd77d991256d2ff793ba3807c51d40cb27a85b4796224f6744df/sqlalchemy-2.0.51-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:436728ce18a80f6951a1e11cc6112c2ede9faf20766f1a26195a7c441ca12dbd", size = 3319675, upload-time = "2026-06-15T16:12:25.658Z" },
- { url = "https://files.pythonhosted.org/packages/29/a5/de0592acaf5906cd7430874392d6f7e8b4a7c8437610953ee2d1501c0b44/sqlalchemy-2.0.51-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dc261707bf5739aea8a541593f3cc1d463c2701fb05fbcbba0ce031b69a21260", size = 3270777, upload-time = "2026-06-15T16:10:42.125Z" },
- { url = "https://files.pythonhosted.org/packages/cb/14/a44c90739c780b362238e4ac3cb19dd0ca40d13e6ddc5daa112166ddab4f/sqlalchemy-2.0.51-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a6d26094615306d116dd5e4a51b0304c99dd2356fc569eed6922a80a6bd3b265", size = 3293940, upload-time = "2026-06-15T16:12:27.156Z" },
- { url = "https://files.pythonhosted.org/packages/65/eb/fbd0f206a330e66f8c602a99c37c4e731f107faed62954b41b01f16dd9d9/sqlalchemy-2.0.51-cp311-cp311-win32.whl", hash = "sha256:ca8435d13829b92f4a97362d91975154a4015db3a2634154e1754e9a915e6b86", size = 2121183, upload-time = "2026-06-15T16:13:29.905Z" },
- { url = "https://files.pythonhosted.org/packages/ad/fd/005bf80f3cf6e5c62b5dd68616280f51cd012c60840fa74781b3ed7b1623/sqlalchemy-2.0.51-cp311-cp311-win_amd64.whl", hash = "sha256:4a011ea4510683319ce4ed274b56ee05194b39b6da9d09ca7a39388f0fa84dcc", size = 2145796, upload-time = "2026-06-15T16:13:31.283Z" },
{ url = "https://files.pythonhosted.org/packages/d5/70/e868bc5412acd101a8280f25c95f10eeae0771c4eb806b02491142810ee8/sqlalchemy-2.0.51-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d78702b26ba1c18b2d0fb2ea940ba7f17a9581b42e8361ff93920ebbee1235a", size = 2160291, upload-time = "2026-06-15T16:08:48.918Z" },
{ url = "https://files.pythonhosted.org/packages/e5/1c/71ee0f8a6b9d7316a1ccd30430b4c62b6c2e36adc96017a4e3a72dce49d6/sqlalchemy-2.0.51-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:581921d849d6e6f994d560389192955e80e2950e18fcdfe2ccea863e01158e6e", size = 3343835, upload-time = "2026-06-15T16:19:42.613Z" },
{ url = "https://files.pythonhosted.org/packages/2b/7c/7ab9f9aadc5944fdd06612484ed7918fe376ad871a5f50404dc1536e0194/sqlalchemy-2.0.51-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1d21ce524ab86c23046e992a5b81cb54c21079c6df6e78b8fc77d77cac70a6b9", size = 3358470, upload-time = "2026-06-15T16:26:38.011Z" },
@@ -2589,6 +2322,30 @@ asyncio = [
{ name = "greenlet" },
]
+[[package]]
+name = "starlark-pyo3"
+version = "2026.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/2d/ee/8fad5836bf22ee73f7b02e5b2b80808f20c28b1906c59c014a04d7a97996/starlark_pyo3-2026.1.tar.gz", hash = "sha256:c2aab1df537901be9244faca4b859739e18d4567db302d472e46e1f35cb13847", size = 42029, upload-time = "2026-05-18T21:38:13.966Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e7/c9/a74f38f41b4915b39210261d6d2287006f4fb73a9c3571eb01fe1b86bf74/starlark_pyo3-2026.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c98773d957ac5236b393786fb000ce83a9b7f77f19cfc4149efb0199c9997b73", size = 3102716, upload-time = "2026-05-18T21:37:45.301Z" },
+ { url = "https://files.pythonhosted.org/packages/10/a1/0da624093605bedf3a15b29f1f2ed1e02305588f27bc1609297e3190a274/starlark_pyo3-2026.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8192b394b637cb2e6056b915bc23ce5641abecf5197f51567ccd673a8f520335", size = 3611753, upload-time = "2026-05-18T21:37:46.963Z" },
+ { url = "https://files.pythonhosted.org/packages/68/4c/7ef4d18bfba685b4acc03198146971d3fa68e0a9338cc9e78535cefb929f/starlark_pyo3-2026.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f8bad3ef79246b07abc555e2021cbc947c0c0a38f384dca9a943d2e5df466e4b", size = 3409661, upload-time = "2026-05-18T21:37:48.477Z" },
+ { url = "https://files.pythonhosted.org/packages/44/bb/f6e5e7528b26f48885ac9191d4674bc85f5c8d657faf55107b4fa7d057b9/starlark_pyo3-2026.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:301479c46063b9289f17479e927843f3d652e25b2a6593fdfb40c74ccd94a99a", size = 3544615, upload-time = "2026-05-18T21:37:50.3Z" },
+ { url = "https://files.pythonhosted.org/packages/1c/b1/abf6960722743d17c6bf5290a44bd824759b692d6a39e15f5b9c5747ee65/starlark_pyo3-2026.1-cp312-cp312-win_amd64.whl", hash = "sha256:34e63685186ac77d9d192ae1aa927ce2585529f29e624e9dda3f374943dc37dc", size = 2696097, upload-time = "2026-05-18T21:37:52.186Z" },
+ { url = "https://files.pythonhosted.org/packages/9d/f2/9302070c00b1daca369173763d2d1613a92ba2776b78d020d7b45300c898/starlark_pyo3-2026.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:446ae406d8e14f34fc736524de56572b138a4f80b1a995d1e2f1e8491fc65f1d", size = 3101666, upload-time = "2026-05-18T21:37:53.776Z" },
+ { url = "https://files.pythonhosted.org/packages/66/8d/4f4459c83c03d4551e33704af0f3dad0377e6ac934e5020f81a1bc30c91c/starlark_pyo3-2026.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c781c2a623835ceb47abaa1c6096b52f96ca26f20432ca87f4b72a963d4e9e7", size = 3610912, upload-time = "2026-05-18T21:37:55.371Z" },
+ { url = "https://files.pythonhosted.org/packages/de/e7/f84d3c4d22c502cddef00142c5883ec2bac6005b7c2f3d73b2432f64496a/starlark_pyo3-2026.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:75558cd34cdf4099633dfa147a55436210be24eacfadb08ae00474e1965e6c55", size = 3409460, upload-time = "2026-05-18T21:37:56.72Z" },
+ { url = "https://files.pythonhosted.org/packages/7d/f4/667e251050806da06e35c5759b0e483cca10d7b521a9b5394b7db7af1ad9/starlark_pyo3-2026.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ca09bb18a14cf9745d2c22680da87f76e336a27603283132cb00b4cd9f1f26a", size = 3543957, upload-time = "2026-05-18T21:37:58.297Z" },
+ { url = "https://files.pythonhosted.org/packages/29/25/52b5ed8b8062f398ed4ede50bf33972e9c33747749302da726aed6e2d03e/starlark_pyo3-2026.1-cp313-cp313-win_amd64.whl", hash = "sha256:a2265f4f9468710571a523cdbb25e1c5039c0daac9585eca9bf8f9ce66fd0756", size = 2695227, upload-time = "2026-05-18T21:37:59.817Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/ef/319857e7d953ff067c1a5cc862013c5f7b78ad0f7045c62fb5558a535ab0/starlark_pyo3-2026.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:6f015bcf243dd946ec45ad2742311450ba8608b0c0a76fe83f629fe4ca63f3ec", size = 3103388, upload-time = "2026-05-18T21:38:01.663Z" },
+ { url = "https://files.pythonhosted.org/packages/7a/38/e47dca5bd1f76bd31f0b05a6f90b53e09aad2f011e0158aefbac43c82784/starlark_pyo3-2026.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba22714843d4ed89c403704a7ffe3104eee30acc5e648676a2516580dbf1e727", size = 3544759, upload-time = "2026-05-18T21:38:03.474Z" },
+ { url = "https://files.pythonhosted.org/packages/7e/ae/128ffbc18390553bc78728b302d49f4c0b02c6f61435df21fab5b8770260/starlark_pyo3-2026.1-cp314-cp314-win_amd64.whl", hash = "sha256:849b4a755242f97eae96ccc9140b61c6c37bc01388f5dc69838d8057b867616d", size = 2695285, upload-time = "2026-05-18T21:38:05.29Z" },
+]
+
[[package]]
name = "starlette"
version = "1.3.1"
@@ -2641,7 +2398,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"] },
@@ -2694,12 +2451,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" },
@@ -2752,10 +2512,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'", specifier = ">=2025.2.5" },
{ name = "stevedore", specifier = ">=4,<5" },
{ name = "websockets", marker = "extra == 'graphql'" },
]
-provides-extras = ["graphql", "grpc", "mqtt"]
+provides-extras = ["graphql", "grpc", "mqtt", "scriptable"]
[package.metadata.requires-dev]
dev = [
@@ -2779,7 +2540,7 @@ dev = [
{ name = "pytest-asyncio", specifier = ">=1.3.0" },
{ name = "pytest-cov" },
{ name = "pytest-xdist" },
- { name = "ruff" },
+ { name = "ruff", specifier = ">=0.15.9" },
{ name = "tbump", specifier = ">=6.10.0" },
{ name = "tomli" },
{ name = "tox", specifier = ">4.20,<5" },
@@ -2899,15 +2660,6 @@ 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" },
@@ -3184,12 +2936,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" },
@@ -3240,20 +2986,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" },
@@ -3329,10 +3061,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]]
@@ -3341,15 +3069,6 @@ version = "16.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/04/24/4b2031d72e840ce4c1ccb255f693b15c334757fc50023e4db9537080b8c4/websockets-16.0.tar.gz", hash = "sha256:5f6261a5e56e8d5c42a4497b364ea24d94d9563e8fbd44e78ac40879c60179b5", size = 179346, upload-time = "2026-01-10T09:23:47.181Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/f2/db/de907251b4ff46ae804ad0409809504153b3f30984daf82a1d84a9875830/websockets-16.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:31a52addea25187bde0797a97d6fc3d2f92b6f72a9370792d65a6e84615ac8a8", size = 177340, upload-time = "2026-01-10T09:22:34.539Z" },
- { url = "https://files.pythonhosted.org/packages/f3/fa/abe89019d8d8815c8781e90d697dec52523fb8ebe308bf11664e8de1877e/websockets-16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:417b28978cdccab24f46400586d128366313e8a96312e4b9362a4af504f3bbad", size = 175022, upload-time = "2026-01-10T09:22:36.332Z" },
- { url = "https://files.pythonhosted.org/packages/58/5d/88ea17ed1ded2079358b40d31d48abe90a73c9e5819dbcde1606e991e2ad/websockets-16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:af80d74d4edfa3cb9ed973a0a5ba2b2a549371f8a741e0800cb07becdd20f23d", size = 175319, upload-time = "2026-01-10T09:22:37.602Z" },
- { url = "https://files.pythonhosted.org/packages/d2/ae/0ee92b33087a33632f37a635e11e1d99d429d3d323329675a6022312aac2/websockets-16.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:08d7af67b64d29823fed316505a89b86705f2b7981c07848fb5e3ea3020c1abe", size = 184631, upload-time = "2026-01-10T09:22:38.789Z" },
- { url = "https://files.pythonhosted.org/packages/c8/c5/27178df583b6c5b31b29f526ba2da5e2f864ecc79c99dae630a85d68c304/websockets-16.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7be95cfb0a4dae143eaed2bcba8ac23f4892d8971311f1b06f3c6b78952ee70b", size = 185870, upload-time = "2026-01-10T09:22:39.893Z" },
- { url = "https://files.pythonhosted.org/packages/87/05/536652aa84ddc1c018dbb7e2c4cbcd0db884580bf8e95aece7593fde526f/websockets-16.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d6297ce39ce5c2e6feb13c1a996a2ded3b6832155fcfc920265c76f24c7cceb5", size = 185361, upload-time = "2026-01-10T09:22:41.016Z" },
- { url = "https://files.pythonhosted.org/packages/6d/e2/d5332c90da12b1e01f06fb1b85c50cfc489783076547415bf9f0a659ec19/websockets-16.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1c1b30e4f497b0b354057f3467f56244c603a79c0d1dafce1d16c283c25f6e64", size = 184615, upload-time = "2026-01-10T09:22:42.442Z" },
- { url = "https://files.pythonhosted.org/packages/77/fb/d3f9576691cae9253b51555f841bc6600bf0a983a461c79500ace5a5b364/websockets-16.0-cp311-cp311-win32.whl", hash = "sha256:5f451484aeb5cafee1ccf789b1b66f535409d038c56966d6101740c1614b86c6", size = 178246, upload-time = "2026-01-10T09:22:43.654Z" },
- { url = "https://files.pythonhosted.org/packages/54/67/eaff76b3dbaf18dcddabc3b8c1dba50b483761cccff67793897945b37408/websockets-16.0-cp311-cp311-win_amd64.whl", hash = "sha256:8d7f0659570eefb578dacde98e24fb60af35350193e4f56e11190787bee77dac", size = 178684, upload-time = "2026-01-10T09:22:44.941Z" },
{ url = "https://files.pythonhosted.org/packages/84/7b/bac442e6b96c9d25092695578dda82403c77936104b5682307bd4deb1ad4/websockets-16.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:71c989cbf3254fbd5e84d3bff31e4da39c43f884e64f2551d14bb3c186230f00", size = 177365, upload-time = "2026-01-10T09:22:46.787Z" },
{ url = "https://files.pythonhosted.org/packages/b0/fe/136ccece61bd690d9c1f715baaeefd953bb2360134de73519d5df19d29ca/websockets-16.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8b6e209ffee39ff1b6d0fa7bfef6de950c60dfb91b8fcead17da4ee539121a79", size = 175038, upload-time = "2026-01-10T09:22:47.999Z" },
{ url = "https://files.pythonhosted.org/packages/40/1e/9771421ac2286eaab95b8575b0cb701ae3663abf8b5e1f64f1fd90d0a673/websockets-16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:86890e837d61574c92a97496d590968b23c2ef0aeb8a9bc9421d174cd378ae39", size = 175328, upload-time = "2026-01-10T09:22:49.809Z" },
@@ -3386,11 +3105,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/88/a8/a080593f89b0138b6cba1b28f8df5673b5506f72879322288b031337c0b8/websockets-16.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:32da954ffa2814258030e5a57bc73a3635463238e797c7375dc8091327434206", size = 185356, upload-time = "2026-01-10T09:23:32.627Z" },
{ url = "https://files.pythonhosted.org/packages/c2/b6/b9afed2afadddaf5ebb2afa801abf4b0868f42f8539bfe4b071b5266c9fe/websockets-16.0-cp314-cp314t-win32.whl", hash = "sha256:5a4b4cc550cb665dd8a47f868c8d04c8230f857363ad3c9caf7a0c3bf8c61ca6", size = 178085, upload-time = "2026-01-10T09:23:33.816Z" },
{ url = "https://files.pythonhosted.org/packages/9f/3e/28135a24e384493fa804216b79a6a6759a38cc4ff59118787b9fb693df93/websockets-16.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b14dc141ed6d2dde437cddb216004bcac6a1df0935d79656387bd41632ba0bbd", size = 178531, upload-time = "2026-01-10T09:23:35.016Z" },
- { url = "https://files.pythonhosted.org/packages/72/07/c98a68571dcf256e74f1f816b8cc5eae6eb2d3d5cfa44d37f801619d9166/websockets-16.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:349f83cd6c9a415428ee1005cadb5c2c56f4389bc06a9af16103c3bc3dcc8b7d", size = 174947, upload-time = "2026-01-10T09:23:36.166Z" },
- { url = "https://files.pythonhosted.org/packages/7e/52/93e166a81e0305b33fe416338be92ae863563fe7bce446b0f687b9df5aea/websockets-16.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:4a1aba3340a8dca8db6eb5a7986157f52eb9e436b74813764241981ca4888f03", size = 175260, upload-time = "2026-01-10T09:23:37.409Z" },
- { url = "https://files.pythonhosted.org/packages/56/0c/2dbf513bafd24889d33de2ff0368190a0e69f37bcfa19009ef819fe4d507/websockets-16.0-pp311-pypy311_pp73-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f4a32d1bd841d4bcbffdcb3d2ce50c09c3909fbead375ab28d0181af89fd04da", size = 176071, upload-time = "2026-01-10T09:23:39.158Z" },
- { url = "https://files.pythonhosted.org/packages/a5/8f/aea9c71cc92bf9b6cc0f7f70df8f0b420636b6c96ef4feee1e16f80f75dd/websockets-16.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0298d07ee155e2e9fda5be8a9042200dd2e3bb0b8a38482156576f863a9d457c", size = 176968, upload-time = "2026-01-10T09:23:41.031Z" },
- { url = "https://files.pythonhosted.org/packages/9a/3f/f70e03f40ffc9a30d817eef7da1be72ee4956ba8d7255c399a01b135902a/websockets-16.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:a653aea902e0324b52f1613332ddf50b00c06fdaf7e92624fbf8c77c78fa5767", size = 178735, upload-time = "2026-01-10T09:23:42.259Z" },
{ url = "https://files.pythonhosted.org/packages/6f/28/258ebab549c2bf3e64d2b0217b973467394a9cea8c42f70418ca2c5d0d2e/websockets-16.0-py3-none-any.whl", hash = "sha256:1637db62fad1dc833276dded54215f2c7fa46912301a24bd94d45d46a011ceec", size = 171598, upload-time = "2026-01-10T09:23:45.395Z" },
]
@@ -3429,23 +3143,6 @@ dependencies = [
]
sdist = { url = "https://files.pythonhosted.org/packages/79/12/1e8f37460ea0f7eb59c221fdaf0ed75e7ac43e97f8093b9c6f411df50a78/yarl-1.24.2.tar.gz", hash = "sha256:9ac374123c6fd7abf64d1fec93962b0bd4ee2c19751755a762a72dd96c0378f8", size = 210798, upload-time = "2026-05-19T21:31:05.599Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/c5/c5/1ce244152ff2839645e7cae92f90e7bafcb2c52bea7ff586ac714f14f5df/yarl-1.24.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:36348bebb147b83818b9d7e673ea4debc75970afc6ffdc7e3975ad05ce5a58c1", size = 128971, upload-time = "2026-05-19T21:28:20.543Z" },
- { url = "https://files.pythonhosted.org/packages/87/5a/00f36967203ed89cb3acd2c8ed526cc3fed9418eb70ce128160a911c8499/yarl-1.24.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a97e42c8a2233f2f279ecadd9e4a037bcb5d813b78435e8eedd4db5a9e9708c", size = 91507, upload-time = "2026-05-19T21:28:22.556Z" },
- { url = "https://files.pythonhosted.org/packages/31/d0/1fb0c1cd27288f39f6974da4318c32768d72c9890984541fdf1e2e32a51d/yarl-1.24.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8d027d56f1035e339d1001ac33eceab5b2ec8e42e449787bb75e289fb9a5cd1d", size = 91343, upload-time = "2026-05-19T21:28:24.092Z" },
- { url = "https://files.pythonhosted.org/packages/03/ce/d4a646508bed2f8dec6435b40166fe9308dd191262033d3f307b2bbcaecd/yarl-1.24.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a6377060e7927187a42b7eb202090cbe2b34933a4eeaf90e3bd9e33432e5cae", size = 105704, upload-time = "2026-05-19T21:28:25.872Z" },
- { url = "https://files.pythonhosted.org/packages/4b/07/b3278e82d8bc41485bcf6d856cd0433262593de615b1d3dc43bd3f5bead4/yarl-1.24.2-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:17076578bce0049a5ce57d14ad1bded391b68a3b213e9b81b0097b090244999a", size = 97281, upload-time = "2026-05-19T21:28:27.352Z" },
- { url = "https://files.pythonhosted.org/packages/17/5b/4cee6e7c92e487bebe7afc797da0aa54a248ab4e776a68fe369ec29665a5/yarl-1.24.2-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:50713f1d4d6be6375bb178bb43d140ee1acb8abe589cd723320b7925a275be1e", size = 114020, upload-time = "2026-05-19T21:28:29.458Z" },
- { url = "https://files.pythonhosted.org/packages/5c/82/111076571545a7d4f9cca3fbd5c6f40615af58642be09f12328f48022468/yarl-1.24.2-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:34263e2fa8fb5bb63a0d97706cda38edbad62fddb58c7f12d6acbc092812aa50", size = 111450, upload-time = "2026-05-19T21:28:31.262Z" },
- { url = "https://files.pythonhosted.org/packages/b6/ec/08f671f69a444d704aeecebf92af659b67b97a869942411d0a578b08c334/yarl-1.24.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:49016d82f032b1bd1e10b01078a7d29ae71bf468eeae0ea22df8bab691e60003", size = 106384, upload-time = "2026-05-19T21:28:32.856Z" },
- { url = "https://files.pythonhosted.org/packages/e5/86/ce41e7a7a199340b2330d52b60f25c4074b6636dd0e60b1a80d31a9db042/yarl-1.24.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3f6d2c216318f8f32038ca3f72501ba08536f0fd18a36e858836b121b2deed9f", size = 106153, upload-time = "2026-05-19T21:28:35.222Z" },
- { url = "https://files.pythonhosted.org/packages/c4/5d/31be8a729531ab3e55ac3e7e5c800be8c89ea98947f418b2f6ea259fb6ee/yarl-1.24.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:08d3a33218e0c64393e7610284e770409a9c31c429b078bcb24096ed0a783b8f", size = 105322, upload-time = "2026-05-19T21:28:36.642Z" },
- { url = "https://files.pythonhosted.org/packages/47/9b/b57afb22b386ae87ac9940f09878b98d8c333f89113e6fc96fcf4ca9eb64/yarl-1.24.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:5d699376c4ca3cba49bbfae3a05b5b70ded572937171ce1e0b8d87118e2ba294", size = 99057, upload-time = "2026-05-19T21:28:38.386Z" },
- { url = "https://files.pythonhosted.org/packages/a3/4f/06348c27c8389256c313e8a57d796808fc0264c915dd5e7cfd3c0e314dc7/yarl-1.24.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a1cab588b4fa14bea2e55ebea27478adfb05372f47573738e1acc4a36c0b05d2", size = 113502, upload-time = "2026-05-19T21:28:40.091Z" },
- { url = "https://files.pythonhosted.org/packages/5f/1c/284f307b298e4a17b7943b07d9d7ecc4151537f8d137ba51f3bb6c31ca20/yarl-1.24.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:ec87ccc31bd21db7ad009d8572c127c1000f268517618a4cc09adba3c2a7f21c", size = 105253, upload-time = "2026-05-19T21:28:41.987Z" },
- { url = "https://files.pythonhosted.org/packages/c8/bf/0de123bec8619e45c80cbded9085f61b5b4a9eddb8abe6d25d28ee1ec866/yarl-1.24.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d1dd47a22843b212baa8d74f37796815d43bd046b42a0f41e9da433386c3136b", size = 111345, upload-time = "2026-05-19T21:28:43.93Z" },
- { url = "https://files.pythonhosted.org/packages/90/af/0248eb065e51129d2a9b2436cd1b5c772c19a6b04e5b6a186955671e3319/yarl-1.24.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7b54b9c67c2b06bd7b9a77253d242124b9c95d2c02def5a1144001ee547dd9d5", size = 106558, upload-time = "2026-05-19T21:28:45.806Z" },
- { url = "https://files.pythonhosted.org/packages/21/3c/f960d7a65ef97d8ba9b424fb5128796a4bc710fc6df2ddbbd7dfdc3bbd20/yarl-1.24.2-cp311-cp311-win_amd64.whl", hash = "sha256:f8fdbcff8b2c7c9284e60c196f693588598ddcee31e11c18e14949ce44519d45", size = 92808, upload-time = "2026-05-19T21:28:48.465Z" },
- { url = "https://files.pythonhosted.org/packages/03/1a/49fb03750e4de4d2284cd5b885a383133c34eef45bd59631b2bb8b7e81e8/yarl-1.24.2-cp311-cp311-win_arm64.whl", hash = "sha256:b32c37a7a337e90822c45797bf3d79d60875cfcccd3ecc80e9f453d87026c122", size = 87610, upload-time = "2026-05-19T21:28:50.07Z" },
{ url = "https://files.pythonhosted.org/packages/f0/da/866bcb01076ba49d2b42b309867bed3826421f1c479655eb7a607b44f20b/yarl-1.24.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b975866c184564c827e0877380f0dae57dcca7e52782128381b72feff6dfceb8", size = 129957, upload-time = "2026-05-19T21:28:51.695Z" },
{ url = "https://files.pythonhosted.org/packages/bf/1d/fcefb70922ea2268a8971d8e5874d9a8218644200fb8465f1dcad55e6851/yarl-1.24.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3b075301a2836a0e297b1b658cb6d6135df535d62efefdd60366bd589c2c82f2", size = 92164, upload-time = "2026-05-19T21:28:53.242Z" },
{ url = "https://files.pythonhosted.org/packages/29/b6/170e2b8d4e3bc30e6bfdcca53556537f5bf595e938632dfcb059311f3ff6/yarl-1.24.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8ae44649b00947634ab0dab2a374a638f52923a6e67083f2c156cd5cbd1a881d", size = 91688, upload-time = "2026-05-19T21:28:54.865Z" },