From 1b44818af08f4343e93fd2bfbc79868f1b3ba841 Mon Sep 17 00:00:00 2001 From: Miguel Branco Date: Mon, 24 Nov 2025 13:33:58 +0100 Subject: [PATCH 01/26] Migrate site config to pydantic --- docs/features/python-endpoints.md | 1 + docs/reference/python.md | 6 +- pydantic-migration-plan.md | 122 ++++++++ src/mxcp/runtime/__init__.py | 28 +- src/mxcp/server/admin/service.py | 8 +- src/mxcp/server/core/config/_types.py | 75 ----- src/mxcp/server/core/config/models.py | 231 +++++++++++++++ src/mxcp/server/core/config/parsers.py | 56 ++-- src/mxcp/server/core/config/site_config.py | 183 ++---------- src/mxcp/server/core/config/user_config.py | 15 +- .../server/definitions/endpoints/loader.py | 24 +- src/mxcp/server/definitions/evals/loader.py | 18 +- src/mxcp/server/executor/engine.py | 19 +- src/mxcp/server/executor/runners/endpoint.py | 5 +- src/mxcp/server/executor/runners/test.py | 5 +- src/mxcp/server/interfaces/cli/dbt.py | 8 +- src/mxcp/server/interfaces/cli/log.py | 15 +- src/mxcp/server/interfaces/cli/log_cleanup.py | 15 +- src/mxcp/server/interfaces/cli/serve.py | 2 +- src/mxcp/server/interfaces/cli/utils.py | 9 +- src/mxcp/server/interfaces/server/mcp.py | 49 ++-- .../server/schemas/mxcp-site-schema-1.json | 270 ------------------ src/mxcp/server/services/dbt/runner.py | 38 +-- src/mxcp/server/services/drift/checker.py | 19 +- src/mxcp/server/services/drift/snapshot.py | 18 +- src/mxcp/server/services/endpoints/service.py | 9 +- .../server/services/endpoints/validator.py | 6 +- src/mxcp/server/services/evals/service.py | 9 +- src/mxcp/server/services/tests/service.py | 7 +- .../fixtures/runtime/python/runtime_tests.py | 8 +- tests/server/test_admin_api.py | 5 +- tests/server/test_cli_init.py | 13 +- tests/server/test_init_hooks_db_access.py | 9 +- tests/server/test_integration.py | 22 +- tests/server/test_python_endpoints.py | 4 +- tests/server/test_reload.py | 31 +- tests/server/test_site_config_model.py | 46 +++ tests/server/test_telemetry_integration.py | 53 ++-- tests/server/test_user_config.py | 21 +- uv.lock | 2 +- 40 files changed, 729 insertions(+), 755 deletions(-) create mode 100644 pydantic-migration-plan.md create mode 100644 src/mxcp/server/core/config/models.py delete mode 100644 src/mxcp/server/schemas/mxcp-site-schema-1.json create mode 100644 tests/server/test_site_config_model.py diff --git a/docs/features/python-endpoints.md b/docs/features/python-endpoints.md index 5f440de2..c7d2275c 100644 --- a/docs/features/python-endpoints.md +++ b/docs/features/python-endpoints.md @@ -122,6 +122,7 @@ debug_mode = config.get_setting("debug", default=False) # Access full configs user_config = config.user_config site_config = config.site_config +# site_config is a SiteConfigModel, so use attribute access ``` ### Plugin Access diff --git a/docs/reference/python.md b/docs/reference/python.md index a9125be0..74f919c6 100644 --- a/docs/reference/python.md +++ b/docs/reference/python.md @@ -91,11 +91,13 @@ projects = user_cfg["projects"] if user_cfg else {} ``` ### `config.site_config` -Access full site configuration dictionary. +Access the full `SiteConfigModel` instance. Use attribute access or `model_dump()` if you need a dictionary. ```python site_cfg = config.site_config -secrets_list = site_cfg.get("secrets", []) +if site_cfg: + secrets_list = site_cfg.secrets + default_duckdb = site_cfg.profiles[site_cfg.profile].duckdb.path ``` ## Plugin Access diff --git a/pydantic-migration-plan.md b/pydantic-migration-plan.md new file mode 100644 index 00000000..cf4763f1 --- /dev/null +++ b/pydantic-migration-plan.md @@ -0,0 +1,122 @@ +# Pydantic v2 Migration Strategy + +This document describes a staged plan to converge all MXCP configuration and definition models on Pydantic v2. The focus is on incremental, low-risk rollouts—starting with site configuration (the “root” dependency) and expanding to additional schemas over time. + +--- + +## Guiding Principles +- **Single source of truth**: Pydantic models (with `model_json_schema()` exports if needed) become the canonical definition. Legacy JSON Schema files are deleted only after their equivalents can be auto-generated. +- **Immutable consumer view**: Once validated, configs should be immutable (`frozen=True`) to discourage incidental mutation. Derived values and defaults live inside model validators. +- **Bridge period**: Loader functions may return both models and dicts temporarily to avoid flag days. Downstream modules gradually adopt model instances and drop `.get()` patterns. +- **Tight feedback**: Each phase ships with targeted tests (unit + integration) that exercise both success and failure scenarios, including environment interpolation. +- **No CLI regression**: Existing CLI commands (`mxcp log`, `mxcp log-cleanup`, etc.) must keep current names, options, and output. + +--- + +## Phase 0 – Preparation +1. **Inventory + ownership** + - Confirm all call sites of `SiteConfig` / `UserConfig` TypedDicts and jsonschema validation to understand migration blast radius. + - Establish owners for each schema domain (site, user, endpoints, prompts, evals) so follow-on phases stay staffed. +2. **Decide on doc export** + - If external tooling still needs JSON Schema files, agree on generating them via `BaseModel.model_json_schema()` to avoid drift. +3. **Define common utilities** + - Draft helpers for repo-relative path resolution, environment overrides, and profile-specific defaults so they can be reused by later phases. + +Deliverables: short design doc for the `SiteConfigModel` API surface, agreement on schema export strategy, and tickets for each migration phase. + +--- + +## Phase 1 – Site Configuration (root dependency) +1. **Model definition** + - Create `mxcp/server/core/config/models.py` (new names, e.g., `SiteConfigModel`, `SitePathsConfigModel`, etc.). + - Encode current JSON schema semantics via field types, `Annotated` constraints, and default values in `Field(...)`. + - Add `model_validator(mode="after")` hooks for derived defaults (DuckDB path, drift/audit files, env overrides). Centralize the logic currently found in `_apply_defaults`. + - Set `model_config = ConfigDict(extra="forbid", frozen=True, use_enum_values=True)` to block stray keys and deliver immutable instances. +2. **Loader bridge** + - Update `load_site_config` to: + - Parse YAML → dict + - Run legacy JSON Schema validation (optional, behind a feature flag) for a release or two + - Instantiate `SiteConfigModel` + - Return both the model and `model_dump()` (e.g., `(model, model_dump)` or maintain current signature while storing the model internally) + - Emit warnings when downstream code accesses dict interfaces so we can track migration progress. +3. **Adopt model consumers** + - Prioritize modules closest to configuration loading (e.g., `mxcp/server/services/endpoints/service.py`, `.../dbt/runner.py`, `.../executor/engine.py`) to accept the model. Replace `.get()`/`["key"]` operations with dot access. + - Update tests that rely on plain dict fixtures to instantiate the model instead, or call `model_dump()` explicitly when dicts are required for serialization. +4. **Validation & regression** + - Add new unit tests for `SiteConfigModel` covering: missing sections, repo-root derived paths, env overrides, invalid values, immutability. + - Run `uv run pytest tests/server/test_site_config*.py` (create if needed) plus existing integration suites touched by config loading. +5. **Cleanup** + - Once all runtime code uses the model (no dict consumers remain), delete the `SiteConfig` TypedDict and JSON schema, remove `jsonschema` dependency from this path, and simplify loader return values to just the model. + +Exit criteria: `load_site_config` returns only `SiteConfigModel`, no dict-style access in server modules, and JSON schema file `mxcp-site-schema-1.json` is removed (or auto-generated from the model). + +--- + +## Phase 2 – User Configuration +**Prerequisite**: Site config consumers operate purely on `SiteConfigModel`. + +1. **Model definition** + - Introduce `UserConfigModel` and nested components, mirroring the existing schema plus resolver-specific defaults (vault, 1password, transport, telemetry, etc.). + - Capture dependencies on site config explicitly where required (e.g., default generation uses `SiteConfigModel` values). +2. **Resolver integration** + - Ensure interpolation (`interpolate_all` / `interpolate_selective`) happens before model validation. Any exceptions raised should reference model fields for clarity. +3. **Loader bridge + adoption** + - Mirror the site-config strategy: loader returns both model and dict until consumers are migrated. + - Update CLI initialization and executor setup to consume the model. Replace `TypedDict` imports globally. +4. **Testing & cleanup** + - Expand `tests/server/test_user_config.py` to instantiate the new model, covering env/file/vault/1password references, persistence defaults, telemetry toggles, etc. + - Remove `mxcp-config-schema-1.json` and the `UserConfig` TypedDict once adoption is complete. + +Exit criteria: `load_user_config` returns only `UserConfigModel`, resolver pipeline validated via tests, and no modules mutate the user config post-validation. + +--- + +## Phase 3 – Definition Schemas (endpoints, prompts, resources, evals) +1. **Prioritize endpoints** + - Replace `jsonschema` validation in `EndpointLoader` with Pydantic models (`ToolDefinitionModel`, `PromptDefinitionModel`, etc.). + - These models should encapsulate enabled/disabled logic, URI/name validation, and cross-reference checks. Consider using `RootModel[List[ToolDefinitionModel]]` for directory scanning utilities. +2. **Prompts / resources / evals** + - Repeat the pattern for each schema-heavy component, unifying shared blocks (e.g., `AuditConfigModel`, `LLMModelConfigModel`) to reduce duplication. +3. **Schema export** + - If we still need JSON artifacts for external tooling, generate them from the Pydantic models during packaging (e.g., via a build step that writes to `dist/schemas/`). +4. **Testing** + - Add targeted tests for each loader that ensure invalid definitions trigger `ValidationError` with helpful messages, and that derived defaults (enabled flags, templated names) behave as expected. + +Exit criteria: All schema validations in `mxcp/server/schemas/` are backed by Pydantic models, and `jsonschema` is no longer required in runtime dependencies. + +--- + +## Phase 4 – Final Cleanup & Enforcement +1. **Remove legacy artifacts** + - Delete remaining JSON schema files and any helper utilities that existed solely for `jsonschema`. + - Drop `types-jsonschema` stubs and related dev dependencies if unused elsewhere. +2. **Static analysis** + - Enable mypy/pyright rules (or custom scripts) to flag residual `.get()`/`dict[...]` usage on config objects, ensuring future code sticks to typed models. +3. **Documentation** + - Update user-facing docs (e.g., `docs/guides/configuration.md`) to reference the new schema definitions or the auto-generated JSON schema exports. +4. **Performance & regression review** + - Profile configuration loading to ensure Pydantic validation doesn’t introduce noticeable latency. + - Confirm CLI commands and server startups produce identical behavior and logs. + +Exit criteria: All configs/definitions rely on Pydantic, legacy schemas are gone (or auto-generated), type checking enforces model usage, and jsonschema dependency is fully removed. + +--- + +## Risk Mitigation & Tooling +- **Gradual rollout**: Each phase should ship behind internal feature flags or environment toggles to allow canary testing. +- **Telemetry**: Add temporary counters/logging to measure how often dict fallbacks are used during the bridge period. +- **Backwards compatibility**: Because configs are local files, ensure we support `model_validate` with clear errors that match (or improve upon) current jsonschema messages. +- **Contributor guidance**: Add a short section to `docs/guides/configuration.md` explaining how to add fields to the models, including where defaults/validators live. + +--- + +## Tracking +Create one epic per phase with child tasks for: +- Model definition + validators +- Loader updates +- Consumer migrations (grouped by package) +- Test coverage +- Schema removal / dependency cleanup + +Progress through the phases only after the previous phase meets its exit criteria to avoid overlapping risk areas. This ensures we maintain a fully typed, immutable configuration surface before tackling downstream definitions. + diff --git a/src/mxcp/runtime/__init__.py b/src/mxcp/runtime/__init__.py index aa0eb2d9..4dd0ecf7 100644 --- a/src/mxcp/runtime/__init__.py +++ b/src/mxcp/runtime/__init__.py @@ -20,6 +20,7 @@ from typing import Any, cast from mxcp.sdk.executor.context import get_execution_context +from mxcp.server.core.config.models import SiteConfigModel logger = logging.getLogger(__name__) @@ -66,8 +67,12 @@ def get_secret(self, name: str) -> dict[str, Any] | None: return None # Get project and profile from site config - project = site_config.get("project") - profile = site_config.get("profile") + if isinstance(site_config, SiteConfigModel): + project = site_config.project + profile = site_config.profile + else: + project = site_config.get("project") + profile = site_config.get("profile") if not project or not profile: return None @@ -99,10 +104,16 @@ def get_setting(self, key: str, default: Any = None) -> Any: if not site_config: return default + raw_config = ( + site_config.model_dump(mode="python") + if isinstance(site_config, SiteConfigModel) + else site_config + ) + # Support nested key access (e.g., "dbt.enabled") if "." in key: keys = key.split(".") - value = site_config + value: Any = raw_config for k in keys: if isinstance(value, dict) and k in value: value = value[k] @@ -110,7 +121,7 @@ def get_setting(self, key: str, default: Any = None) -> Any: return default return value else: - return site_config.get(key, default) + return raw_config.get(key, default) @property def user_config(self) -> dict[str, Any] | None: @@ -122,13 +133,16 @@ def user_config(self) -> dict[str, Any] | None: return cast(dict[str, Any] | None, context.get("user_config")) @property - def site_config(self) -> dict[str, Any] | None: - """Access full site configuration.""" + def site_config(self) -> Any | None: + """Access full site configuration (typically a SiteConfigModel).""" context = get_execution_context() if not context: return None - return cast(dict[str, Any] | None, context.get("site_config")) + site_config = context.get("site_config") + if site_config is None: + return None + return site_config class PluginsProxy: diff --git a/src/mxcp/server/admin/service.py b/src/mxcp/server/admin/service.py index e8e1ff36..0cdba903 100644 --- a/src/mxcp/server/admin/service.py +++ b/src/mxcp/server/admin/service.py @@ -11,6 +11,7 @@ from typing import TYPE_CHECKING, Any from mxcp.sdk.audit.backends.noop import NoOpAuditBackend +from mxcp.server.core.config.models import SiteConfigModel from mxcp.server.definitions.endpoints._types import EndpointDefinition from .models import ConfigResponse, EndpointCounts, Features @@ -82,8 +83,13 @@ def get_config_snapshot(self) -> ConfigResponse: Reads public fields directly - no intermediate types needed. """ + site_config_obj = self._server.site_config + if isinstance(site_config_obj, SiteConfigModel): + project_name = site_config_obj.project + else: + project_name = site_config_obj.get("project") return ConfigResponse( - project=self._server.site_config.get("project"), + project=project_name, profile=self._server.profile_name, repository_path=( str(self._server.runtime_environment.duckdb_runtime.database_config.path) diff --git a/src/mxcp/server/core/config/_types.py b/src/mxcp/server/core/config/_types.py index bb59b142..270db06d 100644 --- a/src/mxcp/server/core/config/_types.py +++ b/src/mxcp/server/core/config/_types.py @@ -1,81 +1,6 @@ from typing import Any, Literal, TypedDict -# Site Config Types (mxcp-site.yml) -class SiteExtensionDefinition(TypedDict, total=False): - name: str - repo: str | None # Optional repo name for community/nightly extensions - - -class SitePluginDefinition(TypedDict): - name: str - module: str - config: str | None - - -class SiteDbtConfig(TypedDict, total=False): - enabled: bool | None - # dbt project configuration paths - model_paths: list[str] | None - analysis_paths: list[str] | None - test_paths: list[str] | None - seed_paths: list[str] | None - macro_paths: list[str] | None - snapshot_paths: list[str] | None - target_path: str | None - clean_targets: list[str] | None - - -class SiteSqlToolsConfig(TypedDict, total=False): - enabled: bool | None - - -class SiteDuckDBConfig(TypedDict, total=False): - path: str | None - readonly: bool | None - - -class SiteDriftConfig(TypedDict, total=False): - path: str | None - - -class SiteAuditConfig(TypedDict, total=False): - enabled: bool | None - path: str | None - - -class SiteProfileConfig(TypedDict, total=False): - duckdb: SiteDuckDBConfig | None - drift: SiteDriftConfig | None - audit: SiteAuditConfig | None - - -class SitePathsConfig(TypedDict, total=False): - tools: str | None - resources: str | None - prompts: str | None - evals: str | None - python: str | None - plugins: str | None - sql: str | None - drift: str | None - audit: str | None - data: str | None - - -class SiteConfig(TypedDict): - mxcp: str - project: str - profile: str - secrets: list[str] | None # List of secret names (not definitions) - plugin: list[SitePluginDefinition] | None - extensions: list[str | SiteExtensionDefinition] | None - dbt: SiteDbtConfig | None - sql_tools: SiteSqlToolsConfig | None - paths: SitePathsConfig | None - profiles: dict[str, SiteProfileConfig] - - # User Config Types (~/.mxcp/config.yml) class UserSecretDefinition(TypedDict): name: str diff --git a/src/mxcp/server/core/config/models.py b/src/mxcp/server/core/config/models.py new file mode 100644 index 00000000..bcbebd79 --- /dev/null +++ b/src/mxcp/server/core/config/models.py @@ -0,0 +1,231 @@ +from __future__ import annotations + +import logging +import os +from pathlib import Path +from typing import Any, Literal, Mapping + +from pydantic import ( + BaseModel, + ConfigDict, + Field, + ValidationInfo, + field_serializer, + field_validator, + model_validator, +) + +logger = logging.getLogger(__name__) + + +def _ensure_list(value: Any) -> list[Any]: + if value is None: + return [] + if isinstance(value, list): + return value + return [value] + + +class SiteExtensionDefinitionModel(BaseModel): + model_config = ConfigDict(extra="forbid", frozen=True) + + name: str + repo: Literal["community", "core_nightly"] | None = None + + +class SitePluginDefinitionModel(BaseModel): + model_config = ConfigDict(extra="forbid", frozen=True) + + name: str + module: str + config: str | None = None + + +class SiteDbtConfigModel(BaseModel): + model_config = ConfigDict(extra="forbid", frozen=True) + + enabled: bool = True + model_paths: list[str] = Field(default_factory=lambda: ["models"]) + analysis_paths: list[str] = Field(default_factory=lambda: ["analyses"]) + test_paths: list[str] = Field(default_factory=lambda: ["tests"]) + seed_paths: list[str] = Field(default_factory=lambda: ["seeds"]) + macro_paths: list[str] = Field(default_factory=lambda: ["macros"]) + snapshot_paths: list[str] = Field(default_factory=lambda: ["snapshots"]) + target_path: str = "target" + clean_targets: list[str] = Field(default_factory=lambda: ["target", "dbt_packages"]) + + @field_validator( + "model_paths", + "analysis_paths", + "test_paths", + "seed_paths", + "macro_paths", + "snapshot_paths", + "clean_targets", + mode="before", + ) + @classmethod + def _ensure_str_list(cls, value: Any) -> list[str]: + if value is None: + return [] + if isinstance(value, list): + return value + return [str(value)] + + +class SiteSqlToolsConfigModel(BaseModel): + model_config = ConfigDict(extra="forbid", frozen=True) + + enabled: bool = False + + +class SiteDuckDBConfigModel(BaseModel): + model_config = ConfigDict(extra="forbid", frozen=True) + + path: str | None = None + readonly: bool = False + + +class SiteDriftConfigModel(BaseModel): + model_config = ConfigDict(extra="forbid", frozen=True) + + path: str | None = None + + +class SiteAuditConfigModel(BaseModel): + model_config = ConfigDict(extra="forbid", frozen=True) + + enabled: bool = False + path: str | None = None + + +class SiteProfileConfigModel(BaseModel): + model_config = ConfigDict(extra="forbid", frozen=True) + + duckdb: SiteDuckDBConfigModel = Field(default_factory=SiteDuckDBConfigModel) + drift: SiteDriftConfigModel = Field(default_factory=SiteDriftConfigModel) + audit: SiteAuditConfigModel = Field(default_factory=SiteAuditConfigModel) + + +class SitePathsConfigModel(BaseModel): + model_config = ConfigDict(extra="forbid", frozen=True) + + tools: str = "tools" + resources: str = "resources" + prompts: str = "prompts" + evals: str = "evals" + python: str = "python" + plugins: str = "plugins" + sql: str = "sql" + drift: str = "drift" + audit: str = "audit" + data: str = "data" + + +class SiteConfigModel(BaseModel): + model_config = ConfigDict(extra="forbid", frozen=True) + + mxcp: Literal[1] = 1 + project: str + profile: str + secrets: list[str] = Field(default_factory=list) + plugin: list[SitePluginDefinitionModel] = Field(default_factory=list) + extensions: list[SiteExtensionDefinitionModel] = Field(default_factory=list) + dbt: SiteDbtConfigModel = Field(default_factory=SiteDbtConfigModel) + sql_tools: SiteSqlToolsConfigModel = Field(default_factory=SiteSqlToolsConfigModel) + paths: SitePathsConfigModel = Field(default_factory=SitePathsConfigModel) + profiles: dict[str, SiteProfileConfigModel] = Field(default_factory=dict) + + @field_validator("secrets", mode="before") + @classmethod + def _normalize_secrets(cls, value: Any) -> list[str]: + return _ensure_list(value) + + @field_validator("plugin", mode="before") + @classmethod + def _normalize_plugins(cls, value: Any) -> list[Any]: + return _ensure_list(value) + + @field_validator("extensions", mode="before") + @classmethod + def _normalize_extensions(cls, value: Any) -> list[Any]: + items = _ensure_list(value) + normalized: list[Any] = [] + for item in items: + if isinstance(item, str): + normalized.append({"name": item}) + else: + normalized.append(item) + return normalized + + @field_validator("dbt", "sql_tools", "paths", mode="before") + @classmethod + def _default_object(cls, value: Any) -> Any: + if value is None: + return {} + return value + + @field_validator("profiles", mode="before") + @classmethod + def _normalize_profiles(cls, value: Any) -> dict[str, Any]: + if value is None: + return {} + if isinstance(value, Mapping): + return dict(value) + raise TypeError("profiles must be a mapping") + + @model_validator(mode="after") + def _apply_active_profile_defaults(self, info: ValidationInfo) -> SiteConfigModel: + repo_root = Path(info.context.get("repo_root", Path.cwd())) if info.context else Path.cwd() + + profile_name = self.profile + profiles = dict(self.profiles) + active_profile = profiles.get(profile_name, SiteProfileConfigModel()) + + duckdb = active_profile.duckdb + if not duckdb.path: + db_path = repo_root / self.paths.data / f"db-{profile_name}.duckdb" + duckdb = duckdb.model_copy(update={"path": str(db_path)}) + + env_duckdb_path = os.environ.get("MXCP_DUCKDB_PATH") + if env_duckdb_path: + logger.info("Overriding DuckDB path with MXCP_DUCKDB_PATH: %s", env_duckdb_path) + duckdb = duckdb.model_copy(update={"path": env_duckdb_path}) + + drift = active_profile.drift + if not drift.path: + drift_path = repo_root / self.paths.drift / f"drift-{profile_name}.json" + drift = drift.model_copy(update={"path": str(drift_path)}) + + audit = active_profile.audit + if not audit.path: + audit_path = repo_root / self.paths.audit / f"logs-{profile_name}.jsonl" + audit = audit.model_copy(update={"path": str(audit_path)}) + + audit_env = os.environ.get("MXCP_AUDIT_ENABLED", "").strip().lower() + if audit_env in {"true", "1", "yes"}: + audit = audit.model_copy(update={"enabled": True}) + elif audit_env in {"false", "0", "no"}: + audit = audit.model_copy(update={"enabled": False}) + + profiles[profile_name] = active_profile.model_copy( + update={"duckdb": duckdb, "drift": drift, "audit": audit} + ) + + return self.model_copy(update={"profiles": profiles}) + + @field_serializer("extensions") + def _serialize_extensions( + self, extensions: list[SiteExtensionDefinitionModel] + ) -> list[str | dict[str, str]]: + serialized: list[str | dict[str, str]] = [] + for ext in extensions: + if ext.repo: + serialized.append({"name": ext.name, "repo": ext.repo}) + else: + serialized.append(ext.name) + return serialized + + +# Ensure forward references are resolved for helper usages. +SiteConfigModel.model_rebuild(_types_namespace={"Literal": Literal}) diff --git a/src/mxcp/server/core/config/parsers.py b/src/mxcp/server/core/config/parsers.py index 73f38121..40546c1a 100644 --- a/src/mxcp/server/core/config/parsers.py +++ b/src/mxcp/server/core/config/parsers.py @@ -8,6 +8,7 @@ import logging from collections.abc import Generator from contextlib import contextmanager +from pathlib import Path from typing import Any, cast from mxcp.sdk.duckdb import ( @@ -22,13 +23,17 @@ reset_execution_context, set_execution_context, ) -from mxcp.server.core.config._types import SiteConfig, UserConfig +from mxcp.server.core.config._types import UserConfig +from mxcp.server.core.config.models import SiteConfigModel logger = logging.getLogger(__name__) def create_duckdb_session_config( - site_config: SiteConfig, user_config: UserConfig, profile_name: str, readonly: bool = False + site_config: SiteConfigModel, + user_config: UserConfig, + profile_name: str, + readonly: bool = False, ) -> tuple[DatabaseConfig, list[PluginDefinition], PluginConfig, list[SecretDefinition]]: """Convert MXCP configs to SDK session configuration objects. @@ -42,47 +47,29 @@ def create_duckdb_session_config( Tuple of (database_config, plugins, plugin_config, secrets) """ # Get project name from site config - project_name = site_config["project"] + project_name = site_config.project # Get database configuration from site config profiles section - site_profiles = site_config.get("profiles") or {} - site_profile_config = site_profiles.get(profile_name) or {} - duckdb_config = site_profile_config.get("duckdb") or {} + site_profile_config = site_config.profiles.get(profile_name) + duckdb_config = site_profile_config.duckdb if site_profile_config else None # Get database path from site config (with fallback) - db_path = duckdb_config.get("path") if duckdb_config else None + db_path = duckdb_config.path if duckdb_config and duckdb_config.path else None if not db_path: - db_path = f"data/db-{profile_name}.duckdb" + db_path = str(Path(site_config.paths.data) / f"db-{profile_name}.duckdb") # Get extensions from site config (root level) - extensions_config = site_config.get("extensions") or [] - extensions = [] - for ext in extensions_config: - if isinstance(ext, str): - # Simple string extension name - extensions.append(ExtensionDefinition(name=ext)) - elif isinstance(ext, dict): - # Extension with repo specification - ext_name = ext.get("name") - if ext_name: - extensions.append(ExtensionDefinition(name=ext_name, repo=ext.get("repo"))) + extensions = [ + ExtensionDefinition(name=ext.name, repo=ext.repo) for ext in site_config.extensions + ] database_config = DatabaseConfig(path=db_path, readonly=readonly, extensions=extensions) # Get plugins from site config plugin array - plugins = [] - site_plugins = site_config.get("plugin") or [] - for plugin_def in site_plugins: - plugin_name = plugin_def.get("name") - plugin_module = plugin_def.get("module") - if plugin_name and plugin_module: - plugins.append( - PluginDefinition( - name=plugin_name, - module=plugin_module, - config=plugin_def.get("config"), # References config key in user config - ) - ) + plugins = [ + PluginDefinition(name=plugin_def.name, module=plugin_def.module, config=plugin_def.config) + for plugin_def in site_config.plugin + ] # Get plugin configuration from user config user_projects = user_config.get("projects") or {} @@ -93,8 +80,7 @@ def create_duckdb_session_config( user_plugin_configs = user_plugin_section.get("config") or {} # Get plugins path from site config - site_paths = site_config.get("paths") or {} - plugins_path = site_paths.get("plugins") or "plugins" + plugins_path = site_config.paths.plugins plugin_config = PluginConfig(plugins_path=plugins_path, config=user_plugin_configs) @@ -116,7 +102,7 @@ def create_duckdb_session_config( @contextmanager def execution_context_for_init_hooks( user_config: UserConfig | None = None, - site_config: SiteConfig | None = None, + site_config: SiteConfigModel | None = None, duckdb_runtime: Any | None = None, ) -> Generator[ExecutionContext | None, None, None]: """ diff --git a/src/mxcp/server/core/config/site_config.py b/src/mxcp/server/core/config/site_config.py index 8f92330c..989fec5a 100644 --- a/src/mxcp/server/core/config/site_config.py +++ b/src/mxcp/server/core/config/site_config.py @@ -1,13 +1,14 @@ -import json +from __future__ import annotations + import logging -import os from pathlib import Path -from typing import Any, cast +from typing import Any import yaml -from jsonschema import ValidationError, validate +from pydantic import ValidationError -from mxcp.server.core.config._types import SiteConfig, UserConfig +from mxcp.server.core.config._types import UserConfig, UserProfileConfig +from mxcp.server.core.config.models import SiteConfigModel from mxcp.server.core.refs.migration import check_and_migrate_legacy_version logger = logging.getLogger(__name__) @@ -31,147 +32,7 @@ def find_repo_root() -> Path: raise FileNotFoundError("mxcp-site.yml not found in current directory or any parent directory") -def _apply_defaults(config: dict[str, Any], repo_root: Path) -> SiteConfig: - """Apply default values to the config""" - # Create a copy to avoid modifying the input - config = config.copy() - - # Apply defaults for optional sections - if "dbt" not in config: - config["dbt"] = {"enabled": True} - elif "enabled" not in config["dbt"]: - config["dbt"]["enabled"] = True - - # Apply defaults for dbt configuration paths - dbt_config = config["dbt"] - if "model_paths" not in dbt_config: - dbt_config["model_paths"] = ["models"] - - if "analysis_paths" not in dbt_config: - dbt_config["analysis_paths"] = ["analyses"] - - if "test_paths" not in dbt_config: - dbt_config["test_paths"] = ["tests"] - - if "seed_paths" not in dbt_config: - dbt_config["seed_paths"] = ["seeds"] - - if "macro_paths" not in dbt_config: - dbt_config["macro_paths"] = ["macros"] - - if "snapshot_paths" not in dbt_config: - dbt_config["snapshot_paths"] = ["snapshots"] - - if "target_path" not in dbt_config: - dbt_config["target_path"] = "target" - - if "clean_targets" not in dbt_config: - dbt_config["clean_targets"] = ["target", "dbt_packages"] - - # Initialize extensions section if not present - if "extensions" not in config: - config["extensions"] = [] - - # Initialize paths section if not present (do this early so we can use paths in profile defaults) - if "paths" not in config: - config["paths"] = {} - - # Apply defaults for paths configuration - paths_config = config["paths"] - if "tools" not in paths_config: - paths_config["tools"] = "tools" - - if "resources" not in paths_config: - paths_config["resources"] = "resources" - - if "prompts" not in paths_config: - paths_config["prompts"] = "prompts" - - if "evals" not in paths_config: - paths_config["evals"] = "evals" - - if "python" not in paths_config: - paths_config["python"] = "python" - - if "sql" not in paths_config: - paths_config["sql"] = "sql" - - if "plugins" not in paths_config: - paths_config["plugins"] = "plugins" - - if "drift" not in paths_config: - paths_config["drift"] = "drift" - - if "audit" not in paths_config: - paths_config["audit"] = "audit" - - if "data" not in paths_config: - paths_config["data"] = "data" - - # Initialize profiles section if not present - if "profiles" not in config: - config["profiles"] = {} - - # Get the current profile - profile = config.get("profile", "default") - - # Initialize profile config if not present - if profile not in config["profiles"]: - config["profiles"][profile] = {} - - # Initialize duckdb config for the profile if not present - if "duckdb" not in config["profiles"][profile]: - config["profiles"][profile]["duckdb"] = {} - - # Set default DuckDB path for the profile if not specified (now uses data directory) - if "path" not in config["profiles"][profile]["duckdb"]: - config["profiles"][profile]["duckdb"]["path"] = str( - repo_root / paths_config["data"] / f"db-{profile}.duckdb" - ) - - # Check for environment variable override - env_duckdb_path = os.environ.get("MXCP_DUCKDB_PATH") - if env_duckdb_path: - logger.info(f"Overriding DuckDB path with MXCP_DUCKDB_PATH: {env_duckdb_path}") - config["profiles"][profile]["duckdb"]["path"] = env_duckdb_path - - # Initialize drift config for the profile if not present - if "drift" not in config["profiles"][profile]: - config["profiles"][profile]["drift"] = {} - - # Set default drift manifest path for the profile if not specified (now uses drift directory) - if "path" not in config["profiles"][profile]["drift"]: - config["profiles"][profile]["drift"]["path"] = str( - repo_root / paths_config["drift"] / f"drift-{profile}.json" - ) - - # Initialize audit config for the profile if not present - if "audit" not in config["profiles"][profile]: - config["profiles"][profile]["audit"] = {} - - # Set default audit log path for the profile if not specified (now uses audit directory) - if "path" not in config["profiles"][profile]["audit"]: - config["profiles"][profile]["audit"]["path"] = str( - repo_root / paths_config["audit"] / f"logs-{profile}.jsonl" - ) - - # Environment variable overrides for audit config - # Following same pattern as telemetry: env vars ALWAYS override file config - audit_enabled_env = os.getenv("MXCP_AUDIT_ENABLED", "").lower() - if audit_enabled_env in ("true", "1", "yes"): - # Env var explicitly enables audit - override any file setting - config["profiles"][profile]["audit"]["enabled"] = True - elif audit_enabled_env in ("false", "0", "no"): - # Env var explicitly disables audit - override any file setting - config["profiles"][profile]["audit"]["enabled"] = False - elif "enabled" not in config["profiles"][profile]["audit"]: - # No env var and no file config - use default - config["profiles"][profile]["audit"]["enabled"] = False - - return cast(SiteConfig, config) - - -def load_site_config(repo_path: Path | None = None) -> SiteConfig: +def load_site_config(repo_path: Path | None = None) -> SiteConfigModel: """Load and validate the mxcp-site.yml configuration from the repository. Args: @@ -182,6 +43,7 @@ def load_site_config(repo_path: Path | None = None) -> SiteConfig: Raises: FileNotFoundError: If mxcp-site.yml is not found + ValueError: If validation fails """ if repo_path is None: repo_path = Path.cwd() @@ -191,28 +53,23 @@ def load_site_config(repo_path: Path | None = None) -> SiteConfig: raise FileNotFoundError(f"mxcp-site.yml not found at {config_path}") with open(config_path) as f: - config = yaml.safe_load(f) + config = yaml.safe_load(f) or {} # Check for legacy version format and provide migration guidance (stops execution) check_and_migrate_legacy_version(config, "site", str(config_path)) - # Load and apply JSON Schema validation - schema_path = Path(__file__).parent.parent.parent / "schemas" / "mxcp-site-schema-1.json" - with open(schema_path) as schema_file: - schema = json.load(schema_file) - try: - validate(instance=config, schema=schema) - except ValidationError as e: - raise ValueError(f"Site config validation error: {e.message}") from e - - # Apply defaults (e.g., duckdb.path) - return _apply_defaults(config, repo_path) + return SiteConfigModel.model_validate( + config, + context={"repo_root": config_path.parent}, + ) + except ValidationError as exc: + raise ValueError(f"Site config validation error: {exc}") from exc def get_active_profile( - user_config: UserConfig, site_config: SiteConfig, profile: str | None = None -) -> dict[str, Any]: + user_config: UserConfig, site_config: SiteConfigModel, profile: str | None = None +) -> UserProfileConfig: """Get the active profile from the user config based on site configuration. Args: @@ -223,8 +80,8 @@ def get_active_profile( Returns: The active profile configuration """ - project_name = site_config["project"] - profile_name = profile or site_config["profile"] + project_name = site_config.project + profile_name = profile or site_config.profile if project_name not in user_config["projects"]: raise ValueError(f"Project '{project_name}' not found in user config") @@ -233,4 +90,4 @@ def get_active_profile( if profile_name not in project["profiles"]: raise ValueError(f"Profile '{profile_name}' not found in project '{project_name}'") - return cast(dict[str, Any], project["profiles"][profile_name]) + return project["profiles"][profile_name] diff --git a/src/mxcp/server/core/config/user_config.py b/src/mxcp/server/core/config/user_config.py index ed28eabe..3f4c50da 100644 --- a/src/mxcp/server/core/config/user_config.py +++ b/src/mxcp/server/core/config/user_config.py @@ -6,7 +6,8 @@ import yaml from jsonschema import ValidationError, validate -from mxcp.server.core.config._types import SiteConfig, UserConfig +from mxcp.server.core.config._types import UserConfig +from mxcp.server.core.config.models import SiteConfigModel from mxcp.server.core.refs.migration import check_and_migrate_legacy_version from mxcp.server.core.refs.resolver import interpolate_all, interpolate_selective @@ -87,10 +88,10 @@ def _apply_defaults(config: dict[str, Any]) -> UserConfig: return cast(UserConfig, config) -def _generate_default_config(site_config: SiteConfig) -> UserConfig: +def _generate_default_config(site_config: SiteConfigModel) -> UserConfig: """Generate a default user config based on site config""" - project_name = site_config["project"] - profile_name = site_config["profile"] + project_name = site_config.project + profile_name = site_config.profile config = { "mxcp": 1, @@ -102,7 +103,7 @@ def _generate_default_config(site_config: SiteConfig) -> UserConfig: def load_user_config( - site_config: SiteConfig, + site_config: SiteConfigModel, active_profile: str | None = None, generate_default: bool = True, resolve_refs: bool = True, @@ -161,8 +162,8 @@ def load_user_config( check_and_migrate_legacy_version(config, "user", str(path)) # Determine which profile to use for selective interpolation - project_name = site_config["project"] - profile_name = active_profile or site_config["profile"] + project_name = site_config.project + profile_name = active_profile or site_config.profile # Interpolate environment variables and vault URLs in the config if requested if resolve_refs: diff --git a/src/mxcp/server/definitions/endpoints/loader.py b/src/mxcp/server/definitions/endpoints/loader.py index 51bdf51d..a3b4410e 100644 --- a/src/mxcp/server/definitions/endpoints/loader.py +++ b/src/mxcp/server/definitions/endpoints/loader.py @@ -8,7 +8,7 @@ from jsonschema import ValidationError, validate from referencing import Registry, Resource -from mxcp.server.core.config._types import SiteConfig +from mxcp.server.core.config.models import SiteConfigModel from mxcp.server.core.config.site_config import find_repo_root from mxcp.server.definitions.endpoints._types import EndpointDefinition from mxcp.server.definitions.endpoints.utils import get_endpoint_name_or_uri @@ -62,10 +62,10 @@ def extract_validation_error(error: ValidationError | Exception | str) -> str: @dataclass class EndpointLoader: _endpoints: dict[str, EndpointDefinition] - _site_config: SiteConfig + _site_config: SiteConfigModel _repo_root: Path - def __init__(self, site_config: SiteConfig): + def __init__(self, site_config: SiteConfigModel): self._site_config = site_config self._endpoints = {} self._repo_root = find_repo_root() @@ -235,22 +235,19 @@ def _discover_in_directory( def discover_tools(self) -> list[tuple[Path, EndpointDefinition | None, str | None]]: """Discover all tool definition files""" - paths_config = self._site_config.get("paths", {}) - tools_path = paths_config.get("tools", "tools") if paths_config else "tools" + tools_path = self._site_config.paths.tools tools_dir = self._repo_root / str(tools_path) return self._discover_in_directory(tools_dir, "tool-schema-1.json", "tool") def discover_resources(self) -> list[tuple[Path, EndpointDefinition | None, str | None]]: """Discover all resource definition files""" - paths_config = self._site_config.get("paths", {}) - resources_path = paths_config.get("resources", "resources") if paths_config else "resources" + resources_path = self._site_config.paths.resources resources_dir = self._repo_root / str(resources_path) return self._discover_in_directory(resources_dir, "resource-schema-1.json", "resource") def discover_prompts(self) -> list[tuple[Path, EndpointDefinition | None, str | None]]: """Discover all prompt definition files""" - paths_config = self._site_config.get("paths", {}) - prompts_path = paths_config.get("prompts", "prompts") if paths_config else "prompts" + prompts_path = self._site_config.paths.prompts prompts_dir = self._repo_root / str(prompts_path) return self._discover_in_directory(prompts_dir, "prompt-schema-1.json", "prompt") @@ -303,19 +300,16 @@ def load_endpoint( logger.debug(f"Looking for endpoint type: {endpoint_type}, name: {name}") # Determine which directory to search based on endpoint type - paths_config = self._site_config.get("paths", {}) if endpoint_type == "tool": - tools_path = paths_config.get("tools", "tools") if paths_config else "tools" + tools_path = self._site_config.paths.tools search_dir = self._repo_root / str(tools_path) schema_name = "tool-schema-1.json" elif endpoint_type == "resource": - resources_path = ( - paths_config.get("resources", "resources") if paths_config else "resources" - ) + resources_path = self._site_config.paths.resources search_dir = self._repo_root / str(resources_path) schema_name = "resource-schema-1.json" elif endpoint_type == "prompt": - prompts_path = paths_config.get("prompts", "prompts") if paths_config else "prompts" + prompts_path = self._site_config.paths.prompts search_dir = self._repo_root / str(prompts_path) schema_name = "prompt-schema-1.json" else: diff --git a/src/mxcp/server/definitions/evals/loader.py b/src/mxcp/server/definitions/evals/loader.py index 5384da64..f2531573 100644 --- a/src/mxcp/server/definitions/evals/loader.py +++ b/src/mxcp/server/definitions/evals/loader.py @@ -6,7 +6,7 @@ import yaml from jsonschema import validate -from mxcp.server.core.config._types import SiteConfig +from mxcp.server.core.config.models import SiteConfigModel from mxcp.server.core.config.site_config import find_repo_root from mxcp.server.definitions.evals._types import EvalSuite @@ -14,7 +14,7 @@ def discover_eval_files( - site_config: SiteConfig | None = None, + site_config: SiteConfigModel | None = None, ) -> list[tuple[Path, EvalSuite | None, str | None]]: """Discover all eval files in the configured evals directory. @@ -31,16 +31,8 @@ def discover_eval_files( results: list[tuple[Path, EvalSuite | None, str | None]] = [] # Determine the evals directory - if site_config and "paths" in site_config: - paths_config = site_config.get("paths", {}) - if paths_config and "evals" in paths_config: - evals_path = paths_config.get("evals") - if evals_path: - evals_dir = base_path / evals_path - else: - evals_dir = base_path / "evals" - else: - evals_dir = base_path / "evals" + if site_config: + evals_dir = base_path / str(site_config.paths.evals) else: # Fallback to default evals_dir = base_path / "evals" @@ -79,7 +71,7 @@ def discover_eval_files( def load_eval_suite( - suite_name: str, site_config: SiteConfig | None = None + suite_name: str, site_config: SiteConfigModel | None = None ) -> tuple[Path, EvalSuite] | None: """Load a specific eval suite by name. diff --git a/src/mxcp/server/executor/engine.py b/src/mxcp/server/executor/engine.py index 3f042b48..b6298625 100644 --- a/src/mxcp/server/executor/engine.py +++ b/src/mxcp/server/executor/engine.py @@ -51,7 +51,8 @@ from mxcp.sdk.duckdb import DuckDBRuntime from mxcp.sdk.executor import ExecutionEngine from mxcp.sdk.executor.plugins import DuckDBExecutor, PythonExecutor -from mxcp.server.core.config._types import SiteConfig, UserConfig +from mxcp.server.core.config._types import UserConfig +from mxcp.server.core.config.models import SiteConfigModel from mxcp.server.core.config.parsers import ( create_duckdb_session_config, execution_context_for_init_hooks, @@ -93,7 +94,7 @@ def shutdown(self) -> None: def create_runtime_environment( user_config: UserConfig, - site_config: SiteConfig, + site_config: SiteConfigModel, profile: str | None = None, repo_root: Path | None = None, readonly: bool | None = None, @@ -141,17 +142,15 @@ def create_runtime_environment( engine = ExecutionEngine(strict=False) # Get the profile name to use - profile_name = profile or site_config["profile"] + profile_name = profile or site_config.profile # Handle readonly override db_readonly_from_config = False - if "profiles" in site_config: - site_profiles = site_config.get("profiles", {}) - site_profile_config = site_profiles.get(profile_name, {}) - duckdb_config = site_profile_config.get("duckdb", {}) - db_readonly_from_config = ( - bool(duckdb_config.get("readonly", False)) if duckdb_config else False - ) + site_profile_config = site_config.profiles.get(profile_name) + if site_profile_config: + db_readonly_from_config = bool(site_profile_config.duckdb.readonly) + else: + db_readonly_from_config = False db_readonly = readonly if readonly is not None else db_readonly_from_config # Create SDK session configuration using the shared function diff --git a/src/mxcp/server/executor/runners/endpoint.py b/src/mxcp/server/executor/runners/endpoint.py index 658f0d51..47ee98e6 100644 --- a/src/mxcp/server/executor/runners/endpoint.py +++ b/src/mxcp/server/executor/runners/endpoint.py @@ -18,7 +18,8 @@ from mxcp.sdk.executor import ExecutionContext from mxcp.sdk.executor.interfaces import ExecutionEngine from mxcp.sdk.validator import TypeValidator -from mxcp.server.core.config._types import SiteConfig, UserConfig +from mxcp.server.core.config._types import UserConfig +from mxcp.server.core.config.models import SiteConfigModel from mxcp.server.definitions.endpoints._types import ( EndpointDefinition, PromptDefinition, @@ -82,7 +83,7 @@ async def execute_code_with_engine( execution_engine: ExecutionEngine, skip_output_validation: bool, user_config: UserConfig, - site_config: SiteConfig, + site_config: SiteConfigModel, user_context: UserContext | None = None, server_ref: Optional["RAWMCP"] = None, request_headers: dict[str, str] | None = None, diff --git a/src/mxcp/server/executor/runners/test.py b/src/mxcp/server/executor/runners/test.py index 1679fc99..6fd0f791 100644 --- a/src/mxcp/server/executor/runners/test.py +++ b/src/mxcp/server/executor/runners/test.py @@ -14,7 +14,8 @@ from mxcp.sdk.auth import UserContext from mxcp.sdk.executor import ExecutionEngine -from mxcp.server.core.config._types import SiteConfig, UserConfig +from mxcp.server.core.config._types import UserConfig +from mxcp.server.core.config.models import SiteConfigModel from mxcp.server.definitions.endpoints._types import EndpointDefinition, TestDefinition from mxcp.server.definitions.endpoints.loader import EndpointLoader from mxcp.server.services.endpoints import execute_endpoint_with_engine @@ -28,7 +29,7 @@ class TestRunner: def __init__( self, user_config: UserConfig, - site_config: SiteConfig, + site_config: SiteConfigModel, execution_engine: ExecutionEngine, ): """Initialize the test runner. diff --git a/src/mxcp/server/interfaces/cli/dbt.py b/src/mxcp/server/interfaces/cli/dbt.py index 841f325c..ba45e6ab 100644 --- a/src/mxcp/server/interfaces/cli/dbt.py +++ b/src/mxcp/server/interfaces/cli/dbt.py @@ -60,7 +60,7 @@ def dbt_config( debug=debug, ) - click.echo(f" • Project: {click.style(site_config['project'], fg='yellow')}") + click.echo(f" • Project: {click.style(site_config.project, fg='yellow')}") click.echo(f" • Profile: {click.style(active_profile, fg='yellow')}") if embed_secrets: @@ -158,8 +158,8 @@ def dbt_wrapper(ctx: click.Context, profile: str | None, debug: bool) -> None: ) # Check dbt is enabled - dbt_config = site_config.get("dbt", {}) - if not dbt_config or not dbt_config.get("enabled", True): + dbt_config = site_config.dbt + if not dbt_config.enabled: click.echo( f"\n{click.style('❌ Error:', fg='red', bold=True)} dbt integration is disabled in mxcp-site.yml" ) @@ -178,7 +178,7 @@ def dbt_wrapper(ctx: click.Context, profile: str | None, debug: bool) -> None: ) # Get project name - project = site_config["project"] + project = site_config.project # Show what we're doing dbt_command = " ".join(ctx.args) diff --git a/src/mxcp/server/interfaces/cli/log.py b/src/mxcp/server/interfaces/cli/log.py index 935ceaec..0a58f31f 100644 --- a/src/mxcp/server/interfaces/cli/log.py +++ b/src/mxcp/server/interfaces/cli/log.py @@ -171,23 +171,20 @@ async def _log_async( try: # Load site config and extract audit settings directly site_config = load_site_config() - profile_name = profile or site_config["profile"] + profile_name = profile or site_config.profile - if profile_name not in site_config["profiles"]: + profile_config = site_config.profiles.get(profile_name) + if not profile_config: raise ValueError(f"Profile '{profile_name}' not found in configuration") - profile_config = site_config["profiles"][profile_name] - audit_config = profile_config.get("audit", {}) + audit_config = profile_config.audit - if not audit_config or not audit_config.get("enabled", False): + if not audit_config or not audit_config.enabled: raise ValueError( f"Audit logging is not enabled for profile '{profile_name}'. Enable it in mxcp-site.yml under profiles.{profile_name}.audit.enabled" ) - if audit_config and "path" not in audit_config: - raise ValueError("Audit configuration missing required 'path' field") - - log_path_str = audit_config.get("path") if audit_config else None + log_path_str = audit_config.path if audit_config else None if not log_path_str: raise ValueError("Audit configuration missing required 'path' field") log_path = Path(log_path_str) diff --git a/src/mxcp/server/interfaces/cli/log_cleanup.py b/src/mxcp/server/interfaces/cli/log_cleanup.py index bd1f9b37..6497c4b7 100644 --- a/src/mxcp/server/interfaces/cli/log_cleanup.py +++ b/src/mxcp/server/interfaces/cli/log_cleanup.py @@ -95,15 +95,15 @@ async def _cleanup_async( try: # Load site config and extract audit settings site_config = load_site_config() - profile_name = profile or site_config["profile"] + profile_name = profile or site_config.profile - if profile_name not in site_config["profiles"]: + profile_config = site_config.profiles.get(profile_name) + if not profile_config: raise ValueError(f"Profile '{profile_name}' not found in configuration") - profile_config = site_config["profiles"][profile_name] - audit_config = profile_config.get("audit", {}) + audit_config = profile_config.audit - if not audit_config or not audit_config.get("enabled", False): + if not audit_config or not audit_config.enabled: message = f"Audit logging is not enabled for profile '{profile_name}'" if json_output: output_result( @@ -115,10 +115,7 @@ async def _cleanup_async( click.echo(message) return - if audit_config and "path" not in audit_config: - raise ValueError("Audit configuration missing required 'path' field") - - log_path_str = audit_config.get("path") if audit_config else None + log_path_str = audit_config.path if audit_config else None if not log_path_str: raise ValueError("Audit configuration missing required 'path' field") log_path = Path(log_path_str) diff --git a/src/mxcp/server/interfaces/cli/serve.py b/src/mxcp/server/interfaces/cli/serve.py index ce2ead15..42558fac 100644 --- a/src/mxcp/server/interfaces/cli/serve.py +++ b/src/mxcp/server/interfaces/cli/serve.py @@ -133,7 +133,7 @@ def serve( # Show configuration click.echo(f"{click.style('📋 Configuration:', fg='cyan', bold=True)}") - click.echo(f" • Project: {click.style(server.site_config['project'], fg='yellow')}") + click.echo(f" • Project: {click.style(server.site_config.project, fg='yellow')}") click.echo(f" • Profile: {click.style(server.profile_name, fg='yellow')}") click.echo(f" • Transport: {click.style(server.transport, fg='yellow')}") diff --git a/src/mxcp/server/interfaces/cli/utils.py b/src/mxcp/server/interfaces/cli/utils.py index 00dbbdde..a7c7d010 100644 --- a/src/mxcp/server/interfaces/cli/utils.py +++ b/src/mxcp/server/interfaces/cli/utils.py @@ -10,7 +10,8 @@ import click -from mxcp.server.core.config._types import SiteConfig, UserConfig +from mxcp.server.core.config._types import UserConfig +from mxcp.server.core.config.models import SiteConfigModel def get_env_flag(env_var: str, default: bool = False) -> bool: @@ -37,7 +38,7 @@ def get_env_profile() -> str | None: return os.environ.get("MXCP_PROFILE") -def resolve_profile(cli_profile: str | None, site_config: SiteConfig) -> str: +def resolve_profile(cli_profile: str | None, site_config: SiteConfigModel) -> str: """Resolve the active profile with clear priority. Priority order: @@ -59,7 +60,7 @@ def resolve_profile(cli_profile: str | None, site_config: SiteConfig) -> str: if env_profile: return env_profile - return str(site_config["profile"]) + return site_config.profile def get_env_admin_socket_enabled() -> bool: @@ -233,7 +234,7 @@ def output_error(error: Exception, json_output: bool = False, debug: bool = Fals def configure_logging_from_config( - site_config: SiteConfig, + site_config: SiteConfigModel, user_config: UserConfig, debug: bool = False, transport: str | None = None, diff --git a/src/mxcp/server/interfaces/server/mcp.py b/src/mxcp/server/interfaces/server/mcp.py index 6b216cd7..936c1140 100644 --- a/src/mxcp/server/interfaces/server/mcp.py +++ b/src/mxcp/server/interfaces/server/mcp.py @@ -46,11 +46,11 @@ ) from mxcp.server.admin import AdminAPIRunner from mxcp.server.core.config._types import ( - SiteConfig, UserAuthConfig, UserConfig, UserHttpTransportConfig, ) +from mxcp.server.core.config.models import SiteConfigModel from mxcp.server.core.config.site_config import get_active_profile, load_site_config from mxcp.server.core.config.user_config import load_user_config from mxcp.server.core.refs.external import ExternalRefTracker @@ -243,9 +243,9 @@ class RAWMCP: """MXCP MCP Server implementation that bridges MXCP endpoints with MCP protocol.""" # Type annotations for instance attributes - site_config: SiteConfig + site_config: SiteConfigModel user_config: UserConfig - _site_config_template: SiteConfig + _site_config_template: SiteConfigModel _user_config_template: UserConfig host: str port: int @@ -388,15 +388,16 @@ def _resolve_and_apply_configs(self) -> None: interpolation, which only resolves references for the active profile and top-level config. This prevents errors from undefined environment variables in inactive profiles. """ + site_template_dict = self._site_config_template.model_dump(mode="python") + user_template_dict = cast(dict[str, Any], self._user_config_template) + # Check if configs contain unresolved references - config_str = json.dumps(self._site_config_template) + json.dumps(self._user_config_template) + config_str = json.dumps(site_template_dict) + json.dumps(user_template_dict) needs_resolution = any(pattern in config_str for pattern in ["${", "vault://", "file://"]) # Determine active profile (CLI override > site config) - active_profile = str( - self._cli_overrides["profile"] or self._site_config_template["profile"] - ) - project_name = self._site_config_template["project"] + active_profile = str(self._cli_overrides["profile"] or self._site_config_template.profile) + project_name = self._site_config_template.project if needs_resolution: # Set templates and resolve with selective interpolation @@ -404,8 +405,8 @@ def _resolve_and_apply_configs(self) -> None: f"Resolving external configuration references for project={project_name}, profile={active_profile}..." ) self.ref_tracker.set_template( - cast(dict[str, Any], self._site_config_template), - cast(dict[str, Any], self._user_config_template), + site_template_dict, + user_template_dict, ) self._config_templates_loaded = True @@ -414,7 +415,9 @@ def _resolve_and_apply_configs(self) -> None: project_name=project_name, profile_name=active_profile, ) - self.site_config = cast(SiteConfig, resolved_site) + self.site_config = SiteConfigModel.model_validate( + resolved_site, context={"repo_root": self.site_config_path} + ) self.user_config = cast(UserConfig, resolved_user) else: # Already resolved @@ -460,8 +463,8 @@ def _resolve_and_apply_configs(self) -> None: self.readonly = bool(self._cli_overrides["readonly"]) # SQL tools setting - sql_tools_config = self.site_config.get("sql_tools") or {} - site_sql_tools = sql_tools_config.get("enabled", False) if sql_tools_config else False + sql_tools_config = self.site_config.sql_tools + site_sql_tools = bool(sql_tools_config.enabled) self.enable_sql_tools = ( self._cli_overrides["enable_sql_tools"] if self._cli_overrides["enable_sql_tools"] is not None @@ -627,10 +630,10 @@ async def _run_with_admin_api(self, transport: str) -> None: def _initialize_audit_logger(self) -> None: """Initialize audit logger if enabled.""" - profile_config = self.site_config["profiles"][self.profile_name] - audit_config = profile_config.get("audit") or {} - if audit_config and audit_config.get("enabled", False): - log_path_str = audit_config.get("path", "") + profile_config = self.site_config.profiles.get(self.profile_name) + audit_config = profile_config.audit if profile_config else None + if audit_config and audit_config.enabled: + log_path_str = audit_config.path or "" log_path = Path(log_path_str) if log_path_str else Path("audit.log") # Ensure parent directory exists log_path.parent.mkdir(parents=True, exist_ok=True) @@ -725,7 +728,7 @@ def _initialize_telemetry(self) -> None: logger.info("Initializing telemetry...") # Get project name from site config - project_name = self.site_config["project"] + project_name = self.site_config.project # Configure telemetry for the current profile and get enabled status self.telemetry_enabled = configure_telemetry_from_config( @@ -769,7 +772,8 @@ def reload_configuration(self) -> "ReloadRequest": # Set templates in tracker self.ref_tracker.set_template( - cast(dict[str, Any], site_template), cast(dict[str, Any], user_template) + site_template.model_dump(mode="python"), + cast(dict[str, Any], user_template), ) self._config_templates_loaded = True logger.info("Raw configuration templates loaded.") @@ -786,12 +790,15 @@ def reload_config_files() -> None: # Update templates in tracker self.ref_tracker.set_template( - cast(dict[str, Any], new_site_template), cast(dict[str, Any], new_user_template) + new_site_template.model_dump(mode="python"), + cast(dict[str, Any], new_user_template), ) # Resolve and update configs new_site_config, new_user_config = self.ref_tracker.resolve_all() - self.site_config = cast(SiteConfig, new_site_config) + self.site_config = SiteConfigModel.model_validate( + new_site_config, context={"repo_root": self.site_config_path} + ) self.user_config = cast(UserConfig, new_user_config) logger.info("Configuration files reloaded") diff --git a/src/mxcp/server/schemas/mxcp-site-schema-1.json b/src/mxcp/server/schemas/mxcp-site-schema-1.json deleted file mode 100644 index d86a5390..00000000 --- a/src/mxcp/server/schemas/mxcp-site-schema-1.json +++ /dev/null @@ -1,270 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "MXCP Site Config (mxcp-site.yml)", - "type": "object", - "required": ["mxcp", "project", "profile"], - "properties": { - "mxcp": { - "type": "integer", - "description": "Version of the mxcp-site.yml format. Must be 1.", - "enum": [1], - "default": 1 - }, - "project": { - "type": "string", - "description": "Project name (must match one in ~/.mxcp/config.yml)." - }, - "profile": { - "type": "string", - "description": "Profile name under the given project." - }, - "secrets": { - "type": "array", - "description": "List of secret names used by this repo (resolved from ~/.mxcp/config.yml).", - "items": { - "type": "string" - } - }, - "plugin": { - "type": "array", - "description": "List of plugin modules to load and their configurations.", - "items": { - "type": "object", - "required": ["name", "module"], - "properties": { - "name": { - "type": "string", - "description": "The name of the plugin instance." - }, - "module": { - "type": "string", - "description": "The Python module containing the MXCP plugin." - }, - "config": { - "type": "string", - "description": "Optional name of the configuration to use from the user config (resolved from ~/.mxcp/config.yml)." - } - }, - "additionalProperties": false - } - }, - "extensions": { - "type": "array", - "description": "List of DuckDB extensions to load. Can be simple strings for core extensions or objects with name/repo for community/nightly extensions.", - "items": { - "oneOf": [ - { - "type": "string", - "description": "Name of a core DuckDB extension" - }, - { - "type": "object", - "required": ["name"], - "properties": { - "name": { - "type": "string", - "description": "Name of the extension" - }, - "repo": { - "type": "string", - "description": "Repository to load the extension from (e.g., 'community', 'core_nightly')", - "enum": ["community", "core_nightly"] - } - }, - "additionalProperties": false - } - ] - } - }, - "dbt": { - "type": "object", - "description": "Controls dbt integration and file paths.", - "properties": { - "enabled": { - "type": "boolean", - "description": "Whether to use dbt in this repo (defaults to true)." - }, - "model_paths": { - "type": "array", - "description": "Paths to dbt model directories (defaults to ['models']).", - "items": { - "type": "string" - } - }, - "analysis_paths": { - "type": "array", - "description": "Paths to dbt analysis directories (defaults to ['analyses']).", - "items": { - "type": "string" - } - }, - "test_paths": { - "type": "array", - "description": "Paths to dbt test directories (defaults to ['tests']).", - "items": { - "type": "string" - } - }, - "seed_paths": { - "type": "array", - "description": "Paths to dbt seed directories (defaults to ['seeds']).", - "items": { - "type": "string" - } - }, - "macro_paths": { - "type": "array", - "description": "Paths to dbt macro directories (defaults to ['macros']).", - "items": { - "type": "string" - } - }, - "snapshot_paths": { - "type": "array", - "description": "Paths to dbt snapshot directories (defaults to ['snapshots']).", - "items": { - "type": "string" - } - }, - "target_path": { - "type": "string", - "description": "Path to dbt target directory (defaults to 'target')." - }, - "clean_targets": { - "type": "array", - "description": "Paths to clean when running dbt clean (defaults to ['target', 'dbt_packages']).", - "items": { - "type": "string" - } - } - }, - "additionalProperties": false - }, - "profiles": { - "type": "object", - "description": "Profile-specific configuration settings.", - "patternProperties": { - "^[a-zA-Z0-9_-]+$": { - "type": "object", - "properties": { - "duckdb": { - "type": "object", - "description": "Profile-specific DuckDB configuration.", - "properties": { - "path": { - "type": "string", - "description": "Path to the DuckDB file for this profile." - }, - "readonly": { - "type": "boolean", - "description": "Whether to open the DuckDB connection in read-only mode (defaults to false).", - "default": false - } - }, - "additionalProperties": false - }, - "drift": { - "type": "object", - "description": "Profile-specific MXCP schema drift detection manifest configuration.", - "properties": { - "path": { - "type": "string", - "description": "Path to the MXCP drift manifest file (JSON) for this profile." - } - }, - "additionalProperties": false - }, - "audit": { - "type": "object", - "description": "Profile-specific audit logging configuration.", - "properties": { - "enabled": { - "type": "boolean", - "description": "Whether to enable audit logging for this profile (defaults to false).", - "default": false - }, - "path": { - "type": "string", - "description": "Path to the audit log JSONL file for this profile." - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - "sql_tools": { - "type": "object", - "description": "Configuration for built-in SQL querying and schema exploration tools.", - "properties": { - "enabled": { - "type": "boolean", - "description": "Whether to enable built-in SQL querying and schema exploration tools (defaults to false).", - "default": false - } - }, - "additionalProperties": false - }, - "paths": { - "type": "object", - "description": "Directory paths for different types of MXCP components.", - "properties": { - "tools": { - "type": "string", - "description": "Directory path for tool definitions (defaults to 'tools').", - "default": "tools" - }, - "resources": { - "type": "string", - "description": "Directory path for resource definitions (defaults to 'resources').", - "default": "resources" - }, - "prompts": { - "type": "string", - "description": "Directory path for prompt definitions (defaults to 'prompts').", - "default": "prompts" - }, - "evals": { - "type": "string", - "description": "Directory path for evaluation definitions (defaults to 'evals').", - "default": "evals" - }, - "python": { - "type": "string", - "description": "Directory path for Python extensions and shared code (defaults to 'python').", - "default": "python" - }, - "plugins": { - "type": "string", - "description": "Directory path for MXCP plugins (defaults to 'plugins').", - "default": "plugins" - }, - "sql": { - "type": "string", - "description": "Directory path for SQL files (defaults to 'sql').", - "default": "sql" - }, - "drift": { - "type": "string", - "description": "Directory path for drift snapshots (defaults to 'drift').", - "default": "drift" - }, - "audit": { - "type": "string", - "description": "Directory path for audit logs (defaults to 'audit').", - "default": "audit" - }, - "data": { - "type": "string", - "description": "Directory path for data files including DuckDB databases (defaults to 'data').", - "default": "data" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false -} diff --git a/src/mxcp/server/services/dbt/runner.py b/src/mxcp/server/services/dbt/runner.py index 88e0d456..9016fd14 100644 --- a/src/mxcp/server/services/dbt/runner.py +++ b/src/mxcp/server/services/dbt/runner.py @@ -8,12 +8,12 @@ import yaml from mxcp.server.core.config._types import ( - SiteConfig, UserConfig, UserProfileConfig, UserProjectConfig, UserSecretDefinition, ) +from mxcp.server.core.config.models import SiteConfigModel from mxcp.server.core.config.site_config import find_repo_root @@ -211,7 +211,7 @@ def _build_profile_block( return block -def _build_dbt_project(project: str, profile: str, site_config: SiteConfig) -> dict[str, Any]: +def _build_dbt_project(project: str, profile: str, site_config: SiteConfigModel) -> dict[str, Any]: """Build dbt_project.yml configuration. Args: @@ -230,21 +230,21 @@ def _build_dbt_project(project: str, profile: str, site_config: SiteConfig) -> d dbt_profile = f"{sanitized_project}_{sanitized_profile}" # Get dbt configuration from site config (defaults already applied) - dbt_config = site_config.get("dbt") or {} + dbt_config = site_config.dbt # Build the configuration using values from site config return { "name": sanitized_project, # Use sanitized project name "profile": dbt_profile, # Use combined profile name "config-version": 2, - "model-paths": dbt_config.get("model_paths", ["models"]), - "analysis-paths": dbt_config.get("analysis_paths", ["analyses"]), - "test-paths": dbt_config.get("test_paths", ["tests"]), - "seed-paths": dbt_config.get("seed_paths", ["seeds"]), - "macro-paths": dbt_config.get("macro_paths", ["macros"]), - "snapshot-paths": dbt_config.get("snapshot_paths", ["snapshots"]), - "target-path": dbt_config.get("target_path", "target"), - "clean-targets": dbt_config.get("clean_targets", ["target", "dbt_packages"]), + "model-paths": dbt_config.model_paths, + "analysis-paths": dbt_config.analysis_paths, + "test-paths": dbt_config.test_paths, + "seed-paths": dbt_config.seed_paths, + "macro-paths": dbt_config.macro_paths, + "snapshot-paths": dbt_config.snapshot_paths, + "target-path": dbt_config.target_path, + "clean-targets": dbt_config.clean_targets, } @@ -305,7 +305,7 @@ def _merge_dbt_project(existing: dict[str, Any], new: dict[str, Any]) -> dict[st def configure_dbt( - site_config: SiteConfig, + site_config: SiteConfigModel, user_config: UserConfig, profile: str | None = None, dry_run: bool = False, @@ -323,8 +323,8 @@ def configure_dbt( embed_secrets: If True, embed secrets directly in profiles.yml """ # 1. Check dbt is enabled - dbt_config = site_config.get("dbt") or {} - if not dbt_config.get("enabled", True): + dbt_config = site_config.dbt + if not dbt_config.enabled: raise click.ClickException("dbt integration is disabled in mxcp-site.yml") # 2. Handle embed_secrets requirement @@ -341,17 +341,17 @@ def configure_dbt( click.echo("\nContinuing...") # 3. Get project and profile names - project = site_config["project"] - profile_name = profile or site_config["profile"] + project = site_config.project + profile_name = profile or site_config.profile # Create dbt profile name as _ dbt_profile = f"{project}_{profile_name}" # 4. Get DuckDB path using the same convention as the rest of the codebase repo_root = find_repo_root() - profile_config = site_config.get("profiles", {}).get(profile_name, {}) - duckdb_config = profile_config.get("duckdb") - duckdb_path = duckdb_config.get("path") if duckdb_config else None + profile_config = site_config.profiles.get(profile_name) + duckdb_config = profile_config.duckdb if profile_config else None + duckdb_path = duckdb_config.path if duckdb_config else None if not duckdb_path: raise click.ClickException(f"No DuckDB path configured for profile '{profile_name}'") if not os.path.isabs(duckdb_path): diff --git a/src/mxcp/server/services/drift/checker.py b/src/mxcp/server/services/drift/checker.py index 689ee5a5..0addfb17 100644 --- a/src/mxcp/server/services/drift/checker.py +++ b/src/mxcp/server/services/drift/checker.py @@ -4,7 +4,8 @@ from pathlib import Path from typing import Any, cast -from mxcp.server.core.config._types import SiteConfig, UserConfig +from mxcp.server.core.config._types import UserConfig +from mxcp.server.core.config.models import SiteConfigModel from mxcp.server.services.drift._types import ( DriftReport, DriftSnapshot, @@ -301,7 +302,7 @@ def _compare_definitions(baseline: Any | None, current: Any | None) -> bool: async def check_drift( - site_config: SiteConfig, + site_config: SiteConfigModel, user_config: UserConfig, profile: str | None = None, baseline_path: str | None = None, @@ -317,19 +318,19 @@ async def check_drift( Returns: DriftReport with comparison results """ - profile_name = profile or site_config["profile"] + profile_name = profile or site_config.profile # Determine baseline snapshot path if baseline_path: baseline_snapshot_path = Path(baseline_path) else: - drift_config = site_config["profiles"][profile_name].get("drift") - if not drift_config or "path" not in drift_config: + profile_config = site_config.profiles.get(profile_name) + if not profile_config: + raise ValueError(f"No profile '{profile_name}' found in site config") + drift_config = profile_config.drift + if not drift_config or not drift_config.path: raise ValueError(f"No drift configuration found for profile '{profile_name}'") - drift_path = drift_config.get("path") - if not drift_path: - raise ValueError(f"No drift path configured for profile '{profile_name}'") - baseline_snapshot_path = Path(drift_path) + baseline_snapshot_path = Path(drift_config.path) # Load baseline snapshot try: diff --git a/src/mxcp/server/services/drift/snapshot.py b/src/mxcp/server/services/drift/snapshot.py index e0817cc2..2abd3e39 100644 --- a/src/mxcp/server/services/drift/snapshot.py +++ b/src/mxcp/server/services/drift/snapshot.py @@ -7,7 +7,8 @@ import duckdb from mxcp.sdk.executor.plugins.duckdb import DuckDBExecutor -from mxcp.server.core.config._types import SiteConfig, UserConfig +from mxcp.server.core.config._types import UserConfig +from mxcp.server.core.config.models import SiteConfigModel from mxcp.server.core.config.site_config import find_repo_root from mxcp.server.definitions.endpoints.loader import EndpointLoader from mxcp.server.executor.engine import create_runtime_environment @@ -45,7 +46,7 @@ def get_duckdb_tables(conn: duckdb.DuckDBPyConnection) -> list[Table]: async def generate_snapshot( - site_config: SiteConfig, + site_config: SiteConfigModel, user_config: UserConfig, profile: str | None = None, force: bool = False, @@ -63,15 +64,14 @@ async def generate_snapshot( Returns: Tuple of (snapshot data, snapshot file path) """ - profile_name = profile or site_config["profile"] + profile_name = profile or site_config.profile # Get drift path with safe access - profiles = site_config.get("profiles", {}) - profile_config = profiles.get(profile_name, {}) - drift_config = profile_config.get("drift") or {} - drift_path_str = drift_config.get("path", f"drift-{profile_name}.json") - if not drift_path_str: - drift_path_str = f"drift-{profile_name}.json" + profile_config = site_config.profiles.get(profile_name) + drift_config = profile_config.drift if profile_config else None + drift_path_str = ( + drift_config.path if drift_config and drift_config.path else f"drift-{profile_name}.json" + ) drift_path = Path(drift_path_str) if not drift_path.parent.exists(): drift_path.parent.mkdir(parents=True) diff --git a/src/mxcp/server/services/endpoints/service.py b/src/mxcp/server/services/endpoints/service.py index 5c93b045..7611e86a 100644 --- a/src/mxcp/server/services/endpoints/service.py +++ b/src/mxcp/server/services/endpoints/service.py @@ -19,7 +19,8 @@ PolicyEnforcer, PolicySet, ) -from mxcp.server.core.config._types import SiteConfig, UserConfig +from mxcp.server.core.config._types import UserConfig +from mxcp.server.core.config.models import SiteConfigModel from mxcp.server.core.config.site_config import find_repo_root from mxcp.server.definitions.endpoints._types import PromptDefinition from mxcp.server.definitions.endpoints.loader import EndpointLoader @@ -115,7 +116,7 @@ async def execute_endpoint( name: str, params: dict[str, Any], user_config: UserConfig, - site_config: SiteConfig, + site_config: SiteConfigModel, profile_name: str, readonly: bool = False, skip_output_validation: bool = False, @@ -173,7 +174,7 @@ async def execute_endpoint_with_engine_and_policy( params: dict[str, Any], request_headers: dict[str, str] | None, user_config: UserConfig, - site_config: SiteConfig, + site_config: SiteConfigModel, execution_engine: ExecutionEngine, skip_output_validation: bool = False, user_context: UserContext | None = None, @@ -327,7 +328,7 @@ async def execute_endpoint_with_engine( name: str, params: dict[str, Any], user_config: UserConfig, - site_config: SiteConfig, + site_config: SiteConfigModel, execution_engine: ExecutionEngine, skip_output_validation: bool = False, user_context: UserContext | None = None, diff --git a/src/mxcp/server/services/endpoints/validator.py b/src/mxcp/server/services/endpoints/validator.py index b975e7a4..f5a3493b 100644 --- a/src/mxcp/server/services/endpoints/validator.py +++ b/src/mxcp/server/services/endpoints/validator.py @@ -8,7 +8,7 @@ from referencing import Registry, Resource from mxcp.sdk.executor import ExecutionEngine -from mxcp.server.core.config._types import SiteConfig +from mxcp.server.core.config.models import SiteConfigModel from mxcp.server.core.config.site_config import find_repo_root from mxcp.server.definitions.endpoints._types import EndpointDefinition, ResourceDefinition from mxcp.server.definitions.endpoints.loader import EndpointLoader @@ -39,7 +39,7 @@ def _validate_resource_uri_vs_params( def validate_all_endpoints( - site_config: SiteConfig, execution_engine: ExecutionEngine + site_config: SiteConfigModel, execution_engine: ExecutionEngine ) -> dict[str, Any]: """Validate all endpoints in the repository. @@ -359,7 +359,7 @@ def validate_endpoint_payload( def validate_endpoint( - path: str, site_config: SiteConfig, execution_engine: ExecutionEngine + path: str, site_config: SiteConfigModel, execution_engine: ExecutionEngine ) -> dict[str, Any]: """Validate a single endpoint file.""" try: diff --git a/src/mxcp/server/services/evals/service.py b/src/mxcp/server/services/evals/service.py index 64a90b14..af630379 100644 --- a/src/mxcp/server/services/evals/service.py +++ b/src/mxcp/server/services/evals/service.py @@ -12,7 +12,8 @@ ToolDefinition, ) from mxcp.sdk.validator import TypeSchema -from mxcp.server.core.config._types import SiteConfig, UserConfig +from mxcp.server.core.config._types import UserConfig +from mxcp.server.core.config.models import SiteConfigModel from mxcp.server.core.config.site_config import find_repo_root from mxcp.server.definitions.endpoints._types import EndpointDefinition from mxcp.server.definitions.endpoints.loader import EndpointLoader @@ -66,7 +67,7 @@ def _create_model_config(model: str, user_config: UserConfig) -> ModelConfigType raise ValueError(f"Unknown model type: {model_type}") -def _load_endpoints(site_config: SiteConfig) -> list[EndpointDefinition]: +def _load_endpoints(site_config: SiteConfigModel) -> list[EndpointDefinition]: """Load all available endpoints. Args: @@ -177,7 +178,7 @@ def _convert_endpoints_to_tool_definitions( async def run_eval_suite( suite_name: str, user_config: UserConfig, - site_config: SiteConfig, + site_config: SiteConfigModel, profile: str | None, cli_user_context: UserContext | None = None, override_model: str | None = None, @@ -377,7 +378,7 @@ async def run_eval_suite( async def run_all_evals( user_config: UserConfig, - site_config: SiteConfig, + site_config: SiteConfigModel, profile: str | None, cli_user_context: UserContext | None = None, override_model: str | None = None, diff --git a/src/mxcp/server/services/tests/service.py b/src/mxcp/server/services/tests/service.py index 540bca30..3b2fae7a 100644 --- a/src/mxcp/server/services/tests/service.py +++ b/src/mxcp/server/services/tests/service.py @@ -2,7 +2,8 @@ from typing import Any from mxcp.sdk.auth import UserContext -from mxcp.server.core.config._types import SiteConfig, UserConfig +from mxcp.server.core.config._types import UserConfig +from mxcp.server.core.config.models import SiteConfigModel from mxcp.server.core.config.site_config import find_repo_root from mxcp.server.definitions.endpoints.loader import EndpointLoader from mxcp.server.executor.engine import create_runtime_environment @@ -15,7 +16,7 @@ async def run_all_tests( user_config: UserConfig, - site_config: SiteConfig, + site_config: SiteConfigModel, profile: str | None, readonly: bool | None = None, cli_user_context: UserContext | None = None, @@ -138,7 +139,7 @@ async def run_tests( endpoint_type: str, name: str, user_config: UserConfig, - site_config: SiteConfig, + site_config: SiteConfigModel, profile: str | None, readonly: bool | None = None, cli_user_context: UserContext | None = None, diff --git a/tests/server/fixtures/runtime/python/runtime_tests.py b/tests/server/fixtures/runtime/python/runtime_tests.py index 509b9822..c3fd5e27 100644 --- a/tests/server/fixtures/runtime/python/runtime_tests.py +++ b/tests/server/fixtures/runtime/python/runtime_tests.py @@ -141,8 +141,12 @@ def test_config_properties() -> dict: secrets_count = len(user_cfg["projects"]["runtime_test"]["profiles"]["default"]["secrets"]) # From site config - site_project = site_cfg["project"] - site_secrets = site_cfg["secrets"] + if hasattr(site_cfg, "model_dump"): + site_cfg_dict = site_cfg.model_dump(mode="python") + else: + site_cfg_dict = site_cfg + site_project = site_cfg_dict["project"] + site_secrets = site_cfg_dict["secrets"] access_works = True except Exception: diff --git a/tests/server/test_admin_api.py b/tests/server/test_admin_api.py index a718f34d..2b8fb9bd 100644 --- a/tests/server/test_admin_api.py +++ b/tests/server/test_admin_api.py @@ -21,6 +21,7 @@ from mxcp.server.admin.app import create_admin_app from mxcp.server.admin.service import AdminService from mxcp.server.admin.runner import AdminAPIRunner +from mxcp.server.core.config.models import SiteConfigModel from mxcp.server.core.reload import ReloadRequest from mxcp.server.interfaces.cli.utils import ( get_env_admin_socket_enabled, @@ -58,7 +59,9 @@ def __init__(self): self.profile_name = "test-profile" self.debug = False self.readonly = False - self.site_config = {"project": "test-project"} + self.site_config = SiteConfigModel.model_validate( + {"mxcp": 1, "project": "test-project", "profile": self.profile_name} + ) self.user_config = {} self.reload_called = False self.reload_request = ReloadRequest(description="test-reload") diff --git a/tests/server/test_cli_init.py b/tests/server/test_cli_init.py index 1b35a6eb..17e8a905 100644 --- a/tests/server/test_cli_init.py +++ b/tests/server/test_cli_init.py @@ -9,6 +9,7 @@ import yaml from click.testing import CliRunner +from mxcp.server.core.config.models import SiteConfigModel from mxcp.server.core.config.site_config import load_site_config from mxcp.server.interfaces.cli.init import init @@ -154,7 +155,7 @@ def test_init_bootstrap_complete_directory_structure(tmp_path): from mxcp.server.core.config.site_config import load_site_config - loaded_config = load_site_config() + loaded_config = load_site_config().model_dump(mode="python") # Verify organized paths are configured correctly paths = loaded_config["paths"] @@ -266,7 +267,9 @@ def test_user_config_generation_uses_integer_version(): from mxcp.server.core.config.user_config import _generate_default_config # Create a mock site config (as would be created by mxcp init) - site_config = {"mxcp": 1, "project": "test-new-project", "profile": "default"} + site_config = SiteConfigModel.model_validate( + {"mxcp": 1, "project": "test-new-project", "profile": "default"} + ) # Generate default user config (this is what happens when user config doesn't exist) user_config = _generate_default_config(site_config) @@ -464,7 +467,7 @@ def test_init_bootstrap_complete_directory_structure(): assert tool_config["tool"]["source"]["file"] == "../sql/hello-world.sql" # Check site config can be loaded (validates schema) - site_config = load_site_config(project_dir) + site_config = load_site_config(project_dir).model_dump(mode="python") assert site_config["project"] == "test-project" assert site_config["profile"] == "default" @@ -500,7 +503,9 @@ def test_user_config_generation_uses_integer_version(): from mxcp.server.core.config.user_config import _generate_default_config # Create a mock site config - site_config = {"mxcp": 1, "project": "test-project", "profile": "default"} + site_config = SiteConfigModel.model_validate( + {"mxcp": 1, "project": "test-project", "profile": "default"} + ) config = _generate_default_config(site_config) diff --git a/tests/server/test_init_hooks_db_access.py b/tests/server/test_init_hooks_db_access.py index 71faf72b..e45067c2 100644 --- a/tests/server/test_init_hooks_db_access.py +++ b/tests/server/test_init_hooks_db_access.py @@ -9,6 +9,7 @@ import pytest import yaml +from mxcp.server.core.config.models import SiteConfigModel from mxcp.server.executor.engine import create_runtime_environment from mxcp.server.services.endpoints import execute_endpoint_with_engine @@ -118,7 +119,7 @@ def check_table_data(): ) # Create site config - site_config = { + site_config_data = { "mxcp": 1, "project": "init-test", "profile": "test", @@ -129,7 +130,7 @@ def check_table_data(): } with open(project_dir / "mxcp-site.yml", "w") as f: - yaml.dump(site_config, f) + yaml.dump(site_config_data, f) # Create user config user_config = { @@ -153,6 +154,10 @@ def check_table_data(): try: # Create runtime environment - this will run init hooks + site_config = SiteConfigModel.model_validate( + site_config_data, context={"repo_root": project_dir} + ) + runtime_env = create_runtime_environment( user_config, site_config, repo_root=project_dir ) diff --git a/tests/server/test_integration.py b/tests/server/test_integration.py index 00ffa8d8..7567b8c0 100644 --- a/tests/server/test_integration.py +++ b/tests/server/test_integration.py @@ -541,7 +541,7 @@ def trigger_reload(message: str = "test") -> dict: # Get DuckDB file path site_config = config.site_config - db_path = Path(site_config["profiles"]["default"]["duckdb"]["path"]) + db_path = Path(site_config.profiles["default"].duckdb.path) if not db_path.is_absolute(): db_path = Path.cwd() / db_path @@ -783,12 +783,12 @@ async def test_non_duckdb_secret_types(self, integration_fixture_dir): # Update site config to reference new secrets site_config_path = integration_fixture_dir / "mxcp-site.yml" with open(site_config_path) as f: - site_config = yaml.safe_load(f) + site_config_data = yaml.safe_load(f) or {} - site_config["secrets"] = ["custom_secret", "python_secret"] + site_config_data["secrets"] = ["custom_secret", "python_secret"] with open(site_config_path, "w") as f: - yaml.dump(site_config, f) + yaml.dump(site_config_data, f) # Create endpoint to test these secrets python_code = ''' @@ -1226,12 +1226,13 @@ async def test_sql_tools_asyncio_fix_regression(self, integration_fixture_dir): # Update site config to enable SQL tools site_config_path = integration_fixture_dir / "mxcp-site.yml" with open(site_config_path) as f: - site_config = yaml.safe_load(f) + site_config_data = yaml.safe_load(f) or {} - site_config["sql_tools"] = {"enabled": True} + sql_tools_cfg = site_config_data.setdefault("sql_tools", {}) + sql_tools_cfg["enabled"] = True with open(site_config_path, "w") as f: - yaml.dump(site_config, f) + yaml.dump(site_config_data, f) with ServerProcess(integration_fixture_dir) as server: server.start(extra_args=["--sql-tools", "true"]) @@ -1358,12 +1359,13 @@ async def test_dbt_with_sql_tools(self, integration_fixture_dir): # Update site config to enable SQL tools (dbt already enabled in fixture) site_config_path = integration_fixture_dir / "mxcp-site.yml" with open(site_config_path) as f: - site_config = yaml.safe_load(f) + site_config_data = yaml.safe_load(f) or {} - site_config["sql_tools"] = {"enabled": True} + sql_tools_cfg = site_config_data.setdefault("sql_tools", {}) + sql_tools_cfg["enabled"] = True with open(site_config_path, "w") as f: - yaml.dump(site_config, f) + yaml.dump(site_config_data, f) # Run the proper dbt workflow first original_dir = os.getcwd() diff --git a/tests/server/test_python_endpoints.py b/tests/server/test_python_endpoints.py index 86e0e538..47d4ff42 100644 --- a/tests/server/test_python_endpoints.py +++ b/tests/server/test_python_endpoints.py @@ -680,7 +680,9 @@ async def test_python_endpoint_with_non_duckdb_secret_type( ) # Update site config to reference our secrets - site_config["secrets"] = ["duckdb_test", "api_test", "custom_api", "python_only"] + site_config = site_config.model_copy( + update={"secrets": ["duckdb_test", "api_test", "custom_api", "python_only"]} + ) # Create Python endpoint file python_file = temp_project_dir / "python" / "custom_secrets.py" diff --git a/tests/server/test_reload.py b/tests/server/test_reload.py index 3a43663d..bf5bdc61 100644 --- a/tests/server/test_reload.py +++ b/tests/server/test_reload.py @@ -6,10 +6,12 @@ """ import signal +from pathlib import Path from unittest.mock import MagicMock, patch import pytest +from mxcp.server.core.config.models import SiteConfigModel from mxcp.server.interfaces.server.mcp import RAWMCP from mxcp.server.core.reload import ReloadManager @@ -17,6 +19,25 @@ class TestReloadFunctionality: """Test reload functionality for both SIGHUP and DuckDB reloads.""" + @staticmethod + def _minimal_site_config() -> SiteConfigModel: + return SiteConfigModel.model_validate( + { + "mxcp": 1, + "project": "test", + "profile": "default", + "profiles": { + "default": { + "duckdb": { + "path": str(Path("/tmp") / "db-default.duckdb"), + "readonly": False, + } + } + }, + }, + context={"repo_root": Path("/tmp")}, + ) + def test_sighup_triggers_full_reload(self): """Test that SIGHUP signal triggers full system reload.""" # Create a minimal mock server @@ -35,7 +56,10 @@ def test_sighup_triggers_full_reload(self): server._handle_reload_signal = RAWMCP._handle_reload_signal.__get__(server, RAWMCP) # Mock load_site_config and load_user_config - with patch("mxcp.server.interfaces.server.mcp.load_site_config", return_value={}): + with patch( + "mxcp.server.interfaces.server.mcp.load_site_config", + return_value=self._minimal_site_config(), + ): with patch("mxcp.server.interfaces.server.mcp.load_user_config", return_value={}): # Simulate SIGHUP server._handle_reload_signal(signal.SIGHUP, None) @@ -115,7 +139,10 @@ def test_reload_metrics_recorded(self): server.site_config_path = None # Test that reload_configuration requests a reload - with patch("mxcp.server.interfaces.server.mcp.load_site_config", return_value={}): + with patch( + "mxcp.server.interfaces.server.mcp.load_site_config", + return_value=self._minimal_site_config(), + ): with patch("mxcp.server.interfaces.server.mcp.load_user_config", return_value={}): server.reload_configuration() diff --git a/tests/server/test_site_config_model.py b/tests/server/test_site_config_model.py new file mode 100644 index 00000000..74779218 --- /dev/null +++ b/tests/server/test_site_config_model.py @@ -0,0 +1,46 @@ +import os +from pathlib import Path + +import pytest + +from mxcp.server.core.config.models import SiteConfigModel + + +def _base_config() -> dict: + return { + "mxcp": 1, + "project": "demo", + "profile": "dev", + "profiles": {"dev": {}}, + } + + +def test_site_config_applies_defaults(tmp_path: Path): + model = SiteConfigModel.model_validate(_base_config(), context={"repo_root": tmp_path}) + + assert model.dbt.enabled is True + assert model.paths.tools == "tools" + assert model.extensions == [] + + profile = model.profiles["dev"] + assert profile.duckdb.path == str(tmp_path / "data" / "db-dev.duckdb") + assert profile.drift.path == str(tmp_path / "drift" / "drift-dev.json") + assert profile.audit.path == str(tmp_path / "audit" / "logs-dev.jsonl") + assert profile.audit.enabled is False + + +def test_duckdb_path_env_override(tmp_path: Path, monkeypatch: pytest.MonkeyPatch): + monkeypatch.setenv("MXCP_DUCKDB_PATH", "/tmp/custom.duckdb") + model = SiteConfigModel.model_validate(_base_config(), context={"repo_root": tmp_path}) + assert model.profiles["dev"].duckdb.path == "/tmp/custom.duckdb" + monkeypatch.delenv("MXCP_DUCKDB_PATH") + + +@pytest.mark.parametrize("env_value,expected", [("true", True), ("false", False)]) +def test_audit_env_override( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch, env_value: str, expected: bool +): + monkeypatch.setenv("MXCP_AUDIT_ENABLED", env_value) + model = SiteConfigModel.model_validate(_base_config(), context={"repo_root": tmp_path}) + assert model.profiles["dev"].audit.enabled is expected + monkeypatch.delenv("MXCP_AUDIT_ENABLED") diff --git a/tests/server/test_telemetry_integration.py b/tests/server/test_telemetry_integration.py index ca0e1ff3..a9bcbf9a 100644 --- a/tests/server/test_telemetry_integration.py +++ b/tests/server/test_telemetry_integration.py @@ -9,7 +9,8 @@ from mxcp.sdk.executor import ExecutionContext from mxcp.sdk.telemetry import is_telemetry_enabled, shutdown_telemetry -from mxcp.server.core.config._types import SiteConfig, UserConfig +from mxcp.server.core.config._types import UserConfig +from mxcp.server.core.config.models import SiteConfigModel from mxcp.server.core.telemetry import configure_telemetry_from_config from mxcp.server.executor.engine import create_runtime_environment @@ -64,19 +65,22 @@ def test_telemetry_with_execution_engine(tmp_path): assert is_telemetry_enabled() # Create minimal site config - site_config: SiteConfig = { - "mxcp": "1", - "project": "test", - "profile": "dev", - "profiles": { - "dev": { - "duckdb": { - "path": str(tmp_path / "test.duckdb"), - "readonly": False, + site_config = SiteConfigModel.model_validate( + { + "mxcp": 1, + "project": "test", + "profile": "dev", + "profiles": { + "dev": { + "duckdb": { + "path": str(tmp_path / "test.duckdb"), + "readonly": False, + } } - } + }, }, - } + context={"repo_root": tmp_path}, + ) # Create execution engine with a temporary directory as repo root import tempfile @@ -149,19 +153,22 @@ def test_nested_telemetry_spans(tmp_path): configure_telemetry_from_config(user_config, "test", "dev") # Create site config - site_config: SiteConfig = { - "mxcp": "1", - "project": "test", - "profile": "dev", - "profiles": { - "dev": { - "duckdb": { - "path": str(tmp_path / "test.duckdb"), - "readonly": False, + site_config = SiteConfigModel.model_validate( + { + "mxcp": 1, + "project": "test", + "profile": "dev", + "profiles": { + "dev": { + "duckdb": { + "path": str(tmp_path / "test.duckdb"), + "readonly": False, + } } - } + }, }, - } + context={"repo_root": tmp_path}, + ) # Test nested spans directly with execution engine async def run_nested_operations(): diff --git a/tests/server/test_user_config.py b/tests/server/test_user_config.py index fa30f381..8f49caf2 100644 --- a/tests/server/test_user_config.py +++ b/tests/server/test_user_config.py @@ -4,9 +4,14 @@ import pytest import yaml +from mxcp.server.core.config.models import SiteConfigModel from mxcp.server.core.config.user_config import load_user_config +def make_site_config(project: str = "test_project", profile: str = "dev") -> SiteConfigModel: + return SiteConfigModel.model_validate({"mxcp": 1, "project": project, "profile": profile}) + + def test_env_var_interpolation(tmp_path): """Test environment variable interpolation in user config.""" # Create a test config file @@ -35,7 +40,7 @@ def test_env_var_interpolation(tmp_path): os.environ["ANOTHER_VAR"] = "another_value" # Create a minimal site config - site_config = {"project": "test_project", "profile": "dev"} + site_config = make_site_config("test_project", "dev") # Load and verify config config = load_user_config(site_config) @@ -82,7 +87,7 @@ def test_missing_env_var(tmp_path): os.environ["MXCP_CONFIG"] = str(config_path) # Create a minimal site config - site_config = {"project": "test_project", "profile": "dev"} + site_config = make_site_config("test_project", "dev") # Verify that loading fails with appropriate error with pytest.raises(ValueError, match="Environment variable MISSING_VAR is not set"): @@ -118,7 +123,7 @@ def test_env_var_in_nested_structures(tmp_path): os.environ["NESTED_VAR2"] = "nested_value2" # Create a minimal site config - site_config = {"project": "test_project", "profile": "dev"} + site_config = make_site_config("test_project", "dev") # Load and verify config config = load_user_config(site_config) @@ -170,7 +175,7 @@ def test_file_url_interpolation(tmp_path): os.environ["MXCP_CONFIG"] = str(config_path) # Create a minimal site config - site_config = {"project": "test_project", "profile": "dev"} + site_config = make_site_config("test_project", "dev") # Load and verify config config = load_user_config(site_config) @@ -216,7 +221,7 @@ def test_file_url_relative_path(tmp_path): os.environ["MXCP_CONFIG"] = str(config_path) # Create a minimal site config - site_config = {"project": "test_project", "profile": "dev"} + site_config = make_site_config("test_project", "dev") # Load and verify config config = load_user_config(site_config) @@ -250,7 +255,7 @@ def test_file_url_errors(tmp_path): os.environ["MXCP_CONFIG"] = str(config_path) - site_config = {"project": "test_project", "profile": "dev"} + site_config = make_site_config("test_project", "dev") # Should raise FileNotFoundError with pytest.raises(FileNotFoundError, match="File not found"): @@ -323,7 +328,7 @@ def test_mixed_interpolation_with_files(tmp_path): with open(tmp_path / "config.yml", "w") as f: yaml.dump(user_config_data, f) - site_config = {"project": "test", "profile": "default"} + site_config = make_site_config("test", "default") # Load and verify config = load_user_config(site_config) @@ -370,7 +375,7 @@ def test_load_without_resolving_refs(tmp_path): with open(tmp_path / "config.yml", "w") as f: yaml.dump(user_config_data, f) - site_config = {"project": "test", "profile": "default"} + site_config = make_site_config("test", "default") # Load without resolving references config = load_user_config(site_config, resolve_refs=False) diff --git a/uv.lock b/uv.lock index 196206f0..c4dcb808 100644 --- a/uv.lock +++ b/uv.lock @@ -1440,7 +1440,7 @@ wheels = [ [[package]] name = "mxcp" -version = "0.10.0rc11" +version = "0.10.0rc14" source = { editable = "." } dependencies = [ { name = "aiohttp" }, From 4eae340b6c3bfe56f7e847274219dd8d21755101 Mon Sep 17 00:00:00 2001 From: Miguel Branco Date: Mon, 24 Nov 2025 20:33:39 +0100 Subject: [PATCH 02/26] Migrate UserConfig to pydantic --- docs/features/python-endpoints.md | 2 +- docs/reference/python.md | 7 +- src/mxcp/runtime/__init__.py | 24 +- src/mxcp/server/core/auth/helpers.py | 148 +++-- src/mxcp/server/core/config/_types.py | 186 ------ src/mxcp/server/core/config/models.py | 233 ++++++- src/mxcp/server/core/config/parsers.py | 35 +- src/mxcp/server/core/config/schema_utils.py | 20 +- src/mxcp/server/core/config/site_config.py | 21 +- src/mxcp/server/core/config/user_config.py | 218 ++----- src/mxcp/server/core/refs/resolver.py | 11 +- src/mxcp/server/core/telemetry.py | 63 +- src/mxcp/server/executor/engine.py | 5 +- src/mxcp/server/executor/runners/endpoint.py | 5 +- src/mxcp/server/executor/runners/test.py | 5 +- src/mxcp/server/interfaces/cli/dbt.py | 16 +- src/mxcp/server/interfaces/cli/serve.py | 8 +- src/mxcp/server/interfaces/cli/utils.py | 17 +- src/mxcp/server/interfaces/server/mcp.py | 185 +----- .../server/schemas/mxcp-config-schema-1.json | 614 ------------------ src/mxcp/server/services/dbt/runner.py | 37 +- src/mxcp/server/services/drift/checker.py | 5 +- src/mxcp/server/services/drift/snapshot.py | 5 +- src/mxcp/server/services/endpoints/service.py | 9 +- src/mxcp/server/services/evals/service.py | 51 +- src/mxcp/server/services/tests/service.py | 7 +- .../fixtures/runtime/python/runtime_tests.py | 10 +- tests/server/test_cli_init.py | 18 +- tests/server/test_init_hooks_db_access.py | 5 +- tests/server/test_python_endpoints.py | 5 +- tests/server/test_reload.py | 12 +- tests/server/test_telemetry_config.py | 110 ++-- tests/server/test_telemetry_env_vars.py | 41 +- tests/server/test_telemetry_integration.py | 59 +- tests/server/test_user_config.py | 12 +- tests/server/test_user_config_model.py | 61 ++ 36 files changed, 768 insertions(+), 1502 deletions(-) delete mode 100644 src/mxcp/server/core/config/_types.py delete mode 100644 src/mxcp/server/schemas/mxcp-config-schema-1.json create mode 100644 tests/server/test_user_config_model.py diff --git a/docs/features/python-endpoints.md b/docs/features/python-endpoints.md index c7d2275c..b0ba92c4 100644 --- a/docs/features/python-endpoints.md +++ b/docs/features/python-endpoints.md @@ -122,7 +122,7 @@ debug_mode = config.get_setting("debug", default=False) # Access full configs user_config = config.user_config site_config = config.site_config -# site_config is a SiteConfigModel, so use attribute access +# Both configs are Pydantic models (UserConfigModel / SiteConfigModel), so use attribute access ``` ### Plugin Access diff --git a/docs/reference/python.md b/docs/reference/python.md index 74f919c6..efa8202b 100644 --- a/docs/reference/python.md +++ b/docs/reference/python.md @@ -83,11 +83,14 @@ extensions = config.get_setting("extensions", default=[]) ``` ### `config.user_config` -Access full user configuration dictionary. +Access the full `UserConfigModel` instance. Use attributes or `model_dump()` to inspect nested data. ```python user_cfg = config.user_config -projects = user_cfg["projects"] if user_cfg else {} +if user_cfg: + active_project = user_cfg.projects[user_cfg_site] + active_profile = active_project.profiles[user_cfg.profile] + secret_names = [secret.name for secret in active_profile.secrets] ``` ### `config.site_config` diff --git a/src/mxcp/runtime/__init__.py b/src/mxcp/runtime/__init__.py index 4dd0ecf7..e7065c02 100644 --- a/src/mxcp/runtime/__init__.py +++ b/src/mxcp/runtime/__init__.py @@ -20,7 +20,7 @@ from typing import Any, cast from mxcp.sdk.executor.context import get_execution_context -from mxcp.server.core.config.models import SiteConfigModel +from mxcp.server.core.config.models import SiteConfigModel, UserConfigModel logger = logging.getLogger(__name__) @@ -77,17 +77,22 @@ def get_secret(self, name: str) -> dict[str, Any] | None: if not project or not profile: return None + if isinstance(user_config, UserConfigModel): + project_config = user_config.projects.get(project) + profile_config = project_config.profiles.get(profile) if project_config else None + secrets = profile_config.secrets if profile_config else [] + for secret in secrets: + if secret.name == name: + return secret.parameters + return None + try: - # Navigate to the secrets in user config: projects -> project -> profiles -> profile -> secrets project_config = user_config["projects"][project] profile_config = project_config["profiles"][profile] secrets = profile_config.get("secrets", []) - - # Find secret by name and return its parameters for secret in secrets: if secret.get("name") == name: return cast(dict[str, Any], secret.get("parameters", {})) - return None except (KeyError, TypeError): return None @@ -124,13 +129,18 @@ def get_setting(self, key: str, default: Any = None) -> Any: return raw_config.get(key, default) @property - def user_config(self) -> dict[str, Any] | None: + def user_config(self) -> UserConfigModel | None: """Access full user configuration.""" context = get_execution_context() if not context: return None - return cast(dict[str, Any] | None, context.get("user_config")) + user_config = context.get("user_config") + if isinstance(user_config, UserConfigModel): + return user_config + if user_config is None: + return None + return UserConfigModel.model_validate(user_config) @property def site_config(self) -> Any | None: diff --git a/src/mxcp/server/core/auth/helpers.py b/src/mxcp/server/core/auth/helpers.py index 95513411..fc622862 100644 --- a/src/mxcp/server/core/auth/helpers.py +++ b/src/mxcp/server/core/auth/helpers.py @@ -1,42 +1,65 @@ -"""Authentication helper functions for translating between user config and SDK auth types. +"""Authentication helper functions for translating between MXCP config models and SDK auth types.""" -These functions help bridge the gap between MXCP's configuration format and the -standalone SDK auth types. They don't belong in the SDK itself since they're -specific to MXCP's config structure. -""" +from typing import cast from mxcp.sdk.auth import ExternalOAuthHandler -from mxcp.sdk.auth._types import AuthConfig, HttpTransportConfig +from mxcp.sdk.auth._types import ( + AtlassianAuthConfig, + AuthConfig, + AuthorizationConfig, + AuthPersistenceConfig, + GitHubAuthConfig, + GoogleAuthConfig, + HttpTransportConfig, + KeycloakAuthConfig, + OAuthClientConfig, + SalesforceAuthConfig, +) from mxcp.sdk.auth.providers.atlassian import AtlassianOAuthHandler from mxcp.sdk.auth.providers.github import GitHubOAuthHandler +from mxcp.sdk.auth.providers.google import GoogleOAuthHandler from mxcp.sdk.auth.providers.keycloak import KeycloakOAuthHandler from mxcp.sdk.auth.providers.salesforce import SalesforceOAuthHandler from mxcp.sdk.auth.url_utils import URLBuilder -from mxcp.server.core.config._types import UserAuthConfig, UserConfig, UserHttpTransportConfig - - -def translate_auth_config(user_auth_config: UserAuthConfig) -> AuthConfig: - """Translate user auth config to minimal SDK auth config. - - Only extracts fields needed by GeneralOAuthAuthorizationServer. - Provider-specific configs are handled separately. - - Args: - user_auth_config: User configuration auth section - - Returns: - Minimal SDK-compatible auth configuration - """ - return { - "provider": user_auth_config.get("provider"), - "clients": user_auth_config.get("clients"), - "authorization": user_auth_config.get("authorization"), - "persistence": user_auth_config.get("persistence"), +from mxcp.server.core.config.models import ( + UserAuthConfigModel, + UserConfigModel, + UserHttpTransportConfigModel, +) + + +def translate_auth_config(user_auth_config: UserAuthConfigModel) -> AuthConfig: + """Translate user auth config to minimal SDK auth config.""" + clients: list[OAuthClientConfig] | None = None + if user_auth_config.clients: + clients = [ + cast(OAuthClientConfig, client.model_dump(exclude_none=True)) + for client in user_auth_config.clients + ] + + authorization: AuthorizationConfig | None = None + if user_auth_config.authorization: + authorization = cast( + AuthorizationConfig, user_auth_config.authorization.model_dump(exclude_none=True) + ) + + persistence: AuthPersistenceConfig | None = None + if user_auth_config.persistence: + persistence = cast( + AuthPersistenceConfig, user_auth_config.persistence.model_dump(exclude_none=True) + ) + + config: AuthConfig = { + "provider": user_auth_config.provider, + "clients": clients, + "authorization": authorization, + "persistence": persistence, } + return config def translate_transport_config( - user_transport_config: UserHttpTransportConfig | None, + user_transport_config: UserHttpTransportConfigModel | None, ) -> HttpTransportConfig | None: """Translate user HTTP transport config to SDK transport config. @@ -49,21 +72,14 @@ def translate_transport_config( if not user_transport_config: return None - return { - "port": user_transport_config.get("port"), - "host": user_transport_config.get("host"), - "scheme": user_transport_config.get("scheme"), - "base_url": user_transport_config.get("base_url"), - "trust_proxy": user_transport_config.get("trust_proxy"), - "stateless": user_transport_config.get("stateless"), - } + return cast(HttpTransportConfig, user_transport_config.model_dump(exclude_none=True)) def create_oauth_handler( - user_auth_config: UserAuthConfig, + user_auth_config: UserAuthConfigModel, host: str = "localhost", port: int = 8000, - user_config: UserConfig | None = None, + user_config: UserConfigModel | None = None, ) -> ExternalOAuthHandler | None: """Create an OAuth handler from user configuration. @@ -78,51 +94,59 @@ def create_oauth_handler( Returns: OAuth handler instance or None if provider is 'none' """ - provider = user_auth_config.get("provider", "none") + provider = user_auth_config.provider if provider == "none": return None # Extract transport config if available transport_config = None - if user_config and "transport" in user_config: - transport = user_config["transport"] - user_transport = transport.get("http") if transport else None - transport_config = translate_transport_config(user_transport) + if user_config: + transport_config = translate_transport_config( + user_config.transport.http if user_config.transport else None + ) if provider == "github": - - github_config = user_auth_config.get("github") + github_config = user_auth_config.github if not github_config: raise ValueError("GitHub provider selected but no GitHub configuration found") - return GitHubOAuthHandler(github_config, transport_config, host=host, port=port) - - elif provider == "atlassian": + github_dict = cast(GitHubAuthConfig, github_config.model_dump(exclude_none=True)) + return GitHubOAuthHandler(github_dict, transport_config, host=host, port=port) - atlassian_config = user_auth_config.get("atlassian") + if provider == "atlassian": + atlassian_config = user_auth_config.atlassian if not atlassian_config: raise ValueError("Atlassian provider selected but no Atlassian configuration found") - return AtlassianOAuthHandler(atlassian_config, transport_config, host=host, port=port) + atlassian_dict = cast(AtlassianAuthConfig, atlassian_config.model_dump(exclude_none=True)) + return AtlassianOAuthHandler(atlassian_dict, transport_config, host=host, port=port) - elif provider == "salesforce": - - salesforce_config = user_auth_config.get("salesforce") + if provider == "salesforce": + salesforce_config = user_auth_config.salesforce if not salesforce_config: raise ValueError("Salesforce provider selected but no Salesforce configuration found") - return SalesforceOAuthHandler(salesforce_config, transport_config, host=host, port=port) - - elif provider == "keycloak": + salesforce_dict = cast( + SalesforceAuthConfig, salesforce_config.model_dump(exclude_none=True) + ) + return SalesforceOAuthHandler(salesforce_dict, transport_config, host=host, port=port) - keycloak_config = user_auth_config.get("keycloak") + if provider == "keycloak": + keycloak_config = user_auth_config.keycloak if not keycloak_config: raise ValueError("Keycloak provider selected but no Keycloak configuration found") - return KeycloakOAuthHandler(keycloak_config, transport_config, host=host, port=port) + keycloak_dict = cast(KeycloakAuthConfig, keycloak_config.model_dump(exclude_none=True)) + return KeycloakOAuthHandler(keycloak_dict, transport_config, host=host, port=port) + + if provider == "google": + google_config = user_auth_config.google + if not google_config: + raise ValueError("Google provider selected but no Google configuration found") + google_dict = cast(GoogleAuthConfig, google_config.model_dump(exclude_none=True)) + return GoogleOAuthHandler(google_dict, transport_config, host=host, port=port) - else: - raise ValueError(f"Unsupported auth provider: {provider}") + raise ValueError(f"Unsupported auth provider: {provider}") -def create_url_builder(user_config: UserConfig) -> URLBuilder: +def create_url_builder(user_config: UserConfigModel) -> URLBuilder: """Create a URL builder from user configuration. Args: @@ -131,7 +155,5 @@ def create_url_builder(user_config: UserConfig) -> URLBuilder: Returns: Configured URLBuilder instance """ - transport = user_config.get("transport", {}) - user_transport_config = transport.get("http", {}) if transport else {} - transport_config = translate_transport_config(user_transport_config) + transport_config = translate_transport_config(user_config.transport.http) return URLBuilder(transport_config) diff --git a/src/mxcp/server/core/config/_types.py b/src/mxcp/server/core/config/_types.py deleted file mode 100644 index 270db06d..00000000 --- a/src/mxcp/server/core/config/_types.py +++ /dev/null @@ -1,186 +0,0 @@ -from typing import Any, Literal, TypedDict - - -# User Config Types (~/.mxcp/config.yml) -class UserSecretDefinition(TypedDict): - name: str - type: str - parameters: dict[str, Any] # Can contain strings or nested objects - - -class UserPluginConfig(TypedDict, total=False): - config: dict[str, dict[str, str]] - - -class UserVaultConfig(TypedDict): - enabled: bool - address: str | None - token_env: str | None - - -class UserOnePasswordConfig(TypedDict): - enabled: bool - token_env: str | None - - -class UserHttpTransportConfig(TypedDict, total=False): - port: int | None - host: str | None - scheme: Literal["http", "https"] | None - base_url: str | None - trust_proxy: bool | None - stateless: bool | None - - -class UserTransportConfig(TypedDict, total=False): - provider: Literal["streamable-http", "sse", "stdio"] | None - http: UserHttpTransportConfig | None - - -class UserOAuthClientConfig(TypedDict): - client_id: str - name: str - client_secret: str | None - redirect_uris: list[str] | None - grant_types: list[Literal["authorization_code", "refresh_token"]] | None - scopes: list[str] | None - - -class UserGitHubAuthConfig(TypedDict): - client_id: str - client_secret: str - scope: str | None - callback_path: str - auth_url: str - token_url: str - - -class UserAtlassianAuthConfig(TypedDict): - client_id: str - client_secret: str - scope: str | None - callback_path: str - auth_url: str - token_url: str - - -class UserSalesforceAuthConfig(TypedDict): - client_id: str - client_secret: str - scope: str | None - callback_path: str - auth_url: str - token_url: str - - -class UserKeycloakAuthConfig(TypedDict): - client_id: str - client_secret: str - realm: str - server_url: str - scope: str | None - callback_path: str - - -class UserGoogleAuthConfig(TypedDict): - client_id: str - client_secret: str - scope: str | None - callback_path: str - auth_url: str - token_url: str - - -class UserAuthPersistenceConfig(TypedDict, total=False): - type: Literal["sqlite"] | None - path: str | None - - -class UserAuthorizationConfig(TypedDict, total=False): - required_scopes: list[str] | None - - -class UserAuthConfig(TypedDict, total=False): - provider: Literal["none", "github", "atlassian", "salesforce", "keycloak", "google"] | None - clients: list[UserOAuthClientConfig] | None - github: UserGitHubAuthConfig | None - atlassian: UserAtlassianAuthConfig | None - salesforce: UserSalesforceAuthConfig | None - keycloak: UserKeycloakAuthConfig | None - google: UserGoogleAuthConfig | None - authorization: UserAuthorizationConfig | None - persistence: UserAuthPersistenceConfig | None - - -class UserModelConfig(TypedDict): - type: Literal["claude", "openai"] - api_key: str | None - base_url: str | None # For custom endpoints - timeout: int | None # Request timeout in seconds - max_retries: int | None - - -class UserModelsConfig(TypedDict, total=False): - default: str | None # Default model to use - models: dict[str, UserModelConfig] | None # Model configurations - - -class UserTracingConfig(TypedDict, total=False): - """Tracing-specific configuration.""" - - enabled: bool | None - console_export: bool | None # For debugging - print spans to console - - -class UserLoggingConfig(TypedDict, total=False): - """Application logging configuration.""" - - enabled: bool | None # Enable file logging - path: str | None # Path to log file - level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] | None # Log level - max_bytes: int | None # Max file size before rotation - backup_count: int | None # Number of backup files to keep - - -class UserMetricsConfig(TypedDict, total=False): - """Metrics-specific configuration.""" - - enabled: bool | None - export_interval: int | None # Export interval in seconds - - -class UserTelemetryConfig(TypedDict, total=False): - """Unified telemetry configuration treating all signals as equals.""" - - enabled: bool # Global enable/disable - endpoint: str | None # OTLP endpoint (e.g., http://localhost:4318) - headers: dict[str, str] | None # Additional headers for OTLP exporter - service_name: str | None # Override default service name - service_version: str | None # Service version - environment: str | None # Deployment environment - resource_attributes: dict[str, Any] | None # Additional resource attributes - - # Signal-specific configurations - tracing: UserTracingConfig | None # Tracing configuration - metrics: UserMetricsConfig | None # Metrics configuration - - -class UserProfileConfig(TypedDict, total=False): - secrets: list[UserSecretDefinition] | None - plugin: UserPluginConfig | None - auth: UserAuthConfig | None - telemetry: UserTelemetryConfig | None - - -class UserProjectConfig(TypedDict): - profiles: dict[str, UserProfileConfig] - - -class UserConfig(TypedDict): - mxcp: str - projects: dict[str, UserProjectConfig] - vault: UserVaultConfig | None - onepassword: UserOnePasswordConfig | None - transport: UserTransportConfig | None - models: UserModelsConfig | None - logging: UserLoggingConfig | None diff --git a/src/mxcp/server/core/config/models.py b/src/mxcp/server/core/config/models.py index bcbebd79..c320685b 100644 --- a/src/mxcp/server/core/config/models.py +++ b/src/mxcp/server/core/config/models.py @@ -2,8 +2,9 @@ import logging import os +from collections.abc import Mapping from pathlib import Path -from typing import Any, Literal, Mapping +from typing import Any, Literal from pydantic import ( BaseModel, @@ -229,3 +230,233 @@ def _serialize_extensions( # Ensure forward references are resolved for helper usages. SiteConfigModel.model_rebuild(_types_namespace={"Literal": Literal}) + + +class UserSecretDefinitionModel(BaseModel): + model_config = ConfigDict(extra="forbid", frozen=True) + + name: str + type: str + parameters: dict[str, Any] = Field(default_factory=dict) + + +class UserPluginConfigModel(BaseModel): + model_config = ConfigDict(extra="forbid", frozen=True) + + config: dict[str, dict[str, str]] = Field(default_factory=dict) + + +class UserVaultConfigModel(BaseModel): + model_config = ConfigDict(extra="forbid", frozen=True) + + enabled: bool = False + address: str | None = None + token_env: str | None = None + + +class UserOnePasswordConfigModel(BaseModel): + model_config = ConfigDict(extra="forbid", frozen=True) + + enabled: bool = False + token_env: str | None = None + + +class UserHttpTransportConfigModel(BaseModel): + model_config = ConfigDict(extra="forbid", frozen=True) + + port: int = 8000 + host: str = "localhost" + scheme: Literal["http", "https"] = "http" + base_url: str | None = None + trust_proxy: bool = False + stateless: bool = False + + +class UserTransportConfigModel(BaseModel): + model_config = ConfigDict(extra="forbid", frozen=True) + + provider: Literal["streamable-http", "sse", "stdio"] = "streamable-http" + http: UserHttpTransportConfigModel = Field(default_factory=UserHttpTransportConfigModel) + + +class UserOAuthClientModel(BaseModel): + model_config = ConfigDict(extra="forbid", frozen=True) + + client_id: str + name: str + client_secret: str | None = None + redirect_uris: list[str] = Field(default_factory=list) + grant_types: list[Literal["authorization_code", "refresh_token"]] = Field(default_factory=list) + scopes: list[str] = Field(default_factory=list) + + +class UserAuthPersistenceConfigModel(BaseModel): + model_config = ConfigDict(extra="forbid", frozen=True) + + type: Literal["sqlite"] = "sqlite" + path: str = Field(default_factory=lambda: str(Path.home() / ".mxcp" / "oauth.db")) + + +class UserAuthorizationConfigModel(BaseModel): + model_config = ConfigDict(extra="forbid", frozen=True) + + required_scopes: list[str] = Field(default_factory=list) + + +class UserGitHubAuthConfigModel(BaseModel): + model_config = ConfigDict(extra="forbid", frozen=True) + + client_id: str + client_secret: str + scope: str | None = None + callback_path: str + auth_url: str + token_url: str + + +class UserAtlassianAuthConfigModel(BaseModel): + model_config = ConfigDict(extra="forbid", frozen=True) + + client_id: str + client_secret: str + scope: str | None = None + callback_path: str + auth_url: str + token_url: str + + +class UserSalesforceAuthConfigModel(BaseModel): + model_config = ConfigDict(extra="forbid", frozen=True) + + client_id: str + client_secret: str + scope: str | None = None + callback_path: str + auth_url: str + token_url: str + + +class UserKeycloakAuthConfigModel(BaseModel): + model_config = ConfigDict(extra="forbid", frozen=True) + + client_id: str + client_secret: str + realm: str + server_url: str + scope: str | None = None + callback_path: str + + +class UserGoogleAuthConfigModel(BaseModel): + model_config = ConfigDict(extra="forbid", frozen=True) + + client_id: str + client_secret: str + scope: str | None = None + callback_path: str + auth_url: str + token_url: str + + +class UserAuthConfigModel(BaseModel): + model_config = ConfigDict(extra="forbid", frozen=True) + + provider: Literal["none", "github", "atlassian", "salesforce", "keycloak", "google"] = "none" + clients: list[UserOAuthClientModel] = Field(default_factory=list) + github: UserGitHubAuthConfigModel | None = None + atlassian: UserAtlassianAuthConfigModel | None = None + salesforce: UserSalesforceAuthConfigModel | None = None + keycloak: UserKeycloakAuthConfigModel | None = None + google: UserGoogleAuthConfigModel | None = None + authorization: UserAuthorizationConfigModel | None = None + persistence: UserAuthPersistenceConfigModel | None = None + + @model_validator(mode="after") + def _apply_defaults(self) -> UserAuthConfigModel: + provider = self.provider or "none" + persistence = self.persistence + if provider != "none" and persistence is None: + persistence = UserAuthPersistenceConfigModel() + return self.model_copy(update={"provider": provider, "persistence": persistence}) + + +class UserModelConfigModel(BaseModel): + model_config = ConfigDict(extra="forbid", frozen=True) + + type: Literal["claude", "openai"] + api_key: str | None = None + base_url: str | None = None + timeout: int | None = None + max_retries: int | None = None + + +class UserModelsConfigModel(BaseModel): + model_config = ConfigDict(extra="forbid", frozen=True) + + default: str | None = None + models: dict[str, UserModelConfigModel] = Field(default_factory=dict) + + +class UserTracingConfigModel(BaseModel): + model_config = ConfigDict(extra="forbid", frozen=True) + + enabled: bool = False + console_export: bool = False + + +class UserMetricsConfigModel(BaseModel): + model_config = ConfigDict(extra="forbid", frozen=True) + + enabled: bool = False + export_interval: int = 60 + + +class UserTelemetryConfigModel(BaseModel): + model_config = ConfigDict(extra="forbid", frozen=True) + + enabled: bool = False + endpoint: str | None = None + headers: dict[str, str] | None = None + service_name: str | None = None + service_version: str | None = None + environment: str | None = None + resource_attributes: dict[str, Any] | None = None + tracing: UserTracingConfigModel | None = None + metrics: UserMetricsConfigModel | None = None + + +class UserLoggingConfigModel(BaseModel): + model_config = ConfigDict(extra="forbid", frozen=True) + + enabled: bool = True + path: str | None = None + level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] = "WARNING" + max_bytes: int = 10_485_760 + backup_count: int = 5 + + +class UserProfileConfigModel(BaseModel): + model_config = ConfigDict(extra="forbid", frozen=True) + + secrets: list[UserSecretDefinitionModel] = Field(default_factory=list) + plugin: UserPluginConfigModel = Field(default_factory=UserPluginConfigModel) + auth: UserAuthConfigModel = Field(default_factory=UserAuthConfigModel) + telemetry: UserTelemetryConfigModel | None = None + + +class UserProjectConfigModel(BaseModel): + model_config = ConfigDict(extra="forbid", frozen=True) + + profiles: dict[str, UserProfileConfigModel] = Field(default_factory=dict) + + +class UserConfigModel(BaseModel): + model_config = ConfigDict(extra="forbid", frozen=True) + + mxcp: Literal[1] = 1 + projects: dict[str, UserProjectConfigModel] = Field(default_factory=dict) + vault: UserVaultConfigModel | None = None + onepassword: UserOnePasswordConfigModel | None = None + transport: UserTransportConfigModel = Field(default_factory=UserTransportConfigModel) + models: UserModelsConfigModel | None = None + logging: UserLoggingConfigModel = Field(default_factory=UserLoggingConfigModel) diff --git a/src/mxcp/server/core/config/parsers.py b/src/mxcp/server/core/config/parsers.py index 40546c1a..0acb061d 100644 --- a/src/mxcp/server/core/config/parsers.py +++ b/src/mxcp/server/core/config/parsers.py @@ -9,7 +9,7 @@ from collections.abc import Generator from contextlib import contextmanager from pathlib import Path -from typing import Any, cast +from typing import Any from mxcp.sdk.duckdb import ( DatabaseConfig, @@ -23,15 +23,14 @@ reset_execution_context, set_execution_context, ) -from mxcp.server.core.config._types import UserConfig -from mxcp.server.core.config.models import SiteConfigModel +from mxcp.server.core.config.models import SiteConfigModel, UserConfigModel logger = logging.getLogger(__name__) def create_duckdb_session_config( site_config: SiteConfigModel, - user_config: UserConfig, + user_config: UserConfigModel, profile_name: str, readonly: bool = False, ) -> tuple[DatabaseConfig, list[PluginDefinition], PluginConfig, list[SecretDefinition]]: @@ -72,12 +71,9 @@ def create_duckdb_session_config( ] # Get plugin configuration from user config - user_projects = user_config.get("projects") or {} - user_project = cast(dict[str, Any], user_projects.get(project_name) or {}) - user_profiles = user_project.get("profiles") or {} - user_profile = user_profiles.get(profile_name) or {} - user_plugin_section = user_profile.get("plugin") or {} - user_plugin_configs = user_plugin_section.get("config") or {} + user_project = user_config.projects.get(project_name) + user_profile = user_project.profiles.get(profile_name) if user_project else None + user_plugin_configs = user_profile.plugin.config if user_profile else {} # Get plugins path from site config plugins_path = site_config.paths.plugins @@ -86,14 +82,15 @@ def create_duckdb_session_config( # Get secrets from user config profile secrets = [] - user_secrets = user_profile.get("secrets") or [] + user_secrets = user_profile.secrets if user_profile else [] for secret in user_secrets: - secret_name = secret.get("name") - secret_type = secret.get("type") - secret_params = secret.get("parameters") - if secret_name and secret_type and secret_params: + if secret.parameters: secrets.append( - SecretDefinition(name=secret_name, type=secret_type, parameters=secret_params) + SecretDefinition( + name=secret.name, + type=secret.type, + parameters=secret.parameters, + ) ) return database_config, plugins, plugin_config, secrets @@ -101,7 +98,7 @@ def create_duckdb_session_config( @contextmanager def execution_context_for_init_hooks( - user_config: UserConfig | None = None, + user_config: UserConfigModel | None = None, site_config: SiteConfigModel | None = None, duckdb_runtime: Any | None = None, ) -> Generator[ExecutionContext | None, None, None]: @@ -113,8 +110,8 @@ def execution_context_for_init_hooks( cleans it up when done. Args: - user_config: UserConfig object containing user configuration for runtime context - site_config: SiteConfig object containing site configuration for runtime context + user_config: UserConfigModel instance containing runtime configuration + site_config: SiteConfigModel instance containing site configuration duckdb_runtime: DuckDB runtime instance for database access Yields: diff --git a/src/mxcp/server/core/config/schema_utils.py b/src/mxcp/server/core/config/schema_utils.py index 01d461fc..16c0e502 100644 --- a/src/mxcp/server/core/config/schema_utils.py +++ b/src/mxcp/server/core/config/schema_utils.py @@ -1,9 +1,9 @@ -"""Utilities for working with MXCP JSON schemas.""" +"""Utilities for working with MXCP configuration schema metadata.""" -import json from collections.abc import Sequence from functools import lru_cache -from pathlib import Path + +from mxcp.server.core.config.models import UserConfigModel @lru_cache(maxsize=1) @@ -21,17 +21,9 @@ def get_user_config_top_level_keys() -> set[str]: Note: This is cached since the schema doesn't change at runtime. """ - schema_path = Path(__file__).parent.parent.parent / "schemas" / "mxcp-config-schema-1.json" - with open(schema_path) as f: - schema = json.load(f) - - # Get all top-level properties except 'projects' (which needs special handling) - all_props = set(schema.get("properties", {}).keys()) - - # Remove 'projects' - it's handled specially in selective interpolation - all_props.discard("projects") - - return all_props + fields = set(UserConfigModel.model_fields.keys()) + fields.discard("projects") + return fields def should_interpolate_path( diff --git a/src/mxcp/server/core/config/site_config.py b/src/mxcp/server/core/config/site_config.py index 989fec5a..41792661 100644 --- a/src/mxcp/server/core/config/site_config.py +++ b/src/mxcp/server/core/config/site_config.py @@ -2,13 +2,15 @@ import logging from pathlib import Path -from typing import Any import yaml from pydantic import ValidationError -from mxcp.server.core.config._types import UserConfig, UserProfileConfig -from mxcp.server.core.config.models import SiteConfigModel +from mxcp.server.core.config.models import ( + SiteConfigModel, + UserConfigModel, + UserProfileConfigModel, +) from mxcp.server.core.refs.migration import check_and_migrate_legacy_version logger = logging.getLogger(__name__) @@ -68,8 +70,8 @@ def load_site_config(repo_path: Path | None = None) -> SiteConfigModel: def get_active_profile( - user_config: UserConfig, site_config: SiteConfigModel, profile: str | None = None -) -> UserProfileConfig: + user_config: UserConfigModel, site_config: SiteConfigModel, profile: str | None = None +) -> UserProfileConfigModel: """Get the active profile from the user config based on site configuration. Args: @@ -83,11 +85,12 @@ def get_active_profile( project_name = site_config.project profile_name = profile or site_config.profile - if project_name not in user_config["projects"]: + project = user_config.projects.get(project_name) + if not project: raise ValueError(f"Project '{project_name}' not found in user config") - project = user_config["projects"][project_name] - if profile_name not in project["profiles"]: + profile_config = project.profiles.get(profile_name) + if not profile_config: raise ValueError(f"Profile '{profile_name}' not found in project '{project_name}'") - return project["profiles"][profile_name] + return profile_config diff --git a/src/mxcp/server/core/config/user_config.py b/src/mxcp/server/core/config/user_config.py index 3f4c50da..a91f2a8d 100644 --- a/src/mxcp/server/core/config/user_config.py +++ b/src/mxcp/server/core/config/user_config.py @@ -1,13 +1,11 @@ -import json import os from pathlib import Path -from typing import Any, cast +from typing import Any import yaml -from jsonschema import ValidationError, validate +from pydantic import ValidationError -from mxcp.server.core.config._types import UserConfig -from mxcp.server.core.config.models import SiteConfigModel +from mxcp.server.core.config.models import SiteConfigModel, UserConfigModel from mxcp.server.core.refs.migration import check_and_migrate_legacy_version from mxcp.server.core.refs.resolver import interpolate_all, interpolate_selective @@ -16,80 +14,8 @@ __all__ = ["load_user_config"] -def _apply_defaults(config: dict[str, Any]) -> UserConfig: - """Apply default values to the user config""" - # Create a copy to avoid modifying the input - config = config.copy() - - # Apply transport defaults - if "transport" not in config: - config["transport"] = {} - - transport = config["transport"] - if "provider" not in transport: - transport["provider"] = "streamable-http" - - if "http" not in transport: - transport["http"] = {} - - http_config = transport["http"] - if "port" not in http_config: - http_config["port"] = 8000 - if "host" not in http_config: - http_config["host"] = "localhost" - if "stateless" not in http_config: - http_config["stateless"] = False - - # Apply logging defaults (top-level, not per profile) - if "logging" not in config: - config["logging"] = {} - - logging_config = config["logging"] - if "enabled" not in logging_config: - logging_config["enabled"] = True - if "level" not in logging_config: - logging_config["level"] = "WARNING" - if "max_bytes" not in logging_config: - logging_config["max_bytes"] = 10485760 # 10MB - if "backup_count" not in logging_config: - logging_config["backup_count"] = 5 - - # Ensure each profile has at least empty secrets, plugin, and auth config - for project in config.get("projects", {}).values(): - for profile in project.get("profiles", {}).values(): - if profile is None: - profile = {} - if "secrets" not in profile: - profile["secrets"] = [] - if "plugin" not in profile: - profile["plugin"] = {"config": {}} - elif "config" not in profile["plugin"]: - profile["plugin"]["config"] = {} - if "auth" not in profile or profile["auth"] is None: - profile["auth"] = {"provider": "none"} - else: - # Ensure persistence defaults are set if auth is enabled and provider is not 'none' - auth = profile["auth"] - if auth.get("provider", "none") != "none": - if "persistence" not in auth: - # Add default persistence configuration - auth["persistence"] = { - "type": "sqlite", - "path": str(Path.home() / ".mxcp" / "oauth.db"), - } - else: - # Apply defaults to existing persistence config - persistence = auth["persistence"] - if "type" not in persistence: - persistence["type"] = "sqlite" - if "path" not in persistence: - persistence["path"] = str(Path.home() / ".mxcp" / "oauth.db") - - return cast(UserConfig, config) - - -def _generate_default_config(site_config: SiteConfigModel) -> UserConfig: - """Generate a default user config based on site config""" +def _generate_default_config(site_config: SiteConfigModel) -> UserConfigModel: + """Generate a default user config based on site config.""" project_name = site_config.project profile_name = site_config.profile @@ -99,7 +25,14 @@ def _generate_default_config(site_config: SiteConfigModel) -> UserConfig: project_name: {"profiles": {profile_name: {"secrets": [], "plugin": {"config": {}}}}} }, } - return cast(UserConfig, config) + return UserConfigModel.model_validate(config) + + +def _ensure_project_structure(config: dict[str, Any], project_name: str, profile_name: str) -> None: + projects = config.setdefault("projects", {}) + project_config = projects.setdefault(project_name, {}) + profiles = project_config.setdefault("profiles", {}) + profiles.setdefault(profile_name, {}) def load_user_config( @@ -107,105 +40,40 @@ def load_user_config( active_profile: str | None = None, generate_default: bool = True, resolve_refs: bool = True, -) -> UserConfig: - """Load the user configuration from ~/.mxcp/config.yml or MXCP_CONFIG env var. - - If the config file doesn't exist and MXCP_CONFIG is not set, generates a default config - based on the site config if generate_default is True. - - The configuration supports multiple ways to inject values: - - 1. Environment variable interpolation using ${ENV_VAR} syntax: - database: ${DB_NAME} - password: ${DB_PASSWORD} - - 2. Vault integration using vault:// URLs: - password: vault://secret/db#password - - 3. 1Password integration using op:// URLs: - password: op://vault/item/field - totp: op://vault/item/field?attribute=otp - - 4. File path references using file:// URLs: - api_key: file:///path/to/api_key.txt - ssl_cert: file://certs/server.crt - - Args: - site_config: The site configuration loaded from mxcp-site.yml - active_profile: The active profile name. If provided, only resolves references for - this profile (not all profiles). This prevents errors when env vars - for inactive profiles are not set. Falls back to site_config["profile"]. - generate_default: Whether to generate a default config if the file doesn't exist - resolve_refs: Whether to resolve external references (vault://, op://, file://, ${ENV_VAR}). - Set to False to get the raw template configuration. - - Returns: - The validated user configuration (with resolved values if resolve_refs=True) - - Raises: - FileNotFoundError: If the config file doesn't exist and generate_default is False - ValueError: If an environment variable is referenced but not set, or URL resolution fails - """ +) -> UserConfigModel: + """Load the user configuration from ~/.mxcp/config.yml or MXCP_CONFIG env var.""" path = Path(os.environ.get("MXCP_CONFIG", Path.home() / ".mxcp" / "config.yml")) + project_name = site_config.project + profile_name = active_profile or site_config.profile if not path.exists(): - # If MXCP_CONFIG is not set, generate a default config based on site config if "MXCP_CONFIG" not in os.environ and generate_default: - config = _generate_default_config(site_config) + return _generate_default_config(site_config) + raise FileNotFoundError(f"MXCP user config not found at {path}") + + with open(path) as f: + config_data = yaml.safe_load(f) or {} + + if not isinstance(config_data, dict): + raise ValueError("MXCP user config must be a mapping") + + # Check for legacy version format and provide migration guidance (stops execution) + check_and_migrate_legacy_version(config_data, "user", str(path)) + + # Interpolate environment variables and vault URLs in the config if requested + if resolve_refs: + vault_config = config_data.get("vault") + op_config = config_data.get("onepassword") + if active_profile is not None: + config_data = interpolate_selective( + config_data, project_name, profile_name, vault_config, op_config + ) else: - raise FileNotFoundError(f"MXCP user config not found at {path}") - else: - with open(path) as f: - config = yaml.safe_load(f) - - # Check for legacy version format and provide migration guidance (stops execution) - check_and_migrate_legacy_version(config, "user", str(path)) - - # Determine which profile to use for selective interpolation - project_name = site_config.project - profile_name = active_profile or site_config.profile - - # Interpolate environment variables and vault URLs in the config if requested - if resolve_refs: - vault_config = config.get("vault") - op_config = config.get("onepassword") - - # Use selective interpolation if active_profile is provided - # This prevents errors when env vars for inactive profiles are not set - if active_profile is not None: - config = interpolate_selective( - config, project_name, profile_name, vault_config, op_config - ) - else: - # Fall back to full interpolation if no active profile specified - config = interpolate_all(config, vault_config, op_config) - - if "projects" not in config: - config["projects"] = {} - - if project_name not in config["projects"]: - config["projects"][project_name] = {"profiles": {}} - - if "profiles" not in config["projects"][project_name]: - config["projects"][project_name]["profiles"] = {} - - if profile_name not in config["projects"][project_name]["profiles"]: - config["projects"][project_name]["profiles"][profile_name] = { - "secrets": [], - "plugin": {"config": {}}, - } - - # Apply defaults before validation - validated_config = _apply_defaults(cast(dict[str, Any], config)) - - # Load and apply JSON Schema validation - schema_path = Path(__file__).parent.parent.parent / "schemas" / "mxcp-config-schema-1.json" - with open(schema_path) as schema_file: - schema = json.load(schema_file) + config_data = interpolate_all(config_data, vault_config, op_config) - try: - validate(instance=validated_config, schema=schema) - except ValidationError as e: - raise ValueError(f"Invalid user config: {e.message}") from e + _ensure_project_structure(config_data, project_name, profile_name) - return validated_config + try: + return UserConfigModel.model_validate(config_data) + except ValidationError as exc: + raise ValueError(f"Invalid user config: {exc}") from exc diff --git a/src/mxcp/server/core/refs/resolver.py b/src/mxcp/server/core/refs/resolver.py index fd89f71b..87372825 100644 --- a/src/mxcp/server/core/refs/resolver.py +++ b/src/mxcp/server/core/refs/resolver.py @@ -11,10 +11,7 @@ import os import re from pathlib import Path -from typing import TYPE_CHECKING, Any, cast - -if TYPE_CHECKING: - from mxcp.server.core.config._types import UserConfig +from typing import Any, cast from mxcp.server.core.config.schema_utils import should_interpolate_path @@ -344,12 +341,12 @@ def interpolate_all( def interpolate_selective( - config: "UserConfig", + config: dict[str, Any], project_name: str, profile_name: str, vault_config: dict[str, Any] | None = None, op_config: dict[str, Any] | None = None, -) -> "UserConfig": +) -> dict[str, Any]: """Selectively interpolate external references only for active profile and top-level config. This avoids resolving environment variables for inactive profiles, preventing @@ -389,7 +386,7 @@ def _interpolate_recursive(value: Any, path: list[str | int]) -> Any: # Start recursion with root path result = _interpolate_recursive(config, ["user"]) - return cast("UserConfig", result) + return cast(dict[str, Any], result) def find_references( diff --git a/src/mxcp/server/core/telemetry.py b/src/mxcp/server/core/telemetry.py index 0327bc12..cfe7cf7c 100644 --- a/src/mxcp/server/core/telemetry.py +++ b/src/mxcp/server/core/telemetry.py @@ -6,7 +6,7 @@ import logging import os -from typing import Any, cast +from typing import Any from mxcp.sdk.core import PACKAGE_NAME, PACKAGE_VERSION from mxcp.sdk.telemetry import ( @@ -15,13 +15,16 @@ get_current_trace_id, shutdown_telemetry, ) -from mxcp.server.core.config._types import UserConfig, UserTelemetryConfig +from mxcp.server.core.config.models import ( + UserConfigModel, + UserTelemetryConfigModel, +) logger = logging.getLogger(__name__) def configure_telemetry_from_config( - user_config: UserConfig, + user_config: UserConfigModel, project: str, profile: str, ) -> bool: @@ -47,30 +50,25 @@ def configure_telemetry_from_config( configure_all(enabled=False) return False - # Create unified SDK telemetry config from user config - # Build a proper dict from the TypedDict + data = telemetry_config.model_dump(mode="python", exclude_none=True) config_dict: dict[str, Any] = { - "enabled": telemetry_config.get("enabled", False), - "endpoint": telemetry_config.get("endpoint"), - "headers": telemetry_config.get("headers"), - "service_name": telemetry_config.get("service_name", PACKAGE_NAME), - "service_version": telemetry_config.get("service_version", PACKAGE_VERSION), - "environment": telemetry_config.get("environment", profile), + "enabled": data.get("enabled", False), + "endpoint": data.get("endpoint"), + "headers": data.get("headers"), + "service_name": data.get("service_name", PACKAGE_NAME), + "service_version": data.get("service_version", PACKAGE_VERSION), + "environment": data.get("environment", profile), "resource_attributes": { "mxcp.project": project, "mxcp.profile": profile, + **(data.get("resource_attributes") or {}), }, } - # Add any additional resource attributes from config - if telemetry_config.get("resource_attributes"): - config_dict["resource_attributes"].update(telemetry_config["resource_attributes"]) - - # Handle signal-specific configs - if "tracing" in telemetry_config: - config_dict["tracing"] = telemetry_config["tracing"] - if "metrics" in telemetry_config: - config_dict["metrics"] = telemetry_config["metrics"] + if tracing := data.get("tracing"): + config_dict["tracing"] = tracing + if metrics := data.get("metrics"): + config_dict["metrics"] = metrics # Create config object config = TelemetryConfig.from_dict(config_dict) @@ -206,8 +204,8 @@ def _get_telemetry_from_env() -> dict[str, Any] | None: def _merge_telemetry_configs( - file_config: UserTelemetryConfig | None, env_config: dict[str, Any] | None -) -> UserTelemetryConfig | None: + file_config: UserTelemetryConfigModel | None, env_config: dict[str, Any] | None +) -> UserTelemetryConfigModel | None: """Merge telemetry configuration from file and environment. Environment variables take precedence over file configuration. @@ -222,8 +220,9 @@ def _merge_telemetry_configs( if not file_config and not env_config: return None - # Start with file config or empty dict - merged: dict[str, Any] = dict(file_config) if file_config else {} + merged: dict[str, Any] = ( + file_config.model_dump(mode="python", exclude_none=True) if file_config else {} + ) # Apply env overrides if env_config: @@ -237,12 +236,15 @@ def _merge_telemetry_configs( # Override top-level keys merged[key] = value - return cast(UserTelemetryConfig, merged) + if not merged: + return None + + return UserTelemetryConfigModel.model_validate(merged) def _get_telemetry_config( - user_config: UserConfig, project: str, profile: str -) -> UserTelemetryConfig | None: + user_config: UserConfigModel, project: str, profile: str +) -> UserTelemetryConfigModel | None: """Get telemetry configuration for a specific profile. Merges configuration from user config file and environment variables, @@ -257,10 +259,9 @@ def _get_telemetry_config( Telemetry configuration dict or None if not found """ # Get config from file - try: - file_config = user_config["projects"][project]["profiles"][profile].get("telemetry") - except KeyError: - file_config = None + project_config = user_config.projects.get(project) + profile_config = project_config.profiles.get(profile) if project_config else None + file_config = profile_config.telemetry if profile_config else None # Get config from environment env_config = _get_telemetry_from_env() diff --git a/src/mxcp/server/executor/engine.py b/src/mxcp/server/executor/engine.py index b6298625..0b8fa1c1 100644 --- a/src/mxcp/server/executor/engine.py +++ b/src/mxcp/server/executor/engine.py @@ -51,8 +51,7 @@ from mxcp.sdk.duckdb import DuckDBRuntime from mxcp.sdk.executor import ExecutionEngine from mxcp.sdk.executor.plugins import DuckDBExecutor, PythonExecutor -from mxcp.server.core.config._types import UserConfig -from mxcp.server.core.config.models import SiteConfigModel +from mxcp.server.core.config.models import SiteConfigModel, UserConfigModel from mxcp.server.core.config.parsers import ( create_duckdb_session_config, execution_context_for_init_hooks, @@ -93,7 +92,7 @@ def shutdown(self) -> None: def create_runtime_environment( - user_config: UserConfig, + user_config: UserConfigModel, site_config: SiteConfigModel, profile: str | None = None, repo_root: Path | None = None, diff --git a/src/mxcp/server/executor/runners/endpoint.py b/src/mxcp/server/executor/runners/endpoint.py index 47ee98e6..9623d0bc 100644 --- a/src/mxcp/server/executor/runners/endpoint.py +++ b/src/mxcp/server/executor/runners/endpoint.py @@ -18,8 +18,7 @@ from mxcp.sdk.executor import ExecutionContext from mxcp.sdk.executor.interfaces import ExecutionEngine from mxcp.sdk.validator import TypeValidator -from mxcp.server.core.config._types import UserConfig -from mxcp.server.core.config.models import SiteConfigModel +from mxcp.server.core.config.models import SiteConfigModel, UserConfigModel from mxcp.server.definitions.endpoints._types import ( EndpointDefinition, PromptDefinition, @@ -82,7 +81,7 @@ async def execute_code_with_engine( params: dict[str, Any], execution_engine: ExecutionEngine, skip_output_validation: bool, - user_config: UserConfig, + user_config: UserConfigModel, site_config: SiteConfigModel, user_context: UserContext | None = None, server_ref: Optional["RAWMCP"] = None, diff --git a/src/mxcp/server/executor/runners/test.py b/src/mxcp/server/executor/runners/test.py index 6fd0f791..cec8d477 100644 --- a/src/mxcp/server/executor/runners/test.py +++ b/src/mxcp/server/executor/runners/test.py @@ -14,8 +14,7 @@ from mxcp.sdk.auth import UserContext from mxcp.sdk.executor import ExecutionEngine -from mxcp.server.core.config._types import UserConfig -from mxcp.server.core.config.models import SiteConfigModel +from mxcp.server.core.config.models import SiteConfigModel, UserConfigModel from mxcp.server.definitions.endpoints._types import EndpointDefinition, TestDefinition from mxcp.server.definitions.endpoints.loader import EndpointLoader from mxcp.server.services.endpoints import execute_endpoint_with_engine @@ -28,7 +27,7 @@ class TestRunner: def __init__( self, - user_config: UserConfig, + user_config: UserConfigModel, site_config: SiteConfigModel, execution_engine: ExecutionEngine, ): diff --git a/src/mxcp/server/interfaces/cli/dbt.py b/src/mxcp/server/interfaces/cli/dbt.py index ba45e6ab..a425268c 100644 --- a/src/mxcp/server/interfaces/cli/dbt.py +++ b/src/mxcp/server/interfaces/cli/dbt.py @@ -1,7 +1,6 @@ import os import subprocess import sys -from typing import Any import click @@ -190,18 +189,15 @@ def dbt_wrapper(ctx: click.Context, profile: str | None, debug: bool) -> None: click.echo(f" • Command: {click.style(f'dbt {dbt_command}', fg='green')}") # Get secrets from user config - project_config: Any = user_config.get("projects", {}).get(project, {}) - profile_config = project_config.get("profiles", {}).get(active_profile, {}) - secrets = profile_config.get("secrets", []) + project_config = user_config.projects.get(project) + profile_config = project_config.profiles.get(active_profile) if project_config else None + secrets = profile_config.secrets if profile_config else [] # Prepare environment env = os.environ.copy() - for secret in secrets or []: - if not isinstance(secret, dict) or "name" not in secret or "parameters" not in secret: - continue - - secret_name = secret["name"] - parameters = secret["parameters"] + for secret in secrets: + secret_name = secret.name + parameters = secret.parameters or {} # Handle both string and object parameters for param_name, param_value in parameters.items(): diff --git a/src/mxcp/server/interfaces/cli/serve.py b/src/mxcp/server/interfaces/cli/serve.py index 42558fac..c63040bd 100644 --- a/src/mxcp/server/interfaces/cli/serve.py +++ b/src/mxcp/server/interfaces/cli/serve.py @@ -94,13 +94,7 @@ def serve( user_config = load_user_config(site_config, active_profile=active_profile) # Determine effective transport (CLI flag > user config > default) - effective_transport = transport - if not effective_transport: - transport_config = user_config.get("transport") - if transport_config: - effective_transport = transport_config.get("provider", "streamable-http") - else: - effective_transport = "streamable-http" + effective_transport = transport or user_config.transport.provider or "streamable-http" # Configure logging ONCE with all settings configure_logging_from_config( diff --git a/src/mxcp/server/interfaces/cli/utils.py b/src/mxcp/server/interfaces/cli/utils.py index a7c7d010..c7ea481c 100644 --- a/src/mxcp/server/interfaces/cli/utils.py +++ b/src/mxcp/server/interfaces/cli/utils.py @@ -10,8 +10,7 @@ import click -from mxcp.server.core.config._types import UserConfig -from mxcp.server.core.config.models import SiteConfigModel +from mxcp.server.core.config.models import SiteConfigModel, UserConfigModel def get_env_flag(env_var: str, default: bool = False) -> bool: @@ -235,7 +234,7 @@ def output_error(error: Exception, json_output: bool = False, debug: bool = Fals def configure_logging_from_config( site_config: SiteConfigModel, - user_config: UserConfig, + user_config: UserConfigModel, debug: bool = False, transport: str | None = None, ) -> None: @@ -254,20 +253,20 @@ def configure_logging_from_config( If "stdio", stderr logging is disabled to avoid protocol corruption """ # Get top-level logging config - logging_config = user_config.get("logging") + logging_config = user_config.logging # If logging config is not set or disabled, use basic logging - if not logging_config or not logging_config.get("enabled", True): + if not logging_config.enabled: # Logging disabled - only configure basic stderr (unless stdio) configure_logging(debug=debug, transport=transport) return # Get logging settings - log_path_str = logging_config.get("path") + log_path_str = logging_config.path log_file = Path(log_path_str) if log_path_str else None - log_level = logging_config.get("level", "WARNING") - max_bytes = logging_config.get("max_bytes") or 10 * 1024 * 1024 - backup_count = logging_config.get("backup_count") or 5 + log_level = logging_config.level + max_bytes = logging_config.max_bytes + backup_count = logging_config.backup_count # Configure logging with all settings configure_logging( diff --git a/src/mxcp/server/interfaces/server/mcp.py b/src/mxcp/server/interfaces/server/mcp.py index 936c1140..b67d4731 100644 --- a/src/mxcp/server/interfaces/server/mcp.py +++ b/src/mxcp/server/interfaces/server/mcp.py @@ -25,16 +25,9 @@ from starlette.responses import JSONResponse from mxcp.sdk.audit import AuditLogger -from mxcp.sdk.auth import ExternalOAuthHandler, GeneralOAuthAuthorizationServer -from mxcp.sdk.auth._types import HttpTransportConfig +from mxcp.sdk.auth import GeneralOAuthAuthorizationServer from mxcp.sdk.auth.context import get_user_context from mxcp.sdk.auth.middleware import AuthenticationMiddleware -from mxcp.sdk.auth.providers.atlassian import AtlassianOAuthHandler -from mxcp.sdk.auth.providers.github import GitHubOAuthHandler -from mxcp.sdk.auth.providers.google import GoogleOAuthHandler -from mxcp.sdk.auth.providers.keycloak import KeycloakOAuthHandler -from mxcp.sdk.auth.providers.salesforce import SalesforceOAuthHandler -from mxcp.sdk.auth.url_utils import URLBuilder from mxcp.sdk.core import PACKAGE_VERSION from mxcp.sdk.executor import ExecutionContext from mxcp.sdk.telemetry import ( @@ -45,12 +38,12 @@ traced_operation, ) from mxcp.server.admin import AdminAPIRunner -from mxcp.server.core.config._types import ( - UserAuthConfig, - UserConfig, - UserHttpTransportConfig, +from mxcp.server.core.auth.helpers import ( + create_oauth_handler, + create_url_builder, + translate_auth_config, ) -from mxcp.server.core.config.models import SiteConfigModel +from mxcp.server.core.config.models import SiteConfigModel, UserConfigModel from mxcp.server.core.config.site_config import get_active_profile, load_site_config from mxcp.server.core.config.user_config import load_user_config from mxcp.server.core.refs.external import ExternalRefTracker @@ -130,123 +123,14 @@ async def wrapper(self: Any, *args: Any, **kwargs: Any) -> Any: return cast(T, wrapper) -def translate_transport_config( - user_transport_config: UserHttpTransportConfig | None, -) -> HttpTransportConfig | None: - """Translate user HTTP transport config to SDK transport config. - - Args: - user_transport_config: User configuration transport section - - Returns: - SDK-compatible HTTP transport configuration - """ - if not user_transport_config: - return None - - return { - "port": user_transport_config.get("port"), - "host": user_transport_config.get("host"), - "scheme": user_transport_config.get("scheme"), - "base_url": user_transport_config.get("base_url"), - "trust_proxy": user_transport_config.get("trust_proxy"), - "stateless": user_transport_config.get("stateless"), - } - - -def create_oauth_handler( - user_auth_config: UserAuthConfig, - host: str = "localhost", - port: int = 8000, - user_config: UserConfig | None = None, -) -> ExternalOAuthHandler | None: - """Create an OAuth handler from user configuration. - - This helper translates user config to SDK types and instantiates the appropriate handler. - - Args: - user_auth_config: User authentication configuration - host: The server host to use for callback URLs - port: The server port to use for callback URLs - user_config: Full user configuration for transport settings (optional) - - Returns: - OAuth handler instance or None if provider is 'none' - """ - provider = user_auth_config.get("provider", "none") - - if provider == "none": - return None - - # Extract transport config if available - transport_config = None - if user_config and "transport" in user_config: - transport = user_config["transport"] - user_transport = transport.get("http") if transport else None - transport_config = translate_transport_config(user_transport) - - if provider == "github": - - github_config = user_auth_config.get("github") - if not github_config: - raise ValueError("GitHub provider selected but no GitHub configuration found") - return GitHubOAuthHandler(github_config, transport_config, host=host, port=port) - - elif provider == "atlassian": - - atlassian_config = user_auth_config.get("atlassian") - if not atlassian_config: - raise ValueError("Atlassian provider selected but no Atlassian configuration found") - return AtlassianOAuthHandler(atlassian_config, transport_config, host=host, port=port) - - elif provider == "salesforce": - - salesforce_config = user_auth_config.get("salesforce") - if not salesforce_config: - raise ValueError("Salesforce provider selected but no Salesforce configuration found") - return SalesforceOAuthHandler(salesforce_config, transport_config, host=host, port=port) - - elif provider == "keycloak": - - keycloak_config = user_auth_config.get("keycloak") - if not keycloak_config: - raise ValueError("Keycloak provider selected but no Keycloak configuration found") - return KeycloakOAuthHandler(keycloak_config, transport_config, host=host, port=port) - - elif provider == "google": - - google_config = user_auth_config.get("google") - if not google_config: - raise ValueError("Google provider selected but no Google configuration found") - return GoogleOAuthHandler(google_config, transport_config, host=host, port=port) - - else: - raise ValueError(f"Unsupported auth provider: {provider}") - - -def create_url_builder(user_config: UserConfig) -> URLBuilder: - """Create a URL builder from user configuration. - - Args: - user_config: User configuration dictionary - - Returns: - Configured URLBuilder instance - """ - transport = user_config.get("transport", {}) - user_transport_config = transport.get("http", {}) if transport else {} - transport_config = translate_transport_config(user_transport_config) - return URLBuilder(transport_config) - - class RAWMCP: """MXCP MCP Server implementation that bridges MXCP endpoints with MCP protocol.""" # Type annotations for instance attributes site_config: SiteConfigModel - user_config: UserConfig + user_config: UserConfigModel _site_config_template: SiteConfigModel - _user_config_template: UserConfig + _user_config_template: UserConfigModel host: str port: int profile_name: str @@ -389,7 +273,7 @@ def _resolve_and_apply_configs(self) -> None: config. This prevents errors from undefined environment variables in inactive profiles. """ site_template_dict = self._site_config_template.model_dump(mode="python") - user_template_dict = cast(dict[str, Any], self._user_config_template) + user_template_dict = self._user_config_template.model_dump(mode="python") # Check if configs contain unresolved references config_str = json.dumps(site_template_dict) + json.dumps(user_template_dict) @@ -418,7 +302,7 @@ def _resolve_and_apply_configs(self) -> None: self.site_config = SiteConfigModel.model_validate( resolved_site, context={"repo_root": self.site_config_path} ) - self.user_config = cast(UserConfig, resolved_user) + self.user_config = UserConfigModel.model_validate(resolved_user) else: # Already resolved self.site_config = self._site_config_template @@ -432,27 +316,18 @@ def _resolve_and_apply_configs(self) -> None: ) # Extract transport config with overrides - transport_config = self.user_config.get("transport") or {} + transport_model = self.user_config.transport + http_config = transport_model.http + self.transport = str( - self._cli_overrides["transport"] - or (transport_config.get("provider") if transport_config else "streamable-http") - or "streamable-http" + self._cli_overrides["transport"] or transport_model.provider or "streamable-http" ) - http_config = transport_config.get("http") if transport_config else {} - self.host = str( - self._cli_overrides["host"] - or (http_config.get("host") if http_config else "localhost") - or "localhost" - ) - port_value = ( - self._cli_overrides["port"] - or (http_config.get("port") if http_config else 8000) - or 8000 - ) + self.host = str(self._cli_overrides["host"] or http_config.host or "localhost") + port_value = self._cli_overrides["port"] or http_config.port or 8000 self.port = int(port_value) - config_stateless = http_config.get("stateless", False) if http_config else False + config_stateless = http_config.stateless or False self.stateless_http = ( self._cli_overrides["stateless_http"] if self._cli_overrides["stateless_http"] is not None @@ -524,7 +399,7 @@ def _load_endpoints(self) -> None: def _initialize_oauth(self) -> None: """Initialize OAuth authentication using profile-specific auth config.""" - auth_config = self.active_profile.get("auth", {}) + auth_config = self.active_profile.auth self.oauth_handler = create_oauth_handler( auth_config, host=self.host, @@ -535,8 +410,10 @@ def _initialize_oauth(self) -> None: self.auth_settings = None if self.oauth_handler: + auth_config_dict = translate_auth_config(auth_config) + user_config_dict = self.user_config.model_dump(mode="python") self.oauth_server = GeneralOAuthAuthorizationServer( - self.oauth_handler, auth_config, cast(dict[str, Any], self.user_config) + self.oauth_handler, auth_config_dict, user_config_dict ) # Use URL builder for OAuth endpoints @@ -544,8 +421,8 @@ def _initialize_oauth(self) -> None: base_url = url_builder.get_base_url(host=self.host, port=self.port) # Get authorization configuration - auth_authorization = auth_config.get("authorization", {}) - required_scopes = auth_authorization.get("required_scopes", []) + auth_authorization = auth_config.authorization + required_scopes = auth_authorization.required_scopes if auth_authorization else [] logger.info( f"Authorization configured - required scopes: {required_scopes or 'none (authentication only)'}" @@ -561,9 +438,7 @@ def _initialize_oauth(self) -> None: ), required_scopes=required_scopes if required_scopes else None, ) - logger.info( - f"OAuth authentication enabled with provider: {auth_config.get('provider')}" - ) + logger.info(f"OAuth authentication enabled with provider: {auth_config.provider}") else: logger.info("OAuth authentication disabled") @@ -773,7 +648,7 @@ def reload_configuration(self) -> "ReloadRequest": # Set templates in tracker self.ref_tracker.set_template( site_template.model_dump(mode="python"), - cast(dict[str, Any], user_template), + user_template.model_dump(mode="python"), ) self._config_templates_loaded = True logger.info("Raw configuration templates loaded.") @@ -791,7 +666,7 @@ def reload_config_files() -> None: # Update templates in tracker self.ref_tracker.set_template( new_site_template.model_dump(mode="python"), - cast(dict[str, Any], new_user_template), + new_user_template.model_dump(mode="python"), ) # Resolve and update configs @@ -799,7 +674,7 @@ def reload_config_files() -> None: self.site_config = SiteConfigModel.model_validate( new_site_config, context={"repo_root": self.site_config_path} ) - self.user_config = cast(UserConfig, new_user_config) + self.user_config = UserConfigModel.model_validate(new_user_config) logger.info("Configuration files reloaded") @@ -2091,9 +1966,9 @@ async def oauth_protected_resource_metadata(request: Any) -> Any: base_url = url_builder.get_base_url(request) # Get supported scopes from configuration - auth_config = self.active_profile.get("auth", {}) - auth_authorization = auth_config.get("authorization", {}) - supported_scopes = auth_authorization.get("required_scopes", []) + auth_config = self.active_profile.auth + auth_authorization = auth_config.authorization + supported_scopes = auth_authorization.required_scopes if auth_authorization else [] metadata = { "resource": base_url, diff --git a/src/mxcp/server/schemas/mxcp-config-schema-1.json b/src/mxcp/server/schemas/mxcp-config-schema-1.json deleted file mode 100644 index 53237e2f..00000000 --- a/src/mxcp/server/schemas/mxcp-config-schema-1.json +++ /dev/null @@ -1,614 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "MXCP Config", - "type": "object", - "required": ["mxcp", "projects"], - "properties": { - "mxcp": { - "type": "integer", - "description": "Schema version. Must be 1.", - "enum": [1], - "default": 1 - }, - "vault": { - "type": "object", - "description": "Configuration for Vault integration.", - "required": ["enabled"], - "properties": { - "enabled": { "type": "boolean" }, - "address": { - "type": "string", - "format": "uri" - }, - "token_env": { - "type": "string", - "description": "The environment variable name containing the Vault token." - } - }, - "additionalProperties": false - }, - "onepassword": { - "type": "object", - "description": "Configuration for 1Password integration using service account.", - "required": ["enabled"], - "properties": { - "enabled": { "type": "boolean" }, - "token_env": { - "type": "string", - "description": "The environment variable name containing the 1Password service account token.", - "default": "OP_SERVICE_ACCOUNT_TOKEN" - } - }, - "additionalProperties": false - }, - "transport": { - "type": "object", - "description": "Default transport configuration for serving endpoints.", - "properties": { - "provider": { - "type": "string", - "enum": ["streamable-http", "sse", "stdio"], - "default": "streamable-http", - "description": "Default transport protocol to use." - }, - "http": { - "type": "object", - "description": "HTTP transport specific configuration.", - "properties": { - "port": { - "type": "integer", - "minimum": 1, - "maximum": 65535, - "default": 8000, - "description": "Default port number for HTTP transport." - }, - "host": { - "type": "string", - "default": "localhost", - "description": "Default host to bind the HTTP server to." - }, - "scheme": { - "type": "string", - "enum": ["http", "https"], - "default": "http", - "description": "URL scheme to use for generating callback URLs and OAuth endpoints. Use 'https' when behind SSL-terminating reverse proxy." - }, - "base_url": { - "type": "string", - "format": "uri", - "description": "Complete base URL for the server (e.g., 'https://api.example.com'). When provided, overrides scheme, host, and port for URL generation." - }, - "trust_proxy": { - "type": "boolean", - "default": false, - "description": "Whether to trust X-Forwarded-* headers from reverse proxies for scheme detection." - }, - "stateless": { - "type": "boolean", - "default": false, - "description": "Enable stateless HTTP mode for serverless deployments. In stateless mode, no session state is maintained between requests." - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - "models": { - "type": "object", - "description": "Configuration for LLM models used in evals.", - "properties": { - "default": { - "type": "string", - "description": "Default model to use when not specified in eval suite.", - "enum": ["claude-4-sonnet", "claude-4-opus", "gpt-4o", "gpt-4.1"] - }, - "models": { - "type": "object", - "description": "Model-specific configurations.", - "patternProperties": { - "^(claude-4-sonnet|claude-4-opus|gpt-4o|gpt-4\\.1)$": { - "type": "object", - "required": ["type"], - "properties": { - "type": { - "type": "string", - "enum": ["claude", "openai"], - "description": "Provider type for this model." - }, - "api_key": { - "type": "string", - "description": "API key for this model." - }, - "base_url": { - "type": "string", - "format": "uri", - "description": "Custom API endpoint URL." - }, - "timeout": { - "type": "integer", - "minimum": 1, - "default": 30, - "description": "Request timeout in seconds." - }, - "max_retries": { - "type": "integer", - "minimum": 0, - "default": 3, - "description": "Maximum number of retries for failed requests." - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - "projects": { - "type": "object", - "description": "All configured RAW projects.", - "patternProperties": { - "^[a-zA-Z0-9_-]+$": { - "type": "object", - "required": ["profiles"], - "properties": { - "profiles": { - "type": "object", - "description": "Profiles under this project.", - "patternProperties": { - "^[a-zA-Z0-9_-]+$": { - "type": "object", - "properties": { - "secrets": { - "type": "array", - "items": { - "type": "object", - "required": ["name", "type", "parameters"], - "properties": { - "name": { "type": "string", "description": "The secret name." }, - "type": { "type": "string", "description": "The secret type." }, - "parameters": { - "type": "object", - "description": "The secret definition.", - "additionalProperties": { - "oneOf": [ - { "type": "string" }, - { - "type": "object", - "additionalProperties": { "type": "string" } - } - ] - } - } - }, - "additionalProperties": false - } - }, - "telemetry": { - "type": "object", - "description": "Unified telemetry configuration for traces, metrics, and logs.", - "properties": { - "enabled": { - "type": "boolean", - "description": "Global telemetry enable/disable.", - "default": false - }, - "endpoint": { - "type": "string", - "description": "OTLP endpoint URL (e.g., http://localhost:4318).", - "format": "uri" - }, - "headers": { - "type": "object", - "description": "Additional headers for the OTLP exporter.", - "additionalProperties": { - "type": "string" - } - }, - "service_name": { - "type": "string", - "description": "Override the default service name (mxcp)." - }, - "service_version": { - "type": "string", - "description": "Service version string." - }, - "environment": { - "type": "string", - "description": "Deployment environment (e.g., production, staging)." - }, - "resource_attributes": { - "type": "object", - "description": "Additional resource attributes for all telemetry.", - "additionalProperties": { - "type": "string" - } - }, - "tracing": { - "type": "object", - "description": "Distributed tracing configuration.", - "properties": { - "enabled": { - "type": "boolean", - "description": "Whether tracing is enabled.", - "default": true - }, - "console_export": { - "type": "boolean", - "description": "Export spans to console for debugging.", - "default": false - } - }, - "additionalProperties": false - }, - "metrics": { - "type": "object", - "description": "Metrics collection configuration.", - "properties": { - "enabled": { - "type": "boolean", - "description": "Whether metrics collection is enabled.", - "default": true - }, - "export_interval": { - "type": "integer", - "description": "Export interval in seconds.", - "default": 60, - "minimum": 1 - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - "plugin": { - "type": "object", - "description": "Plugin configuration for this profile.", - "properties": { - "config": { - "type": "object", - "description": "Plugin-specific configurations.", - "patternProperties": { - "^[a-zA-Z0-9_-]+$": { - "type": "object", - "description": "Configuration for a specific plugin.", - "additionalProperties": { - "type": "string" - } - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - "auth": { - "type": "object", - "description": "Authentication configuration for this profile", - "properties": { - "provider": { - "type": "string", - "enum": ["none", "github", "atlassian", "salesforce", "keycloak", "google"], - "default": "none", - "description": "OAuth provider to use for authentication" - }, - "authorization": { - "type": "object", - "description": "Authorization settings for controlling access to MCP functionality", - "properties": { - "required_scopes": { - "type": "array", - "items": {"type": "string"}, - "default": [], - "description": "List of scopes required to access any MCP endpoint. Empty list means no scopes required (authentication only)." - } - } - }, - "clients": { - "type": "array", - "description": "Pre-registered OAuth clients for development and testing.", - "items": { - "type": "object", - "required": ["client_id", "name"], - "properties": { - "client_id": { - "type": "string", - "description": "OAuth client ID." - }, - "client_secret": { - "type": "string", - "description": "OAuth client secret (optional for public clients)." - }, - "name": { - "type": "string", - "description": "Human-readable name for this client." - }, - "redirect_uris": { - "type": "array", - "items": { - "type": "string", - "format": "uri" - }, - "description": "Allowed redirect URIs for this client.", - "default": ["http://127.0.0.1:49153/oauth/callback"] - }, - "grant_types": { - "type": "array", - "items": { - "type": "string", - "enum": ["authorization_code", "refresh_token"] - }, - "description": "Allowed OAuth grant types.", - "default": ["authorization_code"] - }, - "scopes": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Allowed OAuth scopes.", - "default": ["mxcp:access"] - } - }, - "additionalProperties": false - } - }, - "github": { - "type": "object", - "description": "GitHub OAuth configuration (required when provider is 'github').", - "required": ["client_id", "client_secret", "callback_path", "auth_url", "token_url"], - "properties": { - "client_id": { - "type": "string", - "description": "GitHub OAuth client ID." - }, - "client_secret": { - "type": "string", - "description": "GitHub OAuth client secret." - }, - "scope": { - "type": "string", - "description": "OAuth scope to request (optional).", - "default": "user:email" - }, - "callback_path": { - "type": "string", - "description": "Callback path for OAuth flow.", - "default": "/github/callback" - }, - "auth_url": { - "type": "string", - "format": "uri", - "description": "GitHub authorization URL." - }, - "token_url": { - "type": "string", - "format": "uri", - "description": "GitHub token exchange URL." - } - }, - "additionalProperties": false - }, - "atlassian": { - "type": "object", - "description": "Atlassian OAuth configuration (required when provider is 'atlassian').", - "required": ["client_id", "client_secret", "callback_path", "auth_url", "token_url"], - "properties": { - "client_id": { - "type": "string", - "description": "Atlassian OAuth client ID." - }, - "client_secret": { - "type": "string", - "description": "Atlassian OAuth client secret." - }, - "scope": { - "type": "string", - "description": "OAuth scopes to request (space-separated).", - "default": "read:jira-work read:jira-user read:confluence-content.all read:confluence-user offline_access" - }, - "callback_path": { - "type": "string", - "description": "Callback path for OAuth flow.", - "default": "/atlassian/callback" - }, - "auth_url": { - "type": "string", - "format": "uri", - "description": "Atlassian authorization URL.", - "default": "https://auth.atlassian.com/authorize" - }, - "token_url": { - "type": "string", - "format": "uri", - "description": "Atlassian token exchange URL.", - "default": "https://auth.atlassian.com/oauth/token" - } - }, - "additionalProperties": false - }, - "salesforce": { - "type": "object", - "description": "Salesforce OAuth configuration (required when provider is 'salesforce').", - "required": ["client_id", "client_secret", "callback_path", "auth_url", "token_url"], - "properties": { - "client_id": { - "type": "string", - "description": "Salesforce OAuth client ID." - }, - "client_secret": { - "type": "string", - "description": "Salesforce OAuth client secret." - }, - "scope": { - "type": "string", - "description": "OAuth scopes to request (space-separated).", - "default": "api refresh_token openid profile email" - }, - "callback_path": { - "type": "string", - "description": "Callback path for OAuth flow.", - "default": "/salesforce/callback" - }, - "auth_url": { - "type": "string", - "format": "uri", - "description": "Salesforce authorization URL.", - "default": "https://login.salesforce.com/services/oauth2/authorize" - }, - "token_url": { - "type": "string", - "format": "uri", - "description": "Salesforce token exchange URL.", - "default": "https://login.salesforce.com/services/oauth2/token" - } - }, - "additionalProperties": false - }, - "keycloak": { - "type": "object", - "description": "Keycloak OAuth configuration (required when provider is 'keycloak').", - "required": ["client_id", "client_secret", "realm", "server_url"], - "properties": { - "client_id": { - "type": "string", - "description": "Keycloak OAuth client ID." - }, - "client_secret": { - "type": "string", - "description": "Keycloak OAuth client secret." - }, - "realm": { - "type": "string", - "description": "Keycloak realm name." - }, - "server_url": { - "type": "string", - "format": "uri", - "description": "Keycloak server base URL (e.g., 'http://localhost:8080')." - }, - "scope": { - "type": "string", - "description": "OAuth scopes to request (space-separated).", - "default": "openid profile email" - }, - "callback_path": { - "type": "string", - "description": "Callback path for OAuth flow.", - "default": "/keycloak/callback" - } - }, - "additionalProperties": false - }, - "google": { - "type": "object", - "description": "Google OAuth configuration (required when provider is 'google').", - "required": ["client_id", "client_secret", "callback_path", "auth_url", "token_url"], - "properties": { - "client_id": { - "type": "string", - "description": "Google OAuth client ID." - }, - "client_secret": { - "type": "string", - "description": "Google OAuth client secret." - }, - "scope": { - "type": "string", - "description": "OAuth scopes to request (space-separated).", - "default": "https://www.googleapis.com/auth/calendar.readonly openid profile email" - }, - "callback_path": { - "type": "string", - "description": "Callback path for OAuth flow.", - "default": "/google/callback" - }, - "auth_url": { - "type": "string", - "format": "uri", - "description": "Google authorization URL.", - "default": "https://accounts.google.com/o/oauth2/v2/auth" - }, - "token_url": { - "type": "string", - "format": "uri", - "description": "Google token exchange URL.", - "default": "https://oauth2.googleapis.com/token" - } - }, - "additionalProperties": false - }, - "persistence": { - "type": "object", - "description": "OAuth state persistence configuration for maintaining authentication state across server restarts.", - "properties": { - "type": { - "type": "string", - "enum": ["sqlite"], - "default": "sqlite", - "description": "Type of persistence backend to use." - }, - "path": { - "type": "string", - "description": "Path to the SQLite database file for storing OAuth state.", - "default": "~/.mxcp/oauth.db" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - "logging": { - "type": "object", - "description": "Application logging configuration (top-level, applies to all projects).", - "properties": { - "enabled": { - "type": "boolean", - "description": "Enable file logging. If false, logs only to stderr (unless stdio mode).", - "default": true - }, - "path": { - "type": "string", - "description": "Path to the log file. Supports file rotation.", - "default": "logs/mxcp.log" - }, - "level": { - "type": "string", - "enum": ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"], - "description": "Minimum log level to capture.", - "default": "WARNING" - }, - "max_bytes": { - "type": "integer", - "description": "Maximum log file size in bytes before rotation.", - "default": 10485760, - "minimum": 1024 - }, - "backup_count": { - "type": "integer", - "description": "Number of rotated log files to keep.", - "default": 5, - "minimum": 0 - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false -} diff --git a/src/mxcp/server/services/dbt/runner.py b/src/mxcp/server/services/dbt/runner.py index 9016fd14..1e9a654f 100644 --- a/src/mxcp/server/services/dbt/runner.py +++ b/src/mxcp/server/services/dbt/runner.py @@ -2,18 +2,16 @@ import os import time from pathlib import Path -from typing import Any, cast +from typing import Any import click import yaml -from mxcp.server.core.config._types import ( - UserConfig, - UserProfileConfig, - UserProjectConfig, - UserSecretDefinition, +from mxcp.server.core.config.models import ( + SiteConfigModel, + UserConfigModel, + UserProfileConfigModel, ) -from mxcp.server.core.config.models import SiteConfigModel from mxcp.server.core.config.site_config import find_repo_root @@ -306,7 +304,7 @@ def _merge_dbt_project(existing: dict[str, Any], new: dict[str, Any]) -> dict[st def configure_dbt( site_config: SiteConfigModel, - user_config: UserConfig, + user_config: UserConfigModel, profile: str | None = None, dry_run: bool = False, force: bool = False, @@ -359,27 +357,29 @@ def configure_dbt( duckdb_path = str(repo_root / duckdb_path) # 5. Get secrets from user config - projects = user_config.get("projects", {}) - project_config: UserProjectConfig | None = projects.get(project) if projects else None + project_config = user_config.projects.get(project) if not project_config: click.echo( f"Warning: Project '{project}' not found in user config, assuming empty configuration", err=True, ) - project_config = None - - profiles = project_config.get("profiles", {}) if project_config else {} - user_profile_config: UserProfileConfig | None = profiles.get(profile_name) if profiles else None + user_profile_config: UserProfileConfigModel | None = None + else: + user_profile_config = project_config.profiles.get(profile_name) if not user_profile_config: click.echo( f"Warning: Profile '{profile_name}' not found in project '{project}', assuming empty configuration", err=True, ) - user_profile_config = {} + secrets_models = [] + else: + secrets_models = user_profile_config.secrets - secrets: list[UserSecretDefinition] | None = ( - user_profile_config.get("secrets") if user_profile_config else None + secrets_dict: list[dict[str, Any]] | None = ( + [secret.parameters for secret in secrets_models if secret.parameters] + if secrets_models + else None ) # 6. Load existing profiles and project config @@ -388,9 +388,6 @@ def configure_dbt( # 7. Build new profile block # Cast secrets to Dict[str, Any] for _build_profile_block - secrets_dict: list[dict[str, Any]] | None = ( - cast(list[dict[str, Any]] | None, secrets) if secrets else None - ) new_profile_block = _build_profile_block( project=project, profile=profile_name, diff --git a/src/mxcp/server/services/drift/checker.py b/src/mxcp/server/services/drift/checker.py index 0addfb17..7cf8285e 100644 --- a/src/mxcp/server/services/drift/checker.py +++ b/src/mxcp/server/services/drift/checker.py @@ -4,8 +4,7 @@ from pathlib import Path from typing import Any, cast -from mxcp.server.core.config._types import UserConfig -from mxcp.server.core.config.models import SiteConfigModel +from mxcp.server.core.config.models import SiteConfigModel, UserConfigModel from mxcp.server.services.drift._types import ( DriftReport, DriftSnapshot, @@ -303,7 +302,7 @@ def _compare_definitions(baseline: Any | None, current: Any | None) -> bool: async def check_drift( site_config: SiteConfigModel, - user_config: UserConfig, + user_config: UserConfigModel, profile: str | None = None, baseline_path: str | None = None, ) -> DriftReport: diff --git a/src/mxcp/server/services/drift/snapshot.py b/src/mxcp/server/services/drift/snapshot.py index 2abd3e39..3715238d 100644 --- a/src/mxcp/server/services/drift/snapshot.py +++ b/src/mxcp/server/services/drift/snapshot.py @@ -7,8 +7,7 @@ import duckdb from mxcp.sdk.executor.plugins.duckdb import DuckDBExecutor -from mxcp.server.core.config._types import UserConfig -from mxcp.server.core.config.models import SiteConfigModel +from mxcp.server.core.config.models import SiteConfigModel, UserConfigModel from mxcp.server.core.config.site_config import find_repo_root from mxcp.server.definitions.endpoints.loader import EndpointLoader from mxcp.server.executor.engine import create_runtime_environment @@ -47,7 +46,7 @@ def get_duckdb_tables(conn: duckdb.DuckDBPyConnection) -> list[Table]: async def generate_snapshot( site_config: SiteConfigModel, - user_config: UserConfig, + user_config: UserConfigModel, profile: str | None = None, force: bool = False, dry_run: bool = False, diff --git a/src/mxcp/server/services/endpoints/service.py b/src/mxcp/server/services/endpoints/service.py index 7611e86a..021b6923 100644 --- a/src/mxcp/server/services/endpoints/service.py +++ b/src/mxcp/server/services/endpoints/service.py @@ -19,8 +19,7 @@ PolicyEnforcer, PolicySet, ) -from mxcp.server.core.config._types import UserConfig -from mxcp.server.core.config.models import SiteConfigModel +from mxcp.server.core.config.models import SiteConfigModel, UserConfigModel from mxcp.server.core.config.site_config import find_repo_root from mxcp.server.definitions.endpoints._types import PromptDefinition from mxcp.server.definitions.endpoints.loader import EndpointLoader @@ -115,7 +114,7 @@ async def execute_endpoint( endpoint_type: str, name: str, params: dict[str, Any], - user_config: UserConfig, + user_config: UserConfigModel, site_config: SiteConfigModel, profile_name: str, readonly: bool = False, @@ -173,7 +172,7 @@ async def execute_endpoint_with_engine_and_policy( name: str, params: dict[str, Any], request_headers: dict[str, str] | None, - user_config: UserConfig, + user_config: UserConfigModel, site_config: SiteConfigModel, execution_engine: ExecutionEngine, skip_output_validation: bool = False, @@ -327,7 +326,7 @@ async def execute_endpoint_with_engine( endpoint_type: str, name: str, params: dict[str, Any], - user_config: UserConfig, + user_config: UserConfigModel, site_config: SiteConfigModel, execution_engine: ExecutionEngine, skip_output_validation: bool = False, diff --git a/src/mxcp/server/services/evals/service.py b/src/mxcp/server/services/evals/service.py index af630379..506b4a42 100644 --- a/src/mxcp/server/services/evals/service.py +++ b/src/mxcp/server/services/evals/service.py @@ -12,8 +12,7 @@ ToolDefinition, ) from mxcp.sdk.validator import TypeSchema -from mxcp.server.core.config._types import UserConfig -from mxcp.server.core.config.models import SiteConfigModel +from mxcp.server.core.config.models import SiteConfigModel, UserConfigModel from mxcp.server.core.config.site_config import find_repo_root from mxcp.server.definitions.endpoints._types import EndpointDefinition from mxcp.server.definitions.endpoints.loader import EndpointLoader @@ -24,7 +23,7 @@ logger = logging.getLogger(__name__) -def _create_model_config(model: str, user_config: UserConfig) -> ModelConfigType: +def _create_model_config(model: str, user_config: UserConfigModel) -> ModelConfigType: """Create a model configuration from user config. Args: @@ -37,31 +36,27 @@ def _create_model_config(model: str, user_config: UserConfig) -> ModelConfigType Raises: ValueError: If model is not configured or has invalid type """ - models_config = user_config.get("models", {}) - if not models_config: + models_config = user_config.models + if not models_config or not models_config.models: raise ValueError("No models configuration found in user config") - models_dict = models_config.get("models", {}) - if not models_dict: - raise ValueError("No models defined in models configuration") - - model_config: dict[str, Any] = cast(dict[str, Any], models_dict.get(model, {})) + model_config = models_config.models.get(model) if not model_config: raise ValueError(f"Model '{model}' not configured in user config") - model_type = model_config.get("type") - api_key = model_config.get("api_key") + model_type = model_config.type + api_key = model_config.api_key if not api_key: raise ValueError(f"No API key configured for model '{model}'") if model_type == "claude": - base_url = model_config.get("base_url") or "https://api.anthropic.com" - timeout = model_config.get("timeout") or 30 + base_url = model_config.base_url or "https://api.anthropic.com" + timeout = model_config.timeout or 30 return ClaudeConfig(name=model, api_key=api_key, base_url=base_url, timeout=timeout) elif model_type == "openai": - base_url = model_config.get("base_url") or "https://api.openai.com/v1" - timeout = model_config.get("timeout") or 30 + base_url = model_config.base_url or "https://api.openai.com/v1" + timeout = model_config.timeout or 30 return OpenAIConfig(name=model, api_key=api_key, base_url=base_url, timeout=timeout) else: raise ValueError(f"Unknown model type: {model_type}") @@ -177,7 +172,7 @@ def _convert_endpoints_to_tool_definitions( async def run_eval_suite( suite_name: str, - user_config: UserConfig, + user_config: UserConfigModel, site_config: SiteConfigModel, profile: str | None, cli_user_context: UserContext | None = None, @@ -206,9 +201,8 @@ async def run_eval_suite( # Determine which model to use model = override_model or eval_suite.get("model") if not model: - # Try to get default model from user config - models_config = user_config.get("models") or {} - model = models_config.get("default") if models_config else None + models_config = user_config.models + model = models_config.default if models_config else None if not model: return { @@ -377,7 +371,7 @@ async def run_eval_suite( async def run_all_evals( - user_config: UserConfig, + user_config: UserConfigModel, site_config: SiteConfigModel, profile: str | None, cli_user_context: UserContext | None = None, @@ -446,7 +440,7 @@ async def run_all_evals( def get_model_config( - user_config: UserConfig, model_name: str | None = None + user_config: UserConfigModel, model_name: str | None = None ) -> dict[str, Any] | None: """Get model configuration from user config. @@ -457,18 +451,17 @@ def get_model_config( Returns: Model configuration if found, None otherwise """ - models_config = user_config.get("models", {}) - if not models_config: + models_config = user_config.models + if not models_config or not models_config.models: return None # If no model name provided, try to get default if not model_name: - model_name = models_config.get("default") + model_name = models_config.default if not model_name: return None - # Get specific model config - model_configs = models_config.get("models", {}) - if not model_configs: + model_config = models_config.models.get(model_name) + if not model_config: return None - return cast(dict[str, Any] | None, model_configs.get(model_name)) + return model_config.model_dump(exclude_none=True) diff --git a/src/mxcp/server/services/tests/service.py b/src/mxcp/server/services/tests/service.py index 3b2fae7a..d0cb8b3d 100644 --- a/src/mxcp/server/services/tests/service.py +++ b/src/mxcp/server/services/tests/service.py @@ -2,8 +2,7 @@ from typing import Any from mxcp.sdk.auth import UserContext -from mxcp.server.core.config._types import UserConfig -from mxcp.server.core.config.models import SiteConfigModel +from mxcp.server.core.config.models import SiteConfigModel, UserConfigModel from mxcp.server.core.config.site_config import find_repo_root from mxcp.server.definitions.endpoints.loader import EndpointLoader from mxcp.server.executor.engine import create_runtime_environment @@ -15,7 +14,7 @@ async def run_all_tests( - user_config: UserConfig, + user_config: UserConfigModel, site_config: SiteConfigModel, profile: str | None, readonly: bool | None = None, @@ -138,7 +137,7 @@ async def run_all_tests( async def run_tests( endpoint_type: str, name: str, - user_config: UserConfig, + user_config: UserConfigModel, site_config: SiteConfigModel, profile: str | None, readonly: bool | None = None, diff --git a/tests/server/fixtures/runtime/python/runtime_tests.py b/tests/server/fixtures/runtime/python/runtime_tests.py index c3fd5e27..e17e13b1 100644 --- a/tests/server/fixtures/runtime/python/runtime_tests.py +++ b/tests/server/fixtures/runtime/python/runtime_tests.py @@ -137,8 +137,14 @@ def test_config_properties() -> dict: # Verify we can access nested values try: # From user config - project_name = list(user_cfg["projects"].keys())[0] - secrets_count = len(user_cfg["projects"]["runtime_test"]["profiles"]["default"]["secrets"]) + if hasattr(user_cfg, "model_dump"): + user_cfg_dict = user_cfg.model_dump(mode="python") + else: + user_cfg_dict = user_cfg + project_name = list(user_cfg_dict["projects"].keys())[0] + secrets_count = len( + user_cfg_dict["projects"]["runtime_test"]["profiles"]["default"]["secrets"] + ) # From site config if hasattr(site_cfg, "model_dump"): diff --git a/tests/server/test_cli_init.py b/tests/server/test_cli_init.py index 17e8a905..1db0a258 100644 --- a/tests/server/test_cli_init.py +++ b/tests/server/test_cli_init.py @@ -275,18 +275,12 @@ def test_user_config_generation_uses_integer_version(): user_config = _generate_default_config(site_config) # Verify the generated user config has integer version - assert ( - user_config["mxcp"] == 1 - ), f"User config should have integer version 1, got {user_config['mxcp']} ({type(user_config['mxcp'])})" - assert isinstance( - user_config["mxcp"], int - ), f"User config version should be int, got {type(user_config['mxcp'])}" + assert user_config.mxcp == 1 + assert isinstance(user_config.mxcp, int) # Verify structure is correct - assert "projects" in user_config - assert "test-new-project" in user_config["projects"] - assert "profiles" in user_config["projects"]["test-new-project"] - assert "default" in user_config["projects"]["test-new-project"]["profiles"] + assert "test-new-project" in user_config.projects + assert "default" in user_config.projects["test-new-project"].profiles def test_init_custom_project_name(tmp_path): @@ -510,8 +504,8 @@ def test_user_config_generation_uses_integer_version(): config = _generate_default_config(site_config) # Version should be integer 1, not string "1.0.0" - assert config["mxcp"] == 1, f"Expected integer 1, got {repr(config['mxcp'])}" - assert isinstance(config["mxcp"], int), f"Expected int type, got {type(config['mxcp'])}" + assert config.mxcp == 1 + assert isinstance(config.mxcp, int) def test_migration_exception_handling(): diff --git a/tests/server/test_init_hooks_db_access.py b/tests/server/test_init_hooks_db_access.py index e45067c2..10f75645 100644 --- a/tests/server/test_init_hooks_db_access.py +++ b/tests/server/test_init_hooks_db_access.py @@ -9,7 +9,7 @@ import pytest import yaml -from mxcp.server.core.config.models import SiteConfigModel +from mxcp.server.core.config.models import SiteConfigModel, UserConfigModel from mxcp.server.executor.engine import create_runtime_environment from mxcp.server.services.endpoints import execute_endpoint_with_engine @@ -158,8 +158,9 @@ def check_table_data(): site_config_data, context={"repo_root": project_dir} ) + user_config_model = UserConfigModel.model_validate(user_config) runtime_env = create_runtime_environment( - user_config, site_config, repo_root=project_dir + user_config_model, site_config, repo_root=project_dir ) # Check if init hook succeeded diff --git a/tests/server/test_python_endpoints.py b/tests/server/test_python_endpoints.py index 47d4ff42..d53d2956 100644 --- a/tests/server/test_python_endpoints.py +++ b/tests/server/test_python_endpoints.py @@ -8,6 +8,7 @@ import yaml from mxcp.runtime import _init_hooks, _shutdown_hooks +from mxcp.server.core.config.models import UserConfigModel from mxcp.server.core.config.site_config import load_site_config from mxcp.server.core.config.user_config import load_user_config from mxcp.server.executor.engine import create_runtime_environment @@ -660,7 +661,8 @@ async def test_python_endpoint_with_non_duckdb_secret_type( user_config, site_config = test_configs # Add custom secrets to user config - user_config["projects"]["test-project"]["profiles"]["test"]["secrets"].extend( + user_config_data = user_config.model_dump(mode="python") + user_config_data["projects"]["test-project"]["profiles"]["test"]["secrets"].extend( [ { "name": "custom_api", @@ -678,6 +680,7 @@ async def test_python_endpoint_with_non_duckdb_secret_type( }, ] ) + user_config = UserConfigModel.model_validate(user_config_data) # Update site config to reference our secrets site_config = site_config.model_copy( diff --git a/tests/server/test_reload.py b/tests/server/test_reload.py index bf5bdc61..65bf9262 100644 --- a/tests/server/test_reload.py +++ b/tests/server/test_reload.py @@ -11,7 +11,7 @@ import pytest -from mxcp.server.core.config.models import SiteConfigModel +from mxcp.server.core.config.models import SiteConfigModel, UserConfigModel from mxcp.server.interfaces.server.mcp import RAWMCP from mxcp.server.core.reload import ReloadManager @@ -60,7 +60,10 @@ def test_sighup_triggers_full_reload(self): "mxcp.server.interfaces.server.mcp.load_site_config", return_value=self._minimal_site_config(), ): - with patch("mxcp.server.interfaces.server.mcp.load_user_config", return_value={}): + with patch( + "mxcp.server.interfaces.server.mcp.load_user_config", + return_value=UserConfigModel.model_validate({}), + ): # Simulate SIGHUP server._handle_reload_signal(signal.SIGHUP, None) @@ -143,7 +146,10 @@ def test_reload_metrics_recorded(self): "mxcp.server.interfaces.server.mcp.load_site_config", return_value=self._minimal_site_config(), ): - with patch("mxcp.server.interfaces.server.mcp.load_user_config", return_value={}): + with patch( + "mxcp.server.interfaces.server.mcp.load_user_config", + return_value=UserConfigModel.model_validate({}), + ): server.reload_configuration() # Verify reload was requested diff --git a/tests/server/test_telemetry_config.py b/tests/server/test_telemetry_config.py index dc7f8481..d5955cf5 100644 --- a/tests/server/test_telemetry_config.py +++ b/tests/server/test_telemetry_config.py @@ -9,7 +9,7 @@ is_telemetry_enabled, traced_operation, ) -from mxcp.server.core.config._types import UserConfig +from mxcp.server.core.config.models import UserConfigModel from mxcp.server.core.telemetry import ( configure_telemetry_from_config, shutdown_telemetry, @@ -44,18 +44,20 @@ def reset_telemetry(): def test_telemetry_config_disabled(): """Test telemetry when not configured.""" - user_config: UserConfig = { - "mxcp": "1", - "projects": { - "test": { - "profiles": { - "dev": { - # No telemetry config + user_config = UserConfigModel.model_validate( + { + "mxcp": 1, + "projects": { + "test": { + "profiles": { + "dev": { + # No telemetry config + } } } - } - }, - } + }, + } + ) configure_telemetry_from_config(user_config, "test", "dev") assert not is_telemetry_enabled() @@ -68,23 +70,26 @@ def test_telemetry_config_disabled(): def test_telemetry_config_enabled(): """Test telemetry when enabled in config.""" - user_config: UserConfig = { - "mxcp": "1", - "projects": { - "test": { - "profiles": { - "dev": { - "telemetry": { - "enabled": True, - "console_export": True, - "service_name": "test-service", - "environment": "testing", + user_config = UserConfigModel.model_validate( + { + "mxcp": 1, + "projects": { + "test": { + "profiles": { + "dev": { + "telemetry": { + "enabled": True, + "endpoint": "http://localhost:4318", + "service_name": "test-service", + "environment": "testing", + "tracing": {"enabled": True, "console_export": True}, + } } } } - } - }, - } + }, + } + ) configure_telemetry_from_config(user_config, "test", "dev") assert is_telemetry_enabled() @@ -99,23 +104,24 @@ def test_telemetry_config_enabled(): def test_telemetry_config_with_endpoint(): """Test telemetry with OTLP endpoint.""" - user_config: UserConfig = { - "mxcp": "1", - "projects": { - "prod": { - "profiles": { - "main": { - "telemetry": { - "enabled": True, - "endpoint": "http://localhost:4318", - "headers": {"Authorization": "Bearer token"}, - "sampling_rate": 0.1, + user_config = UserConfigModel.model_validate( + { + "mxcp": 1, + "projects": { + "prod": { + "profiles": { + "main": { + "telemetry": { + "enabled": True, + "endpoint": "http://localhost:4318", + "headers": {"Authorization": "Bearer token"}, + } } } } - } - }, - } + }, + } + ) configure_telemetry_from_config(user_config, "prod", "main") assert is_telemetry_enabled() @@ -123,21 +129,23 @@ def test_telemetry_config_with_endpoint(): def test_telemetry_disabled_by_default(): """Test telemetry is disabled when enabled=false.""" - user_config: UserConfig = { - "mxcp": "1", - "projects": { - "test": { - "profiles": { - "dev": { - "telemetry": { - "enabled": False, - "endpoint": "http://localhost:4318", + user_config = UserConfigModel.model_validate( + { + "mxcp": 1, + "projects": { + "test": { + "profiles": { + "dev": { + "telemetry": { + "enabled": False, + "endpoint": "http://localhost:4318", + } } } } - } - }, - } + }, + } + ) configure_telemetry_from_config(user_config, "test", "dev") assert not is_telemetry_enabled() diff --git a/tests/server/test_telemetry_env_vars.py b/tests/server/test_telemetry_env_vars.py index e9974eb2..97fe6222 100644 --- a/tests/server/test_telemetry_env_vars.py +++ b/tests/server/test_telemetry_env_vars.py @@ -5,6 +5,7 @@ import pytest +from mxcp.server.core.config.models import UserTelemetryConfigModel from mxcp.server.core.telemetry import ( _get_telemetry_from_env, _merge_telemetry_configs, @@ -135,8 +136,12 @@ def test_merge_telemetry_configs_only_file(): "endpoint": "http://localhost:4318", "service_name": "mxcp-file", } - result = _merge_telemetry_configs(file_config, None) - assert result == file_config + file_model = UserTelemetryConfigModel.model_validate(file_config) + result = _merge_telemetry_configs(file_model, None) + assert result is not None + assert result.model_dump(mode="python", exclude_none=True) == file_model.model_dump( + mode="python", exclude_none=True + ) def test_merge_telemetry_configs_only_env(): @@ -147,7 +152,8 @@ def test_merge_telemetry_configs_only_env(): "service_name": "mxcp-env", } result = _merge_telemetry_configs(None, env_config) - assert result == env_config + assert result is not None + assert result.model_dump(mode="python", exclude_none=True) == env_config def test_merge_telemetry_configs_env_overrides(): @@ -163,18 +169,20 @@ def test_merge_telemetry_configs_env_overrides(): "endpoint": "http://env-endpoint:4318", "tracing": {"console_export": True}, } - result = _merge_telemetry_configs(file_config, env_config) + file_model = UserTelemetryConfigModel.model_validate(file_config) + result = _merge_telemetry_configs(file_model, env_config) # Env should override top-level keys - assert result["enabled"] is True - assert result["endpoint"] == "http://env-endpoint:4318" + assert result.enabled is True + assert result.endpoint == "http://env-endpoint:4318" # File config value should remain if not overridden - assert result["service_name"] == "mxcp-file" + assert result.service_name == "mxcp-file" # Nested configs should merge - assert result["tracing"]["enabled"] is True # from file - assert result["tracing"]["console_export"] is True # from env + assert result.tracing is not None + assert result.tracing.enabled is True # from file + assert result.tracing.console_export is True # from env def test_merge_telemetry_configs_nested_merge(): @@ -188,13 +196,16 @@ def test_merge_telemetry_configs_nested_merge(): "tracing": {"console_export": True}, "metrics": {"export_interval": 30}, } - result = _merge_telemetry_configs(file_config, env_config) + file_model = UserTelemetryConfigModel.model_validate(file_config) + result = _merge_telemetry_configs(file_model, env_config) # Top level should remain - assert result["enabled"] is True + assert result.enabled is True # Nested configs should merge - assert result["tracing"]["enabled"] is True # from file - assert result["tracing"]["console_export"] is True # from env (overrides) - assert result["metrics"]["enabled"] is True # from file - assert result["metrics"]["export_interval"] == 30 # from env (overrides) + assert result.tracing is not None + assert result.tracing.enabled is True # from file + assert result.tracing.console_export is True # from env (overrides) + assert result.metrics is not None + assert result.metrics.enabled is True # from file + assert result.metrics.export_interval == 30 # from env (overrides) diff --git a/tests/server/test_telemetry_integration.py b/tests/server/test_telemetry_integration.py index a9bcbf9a..85c1262e 100644 --- a/tests/server/test_telemetry_integration.py +++ b/tests/server/test_telemetry_integration.py @@ -9,8 +9,7 @@ from mxcp.sdk.executor import ExecutionContext from mxcp.sdk.telemetry import is_telemetry_enabled, shutdown_telemetry -from mxcp.server.core.config._types import UserConfig -from mxcp.server.core.config.models import SiteConfigModel +from mxcp.server.core.config.models import SiteConfigModel, UserConfigModel from mxcp.server.core.telemetry import configure_telemetry_from_config from mxcp.server.executor.engine import create_runtime_environment @@ -44,22 +43,25 @@ def reset_telemetry(): def test_telemetry_with_execution_engine(tmp_path): """Test that telemetry properly traces execution engine operations.""" # Configure telemetry - user_config: UserConfig = { - "mxcp": "1", - "projects": { - "test": { - "profiles": { - "dev": { - "telemetry": { - "enabled": True, - "console_export": True, - "service_name": "test-service", + user_config = UserConfigModel.model_validate( + { + "mxcp": 1, + "projects": { + "test": { + "profiles": { + "dev": { + "telemetry": { + "enabled": True, + "endpoint": "http://localhost:4318", + "service_name": "test-service", + "tracing": {"enabled": True, "console_export": True}, + } } } } - } - }, - } + }, + } + ) configure_telemetry_from_config(user_config, "test", "dev") assert is_telemetry_enabled() @@ -134,21 +136,24 @@ async def run_python(): def test_nested_telemetry_spans(tmp_path): """Test that nested operations create proper parent-child span relationships.""" # Configure telemetry - user_config: UserConfig = { - "mxcp": "1", - "projects": { - "test": { - "profiles": { - "dev": { - "telemetry": { - "enabled": True, - "console_export": True, + user_config = UserConfigModel.model_validate( + { + "mxcp": 1, + "projects": { + "test": { + "profiles": { + "dev": { + "telemetry": { + "enabled": True, + "endpoint": "http://localhost:4318", + "tracing": {"enabled": True, "console_export": True}, + } } } } - } - }, - } + }, + } + ) configure_telemetry_from_config(user_config, "test", "dev") diff --git a/tests/server/test_user_config.py b/tests/server/test_user_config.py index 8f49caf2..c211ba3a 100644 --- a/tests/server/test_user_config.py +++ b/tests/server/test_user_config.py @@ -43,7 +43,7 @@ def test_env_var_interpolation(tmp_path): site_config = make_site_config("test_project", "dev") # Load and verify config - config = load_user_config(site_config) + config = load_user_config(site_config).model_dump(mode="python") assert ( config["projects"]["test_project"]["profiles"]["dev"]["secrets"][0]["parameters"]["simple"] == "simple_value" @@ -126,7 +126,7 @@ def test_env_var_in_nested_structures(tmp_path): site_config = make_site_config("test_project", "dev") # Load and verify config - config = load_user_config(site_config) + config = load_user_config(site_config).model_dump(mode="python") params = config["projects"]["test_project"]["profiles"]["dev"]["secrets"][0]["parameters"] assert params["nested"]["key1"] == "nested_value1" assert params["nested"]["key2"] == "nested_value2" @@ -178,7 +178,7 @@ def test_file_url_interpolation(tmp_path): site_config = make_site_config("test_project", "dev") # Load and verify config - config = load_user_config(site_config) + config = load_user_config(site_config).model_dump(mode="python") api_params = config["projects"]["test_project"]["profiles"]["dev"]["secrets"][0]["parameters"] db_params = config["projects"]["test_project"]["profiles"]["dev"]["secrets"][1]["parameters"] @@ -224,7 +224,7 @@ def test_file_url_relative_path(tmp_path): site_config = make_site_config("test_project", "dev") # Load and verify config - config = load_user_config(site_config) + config = load_user_config(site_config).model_dump(mode="python") params = config["projects"]["test_project"]["profiles"]["dev"]["secrets"][0]["parameters"] assert params["value"] == "relative-secret-value" @@ -331,7 +331,7 @@ def test_mixed_interpolation_with_files(tmp_path): site_config = make_site_config("test", "default") # Load and verify - config = load_user_config(site_config) + config = load_user_config(site_config).model_dump(mode="python") params = config["projects"]["test"]["profiles"]["default"]["secrets"][0]["parameters"] assert params["env_var"] == "from_env" @@ -378,7 +378,7 @@ def test_load_without_resolving_refs(tmp_path): site_config = make_site_config("test", "default") # Load without resolving references - config = load_user_config(site_config, resolve_refs=False) + config = load_user_config(site_config, resolve_refs=False).model_dump(mode="python") secret_params = config["projects"]["test"]["profiles"]["default"]["secrets"][0]["parameters"] plugin_config = config["projects"]["test"]["profiles"]["default"]["plugin"]["config"][ "test_plugin" diff --git a/tests/server/test_user_config_model.py b/tests/server/test_user_config_model.py new file mode 100644 index 00000000..bbc8d741 --- /dev/null +++ b/tests/server/test_user_config_model.py @@ -0,0 +1,61 @@ +"""Unit tests for UserConfigModel defaults and validators.""" + +from mxcp.server.core.config.models import UserConfigModel + + +def _base_user_config() -> dict: + return { + "mxcp": 1, + "projects": { + "demo": { + "profiles": { + "default": { + "secrets": [], + } + } + } + }, + } + + +def test_user_config_model_defaults(): + """Minimal configs should pick up sensible defaults.""" + model = UserConfigModel.model_validate(_base_user_config()) + + assert model.transport.provider == "streamable-http" + assert model.transport.http.port == 8000 + assert model.transport.http.host == "localhost" + assert model.logging.enabled is True + assert "demo" in model.projects + assert "default" in model.projects["demo"].profiles + profile = model.projects["demo"].profiles["default"] + assert profile.secrets == [] + assert profile.plugin.config == {} + assert profile.auth.provider == "none" + + +def test_user_auth_persistence_default(tmp_path): + """Auth providers should automatically receive persistence defaults.""" + data = _base_user_config() + data["projects"]["demo"]["profiles"]["default"]["auth"] = {"provider": "github"} + + model = UserConfigModel.model_validate(data) + auth = model.projects["demo"].profiles["default"].auth + assert auth.provider == "github" + assert auth.persistence is not None + assert auth.persistence.path.endswith("oauth.db") + + +def test_transport_overrides_respected(): + """Explicit transport overrides should be preserved.""" + data = _base_user_config() + data["transport"] = { + "provider": "sse", + "http": {"host": "0.0.0.0", "port": 9000, "stateless": True}, + } + + model = UserConfigModel.model_validate(data) + assert model.transport.provider == "sse" + assert model.transport.http.host == "0.0.0.0" + assert model.transport.http.port == 9000 + assert model.transport.http.stateless is True From 6a0866bea3efb914a51c068c0e4e8f005a3d8717 Mon Sep 17 00:00:00 2001 From: Miguel Branco Date: Tue, 25 Nov 2025 22:22:15 +0100 Subject: [PATCH 03/26] Migration complete --- pydantic-migration-plan.md | 10 + src/mxcp/server/admin/endpoints/endpoints.py | 38 +- src/mxcp/server/admin/service.py | 4 +- .../server/definitions/endpoints/_types.py | 157 ------ .../server/definitions/endpoints/loader.py | 233 +++----- .../server/definitions/endpoints/models.py | 190 +++++++ .../server/definitions/endpoints/utils.py | 104 ++-- src/mxcp/server/definitions/evals/_types.py | 30 -- src/mxcp/server/definitions/evals/loader.py | 39 +- src/mxcp/server/definitions/evals/models.py | 74 +++ src/mxcp/server/executor/runners/endpoint.py | 90 ++-- src/mxcp/server/executor/runners/test.py | 196 ++++--- src/mxcp/server/executor/runners/tool.py | 36 +- src/mxcp/server/interfaces/cli/drift_check.py | 28 +- .../server/interfaces/cli/drift_snapshot.py | 22 +- src/mxcp/server/interfaces/cli/lint.py | 496 +++++++++--------- src/mxcp/server/interfaces/cli/list.py | 39 +- src/mxcp/server/interfaces/cli/test.py | 131 ++--- src/mxcp/server/interfaces/cli/validate.py | 39 +- src/mxcp/server/interfaces/server/mcp.py | 268 +++++----- .../server/schemas/common-types-schema-1.json | 171 ------ .../server/schemas/drift-report-schema-1.json | 145 ----- .../schemas/drift-snapshot-schema-1.json | 145 ----- src/mxcp/server/schemas/eval-schema-1.json | 111 ---- src/mxcp/server/schemas/prompt-schema-1.json | 76 --- .../server/schemas/resource-schema-1.json | 149 ------ src/mxcp/server/schemas/tool-schema-1.json | 168 ------ src/mxcp/server/services/drift/_types.py | 158 ------ src/mxcp/server/services/drift/checker.py | 165 +++--- src/mxcp/server/services/drift/models.py | 96 ++++ src/mxcp/server/services/drift/snapshot.py | 99 ++-- .../server/services/endpoints/__init__.py | 7 + src/mxcp/server/services/endpoints/models.py | 26 + src/mxcp/server/services/endpoints/service.py | 103 ++-- .../server/services/endpoints/validator.py | 445 +++++++--------- src/mxcp/server/services/evals/service.py | 269 +++++----- src/mxcp/server/services/tests/__init__.py | 16 +- src/mxcp/server/services/tests/models.py | 52 ++ src/mxcp/server/services/tests/service.py | 112 ++-- tests/misc/test_no_dict_model_unions.py | 23 + tests/sdk/validator/test_schema_comparison.py | 56 +- .../fixtures/tester/prompts/valid_prompt.yml | 21 +- tests/server/test_drift_check.py | 110 ++-- tests/server/test_endpoint_execution.py | 18 +- tests/server/test_evals.py | 36 +- tests/server/test_evals_tool_executor.py | 174 +++--- tests/server/test_list.py | 76 ++- tests/server/test_mcp.py | 24 +- tests/server/test_runtime.py | 44 +- tests/server/test_secrets.py | 23 +- tests/server/test_tester.py | 54 +- tests/server/test_tester_assertions.py | 126 ++--- tests/server/test_validation.py | 75 +-- 53 files changed, 2300 insertions(+), 3297 deletions(-) delete mode 100644 src/mxcp/server/definitions/endpoints/_types.py create mode 100644 src/mxcp/server/definitions/endpoints/models.py delete mode 100644 src/mxcp/server/definitions/evals/_types.py create mode 100644 src/mxcp/server/definitions/evals/models.py delete mode 100644 src/mxcp/server/schemas/common-types-schema-1.json delete mode 100644 src/mxcp/server/schemas/drift-report-schema-1.json delete mode 100644 src/mxcp/server/schemas/drift-snapshot-schema-1.json delete mode 100644 src/mxcp/server/schemas/eval-schema-1.json delete mode 100644 src/mxcp/server/schemas/prompt-schema-1.json delete mode 100644 src/mxcp/server/schemas/resource-schema-1.json delete mode 100644 src/mxcp/server/schemas/tool-schema-1.json delete mode 100644 src/mxcp/server/services/drift/_types.py create mode 100644 src/mxcp/server/services/drift/models.py create mode 100644 src/mxcp/server/services/endpoints/models.py create mode 100644 src/mxcp/server/services/tests/models.py create mode 100644 tests/misc/test_no_dict_model_unions.py diff --git a/pydantic-migration-plan.md b/pydantic-migration-plan.md index cf4763f1..045ad6d0 100644 --- a/pydantic-migration-plan.md +++ b/pydantic-migration-plan.md @@ -120,3 +120,13 @@ Create one epic per phase with child tasks for: Progress through the phases only after the previous phase meets its exit criteria to avoid overlapping risk areas. This ensures we maintain a fully typed, immutable configuration surface before tackling downstream definitions. +--- + +## Post-Migration Cleanup (Completed) + +- **Structured outputs everywhere** – Test runner, endpoint validation, and CLI lint now emit dedicated Pydantic models. Sanitizers such as `_sanitize_test_results` were removed, and JSON output is derived via `model_dump()` only at the final serialization boundary. +- **No more dict↔model unions in server code** – Inputs are validated upfront (e.g., policy parsing, schema helpers), and a regression test (`tests/misc/test_no_dict_model_unions.py`) guards against reintroducing `dict[str, Any] | Model` type hints. Union uses remain only in SDK-facing code where dicts are part of the public contract. +- **Metadata remains optional by design** – The models preserve existing semantics, while lint rules continue to warn when authors omit descriptions/tests/examples. This matches the long-standing behavior without forcing breaking schema changes. + +With these tightening steps and the automated guard in place, the Pydantic migration is fully complete. + diff --git a/src/mxcp/server/admin/endpoints/endpoints.py b/src/mxcp/server/admin/endpoints/endpoints.py index c22788b6..c45fdff8 100644 --- a/src/mxcp/server/admin/endpoints/endpoints.py +++ b/src/mxcp/server/admin/endpoints/endpoints.py @@ -70,26 +70,34 @@ async def list_endpoints() -> EndpointListResponse: elif endpoint_def: # Extract endpoint data from definition endpoint_type: Literal["tool", "resource", "prompt"] | None = None - endpoint_data = None enabled = True - if "tool" in endpoint_def: + if endpoint_def.tool is not None: endpoint_type = "tool" - endpoint_data = endpoint_def["tool"] - elif "resource" in endpoint_def: + endpoint_data = endpoint_def.tool + name = endpoint_data.name + description = endpoint_data.description + language = endpoint_data.language + enabled = endpoint_data.enabled + elif endpoint_def.resource is not None: endpoint_type = "resource" - endpoint_data = endpoint_def["resource"] - elif "prompt" in endpoint_def: + endpoint_data = endpoint_def.resource + name = endpoint_data.name or endpoint_data.uri + description = endpoint_data.description + language = endpoint_data.language + enabled = endpoint_data.enabled + elif endpoint_def.prompt is not None: endpoint_type = "prompt" - endpoint_data = endpoint_def["prompt"] - - if endpoint_data: - enabled = bool(endpoint_data.get("enabled", True)) - - # Extract metadata - name = endpoint_data.get("name") if endpoint_data else None - description = endpoint_data.get("description") if endpoint_data else None - language = endpoint_data.get("language") if endpoint_data else None + endpoint_data = endpoint_def.prompt + name = endpoint_data.name + description = endpoint_data.description + language = None + enabled = endpoint_data.enabled + else: + endpoint_data = None + name = None + description = None + language = None endpoints.append( EndpointMetadata( diff --git a/src/mxcp/server/admin/service.py b/src/mxcp/server/admin/service.py index 0cdba903..4c3db5f5 100644 --- a/src/mxcp/server/admin/service.py +++ b/src/mxcp/server/admin/service.py @@ -12,7 +12,7 @@ from mxcp.sdk.audit.backends.noop import NoOpAuditBackend from mxcp.server.core.config.models import SiteConfigModel -from mxcp.server.definitions.endpoints._types import EndpointDefinition +from mxcp.server.definitions.endpoints.models import EndpointDefinitionModel from .models import ConfigResponse, EndpointCounts, Features @@ -112,7 +112,7 @@ def get_config_snapshot(self) -> ConfigResponse: transport=self._server.transport, ) - def discover_endpoints(self) -> list[tuple[Path, EndpointDefinition | None, str | None]]: + def discover_endpoints(self) -> list[tuple[Path, EndpointDefinitionModel | None, str | None]]: """ Discover all endpoints using the server's endpoint loader. diff --git a/src/mxcp/server/definitions/endpoints/_types.py b/src/mxcp/server/definitions/endpoints/_types.py deleted file mode 100644 index 2355d1c9..00000000 --- a/src/mxcp/server/definitions/endpoints/_types.py +++ /dev/null @@ -1,157 +0,0 @@ -from typing import Any, Literal, Optional, TypedDict - -from mxcp.server.interfaces.cli._types import TestResults - - -class SourceDefinition(TypedDict, total=False): - code: str - file: str - language: str # Optional, rarely used - - -class TestArgument(TypedDict): - key: str - value: object - - -class TestDefinition(TypedDict): - name: str - description: str | None - arguments: list[TestArgument] - result: object | None - user_context: dict[str, Any] | None # User context for policy testing - result_contains: object | None # Partial match for objects/arrays - result_not_contains: list[str] | None # Fields that should NOT exist - result_contains_item: object | None # At least one array item matches - result_contains_all: list[object] | None # All items must be present (any order) - result_length: int | None # Array must have specific length - result_contains_text: str | None # Substring match for strings - - -class TypeDefinition(TypedDict): - type: str - format: str | None # email, uri, date, time, date-time, duration, timestamp - sensitive: bool | None # Whether this field contains sensitive data - minLength: int | None - maxLength: int | None - minimum: float | None - maximum: float | None - exclusiveMinimum: float | None - exclusiveMaximum: float | None - multipleOf: float | None - minItems: int | None - maxItems: int | None - uniqueItems: bool | None - items: Optional["TypeDefinition"] - properties: dict[str, "TypeDefinition"] | None - required: list[str] | None - additionalProperties: ( - bool | None - ) # Whether to allow additional properties not defined in the schema - - -class ParamDefinition(TypedDict): - name: str - type: str - description: str - default: object | None - examples: list[object] | None - enum: list[object] | None - # Type constraints inherited from TypeDefinition - format: str | None - sensitive: bool | None # Whether this parameter contains sensitive data - minLength: int | None - maxLength: int | None - minItems: int | None - maxItems: int | None - items: TypeDefinition | None - properties: dict[str, TypeDefinition] | None - required: list[str] | None - - -class PolicyRule(TypedDict): - condition: str - action: Literal["deny", "filter_fields", "mask_fields", "filter_sensitive_fields"] - reason: str | None - fields: list[str] | None # For filter_fields and mask_fields actions - - -class PoliciesDefinition(TypedDict): - input: list[PolicyRule] | None - output: list[PolicyRule] | None - - -class ToolDefinition(TypedDict): - name: str - description: str | None - tags: list[str] | None - annotations: dict[str, Any] | None - parameters: list[ParamDefinition] | None - return_: TypeDefinition | None - language: Literal["sql"] | None - source: SourceDefinition - enabled: bool | None - tests: list[TestDefinition] | None - policies: PoliciesDefinition | None - - -class ResourceDefinition(TypedDict): - uri: str - description: str | None - tags: list[str] | None - mime_type: str | None - parameters: list[ParamDefinition] | None - return_: TypeDefinition | None - language: Literal["sql"] | None - source: SourceDefinition - enabled: bool | None - tests: list[TestDefinition] | None - policies: PoliciesDefinition | None - - -class PromptMessage(TypedDict): - prompt: str - role: str | None - type: str | None - - -class PromptDefinition(TypedDict): - name: str - description: str | None - tags: list[str] | None - parameters: list[ParamDefinition] | None - return_: TypeDefinition | None - messages: list[PromptMessage] - enabled: bool | None - tests: list[TestDefinition] | None - policies: PoliciesDefinition | None - - -class EndpointDefinition(TypedDict): - mxcp: str - tool: ToolDefinition | None - resource: ResourceDefinition | None - prompt: PromptDefinition | None - metadata: dict[str, Any] | None - - -class EndpointTestsResultRequired(TypedDict): - """Required fields for endpoint test result.""" - - endpoint: str - path: str - - -class EndpointTestsResult(EndpointTestsResultRequired, total=False): - """Result from testing a single endpoint.""" - - test_results: TestResults - error: str - - -class AllEndpointsTestResults(TypedDict): - """Results from testing all endpoints.""" - - status: str # "ok", "error", "failed" - tests_run: int - endpoints: list[EndpointTestsResult] diff --git a/src/mxcp/server/definitions/endpoints/loader.py b/src/mxcp/server/definitions/endpoints/loader.py index a3b4410e..959b5c53 100644 --- a/src/mxcp/server/definitions/endpoints/loader.py +++ b/src/mxcp/server/definitions/endpoints/loader.py @@ -1,16 +1,14 @@ -import json import logging from dataclasses import dataclass from pathlib import Path -from typing import Any, cast +from typing import Any import yaml -from jsonschema import ValidationError, validate -from referencing import Registry, Resource +from pydantic import ValidationError from mxcp.server.core.config.models import SiteConfigModel from mxcp.server.core.config.site_config import find_repo_root -from mxcp.server.definitions.endpoints._types import EndpointDefinition +from mxcp.server.definitions.endpoints.models import EndpointDefinitionModel from mxcp.server.definitions.endpoints.utils import get_endpoint_name_or_uri # Configure logging @@ -18,36 +16,19 @@ def extract_validation_error(error: ValidationError | Exception | str) -> str: - """Extract a concise validation error message from jsonschema error. + """Extract a concise validation error message from pydantic/jsonschema error.""" - Args: - error: The ValidationError object, Exception, or error message string - - Returns: - A concise error message with key path when available - """ - # Handle ValidationError objects directly if isinstance(error, ValidationError): - # Build key path from absolute_path - if error.absolute_path: - path_parts = [] - for part in error.absolute_path: - if isinstance(part, str): - path_parts.append(part) - else: - path_parts.append(str(part)) - key_path = ".".join(path_parts) - return f"{key_path}: {error.message}" - else: - return error.message - - # Handle other exceptions by converting to string - if isinstance(error, Exception): - error_msg = str(error) - else: - error_msg = error - - # For type errors in string format (fallback) + issues = error.errors() + if issues: + first = issues[0] + loc = ".".join(str(part) for part in first.get("loc", [])) + message = first.get("msg", str(error)) + return f"{loc}: {message}" if loc else message + return str(error) + + error_msg = str(error) if isinstance(error, Exception) else error + if "is not of a type" in error_msg: parts = error_msg.split("'") if len(parts) >= 4: @@ -55,13 +36,12 @@ def extract_validation_error(error: ValidationError | Exception | str) -> str: expected_type = parts[3] return f"Invalid type for {field}: expected {expected_type}" - # For other validation errors, return just the first line return error_msg.split("\n")[0] @dataclass class EndpointLoader: - _endpoints: dict[str, EndpointDefinition] + _endpoints: dict[str, EndpointDefinitionModel] _site_config: SiteConfigModel _repo_root: Path @@ -70,7 +50,7 @@ def __init__(self, site_config: SiteConfigModel): self._endpoints = {} self._repo_root = find_repo_root() - def _is_endpoint_enabled(self, endpoint_data: EndpointDefinition) -> bool: + def _is_endpoint_enabled(self, endpoint_data: EndpointDefinitionModel) -> bool: """Check if an endpoint is enabled. Args: @@ -79,23 +59,22 @@ def _is_endpoint_enabled(self, endpoint_data: EndpointDefinition) -> bool: Returns: True if the endpoint is enabled (default), False otherwise """ - # Check each endpoint type for the enabled field - if "tool" in endpoint_data: - tool_def = endpoint_data.get("tool") - if tool_def: - return bool(tool_def.get("enabled", True)) - elif "resource" in endpoint_data: - resource_def = endpoint_data.get("resource") - if resource_def: - return bool(resource_def.get("enabled", True)) - elif "prompt" in endpoint_data: - prompt_def = endpoint_data.get("prompt") - if prompt_def: - return bool(prompt_def.get("enabled", True)) + tool_def = endpoint_data.tool + if tool_def is not None: + return bool(tool_def.enabled) + + resource_def = endpoint_data.resource + if resource_def is not None: + return bool(resource_def.enabled) + + prompt_def = endpoint_data.prompt + if prompt_def is not None: + return bool(prompt_def.enabled) + return True def _check_duplicate_endpoint_names( - self, endpoints: list[tuple[Path, EndpointDefinition | None, str | None]] + self, endpoints: list[tuple[Path, EndpointDefinitionModel | None, str | None]] ) -> dict[Path, str]: """Check for duplicate endpoint names/URIs across all endpoints. @@ -115,7 +94,8 @@ def _check_duplicate_endpoint_names( # Find endpoint type and extract name/uri for endpoint_type in ("tool", "prompt", "resource"): - if endpoint_type in endpoint: + endpoint_obj = getattr(endpoint, endpoint_type, None) + if endpoint_obj is not None: name = get_endpoint_name_or_uri(endpoint, endpoint_type) name_to_info.setdefault(name, []).append((path, endpoint_type)) break @@ -149,81 +129,51 @@ def _check_duplicate_endpoint_names( return duplicate_errors - def _load_schema(self, schema_name: str) -> tuple[dict[str, Any], Registry]: - """Load a schema file by name and create a registry for cross-file references""" - schemas_dir = (Path(__file__).parent.parent.parent / "schemas").resolve() - schema_path = schemas_dir / schema_name - - with open(schema_path) as f: - schema = json.load(f) - - # Load common schema for registry - common_schema_path = schemas_dir / "common-types-schema-1.json" - with open(common_schema_path) as common_file: - common_schema = json.load(common_file) - - # Create registry with common schema - # The URI needs to match what's expected in the $ref - registry = Registry().with_resource( - uri="common-types-schema-1.json", resource=Resource.from_contents(common_schema) - ) - - return schema, registry - def _discover_in_directory( - self, directory: Path, schema_name: str, endpoint_type: str - ) -> list[tuple[Path, EndpointDefinition | None, str | None]]: + self, directory: Path, endpoint_type: str + ) -> list[tuple[Path, EndpointDefinitionModel | None, str | None]]: """Discover endpoint files in a specific directory. Args: directory: Directory to search in - schema_name: Name of the schema file to validate against endpoint_type: Type of endpoint (tool, resource, prompt) Returns: List of tuples where each tuple contains: - file_path: Path to the endpoint file - - endpoint_dict: The loaded endpoint dictionary if successful, None if failed + - endpoint_dict: The loaded endpoint definition if successful, None if failed - error_message: Error message if loading failed, None if successful """ - endpoints: list[tuple[Path, EndpointDefinition | None, str | None]] = [] + endpoints: list[tuple[Path, EndpointDefinitionModel | None, str | None]] = [] # Skip if directory doesn't exist if not directory.exists(): logger.info(f"Directory {directory} does not exist, skipping {endpoint_type} discovery") return endpoints - schema, registry = self._load_schema(schema_name) - for f in directory.rglob("*.yml"): try: with open(f) as file: data = yaml.safe_load(file) - # Check if this is a mxcp endpoint file - if "mxcp" not in data: - logger.warning( - f"Skipping {f}: Not a mxcp endpoint file (missing 'mxcp' field)" - ) - continue + if not data: + raise ValueError("Endpoint file is empty") + + model = EndpointDefinitionModel.model_validate(data) - # Check if it has the expected endpoint type - if endpoint_type not in data: + if getattr(model, endpoint_type, None) is None: logger.warning( f"Skipping {f}: Expected {endpoint_type} definition but not found" ) continue - # Validate against schema with registry - validate(instance=data, schema=schema, registry=registry) - # Check if endpoint is enabled - if not self._is_endpoint_enabled(data): + if not self._is_endpoint_enabled(model): logger.info(f"Skipping disabled endpoint: {f}") continue - endpoints.append((f, cast(EndpointDefinition, data), None)) - self._endpoints[str(f)] = cast(EndpointDefinition, data) + endpoints.append((f, model, None)) + self._endpoints[str(f)] = model except ValidationError as e: error_msg = extract_validation_error(e) endpoints.append((f, None, error_msg)) @@ -233,25 +183,25 @@ def _discover_in_directory( return endpoints - def discover_tools(self) -> list[tuple[Path, EndpointDefinition | None, str | None]]: + def discover_tools(self) -> list[tuple[Path, EndpointDefinitionModel | None, str | None]]: """Discover all tool definition files""" tools_path = self._site_config.paths.tools tools_dir = self._repo_root / str(tools_path) - return self._discover_in_directory(tools_dir, "tool-schema-1.json", "tool") + return self._discover_in_directory(tools_dir, "tool") - def discover_resources(self) -> list[tuple[Path, EndpointDefinition | None, str | None]]: + def discover_resources(self) -> list[tuple[Path, EndpointDefinitionModel | None, str | None]]: """Discover all resource definition files""" resources_path = self._site_config.paths.resources resources_dir = self._repo_root / str(resources_path) - return self._discover_in_directory(resources_dir, "resource-schema-1.json", "resource") + return self._discover_in_directory(resources_dir, "resource") - def discover_prompts(self) -> list[tuple[Path, EndpointDefinition | None, str | None]]: + def discover_prompts(self) -> list[tuple[Path, EndpointDefinitionModel | None, str | None]]: """Discover all prompt definition files""" prompts_path = self._site_config.paths.prompts prompts_dir = self._repo_root / str(prompts_path) - return self._discover_in_directory(prompts_dir, "prompt-schema-1.json", "prompt") + return self._discover_in_directory(prompts_dir, "prompt") - def discover_endpoints(self) -> list[tuple[Path, EndpointDefinition | None, str | None]]: + def discover_endpoints(self) -> list[tuple[Path, EndpointDefinitionModel | None, str | None]]: """Discover all endpoint files from their respective directories. Returns: @@ -280,13 +230,13 @@ def discover_endpoints(self) -> list[tuple[Path, EndpointDefinition | None, str return all_endpoints - def get_endpoint(self, path: str) -> EndpointDefinition | None: + def get_endpoint(self, path: str) -> EndpointDefinitionModel | None: """Get a specific endpoint by its path""" return self._endpoints.get(path) def load_endpoint( self, endpoint_type: str, name: str - ) -> tuple[Path, EndpointDefinition] | None: + ) -> tuple[Path, EndpointDefinitionModel] | None: """Load a specific endpoint by type and name Args: @@ -294,24 +244,18 @@ def load_endpoint( name: Name or identifier of the endpoint Returns: - Optional[tuple[Path, EndpointDefinition]]: A tuple of (file_path, endpoint_data) if found, None otherwise + Optional[tuple[Path, EndpointDefinitionModel]]: Matching endpoint path and definition """ try: logger.debug(f"Looking for endpoint type: {endpoint_type}, name: {name}") # Determine which directory to search based on endpoint type if endpoint_type == "tool": - tools_path = self._site_config.paths.tools - search_dir = self._repo_root / str(tools_path) - schema_name = "tool-schema-1.json" + search_dir = self._repo_root / str(self._site_config.paths.tools) elif endpoint_type == "resource": - resources_path = self._site_config.paths.resources - search_dir = self._repo_root / str(resources_path) - schema_name = "resource-schema-1.json" + search_dir = self._repo_root / str(self._site_config.paths.resources) elif endpoint_type == "prompt": - prompts_path = self._site_config.paths.prompts - search_dir = self._repo_root / str(prompts_path) - schema_name = "prompt-schema-1.json" + search_dir = self._repo_root / str(self._site_config.paths.prompts) else: logger.error(f"Unknown endpoint type: {endpoint_type}") return None @@ -320,57 +264,30 @@ def load_endpoint( logger.error(f"Directory {search_dir} does not exist") return None - schema, registry = self._load_schema(schema_name) - # Search in the appropriate directory for f in search_dir.rglob("*.yml"): logger.debug(f"Checking file: {f}") try: with open(f) as file: - data = yaml.safe_load(file) - logger.debug(f"YAML contents keys: {list(data.keys())}") - - # Check if this is a mxcp endpoint file - if "mxcp" not in data: - logger.debug( - f"Skipping {f}: Not a mxcp endpoint file (missing 'mxcp' field)" - ) - continue - - # Check if it has the expected endpoint type - if endpoint_type not in data: - logger.debug( - f"Skipping {f}: Expected {endpoint_type} definition but not found" - ) - continue - - # Check if this is the endpoint we're looking for - endpoint_data = data[endpoint_type] - if ( - endpoint_type == "tool" - and endpoint_data.get("name") == name - or endpoint_type == "resource" - and endpoint_data.get("uri") == name - or endpoint_type == "prompt" - and endpoint_data.get("name") == name - ): - found = True - else: - found = False - - if found: - logger.debug(f"Found matching endpoint in {f}") - - # Check if endpoint is enabled - if not self._is_endpoint_enabled(data): - logger.info(f"Skipping disabled endpoint: {f}") - continue - - # Validate against schema with registry - validate(instance=data, schema=schema, registry=registry) - self._endpoints[str(f)] = data - return (f, data) + data = yaml.safe_load(file) or {} + + model = EndpointDefinitionModel.model_validate(data) + endpoint_obj = getattr(model, endpoint_type, None) + if endpoint_obj is None: + continue + + identifier = endpoint_obj.uri if endpoint_type == "resource" else endpoint_obj.name + if identifier != name: + continue + + if not self._is_endpoint_enabled(model): + logger.info(f"Skipping disabled endpoint: {f}") + continue + self._endpoints[str(f)] = model + return (f, model) + except ValidationError as e: + logger.error(f"Failed to load endpoint {f}: {extract_validation_error(e)}") except Exception as e: logger.error(f"Warning: Failed to load endpoint {f}: {e}") continue @@ -382,6 +299,6 @@ def load_endpoint( logger.error(f"Warning: Failed to load endpoint {endpoint_type}/{name}: {e}") return None - def list_endpoints(self) -> list[EndpointDefinition]: + def list_endpoints(self) -> list[EndpointDefinitionModel]: """List all discovered endpoints""" return list(self._endpoints.values()) diff --git a/src/mxcp/server/definitions/endpoints/models.py b/src/mxcp/server/definitions/endpoints/models.py new file mode 100644 index 00000000..ccb592af --- /dev/null +++ b/src/mxcp/server/definitions/endpoints/models.py @@ -0,0 +1,190 @@ +from __future__ import annotations + +from typing import Any, Literal + +from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator + + +class DefinitionModel(BaseModel): + """Base model for endpoint definitions enforcing immutability and strict fields.""" + + model_config = ConfigDict(extra="forbid", frozen=True, populate_by_name=True) + + +class TypeDefinitionModel(DefinitionModel): + name: str | None = None + type: Literal["string", "number", "integer", "boolean", "array", "object"] + format: ( + Literal["email", "uri", "date", "time", "date-time", "duration", "timestamp"] | None + ) = None + description: str | None = None + default: Any | None = None + examples: list[Any] | None = None + enum: list[Any] | None = None + sensitive: bool = False + minLength: int | None = Field(default=None, ge=0) + maxLength: int | None = Field(default=None, ge=0) + pattern: str | None = None + minimum: float | None = None + maximum: float | None = None + exclusiveMinimum: float | None = None + exclusiveMaximum: float | None = None + multipleOf: float | None = None + minItems: int | None = Field(default=None, ge=0) + maxItems: int | None = Field(default=None, ge=0) + uniqueItems: bool | None = None + items: TypeDefinitionModel | None = None + properties: dict[str, TypeDefinitionModel] | None = None + required: list[str] | None = None + additionalProperties: bool | None = None + + +class ParamDefinitionModel(TypeDefinitionModel): + name: str + description: str | None = None + + @field_validator("name") + @classmethod + def validate_name(cls, value: str) -> str: + if not value: + raise ValueError("Parameter name cannot be empty") + if not value[0].isalpha() and value[0] != "_": + raise ValueError("Parameter name must start with a letter or underscore") + if not all(ch.isalnum() or ch == "_" for ch in value): + raise ValueError("Parameter name must contain only alphanumeric characters or underscores") + return value + + +class SourceDefinitionModel(DefinitionModel): + code: str | None = None + file: str | None = None + language: Literal["sql", "python"] | None = None + + @model_validator(mode="after") + def validate_source(self) -> "SourceDefinitionModel": + if bool(self.code) == bool(self.file): + raise ValueError("Source must provide exactly one of 'code' or 'file'") + return self + + +class TestArgumentModel(DefinitionModel): + key: str + value: Any + + +class TestDefinitionModel(DefinitionModel): + name: str + description: str | None = None + arguments: list[TestArgumentModel] + result: Any | None = None + user_context: dict[str, Any] | None = None + result_contains: Any | None = None + result_not_contains: list[str] | None = None + result_contains_item: Any | None = None + result_contains_all: list[Any] | None = None + result_length: int | None = Field(default=None, ge=0) + result_contains_text: str | None = None + + +class PolicyRuleModel(DefinitionModel): + condition: str + action: Literal["deny", "filter_fields", "mask_fields", "filter_sensitive_fields"] + reason: str | None = None + fields: list[str] | None = None + + +class PoliciesDefinitionModel(DefinitionModel): + input: list[PolicyRuleModel] | None = None + output: list[PolicyRuleModel] | None = None + + +class ToolAnnotationsModel(DefinitionModel): + title: str | None = None + readOnlyHint: bool | None = None + destructiveHint: bool | None = None + idempotentHint: bool | None = None + openWorldHint: bool | None = None + + +class ToolDefinitionModel(DefinitionModel): + name: str + description: str | None = None + tags: list[str] | None = None + annotations: ToolAnnotationsModel | None = None + parameters: list[ParamDefinitionModel] | None = None + return_: TypeDefinitionModel | None = Field(default=None, alias="return") + language: Literal["sql", "python"] = "sql" + source: SourceDefinitionModel + enabled: bool = True + tests: list[TestDefinitionModel] | None = None + policies: PoliciesDefinitionModel | None = None + + +class ResourceDefinitionModel(DefinitionModel): + uri: str + name: str | None = None + description: str | None = None + tags: list[str] | None = None + mime_type: str | None = None + parameters: list[ParamDefinitionModel] | None = None + return_: TypeDefinitionModel | None = Field(default=None, alias="return") + language: Literal["sql", "python"] = "sql" + source: SourceDefinitionModel + enabled: bool = True + tests: list[TestDefinitionModel] | None = None + policies: PoliciesDefinitionModel | None = None + + +class PromptMessageModel(DefinitionModel): + prompt: str + role: str | None = None + type: str | None = None + + +class PromptDefinitionModel(DefinitionModel): + name: str + description: str | None = None + tags: list[str] | None = None + parameters: list[ParamDefinitionModel] | None = None + return_: TypeDefinitionModel | None = Field(default=None, alias="return") + messages: list[PromptMessageModel] + enabled: bool = True + tests: list[TestDefinitionModel] | None = None + policies: PoliciesDefinitionModel | None = None + + +class EndpointDefinitionModel(DefinitionModel): + mxcp: int = 1 + tool: ToolDefinitionModel | None = None + resource: ResourceDefinitionModel | None = None + prompt: PromptDefinitionModel | None = None + metadata: dict[str, Any] | None = None + + @model_validator(mode="before") + @classmethod + def coerce_mxcp(cls, data: Any) -> Any: + if isinstance(data, dict) and "mxcp" in data: + value = data["mxcp"] + if isinstance(value, str): + try: + data["mxcp"] = int(float(value)) + except ValueError: + pass + return data + + @model_validator(mode="after") + def ensure_endpoint_type(self) -> "EndpointDefinitionModel": + present = [ + bool(self.tool), + bool(self.resource), + bool(self.prompt), + ] + if not any(present): + raise ValueError("Endpoint definition must include a tool, resource, or prompt definition") + if sum(1 for flag in present if flag) > 1: + raise ValueError("Endpoint definition must contain exactly one endpoint type") + return self + + +TypeDefinitionModel.model_rebuild() + diff --git a/src/mxcp/server/definitions/endpoints/utils.py b/src/mxcp/server/definitions/endpoints/utils.py index 2d69d8a3..57670c25 100644 --- a/src/mxcp/server/definitions/endpoints/utils.py +++ b/src/mxcp/server/definitions/endpoints/utils.py @@ -8,7 +8,7 @@ from enum import Enum from pathlib import Path -from ._types import EndpointDefinition, SourceDefinition +from .models import EndpointDefinitionModel, SourceDefinitionModel logger = logging.getLogger(__name__) @@ -22,7 +22,7 @@ class EndpointType(Enum): def get_endpoint_source_code( - endpoint_definition: EndpointDefinition, + endpoint_definition: EndpointDefinitionModel, endpoint_type: str, endpoint_file_path: Path, repo_root: Path, @@ -41,38 +41,33 @@ def get_endpoint_source_code( Raises: ValueError: If no source code found in endpoint definition """ - # Get source based on endpoint type - source: SourceDefinition | None = None - if endpoint_type == "tool": - tool_def = endpoint_definition.get("tool") + tool_def = endpoint_definition.tool if not tool_def: raise ValueError("No tool definition found") - source = tool_def.get("source") + source = tool_def.source elif endpoint_type == "resource": - resource_def = endpoint_definition.get("resource") + resource_def = endpoint_definition.resource if not resource_def: raise ValueError("No resource definition found") - source = resource_def.get("source") + source = resource_def.source else: raise ValueError("Prompts don't have source code") - if not source: - raise ValueError(f"No source definition found in {endpoint_type}") - if "code" in source: - return source["code"] - elif "file" in source: - source_path = Path(source["file"]) + if source.code is not None: + return source.code + + if source.file is not None: + source_path = Path(source.file) if source_path.is_absolute(): full_path = repo_root / source_path.relative_to("/") else: full_path = endpoint_file_path.parent / source_path return full_path.read_text() - else: - raise ValueError("No source code found in endpoint definition") + raise ValueError("No source code found in endpoint definition") -def extract_source_info(source: SourceDefinition) -> tuple[str, str]: +def extract_source_info(source: SourceDefinitionModel) -> tuple[str, str]: """Extract source code and determine if it's inline code or file reference. Args: @@ -86,15 +81,14 @@ def extract_source_info(source: SourceDefinition) -> tuple[str, str]: Raises: ValueError: If no source code or file found """ - if "code" in source: - return ("code", source["code"]) - elif "file" in source: - return ("file", source["file"]) - else: - raise ValueError("No source code or file found in source definition") + if source.code is not None: + return ("code", source.code) + if source.file is not None: + return ("file", source.file) + raise ValueError("No source code or file found in source definition") -def detect_language_from_source(source: SourceDefinition, file_path: str | None = None) -> str: +def detect_language_from_source(source: SourceDefinitionModel, file_path: str | None = None) -> str: """Detect programming language from source definition. Args: @@ -105,10 +99,10 @@ def detect_language_from_source(source: SourceDefinition, file_path: str | None Language string ("python", "sql", etc.) """ # Check if language is explicitly specified - if "language" in source: - return source["language"] + if source.language: + return source.language # Try to infer from file extension - path_to_check = file_path or source.get("file") + path_to_check = file_path or source.file if path_to_check: if path_to_check.endswith((".py", ".python")): return "python" @@ -135,7 +129,7 @@ def resolve_file_path(file_path: str, endpoint_file_path: Path, repo_root: Path) return endpoint_file_path.parent / source_path -def get_endpoint_name_or_uri(endpoint_definition: EndpointDefinition, endpoint_type: str) -> str: +def get_endpoint_name_or_uri(endpoint_definition: EndpointDefinitionModel, endpoint_type: str) -> str: """Get the name or URI identifier for an endpoint. Args: @@ -149,29 +143,25 @@ def get_endpoint_name_or_uri(endpoint_definition: EndpointDefinition, endpoint_t ValueError: If endpoint type not found in definition """ if endpoint_type == "tool": - tool_def = endpoint_definition.get("tool") + tool_def = endpoint_definition.tool if not tool_def: raise ValueError("No tool definition found") - name = tool_def.get("name", "unnamed") - return name - elif endpoint_type == "resource": - resource_def = endpoint_definition.get("resource") + return tool_def.name + if endpoint_type == "resource": + resource_def = endpoint_definition.resource if not resource_def: raise ValueError("No resource definition found") - uri = resource_def.get("uri", "unknown") - return uri - elif endpoint_type == "prompt": - prompt_def = endpoint_definition.get("prompt") + return resource_def.uri + if endpoint_type == "prompt": + prompt_def = endpoint_definition.prompt if not prompt_def: raise ValueError("No prompt definition found") - name = prompt_def.get("name", "unnamed") - return name - else: - raise ValueError(f"Unknown endpoint type: {endpoint_type}") + return prompt_def.name + raise ValueError(f"Unknown endpoint type: {endpoint_type}") def prepare_source_for_execution( - endpoint_definition: EndpointDefinition, + endpoint_definition: EndpointDefinitionModel, endpoint_type: str, endpoint_file_path: Path, repo_root: Path, @@ -192,23 +182,23 @@ def prepare_source_for_execution( Tuple of (language, source_code_or_path) ready for execution """ # Get source and language based on endpoint type - source: SourceDefinition | None = None + source: SourceDefinitionModel | None = None language: str | None = None function_name: str | None = None if endpoint_type == "tool": - tool_def = endpoint_definition.get("tool") + tool_def = endpoint_definition.tool if not tool_def: raise ValueError("No tool definition found") - source = tool_def.get("source") - language = tool_def.get("language") - function_name = tool_def.get("name") + source = tool_def.source + language = tool_def.language + function_name = tool_def.name elif endpoint_type == "resource": - resource_def = endpoint_definition.get("resource") + resource_def = endpoint_definition.resource if not resource_def: raise ValueError("No resource definition found") - source = resource_def.get("source") - language = resource_def.get("language") + source = resource_def.source + language = resource_def.language else: raise ValueError("Prompts don't have source code") @@ -220,11 +210,10 @@ def prepare_source_for_execution( language = detect_language_from_source(source) # Handle source code vs file path - if "code" in source: - # Inline code - return as-is - return (language, source["code"]) - elif "file" in source: - file_path = source["file"] + if source.code is not None: + return (language, source.code) + if source.file is not None: + file_path = source.file if language == "python": # For Python files, return file path for module loading resolved_path = resolve_file_path(file_path, endpoint_file_path, repo_root) @@ -245,5 +234,4 @@ def prepare_source_for_execution( endpoint_definition, endpoint_type, endpoint_file_path, repo_root ) return (language, source_code) - else: - raise ValueError("No source code or file specified in endpoint definition") + raise ValueError("No source code or file specified in endpoint definition") diff --git a/src/mxcp/server/definitions/evals/_types.py b/src/mxcp/server/definitions/evals/_types.py deleted file mode 100644 index 8e64a4cd..00000000 --- a/src/mxcp/server/definitions/evals/_types.py +++ /dev/null @@ -1,30 +0,0 @@ -from typing import Any, TypedDict - - -# Eval assertion types -class MustCallAssertion(TypedDict): - tool: str - args: dict[str, Any] - - -class EvalAssertions(TypedDict, total=False): - must_call: list[MustCallAssertion] | None - must_not_call: list[str] | None # List of tool names that should not be called - answer_contains: list[str] | None # List of strings that should appear in the answer - answer_not_contains: list[str] | None # List of strings that should NOT appear - - -class EvalTest(TypedDict): - name: str - description: str | None - prompt: str - user_context: dict[str, Any] | None # Optional user context for the test - assertions: EvalAssertions - - -class EvalSuite(TypedDict): - mxcp: str # Schema version - suite: str # Suite name - description: str | None - model: str | None # Optional model to use (e.g., "claude-3-opus") - tests: list[EvalTest] diff --git a/src/mxcp/server/definitions/evals/loader.py b/src/mxcp/server/definitions/evals/loader.py index f2531573..6c771115 100644 --- a/src/mxcp/server/definitions/evals/loader.py +++ b/src/mxcp/server/definitions/evals/loader.py @@ -1,21 +1,31 @@ -import json import logging from pathlib import Path -from typing import cast import yaml -from jsonschema import validate +from pydantic import ValidationError from mxcp.server.core.config.models import SiteConfigModel from mxcp.server.core.config.site_config import find_repo_root -from mxcp.server.definitions.evals._types import EvalSuite +from mxcp.server.definitions.evals.models import EvalSuiteModel logger = logging.getLogger(__name__) +def _extract_validation_error(error: ValidationError | Exception) -> str: + if isinstance(error, ValidationError): + issues = error.errors() + if issues: + first = issues[0] + loc = ".".join(str(part) for part in first.get("loc", [])) + msg = first.get("msg", str(error)) + return f"{loc}: {msg}" if loc else msg + return str(error) + return str(error) + + def discover_eval_files( site_config: SiteConfigModel | None = None, -) -> list[tuple[Path, EvalSuite | None, str | None]]: +) -> list[tuple[Path, EvalSuiteModel | None, str | None]]: """Discover all eval files in the configured evals directory. Args: @@ -42,28 +52,23 @@ def discover_eval_files( logger.info(f"Evals directory {evals_dir} does not exist, skipping eval discovery") return results - schema_path = Path(__file__).parent.parent.parent / "schemas" / "eval-schema-1.json" - with open(schema_path) as f: - schema = json.load(f) - # Find all YAML files in the evals directory for file_path in evals_dir.rglob("*.yml"): try: with open(file_path) as f: - data = yaml.safe_load(f) + data = yaml.safe_load(f) or {} # Check if this is a mxcp eval file if "mxcp" not in data: logger.warning(f"Skipping {file_path}: Not a mxcp eval file (missing 'mxcp' field)") continue - # Validate against schema - validate(instance=data, schema=schema) + eval_suite = EvalSuiteModel.model_validate(data) - results.append((file_path, cast(EvalSuite, data), None)) + results.append((file_path, eval_suite, None)) logger.debug(f"Loaded eval file: {file_path}") - except Exception as e: - error_msg = f"Failed to load eval file {file_path}: {str(e)}" + except (ValidationError, Exception) as e: + error_msg = f"Failed to load eval file {file_path}: {_extract_validation_error(e)}" results.append((file_path, None, error_msg)) logger.error(error_msg) @@ -72,7 +77,7 @@ def discover_eval_files( def load_eval_suite( suite_name: str, site_config: SiteConfigModel | None = None -) -> tuple[Path, EvalSuite] | None: +) -> tuple[Path, EvalSuiteModel] | None: """Load a specific eval suite by name. Args: @@ -85,7 +90,7 @@ def load_eval_suite( eval_files = discover_eval_files(site_config) for file_path, eval_suite, error in eval_files: - if error is None and eval_suite and eval_suite.get("suite") == suite_name: + if error is None and eval_suite and eval_suite.suite == suite_name: return (file_path, eval_suite) return None diff --git a/src/mxcp/server/definitions/evals/models.py b/src/mxcp/server/definitions/evals/models.py new file mode 100644 index 00000000..cb9aed8f --- /dev/null +++ b/src/mxcp/server/definitions/evals/models.py @@ -0,0 +1,74 @@ +from __future__ import annotations + +from typing import Any, Literal + +from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator + + +class EvalBaseModel(BaseModel): + """Base model for eval suite data.""" + + model_config = ConfigDict(extra="forbid", frozen=True) + + +class MustCallAssertionModel(EvalBaseModel): + tool: str + args: dict[str, Any] + + +class EvalAssertionsModel(EvalBaseModel): + must_call: list[MustCallAssertionModel] | None = None + must_not_call: list[str] | None = None + answer_contains: list[str] | None = None + answer_not_contains: list[str] | None = None + + +class EvalTestModel(EvalBaseModel): + name: str + description: str | None = None + prompt: str + user_context: dict[str, Any] | None = None + assertions: EvalAssertionsModel + + @field_validator("name") + @classmethod + def validate_name(cls, value: str) -> str: + if not value: + raise ValueError("Eval test name cannot be empty") + if not value[0].isalpha() and value[0] != "_": + raise ValueError("Eval test name must start with a letter or underscore") + if not all(ch.isalnum() or ch == "_" for ch in value): + raise ValueError("Eval test name must contain only alphanumeric characters or underscores") + return value + + +class EvalSuiteModel(EvalBaseModel): + mxcp: int = 1 + suite: str + description: str | None = None + model: Literal["claude-4-opus", "claude-4-sonnet", "gpt-4o", "gpt-4.1"] | None = None + tests: list[EvalTestModel] + + @field_validator("suite") + @classmethod + def validate_suite(cls, value: str) -> str: + if not value: + raise ValueError("Eval suite name cannot be empty") + if not value[0].isalpha() and value[0] != "_": + raise ValueError("Eval suite name must start with a letter or underscore") + if not all(ch.isalnum() or ch == "_" for ch in value): + raise ValueError("Eval suite name must contain only alphanumeric characters or underscores") + return value + + @model_validator(mode="before") + @classmethod + def coerce_mxcp(cls, data: Any) -> Any: + if isinstance(data, dict) and "mxcp" in data: + value = data["mxcp"] + if isinstance(value, str): + try: + data["mxcp"] = int(float(value)) + except ValueError: + pass + return data + diff --git a/src/mxcp/server/executor/runners/endpoint.py b/src/mxcp/server/executor/runners/endpoint.py index 9623d0bc..1410c9e8 100644 --- a/src/mxcp/server/executor/runners/endpoint.py +++ b/src/mxcp/server/executor/runners/endpoint.py @@ -19,10 +19,10 @@ from mxcp.sdk.executor.interfaces import ExecutionEngine from mxcp.sdk.validator import TypeValidator from mxcp.server.core.config.models import SiteConfigModel, UserConfigModel -from mxcp.server.definitions.endpoints._types import ( - EndpointDefinition, - PromptDefinition, - TypeDefinition, +from mxcp.server.definitions.endpoints.models import ( + EndpointDefinitionModel, + PromptDefinitionModel, + TypeDefinitionModel, ) from mxcp.server.definitions.endpoints.utils import prepare_source_for_execution @@ -30,7 +30,7 @@ async def execute_prompt_with_validation( - prompt_def: PromptDefinition, params: dict[str, Any], skip_output_validation: bool + prompt_def: PromptDefinitionModel, params: dict[str, Any], skip_output_validation: bool ) -> Any: """Execute prompt endpoint with proper validation and template rendering. @@ -40,33 +40,42 @@ async def execute_prompt_with_validation( validated_params = params if not skip_output_validation: - input_schema = prompt_def.get("parameters") - if input_schema: - # Use correct validator and schema structure (same as SDK executor) - schema_dict = {"input": {"parameters": input_schema}} + input_schema_models = prompt_def.parameters or [] + if input_schema_models: + schema_dict = { + "input": { + "parameters": [ + param.model_dump(mode="python", exclude_unset=True, by_alias=True) + for param in input_schema_models + ] + } + } validator = TypeValidator.from_dict(schema_dict) validated_params = validator.validate_input(params) else: # Apply defaults even when skipping validation (for template rendering) - param_defs = prompt_def.get("parameters") or [] + param_defs = prompt_def.parameters or [] validated_params = params.copy() for param_def in param_defs: - name = param_def["name"] - if name not in validated_params and "default" in param_def: - validated_params[name] = param_def["default"] + name = param_def.name + if ( + name not in validated_params + and "default" in param_def.model_fields_set + ): + validated_params[name] = param_def.default # Template rendering with validated parameters - messages = prompt_def.get("messages", []) + messages = prompt_def.messages or [] processed_messages = [] for msg in messages: - template = Template(msg["prompt"]) + template = Template(msg.prompt) processed_prompt = template.render(**validated_params) processed_msg = { "prompt": processed_prompt, - "role": msg.get("role"), - "type": msg.get("type"), + "role": msg.role, + "type": msg.type, } processed_messages.append(processed_msg) @@ -74,7 +83,7 @@ async def execute_prompt_with_validation( async def execute_code_with_engine( - endpoint_definition: EndpointDefinition, + endpoint_definition: EndpointDefinitionModel, endpoint_type: str, endpoint_file_path: Path, repo_root: Path, @@ -114,31 +123,34 @@ async def execute_code_with_engine( execution_context.set("request_headers", request_headers) # Get validation schemas - SDK executor handles input validation internally input_schema: list[dict[str, Any]] | None = None - output_schema: TypeDefinition | None = None - return_def: TypeDefinition | None = None + output_schema: TypeDefinitionModel | None = None + return_def: TypeDefinitionModel | None = None if endpoint_type == "tool": - tool_def = endpoint_definition.get("tool") + tool_def = endpoint_definition.tool if not tool_def: raise ValueError("No tool definition found") - params_raw = tool_def.get("parameters") - # Cast to List[Dict[str, Any]] for SDK executor compatibility - input_schema = ( - cast(list[dict[str, Any]] | None, params_raw) if isinstance(params_raw, list) else None - ) - # Tools use "return" not "return_" in the YAML - return_def = cast(TypeDefinition | None, tool_def.get("return")) + if tool_def.parameters: + input_schema = [ + param.model_dump(mode="python", exclude_unset=True, by_alias=True) + for param in tool_def.parameters + ] + else: + input_schema = None + return_def = tool_def.return_ output_schema = return_def if not skip_output_validation else None else: # resource - resource_def = endpoint_definition.get("resource") + resource_def = endpoint_definition.resource if not resource_def: raise ValueError("No resource definition found") - params_raw = resource_def.get("parameters") - # Cast to List[Dict[str, Any]] for SDK executor compatibility - input_schema = ( - cast(list[dict[str, Any]] | None, params_raw) if isinstance(params_raw, list) else None - ) - return_def = cast(TypeDefinition | None, resource_def.get("return")) + if resource_def.parameters: + input_schema = [ + param.model_dump(mode="python", exclude_unset=True, by_alias=True) + for param in resource_def.parameters + ] + else: + input_schema = None + return_def = resource_def.return_ output_schema = return_def if not skip_output_validation else None # Execute using the provided SDK engine @@ -181,14 +193,16 @@ async def execute_code_with_engine( # Now validate the transformed result if output_schema and not skip_output_validation: - schema_dict = {"output": output_schema} + schema_dict = { + "output": output_schema.model_dump(mode="python", exclude_unset=True, by_alias=True) + } validator = TypeValidator.from_dict(schema_dict) result = validator.validate_output(result) return result -def transform_result_for_return_type(result: Any, return_def: TypeDefinition) -> Any: +def transform_result_for_return_type(result: Any, return_def: TypeDefinitionModel) -> Any: """Transform result based on return type definition. This replicates the exact logic from the old EndpointExecutor to maintain @@ -207,7 +221,7 @@ def transform_result_for_return_type(result: Any, return_def: TypeDefinition) -> Raises: ValueError: If result shape doesn't match return type expectations """ - return_type = return_def.get("type") + return_type = return_def.type # If return type is array or not specified, don't transform if return_type == "array" or not return_type: diff --git a/src/mxcp/server/executor/runners/test.py b/src/mxcp/server/executor/runners/test.py index cec8d477..e20f3c64 100644 --- a/src/mxcp/server/executor/runners/test.py +++ b/src/mxcp/server/executor/runners/test.py @@ -15,9 +15,16 @@ from mxcp.sdk.auth import UserContext from mxcp.sdk.executor import ExecutionEngine from mxcp.server.core.config.models import SiteConfigModel, UserConfigModel -from mxcp.server.definitions.endpoints._types import EndpointDefinition, TestDefinition +from mxcp.server.definitions.endpoints.models import ( + EndpointDefinitionModel, + TestDefinitionModel, +) from mxcp.server.definitions.endpoints.loader import EndpointLoader from mxcp.server.services.endpoints import execute_endpoint_with_engine +from mxcp.server.services.tests.models import ( + TestCaseResultModel, + TestSuiteResultModel, +) logger = logging.getLogger(__name__) @@ -49,7 +56,7 @@ async def run_tests_for_endpoint( name: str, cli_user_context: UserContext | None = None, request_headers: dict[str, str] | None = None, - ) -> dict[str, Any]: + ) -> TestSuiteResultModel: """Run tests for a specific endpoint. Args: @@ -67,7 +74,12 @@ async def run_tests_for_endpoint( result = self.loader.load_endpoint(endpoint_type, name) if result is None: logger.error(f"Endpoint not found: {endpoint_type}/{name}") - return {"status": "error", "message": f"Endpoint not found: {endpoint_type}/{name}"} + return TestSuiteResultModel( + status="error", + tests_run=0, + tests=[], + message=f"Endpoint not found: {endpoint_type}/{name}", + ) endpoint_file_path, endpoint_def = result @@ -76,7 +88,12 @@ async def run_tests_for_endpoint( logger.info(f"Found {len(tests)} tests") if not tests: - return {"status": "ok", "tests_run": 0, "no_tests": True, "tests": []} + return TestSuiteResultModel( + status="ok", + tests_run=0, + tests=[], + no_tests=True, + ) # Extract column names from return schema column_names = self._extract_column_names(endpoint_def, endpoint_type) @@ -93,9 +110,9 @@ async def run_tests_for_endpoint( ) test_results.append(test_result) - if test_result["status"] == "error": + if test_result.status == "error": has_error = True - elif test_result["status"] == "failed": + elif test_result.status == "failed": has_failed = True # Determine overall status @@ -106,21 +123,30 @@ async def run_tests_for_endpoint( status = "failed" logger.info(f"Final test status: {status}") - return {"status": status, "tests_run": len(tests), "tests": test_results} + return TestSuiteResultModel( + status=status, + tests_run=len(tests), + tests=test_results, + ) except Exception as e: logger.error(f"Error in run_tests_for_endpoint: {str(e)}") - return {"status": "error", "message": str(e)} + return TestSuiteResultModel( + status="error", + tests_run=0, + tests=[], + message=str(e), + ) async def _run_single_test( self, endpoint_type: str, endpoint_name: str, - test_def: TestDefinition, + test_def: TestDefinitionModel, column_names: list[str], cli_user_context: UserContext | None, request_headers: dict[str, str] | None, - ) -> dict[str, Any]: + ) -> TestCaseResultModel: """Run a single test. Args: @@ -134,14 +160,14 @@ async def _run_single_test( Test result dictionary """ start_time = time.time() - test_name = test_def.get("name", "Unnamed test") + test_name = test_def.name logger.info(f"Running test: {test_name}") try: # Convert test arguments to parameters params = {} - for arg in test_def.get("arguments", []): - params[arg["key"]] = arg["value"] + for arg in test_def.arguments: + params[arg.key] = arg.value logger.info(f"Test parameters: {params}") # Determine user context @@ -175,62 +201,61 @@ async def _run_single_test( if not passed: logger.error(f"Test failed: {error}") - return { - "name": test_name, - "description": test_def.get("description", ""), - "status": status, - "error": error, - "time": time.time() - start_time, - } + return TestCaseResultModel( + name=test_name, + description=test_def.description or "", + status=status, + error=error, + time=time.time() - start_time, + ) except Exception as e: logger.error(f"Error during test execution: {str(e)}") - return { - "name": test_name, - "description": test_def.get("description", ""), - "status": "error", - "error": e, # Pass the actual exception object - "time": time.time() - start_time, - } - - def _extract_tests(self, endpoint_def: EndpointDefinition, endpoint_type: str) -> list[Any]: - """Extract test definitions from endpoint definition.""" - if endpoint_def is None or not isinstance(endpoint_def, dict): - return [] - - type_key = endpoint_type # "tool", "resource", or "prompt" - type_def = endpoint_def.get(type_key) - if type_def is None or not isinstance(type_def, dict): - return [] + cause = getattr(e, "__cause__", None) + return TestCaseResultModel( + name=test_name, + description=test_def.description or "", + status="error", + error=str(e), + error_cause=str(cause) if cause else None, + time=time.time() - start_time, + ) - tests = type_def.get("tests") - return tests if tests is not None else [] + def _extract_tests( + self, endpoint_def: EndpointDefinitionModel, endpoint_type: str + ) -> list[TestDefinitionModel]: + """Extract test definitions from endpoint definition.""" + if endpoint_type == "tool" and endpoint_def.tool: + return endpoint_def.tool.tests or [] + if endpoint_type == "resource" and endpoint_def.resource: + return endpoint_def.resource.tests or [] + if endpoint_type == "prompt" and endpoint_def.prompt: + return endpoint_def.prompt.tests or [] + return [] def _extract_column_names( - self, endpoint_def: EndpointDefinition, endpoint_type: str + self, endpoint_def: EndpointDefinitionModel, endpoint_type: str ) -> list[str]: """Extract column names from endpoint return schema.""" - columns = [] - - if endpoint_type in ["tool", "resource"]: - type_def = endpoint_def.get(endpoint_type) - if type_def and isinstance(type_def, dict) and type_def.get("return"): - return_def = type_def["return"] - if return_def and return_def.get("type") == "array" and "items" in return_def: - items = return_def["items"] - if ( - isinstance(items, dict) - and items.get("type") == "object" - and "properties" in items - ): - properties = items.get("properties", {}) - if isinstance(properties, dict): - columns = list(properties.keys()) + columns: list[str] = [] + + component = None + if endpoint_type == "tool": + component = endpoint_def.tool + elif endpoint_type == "resource": + component = endpoint_def.resource + + if component and component.return_: + return_def = component.return_ + if return_def.type == "array" and return_def.items: + items = return_def.items + if items.type == "object" and items.properties: + columns = list(items.properties.keys()) return columns def _get_test_user_context( - self, test_def: TestDefinition, cli_user_context: UserContext | None + self, test_def: TestDefinitionModel, cli_user_context: UserContext | None ) -> UserContext | None: """Determine user context for test execution.""" # CLI user context takes precedence @@ -239,20 +264,19 @@ def _get_test_user_context( return cli_user_context # Check for test-defined context - if "user_context" in test_def: - test_context_data = test_def["user_context"] - if test_context_data is not None: - test_user_context = UserContext( - provider="test", # Special provider for test-defined contexts - user_id=test_context_data.get("user_id", "test_user"), - username=test_context_data.get("username", "test_user"), - email=test_context_data.get("email"), - name=test_context_data.get("name"), - avatar_url=test_context_data.get("avatar_url"), - raw_profile=test_context_data, # Store full context for policy access - ) - logger.info(f"Using test-defined user context: {test_context_data}") - return test_user_context + if test_def.user_context is not None: + test_context_data = test_def.user_context + test_user_context = UserContext( + provider="test", # Special provider for test-defined contexts + user_id=test_context_data.get("user_id", "test_user"), + username=test_context_data.get("username", "test_user"), + email=test_context_data.get("email"), + name=test_context_data.get("name"), + avatar_url=test_context_data.get("avatar_url"), + raw_profile=test_context_data, # Store full context for policy access + ) + logger.info(f"Using test-defined user context: {test_context_data}") + return test_user_context return None @@ -333,7 +357,7 @@ def normalize_result(result: Any, column_names: list[str], endpoint_type: str) - return result -def compare_results(result: Any, test_def: TestDefinition) -> tuple[bool, str | None]: +def compare_results(result: Any, test_def: TestDefinitionModel) -> tuple[bool, str | None]: """Compare result with various assertion types in test definition. Args: @@ -344,8 +368,8 @@ def compare_results(result: Any, test_def: TestDefinition) -> tuple[bool, str | Tuple of (passed, error_message) """ # Exact match with 'result' field - if "result" in test_def: - expected = test_def["result"] + if test_def.result is not None: + expected = test_def.result if expected is not None: # Convert complex objects for comparison def make_serializable(obj: Any) -> Any: @@ -378,8 +402,8 @@ def make_serializable(obj: Any) -> Any: ) # Partial object match with 'result_contains' - if "result_contains" in test_def: - expected_contains = test_def["result_contains"] + if test_def.result_contains is not None: + expected_contains = test_def.result_contains if isinstance(result, dict) and isinstance(expected_contains, dict): for key, expected_value in expected_contains.items(): if key not in result: @@ -417,8 +441,8 @@ def make_serializable(obj: Any) -> Any: ) # Field exclusion with 'result_not_contains' - if "result_not_contains" in test_def: - excluded_fields = test_def["result_not_contains"] + if test_def.result_not_contains: + excluded_fields = test_def.result_not_contains if isinstance(result, dict) and excluded_fields: for field in excluded_fields: if field in result: @@ -427,8 +451,8 @@ def make_serializable(obj: Any) -> Any: return False, f"result_not_contains assertion requires dict result, got {type(result)}" # Array contains specific item with 'result_contains_item' - if "result_contains_item" in test_def: - expected_item = test_def["result_contains_item"] + if test_def.result_contains_item is not None: + expected_item = test_def.result_contains_item if not isinstance(result, list): return ( False, @@ -454,8 +478,8 @@ def make_serializable(obj: Any) -> Any: return False, f"Array does not contain expected item: {expected_item}" # Array contains all items with 'result_contains_all' - if "result_contains_all" in test_def: - expected_items = test_def["result_contains_all"] + if test_def.result_contains_all: + expected_items = test_def.result_contains_all if not isinstance(result, list): return False, f"result_contains_all assertion requires array result, got {type(result)}" @@ -478,16 +502,16 @@ def make_serializable(obj: Any) -> Any: return False, f"Array does not contain expected item: {expected_item}" # Array length check with 'result_length' - if "result_length" in test_def: - expected_length = test_def["result_length"] + if test_def.result_length is not None: + expected_length = test_def.result_length if not isinstance(result, list): return False, f"result_length assertion requires array result, got {type(result)}" if len(result) != expected_length: return False, f"Array has {len(result)} items, expected {expected_length}" # String contains with 'result_contains_text' - if "result_contains_text" in test_def: - expected_text = test_def["result_contains_text"] + if test_def.result_contains_text is not None: + expected_text = test_def.result_contains_text if not isinstance(result, str): result_str = str(result) else: diff --git a/src/mxcp/server/executor/runners/tool.py b/src/mxcp/server/executor/runners/tool.py index 19d26d38..48b9f65d 100644 --- a/src/mxcp/server/executor/runners/tool.py +++ b/src/mxcp/server/executor/runners/tool.py @@ -10,7 +10,7 @@ from mxcp.sdk.auth import UserContext from mxcp.sdk.executor import ExecutionContext, ExecutionEngine -from mxcp.server.definitions.endpoints._types import EndpointDefinition +from mxcp.server.definitions.endpoints.models import EndpointDefinitionModel from mxcp.server.definitions.endpoints.utils import detect_language_from_source, extract_source_info logger = logging.getLogger(__name__) @@ -41,7 +41,7 @@ class EndpointToolExecutor: >>> llm_executor = LLMExecutor(model_config, tool_definitions, tool_executor) """ - def __init__(self, engine: ExecutionEngine, endpoints: list[EndpointDefinition]): + def __init__(self, engine: ExecutionEngine, endpoints: list[EndpointDefinitionModel]): """Initialize the endpoint tool executor. Args: @@ -52,14 +52,12 @@ def __init__(self, engine: ExecutionEngine, endpoints: list[EndpointDefinition]) self.endpoints = endpoints # Create lookup map for faster tool resolution - self._tool_map: dict[str, EndpointDefinition] = {} + self._tool_map: dict[str, EndpointDefinitionModel] = {} for endpoint_def in endpoints: - if "tool" in endpoint_def and endpoint_def["tool"]: - tool = endpoint_def["tool"] - self._tool_map[tool["name"]] = endpoint_def - elif "resource" in endpoint_def and endpoint_def["resource"]: - resource = endpoint_def["resource"] - self._tool_map[resource["uri"]] = endpoint_def + if endpoint_def.tool: + self._tool_map[endpoint_def.tool.name] = endpoint_def + elif endpoint_def.resource: + self._tool_map[endpoint_def.resource.uri] = endpoint_def logger.info(f"EndpointToolExecutor initialized with {len(endpoints)} endpoints") @@ -108,14 +106,14 @@ async def execute_tool( logger.error(f"Tool '{tool_name}' execution failed: {e}") raise - def _get_source_code(self, endpoint_def: EndpointDefinition, tool_name: str) -> str: + def _get_source_code(self, endpoint_def: EndpointDefinitionModel, tool_name: str) -> str: """Extract source code from endpoint definition.""" # Get the tool or resource definition source = None - if "tool" in endpoint_def and endpoint_def["tool"]: - source = endpoint_def["tool"].get("source", {}) - elif "resource" in endpoint_def and endpoint_def["resource"]: - source = endpoint_def["resource"].get("source", {}) + if endpoint_def.tool: + source = endpoint_def.tool.source + elif endpoint_def.resource: + source = endpoint_def.resource.source if not source: raise ValueError(f"No source found for endpoint '{tool_name}'") @@ -124,15 +122,15 @@ def _get_source_code(self, endpoint_def: EndpointDefinition, tool_name: str) -> return source_value def _get_language( - self, endpoint_def: EndpointDefinition, tool_name: str, source_info: str + self, endpoint_def: EndpointDefinitionModel, tool_name: str, source_info: str ) -> str: """Determine the programming language for the endpoint.""" # Get the tool or resource definition source = None - if "tool" in endpoint_def and endpoint_def["tool"]: - source = endpoint_def["tool"].get("source", {}) - elif "resource" in endpoint_def and endpoint_def["resource"]: - source = endpoint_def["resource"].get("source", {}) + if endpoint_def.tool: + source = endpoint_def.tool.source + elif endpoint_def.resource: + source = endpoint_def.resource.source if not source: raise ValueError(f"No source found for endpoint '{tool_name}'") diff --git a/src/mxcp/server/interfaces/cli/drift_check.py b/src/mxcp/server/interfaces/cli/drift_check.py index f7d7569b..5b6ac6f8 100644 --- a/src/mxcp/server/interfaces/cli/drift_check.py +++ b/src/mxcp/server/interfaces/cli/drift_check.py @@ -20,25 +20,27 @@ def format_drift_report(report: Any, debug: bool = False) -> str: """Format drift report for human-readable output""" if isinstance(report, str): return report - + payload = ( + report if isinstance(report, dict) else report.model_dump(mode="python", exclude_none=True) + ) output = [] # Header - output.append(f"Drift Report (Generated: {report['generated_at']})") - output.append(f"Baseline: {report['baseline_snapshot_path']}") - output.append(f"Baseline Generated: {report['baseline_snapshot_generated_at']}") - output.append(f"Current Generated: {report['current_snapshot_generated_at']}") + output.append(f"Drift Report (Generated: {payload['generated_at']})") + output.append(f"Baseline: {payload['baseline_snapshot_path']}") + output.append(f"Baseline Generated: {payload['baseline_snapshot_generated_at']}") + output.append(f"Current Generated: {payload['current_snapshot_generated_at']}") output.append("") # Summary - if report["has_drift"]: + if payload["has_drift"]: output.append("🔴 DRIFT DETECTED") else: output.append("✅ NO DRIFT DETECTED") output.append("") output.append("Summary:") - summary = report["summary"] + summary = payload["summary"] output.append( f" Tables: {summary['tables_added']} added, {summary['tables_removed']} removed, {summary['tables_modified']} modified" ) @@ -48,9 +50,9 @@ def format_drift_report(report: Any, debug: bool = False) -> str: output.append("") # Table changes - if report["table_changes"]: + if payload["table_changes"]: output.append("Table Changes:") - for change in report["table_changes"]: + for change in payload["table_changes"]: change_type = change["change_type"] table_name = change["name"] @@ -86,9 +88,9 @@ def format_drift_report(report: Any, debug: bool = False) -> str: output.append("") # Resource changes - if report["resource_changes"]: + if payload["resource_changes"]: output.append("Resource Changes:") - for change in report["resource_changes"]: + for change in payload["resource_changes"]: change_type = change["change_type"] path = change["path"] endpoint = change.get("endpoint", "unknown") @@ -221,10 +223,10 @@ async def _drift_check_impl( report = await check_drift(site_config, user_config, profile=profile, baseline_path=baseline) if json_output: - output_result(report, json_output, debug) + output_result(report.model_dump(mode="json", exclude_none=True), json_output, debug) else: click.echo(format_drift_report(report, debug)) # Exit with non-zero code if drift detected - if report["has_drift"]: + if report.has_drift: raise SystemExit(1) diff --git a/src/mxcp/server/interfaces/cli/drift_snapshot.py b/src/mxcp/server/interfaces/cli/drift_snapshot.py index 036bf4cf..2a441770 100644 --- a/src/mxcp/server/interfaces/cli/drift_snapshot.py +++ b/src/mxcp/server/interfaces/cli/drift_snapshot.py @@ -13,7 +13,7 @@ output_result, resolve_profile, ) -from mxcp.server.services.drift._types import DriftSnapshot +from mxcp.server.services.drift.models import DriftSnapshot from mxcp.server.services.drift.snapshot import generate_snapshot @@ -26,7 +26,8 @@ def _compute_snapshot_hash(snapshot: DriftSnapshot) -> tuple[str, str]: Returns: Tuple of (snapshot_json_string, drift_hash) """ - snapshot_str = json.dumps(snapshot, sort_keys=True) + snapshot_payload = snapshot.model_dump(mode="json", exclude_none=True) + snapshot_str = json.dumps(snapshot_payload, sort_keys=True) drift_hash = hashlib.sha256(snapshot_str.encode()).hexdigest() return snapshot_str, drift_hash @@ -146,7 +147,7 @@ async def _drift_snapshot_impl( { "path": str(path), "drift_hash": drift_hash, - "generated_at": snapshot["generated_at"], + "generated_at": snapshot.generated_at, }, json_output, debug, @@ -161,16 +162,14 @@ async def _drift_snapshot_impl( # Compute a simple hash for the snapshot snapshot_str, drift_hash = _compute_snapshot_hash(snapshot) click.echo(f" • Hash: {click.style(drift_hash[:12] + '...', fg='yellow')}") - click.echo(f" • Generated: {click.style(snapshot['generated_at'], fg='yellow')}") + click.echo(f" • Generated: {click.style(snapshot.generated_at, fg='yellow')}") # Show what was captured click.echo(f"\n{click.style('📊 Captured State:', fg='cyan', bold=True)}") - if "tables" in snapshot: - table_count = len(snapshot["tables"]) - click.echo(f" • Tables: {click.style(str(table_count), fg='green')}") - if "resources" in snapshot: - resource_count = len(snapshot["resources"]) - click.echo(f" • Resources: {click.style(str(resource_count), fg='green')}") + table_count = len(snapshot.tables) + click.echo(f" • Tables: {click.style(str(table_count), fg='green')}") + resource_count = len(snapshot.resources) + click.echo(f" • Resources: {click.style(str(resource_count), fg='green')}") click.echo(f"\n{click.style('💡 Next Steps:', fg='yellow')}") click.echo( @@ -181,7 +180,8 @@ async def _drift_snapshot_impl( click.echo( f"\n{click.style('🔍 Dry Run - Snapshot Preview:', fg='yellow', bold=True)}\n" ) - click.echo(json.dumps(snapshot, indent=2)) + snapshot_payload = snapshot.model_dump(mode="json", exclude_none=True) + click.echo(json.dumps(snapshot_payload, indent=2)) click.echo( f"\n{click.style('ℹ️ No files were written (dry run mode)', fg='blue')}\n" ) diff --git a/src/mxcp/server/interfaces/cli/lint.py b/src/mxcp/server/interfaces/cli/lint.py index 50a92cf7..6a987a77 100644 --- a/src/mxcp/server/interfaces/cli/lint.py +++ b/src/mxcp/server/interfaces/cli/lint.py @@ -1,18 +1,18 @@ from pathlib import Path -from typing import Any +from typing import Any, Literal, cast import click +from pydantic import BaseModel, ConfigDict, Field from mxcp.server.core.config.analytics import track_command_with_timing from mxcp.server.core.config.site_config import find_repo_root, load_site_config from mxcp.server.core.config.user_config import load_user_config -from mxcp.server.definitions.endpoints._types import ( - EndpointDefinition, - ParamDefinition, - PromptDefinition, - ResourceDefinition, - ToolDefinition, - TypeDefinition, +from mxcp.server.definitions.endpoints.models import ( + EndpointDefinitionModel, + ParamDefinitionModel, + TestDefinitionModel, + ToolDefinitionModel, + TypeDefinitionModel, ) from mxcp.server.definitions.endpoints.loader import EndpointLoader from mxcp.server.interfaces.cli.utils import ( @@ -23,26 +23,43 @@ ) -class LintIssue: +class LintIssueModel(BaseModel): """Represents a single lint issue found in an endpoint.""" - def __init__( - self, - severity: str, - path: str, - location: str, - message: str, - suggestion: str | None = None, - ): - self.severity = severity # "warning" or "error" - self.path = path - self.location = location # e.g., "tool.description", "parameter[0].examples" - self.message = message - self.suggestion = suggestion + model_config = ConfigDict(extra="forbid", frozen=True) + + severity: Literal["error", "warning", "info"] + path: str + location: str + message: str + suggestion: str | None = None + + +class LintFileReportModel(BaseModel): + """Grouping of lint issues for a single file.""" + + model_config = ConfigDict(extra="forbid", frozen=True) + + path: str + issues: list[LintIssueModel] + + +class LintReportModel(BaseModel): + """Aggregated lint command results.""" + + model_config = ConfigDict(extra="forbid", frozen=True) + + total_files: int + files: list[LintFileReportModel] + clean_paths: list[str] = Field(default_factory=list) def lint_parameter( - param: ParamDefinition, index: int, endpoint_type: str, issues: list[LintIssue], path: str + param: ParamDefinitionModel, + index: int, + endpoint_type: str, + issues: list[LintIssueModel], + path: str, ) -> None: """Lint a parameter definition for missing metadata. @@ -53,67 +70,66 @@ def lint_parameter( issues: List to append found issues to path: File path for error reporting """ - param_name = param.get("name", f"parameter[{index}]") + param_name = param.name or f"parameter[{index}]" # Check for description (parameters must have descriptions) - if "description" not in param: + if "description" not in param.model_fields_set or not param.description: issues.append( - LintIssue( - "error", - path, - f"{endpoint_type}.parameters[{index}].description", - f"Parameter '{param_name}' is missing a description", - "Add a 'description' field to explain what this parameter does", + LintIssueModel( + severity="error", + path=path, + location=f"{endpoint_type}.parameters[{index}].description", + message=f"Parameter '{param_name}' is missing a description", + suggestion="Add a 'description' field to explain what this parameter does", ) ) # Check for examples - if "examples" not in param: + if "examples" not in param.model_fields_set or not param.examples: issues.append( - LintIssue( - "info", - path, - f"{endpoint_type}.parameters[{index}].examples", - f"Parameter '{param_name}' has no examples", - "Consider adding an 'examples' array to help LLMs understand valid inputs", + LintIssueModel( + severity="info", + path=path, + location=f"{endpoint_type}.parameters[{index}].examples", + message=f"Parameter '{param_name}' has no examples", + suggestion="Consider adding an 'examples' array to help LLMs understand valid inputs", ) ) # Check for default value on optional parameters - if "default" not in param: + if "default" not in param.model_fields_set: issues.append( - LintIssue( - "info", - path, - f"{endpoint_type}.parameters[{index}].default", - f"Parameter '{param_name}' has no default value", - "Consider adding a 'default' value for optional parameters", + LintIssueModel( + severity="info", + path=path, + location=f"{endpoint_type}.parameters[{index}].default", + message=f"Parameter '{param_name}' has no default value", + suggestion="Consider adding a 'default' value for optional parameters", ) ) # Lint nested type structures within the parameter - if param.get("type") == "array" and "items" in param: - items = param.get("items") - if items is not None: - lint_nested_type( - items, - f"{endpoint_type}.parameters[{index}].items", - issues, - path, - ) - elif param.get("type") == "object" and "properties" in param: - properties = param.get("properties") - if properties is not None: - lint_object_properties( - properties, - f"{endpoint_type}.parameters[{index}].properties", - issues, - path, - ) + if param.type == "array" and param.items is not None: + lint_nested_type( + param.items, + f"{endpoint_type}.parameters[{index}].items", + issues, + path, + ) + elif param.type == "object" and param.properties: + lint_object_properties( + param.properties, + f"{endpoint_type}.parameters[{index}].properties", + issues, + path, + ) def lint_return_type( - return_def: TypeDefinition, endpoint_type: str, issues: list[LintIssue], path: str + return_def: TypeDefinitionModel, + endpoint_type: str, + issues: list[LintIssueModel], + path: str, ) -> None: """Lint a return type definition for missing description. @@ -124,236 +140,192 @@ def lint_return_type( path: File path for error reporting """ # Return types should have descriptions - if "description" not in return_def: + if "description" not in return_def.model_fields_set or not return_def.description: issues.append( - LintIssue( - "warning", - path, - f"{endpoint_type}.return.description", - "Return type is missing a description", - "Add a 'description' field to help LLMs understand the output format", + LintIssueModel( + severity="warning", + path=path, + location=f"{endpoint_type}.return.description", + message="Return type is missing a description", + suggestion="Add a 'description' field to help LLMs understand the output format", ) ) # Lint nested structures - if return_def.get("type") == "array" and "items" in return_def: - items = return_def.get("items") - if items is not None: - lint_nested_type(items, f"{endpoint_type}.return.items", issues, path) - elif return_def.get("type") == "object" and "properties" in return_def: - properties = return_def.get("properties") - if properties is not None: - lint_object_properties(properties, f"{endpoint_type}.return.properties", issues, path) + if return_def.type == "array" and return_def.items is not None: + lint_nested_type(return_def.items, f"{endpoint_type}.return.items", issues, path) + elif return_def.type == "object" and return_def.properties: + lint_object_properties(return_def.properties, f"{endpoint_type}.return.properties", issues, path) def lint_nested_type( - type_def: TypeDefinition, location: str, issues: list[LintIssue], path: str + type_def: TypeDefinitionModel, + location: str, + issues: list[LintIssueModel], + path: str, ) -> None: - """Lint nested type definitions (used within parameters or return types). - - Args: - type_def: A nested type definition (e.g., array items, object properties) - location: The path to this definition in the endpoint - issues: List to append found issues to - path: File path for error reporting - """ - if not isinstance(type_def, dict): - return + """Lint nested type definitions (used within parameters or return types).""" + type_name = type_def.type - type_name = type_def.get("type", "unknown") - - # Nested types should have descriptions - if "description" not in type_def: + if "description" not in type_def.model_fields_set or not type_def.description: issues.append( - LintIssue( - "warning", - path, - location, - f"Type '{type_name}' is missing a description", - "Add a 'description' field to help LLMs understand this type", + LintIssueModel( + severity="warning", + path=path, + location=location, + message=f"Type '{type_name}' is missing a description", + suggestion="Add a 'description' field to help LLMs understand this type", ) ) - # Recursively check nested structures - if type_name == "array" and "items" in type_def: - items = type_def.get("items") - if items is not None: - lint_nested_type(items, f"{location}.items", issues, path) - elif type_name == "object" and "properties" in type_def: - properties = type_def.get("properties") - if properties is not None: - lint_object_properties(properties, f"{location}.properties", issues, path) + if type_def.type == "array" and type_def.items is not None: + lint_nested_type(type_def.items, f"{location}.items", issues, path) + elif type_def.type == "object" and type_def.properties: + lint_object_properties(type_def.properties, f"{location}.properties", issues, path) def lint_object_properties( - properties: dict[str, TypeDefinition], location: str, issues: list[LintIssue], path: str + properties: dict[str, TypeDefinitionModel], + location: str, + issues: list[LintIssueModel], + path: str, ) -> None: - """Lint object properties for missing descriptions. - - Args: - properties: The properties dictionary of an object type - location: The path to this properties section in the endpoint - issues: List to append found issues to - path: File path for error reporting - """ + """Lint object properties for missing descriptions.""" for prop_name, prop_def in properties.items(): - if isinstance(prop_def, dict) and "description" not in prop_def: + if "description" not in prop_def.model_fields_set or not prop_def.description: issues.append( - LintIssue( - "warning", - path, - f"{location}.{prop_name}", - f"Property '{prop_name}' is missing a description", - "Add a 'description' field to help LLMs understand this property", + LintIssueModel( + severity="warning", + path=path, + location=f"{location}.{prop_name}", + message=f"Property '{prop_name}' is missing a description", + suggestion="Add a 'description' field to help LLMs understand this property", ) ) - # Recursively lint nested structures within properties - if isinstance(prop_def, dict): - lint_nested_type(prop_def, f"{location}.{prop_name}", issues, path) + lint_nested_type(prop_def, f"{location}.{prop_name}", issues, path) -def lint_endpoint(path: Path, endpoint: EndpointDefinition) -> list[LintIssue]: +def lint_endpoint(path: Path, endpoint: EndpointDefinitionModel) -> list[LintIssueModel]: """Lint a single endpoint for missing metadata.""" - issues: list[LintIssue] = [] + issues: list[LintIssueModel] = [] - # Determine endpoint type and get the specific definition + definition = None endpoint_type: str | None = None - endpoint_def: ToolDefinition | ResourceDefinition | PromptDefinition | None = None - if endpoint.get("tool") is not None: + if endpoint.tool is not None: endpoint_type = "tool" - endpoint_def = endpoint["tool"] - elif endpoint.get("resource") is not None: + definition = endpoint.tool + elif endpoint.resource is not None: endpoint_type = "resource" - endpoint_def = endpoint["resource"] - elif endpoint.get("prompt") is not None: + definition = endpoint.resource + elif endpoint.prompt is not None: endpoint_type = "prompt" - endpoint_def = endpoint["prompt"] + definition = endpoint.prompt else: - return issues # Invalid endpoint structure, validation will catch this + return issues - # Check for description - if endpoint_def and not endpoint_def.get("description"): + assert definition is not None + + if "description" not in definition.model_fields_set or not definition.description: issues.append( - LintIssue( - "warning", - str(path), - f"{endpoint_type}.description", - f"{endpoint_type.capitalize()} is missing a description", - "Add a 'description' field to help LLMs understand what this endpoint does", + LintIssueModel( + severity="warning", + path=str(path), + location=f"{endpoint_type}.description", + message=f"{endpoint_type.capitalize()} is missing a description", + suggestion="Add a 'description' field to help LLMs understand what this endpoint does", ) ) - # Check for tests (except prompts which don't have tests) - if endpoint_type != "prompt" and endpoint_def and not endpoint_def.get("tests"): + if "tags" not in definition.model_fields_set or not definition.tags: issues.append( - LintIssue( - "warning", - str(path), - f"{endpoint_type}.tests", - f"{endpoint_type.capitalize()} has no tests defined", - "Add at least one test case to ensure the endpoint works correctly", + LintIssueModel( + severity="info", + path=str(path), + location=f"{endpoint_type}.tags", + message=f"{endpoint_type.capitalize()} has no tags", + suggestion="Consider adding tags to help categorize and discover this endpoint", ) ) - elif endpoint_type != "prompt" and endpoint_def and endpoint_def.get("tests") == []: - issues.append( - LintIssue( - "warning", - str(path), - f"{endpoint_type}.tests", - f"{endpoint_type.capitalize()} has an empty tests array", - "Add at least one test case to ensure the endpoint works correctly", + + if endpoint_type != "prompt": + tests = definition.tests or [] + if not tests: + issues.append( + LintIssueModel( + severity="warning", + path=str(path), + location=f"{endpoint_type}.tests", + message=f"{endpoint_type.capitalize()} has no tests defined", + suggestion="Add at least one test case to ensure the endpoint works correctly", + ) ) - ) + else: + for i, test in enumerate(tests): + if "description" not in test.model_fields_set or not test.description: + issues.append( + LintIssueModel( + severity="info", + path=str(path), + location=f"{endpoint_type}.tests[{i}].description", + message=f"Test '{test.name}' is missing a description", + suggestion="Add a 'description' field to explain what this test validates", + ) + ) - # Check test descriptions if tests exist - if endpoint_def and endpoint_def.get("tests"): - tests = endpoint_def.get("tests") or [] - for i, test in enumerate(tests): - if "description" not in test: + for i, param in enumerate(definition.parameters or []): + lint_parameter(param, i, endpoint_type, issues, str(path)) + + if definition.return_ is not None: + lint_return_type(definition.return_, endpoint_type, issues, str(path)) + + if endpoint_type == "tool": + tool_def = cast(ToolDefinitionModel, definition) + annotations = tool_def.annotations + has_annotations = bool(annotations and annotations.model_fields_set) + if not has_annotations: issues.append( - LintIssue( - "info", - str(path), - f"{endpoint_type}.tests[{i}].description", - f"Test '{test.get('name', 'unnamed')}' is missing a description", - "Add a 'description' field to explain what this test validates", + LintIssueModel( + severity="info", + path=str(path), + location=f"{endpoint_type}.annotations", + message="Tool has no behavioral annotations", + suggestion="Consider adding annotations like readOnlyHint, idempotentHint to help LLMs use the tool safely", ) ) - # Check parameters - if endpoint_def and endpoint_def.get("parameters"): - parameters = endpoint_def.get("parameters") or [] - for i, param in enumerate(parameters): - # Use the focused lint_parameter function - lint_parameter(param, i, endpoint_type, issues, str(path)) - - # Check return type - if endpoint_def and endpoint_def.get("return_"): - return_def = endpoint_def.get("return_") - if return_def is not None: - # Use the focused lint_return_type function - lint_return_type(return_def, endpoint_type, issues, str(path)) - - # Check for tags (info level) - if endpoint_def and not endpoint_def.get("tags"): - issues.append( - LintIssue( - "info", - str(path), - f"{endpoint_type}.tags", - f"{endpoint_type.capitalize()} has no tags", - "Consider adding tags to help categorize and discover this endpoint", - ) - ) - - # For tools, check annotations - if endpoint_type == "tool" and endpoint_def and not endpoint_def.get("annotations"): - issues.append( - LintIssue( - "info", - str(path), - f"{endpoint_type}.annotations", - "Tool has no behavioral annotations", - "Consider adding annotations like readOnlyHint, idempotentHint to help LLMs use the tool safely", - ) - ) - return issues -def format_lint_results_as_json( - all_issues: list[tuple[Path, list[LintIssue]]], -) -> list[dict[str, Any]]: +def format_lint_results_as_json(report: LintReportModel) -> list[dict[str, Any]]: """Format lint results as JSON-serializable data structure.""" results = [] - for path, issues in all_issues: - for issue in issues: - results.append( - { - "severity": issue.severity, - "path": str(path), - "location": issue.location, - "message": issue.message, - "suggestion": issue.suggestion, - } - ) + for file_report in report.files: + for issue in file_report.issues: + results.append(issue.model_dump(mode="python", exclude_none=True)) return results -def format_lint_results_as_text(all_issues: list[tuple[Path, list[LintIssue]]]) -> str: +def format_lint_results_as_text(report: LintReportModel) -> str: """Format lint results as human-readable text with colors and formatting.""" - # Human-readable format output = [] - # Count issues by severity - total_files = len(all_issues) - files_with_issues = sum(1 for _, issues in all_issues if issues) + total_files = report.total_files + files_with_issues = len(report.files) + error_count = sum( + sum(1 for i in file_report.issues if i.severity == "error") for file_report in report.files + ) warning_count = sum( - sum(1 for i in issues if i.severity == "warning") for _, issues in all_issues + sum(1 for i in file_report.issues if i.severity == "warning") for file_report in report.files ) - info_count = sum(sum(1 for i in issues if i.severity == "info") for _, issues in all_issues) + info_count = sum( + sum(1 for i in file_report.issues if i.severity == "info") for file_report in report.files + ) + + if total_files == 0: + output.append(click.style("ℹ️ No endpoints were linted", fg="blue")) + return "\n".join(output) # Header output.append(f"\n{click.style('🔍 Lint Results', fg='cyan', bold=True)}") @@ -366,23 +338,31 @@ def format_lint_results_as_text(all_issues: list[tuple[Path, list[LintIssue]]]) return "\n".join(output) output.append(f" • {click.style(str(files_with_issues), fg='yellow')} files with suggestions") + if error_count > 0: + output.append(f" • {click.style(f'{error_count} errors', fg='red')}") if warning_count > 0: output.append(f" • {click.style(f'{warning_count} warnings', fg='yellow')}") if info_count > 0: output.append(f" • {click.style(f'{info_count} suggestions', fg='blue')}") - # Group by file - for path, issues in all_issues: - if not issues: - continue - + for file_report in report.files: + path = file_report.path + issues = file_report.issues output.append( f"\n{click.style('📄', fg='cyan')} {click.style(str(path), fg='cyan', bold=True)}" ) - # Group issues by severity warnings = [i for i in issues if i.severity == "warning"] infos = [i for i in issues if i.severity == "info"] + errors = [i for i in issues if i.severity == "error"] + + for issue in errors: + output.append( + f" {click.style('❌', fg='red')} {click.style(issue.location, fg='red')}" + ) + output.append(f" {issue.message}") + if issue.suggestion: + output.append(f" {click.style('💡', fg='cyan')} {issue.suggestion}") if warnings: for issue in warnings: @@ -402,7 +382,20 @@ def format_lint_results_as_text(all_issues: list[tuple[Path, list[LintIssue]]]) if issue.suggestion: output.append(f" {click.style('💡', fg='cyan')} {issue.suggestion}") - # Summary advice + if infos: + for issue in infos: + output.append( + f" {click.style('ℹ️', fg='blue')} {click.style(issue.location, fg='blue')}" + ) + output.append(f" {issue.message}") + if issue.suggestion: + output.append(f" {click.style('💡', fg='cyan')} {issue.suggestion}") + + if report.clean_paths: + output.append(f"\n{click.style('✅ Passed linting:', fg='green', bold=True)}") + for clean_path in sorted(report.clean_paths): + output.append(f" {click.style('✓', fg='green')} {clean_path}") + output.append(f"\n{click.style('📚 Why this matters:', fg='cyan', bold=True)}") output.append(" • Descriptions help LLMs understand your endpoints better") output.append(" • Examples show LLMs how to use parameters correctly") @@ -472,7 +465,9 @@ def lint(profile: str, json_output: bool, debug: bool, severity: str) -> None: loader = EndpointLoader(site_config) endpoints = loader.discover_endpoints() - all_issues = [] + file_reports: list[LintFileReportModel] = [] + clean_paths: list[str] = [] + linted_files = 0 # Lint each endpoint for path, endpoint, error_msg in endpoints: @@ -480,6 +475,7 @@ def lint(profile: str, json_output: bool, debug: bool, severity: str) -> None: # Skip files with parsing errors continue + linted_files += 1 issues = lint_endpoint(path, endpoint) # Filter by severity @@ -489,14 +485,24 @@ def lint(profile: str, json_output: bool, debug: bool, severity: str) -> None: issues = [i for i in issues if i.severity == "info"] if issues: - all_issues.append((path, issues)) + file_reports.append( + LintFileReportModel(path=str(path), issues=issues) + ) + else: + clean_paths.append(str(path)) + + report = LintReportModel( + total_files=linted_files, + files=file_reports, + clean_paths=clean_paths, + ) # Format and output results if json_output: - results = format_lint_results_as_json(all_issues) + results = format_lint_results_as_json(report) output_result(results, json_output, debug) else: - output = format_lint_results_as_text(all_issues) + output = format_lint_results_as_text(report) click.echo(output) except click.ClickException: diff --git a/src/mxcp/server/interfaces/cli/list.py b/src/mxcp/server/interfaces/cli/list.py index 19a8a732..09207a4d 100644 --- a/src/mxcp/server/interfaces/cli/list.py +++ b/src/mxcp/server/interfaces/cli/list.py @@ -6,7 +6,7 @@ from mxcp.server.core.config.analytics import track_command_with_timing from mxcp.server.core.config.site_config import find_repo_root, load_site_config from mxcp.server.core.config.user_config import load_user_config -from mxcp.server.definitions.endpoints._types import EndpointDefinition +from mxcp.server.definitions.endpoints.models import EndpointDefinitionModel from mxcp.server.definitions.endpoints.loader import EndpointLoader from mxcp.server.interfaces.cli.utils import ( configure_logging_from_config, @@ -16,33 +16,26 @@ ) -def parse_endpoint(path: Path, endpoint: EndpointDefinition) -> tuple[str, str, str | None]: +def parse_endpoint(path: Path, endpoint: EndpointDefinitionModel) -> tuple[str, str, str | None]: """Parse an endpoint definition to determine its type, name, and any error. Returns: Tuple of (kind, name, error_message) """ - if endpoint.get("tool") is not None: - tool = endpoint["tool"] - if tool: - return "tool", tool.get("name", "unnamed"), None - return "tool", "unnamed", None - elif endpoint.get("resource") is not None: - resource = endpoint["resource"] - if resource: - return "resource", resource.get("uri", "unknown"), None - return "resource", "unknown", None - elif endpoint.get("prompt") is not None: - prompt = endpoint["prompt"] - if prompt: - return "prompt", prompt.get("name", "unnamed"), None - return "prompt", "unnamed", None - else: - return ( - "unknown", - "unknown", - f"Invalid endpoint structure in {path}: missing tool/resource/prompt key", - ) + if endpoint.tool is not None: + return "tool", endpoint.tool.name, None + + if endpoint.resource is not None: + return "resource", endpoint.resource.uri, None + + if endpoint.prompt is not None: + return "prompt", endpoint.prompt.name, None + + return ( + "unknown", + "unknown", + f"Invalid endpoint structure in {path}: missing tool/resource/prompt key", + ) @click.command(name="list") diff --git a/src/mxcp/server/interfaces/cli/test.py b/src/mxcp/server/interfaces/cli/test.py index 5664ae6e..322c3efa 100644 --- a/src/mxcp/server/interfaces/cli/test.py +++ b/src/mxcp/server/interfaces/cli/test.py @@ -1,8 +1,6 @@ import asyncio import json from pathlib import Path -from typing import Any - import click from mxcp.sdk.auth import UserContext @@ -17,37 +15,43 @@ output_result, resolve_profile, ) -from mxcp.server.services.tests import run_all_tests, run_tests +from mxcp.server.services.tests.service import run_all_tests, run_tests +from mxcp.server.services.tests.models import ( + EndpointTestResultModel, + MultiEndpointTestResultsModel, + TestCaseResultModel, + TestSuiteResultModel, +) -def format_test_results(results: dict[str, Any] | str, debug: bool = False) -> str: +def format_test_results( + results: TestSuiteResultModel | MultiEndpointTestResultsModel | str, debug: bool = False +) -> str: """Format test results for human-readable output""" if isinstance(results, str): return results output = [] - # Check if this is a single endpoint test result (pure test results) - if "endpoints" not in results: - # Single endpoint test - results are pure test results - endpoint_status = results.get("status", "unknown") + # Single-endpoint test suite + if isinstance(results, TestSuiteResultModel): + endpoint_status = results.status if endpoint_status == "ok": output.append(f"{click.style('✅ All tests passed!', fg='green', bold=True)}") else: output.append(f"{click.style('❌ Some tests failed!', fg='red', bold=True)}") - if "message" in results: - output.append(f"{click.style('Error:', fg='red')} {results['message']}") + if results.message: + output.append(f"{click.style('Error:', fg='red')} {results.message}") # Test results - tests = results.get("tests", []) - if tests: + if results.tests: output.append(f"\n{click.style('📋 Test Results:', fg='cyan', bold=True)}") - for test in tests: - test_name = test.get("name", "Unnamed test") - test_status = test.get("status", "unknown") - test_time = test.get("time", 0) + for test in results.tests: + test_name = test.name or "Unnamed test" + test_status = test.status + test_time = test.time or 0.0 if test_status == "passed": output.append( @@ -57,29 +61,27 @@ def format_test_results(results: dict[str, Any] | str, debug: bool = False) -> s output.append( f" {click.style('✗', fg='red')} {click.style(test_name, fg='cyan')} {click.style(f'({test_time:.2f}s)', fg='bright_black')}" ) - if test.get("error"): - output.append(f" {click.style('Error:', fg='red')} {test['error']}") - if ( - debug - and test.get("error") - and hasattr(test["error"], "__cause__") - and test["error"].__cause__ - ): + if test.error: + output.append(f" {click.style('Error:', fg='red')} {test.error}") + if debug and test.error_cause: output.append( - f" {click.style('Cause:', fg='yellow')} {str(test['error'].__cause__)}" + f" {click.style('Cause:', fg='yellow')} {test.error_cause}" ) + if results.no_tests: + output.append(f"\n{click.style('ℹ️ No tests defined for this endpoint', fg='blue')}") + return "\n".join(output) # Multiple endpoint tests - new structure with test_results nested - endpoints = results.get("endpoints", []) + endpoints: list[EndpointTestResultModel] = results.endpoints if not endpoints: output.append(click.style("ℹ️ No endpoints found to test", fg="blue")) output.append(" Create test cases in your endpoint YAML files") return "\n".join(output) # Count passed and failed endpoints - passed_count = sum(1 for r in endpoints if r.get("test_results", {}).get("status") == "ok") + passed_count = sum(1 for r in endpoints if r.test_results.status == "ok") failed_count = len(endpoints) - passed_count # Header @@ -94,44 +96,38 @@ def format_test_results(results: dict[str, Any] | str, debug: bool = False) -> s # Show failed endpoints first if failed_count > 0: output.append(f"\n{click.style('❌ Failed tests:', fg='red', bold=True)}") - for endpoint_data in sorted(endpoints, key=lambda x: x["endpoint"]): - endpoint = endpoint_data.get("endpoint", "unknown") - path = endpoint_data.get("path", "") - test_results = endpoint_data.get("test_results", {}) + for endpoint_data in sorted(endpoints, key=lambda x: x.endpoint): + endpoint = endpoint_data.endpoint + path = endpoint_data.path + test_results = endpoint_data.test_results - if test_results.get("status") != "ok": + if test_results.status != "ok": output.append( f"\n {click.style('✗', fg='red')} {click.style(endpoint, fg='yellow')} ({path})" ) - if test_results.get("message"): - output.append( - f" {click.style('Error:', fg='red')} {test_results['message']}" - ) + if test_results.message: + output.append(f" {click.style('Error:', fg='red')} {test_results.message}") - tests = test_results.get("tests", []) - if test_results.get("no_tests"): + tests = test_results.tests or [] + if test_results.no_tests: output.append(f" {click.style('(No tests)', fg='bright_black')}") else: for test in tests: - test_name = test.get("name", "Unnamed test") - test_status = test.get("status", "unknown") - test_time = test.get("time", 0) + test_name = test.name or "Unnamed test" + test_status = test.status + test_time = test.time or 0.0 if test_status != "passed": output.append( f" {click.style('✗', fg='red')} {test_name} {click.style(f'({test_time:.2f}s)', fg='bright_black')}" ) - if test.get("error"): + if test.error: output.append( - f" {click.style('Error:', fg='red')} {test['error']}" + f" {click.style('Error:', fg='red')} {test.error}" ) - if ( - debug - and hasattr(test.get("error"), "__cause__") - and test["error"].__cause__ - ): + if debug and test.error_cause: output.append( - f" {click.style('Cause:', fg='yellow')} {str(test['error'].__cause__)}" + f" {click.style('Cause:', fg='yellow')} {test.error_cause}" ) else: output.append( @@ -141,24 +137,24 @@ def format_test_results(results: dict[str, Any] | str, debug: bool = False) -> s # Then show passed endpoints if passed_count > 0: output.append(f"\n{click.style('✅ Passed tests:', fg='green', bold=True)}") - for endpoint_data in sorted(endpoints, key=lambda x: x["endpoint"]): - endpoint = endpoint_data.get("endpoint", "unknown") - path = endpoint_data.get("path", "") - test_results = endpoint_data.get("test_results", {}) + for endpoint_data in sorted(endpoints, key=lambda x: x.endpoint): + endpoint = endpoint_data.endpoint + path = endpoint_data.path + test_results = endpoint_data.test_results - if test_results.get("status") == "ok": + if test_results.status == "ok": output.append( f"\n {click.style('✓', fg='green')} {click.style(endpoint, fg='yellow')} ({path})" ) - tests = test_results.get("tests", []) - if test_results.get("no_tests"): + tests = test_results.tests or [] + if test_results.no_tests: output.append(f" {click.style('(No tests)', fg='bright_black')}") else: for test in tests: - test_name = test.get("name", "Unnamed test") - test_status = test.get("status", "unknown") - test_time = test.get("time", 0) + test_name = test.name or "Unnamed test" + test_status = test.status + test_time = test.time or 0.0 if test_status == "passed": output.append( f" {click.style('✓', fg='green')} {test_name} {click.style(f'({test_time:.2f}s)', fg='bright_black')}" @@ -167,9 +163,9 @@ def format_test_results(results: dict[str, Any] | str, debug: bool = False) -> s output.append( f" {click.style('✗', fg='red')} {test_name} {click.style(f'({test_time:.2f}s)', fg='bright_black')}" ) - if test.get("error"): + if test.error: output.append( - f" {click.style('Error:', fg='red')} {test['error']}" + f" {click.style('Error:', fg='red')} {test.error}" ) # Summary message @@ -181,11 +177,11 @@ def format_test_results(results: dict[str, Any] | str, debug: bool = False) -> s ) # Calculate total test time - total_time = 0 + total_time = 0.0 for endpoint_result in endpoints: - tests = endpoint_result.get("test_results", {}).get("tests", []) + tests = endpoint_result.test_results.tests or [] for test in tests: - total_time += test.get("time", 0) + total_time += test.time or 0.0 output.append(f"\n{click.style('⏱️ Total time:', fg='cyan')} {total_time:.2f}s") @@ -387,6 +383,11 @@ async def _test_impl( ) if json_output: - output_result(results, json_output, debug) + payload = ( + results + if isinstance(results, str) + else results.model_dump(mode="json", exclude_none=True) + ) + output_result(payload, json_output, debug) else: click.echo(format_test_results(results, debug)) diff --git a/src/mxcp/server/interfaces/cli/validate.py b/src/mxcp/server/interfaces/cli/validate.py index 1ae9a8bd..da94a996 100644 --- a/src/mxcp/server/interfaces/cli/validate.py +++ b/src/mxcp/server/interfaces/cli/validate.py @@ -1,5 +1,3 @@ -from typing import Any - import click from mxcp.server.core.config.analytics import track_command_with_timing @@ -13,10 +11,16 @@ output_result, resolve_profile, ) +from mxcp.server.services.endpoints.models import ( + EndpointValidationResultModel, + EndpointValidationSummaryModel, +) from mxcp.server.services.endpoints.validator import validate_all_endpoints, validate_endpoint -def format_validation_results(results: Any) -> str: +def format_validation_results( + results: EndpointValidationResultModel | EndpointValidationSummaryModel | str, +) -> str: """Format validation results for human-readable output""" if isinstance(results, str): return results @@ -24,12 +28,10 @@ def format_validation_results(results: Any) -> str: output = [] # Overall status - status = results.get("status", "unknown") - - # Single endpoint validation - if "path" in results: - path = results["path"] - message = results.get("message", "") + if isinstance(results, EndpointValidationResultModel): + status = results.status + path = results.path + message = results.message or "" if status == "ok": output.append(f"{click.style('✅ Validation passed!', fg='green', bold=True)}") @@ -49,7 +51,8 @@ def format_validation_results(results: Any) -> str: return "\n".join(output) # Multiple endpoint validation - validated = results.get("validated", []) + assert isinstance(results, EndpointValidationSummaryModel) + validated = results.validated if not validated: output.append(click.style("ℹ️ No endpoints found to validate", fg="blue")) output.append( @@ -58,7 +61,7 @@ def format_validation_results(results: Any) -> str: return "\n".join(output) # Count valid and failed endpoints - valid_count = sum(1 for r in validated if r.get("status") == "ok") + valid_count = sum(1 for r in validated if r.status == "ok") failed_count = len(validated) - valid_count # Header @@ -75,11 +78,10 @@ def format_validation_results(results: Any) -> str: failed_endpoints = [] for result in validated: - path = result.get("path", "unknown") - message = result.get("message", "") - result_status = result.get("status", "unknown") + path = result.path + message = result.message or "" - if result_status == "ok": + if result.status == "ok": valid_endpoints.append((path, message)) else: failed_endpoints.append((path, message)) @@ -189,7 +191,12 @@ def validate( result = validate_all_endpoints(site_config, execution_engine) if json_output: - output_result(result, json_output, debug) + payload = ( + result + if isinstance(result, str) + else result.model_dump(mode="json", exclude_none=True) + ) + output_result(payload, json_output, debug) else: click.echo(format_validation_results(result)) finally: diff --git a/src/mxcp/server/interfaces/server/mcp.py b/src/mxcp/server/interfaces/server/mcp.py index b67d4731..9d131b87 100644 --- a/src/mxcp/server/interfaces/server/mcp.py +++ b/src/mxcp/server/interfaces/server/mcp.py @@ -49,11 +49,13 @@ from mxcp.server.core.refs.external import ExternalRefTracker from mxcp.server.core.reload import ReloadManager, ReloadRequest from mxcp.server.core.telemetry import configure_telemetry_from_config, shutdown_telemetry -from mxcp.server.definitions.endpoints._types import ( - ParamDefinition, - PromptDefinition, - ResourceDefinition, - ToolDefinition, +from mxcp.server.definitions.endpoints.models import ( + EndpointDefinitionModel, + ParamDefinitionModel, + PromptDefinitionModel, + ResourceDefinitionModel, + ToolDefinitionModel, + TypeDefinitionModel, ) from mxcp.server.definitions.endpoints.loader import EndpointLoader from mxcp.server.definitions.endpoints.utils import EndpointType @@ -351,17 +353,17 @@ def get_endpoint_counts(self) -> dict[str, int]: tool_count = sum( 1 for _, endpoint, error in self._all_endpoints - if error is None and endpoint is not None and "tool" in endpoint + if error is None and endpoint is not None and endpoint.tool is not None ) resource_count = sum( 1 for _, endpoint, error in self._all_endpoints - if error is None and endpoint is not None and "resource" in endpoint + if error is None and endpoint is not None and endpoint.resource is not None ) prompt_count = sum( 1 for _, endpoint, error in self._all_endpoints - if error is None and endpoint is not None and "prompt" in endpoint + if error is None and endpoint is not None and endpoint.prompt is not None ) return { @@ -815,9 +817,13 @@ def _sanitize_model_name(self, name: str) -> str: # Capitalize first letter for class name convention return name.title().replace("_", "") + def _schema_dict(self, schema: TypeDefinitionModel) -> dict[str, Any]: + """Convert a schema model to a plain dictionary representation.""" + return schema.model_dump(mode="python", exclude_unset=True, by_alias=True) + def _create_pydantic_model_from_schema( self, - schema_def: dict[str, Any], + schema_def: TypeDefinitionModel, model_name: str, endpoint_type: EndpointType | None = None, ) -> Any: # Returns types that can be used in type annotations @@ -831,12 +837,13 @@ def _create_pydantic_model_from_schema( Returns: Pydantic model class or type annotation """ - # Cache key for this schema (include endpoint_type to avoid conflicts) - cache_key = f"{model_name}_{hash(json.dumps(schema_def, sort_keys=True))}_{endpoint_type}" + schema_dict = self._schema_dict(schema_def) + + cache_key = f"{model_name}_{hash(json.dumps(schema_dict, sort_keys=True))}_{endpoint_type}" if cache_key in self._model_cache: return self._model_cache[cache_key] - json_type = schema_def.get("type", "string") + json_type = schema_def.type # Declare variable with explicit type annotation final_type: Any @@ -847,18 +854,13 @@ def _create_pydantic_model_from_schema( # Handle primitive types if json_type == "string": - # Handle enums - if "enum" in schema_def: - enum_values = schema_def["enum"] - if all(isinstance(v, str) for v in enum_values): - # For enums, we'll use a simple str type with validation - # Since we can't dynamically create Literal unions in a type-safe way - if make_nullable: - final_type = str | None - else: - final_type = str - self._model_cache[cache_key] = final_type - return final_type + if schema_def.enum and all(isinstance(v, str) for v in schema_def.enum): + if make_nullable: + final_type = str | None + else: + final_type = str + self._model_cache[cache_key] = final_type + return final_type # Create Field with constraints field_kwargs = self._extract_field_constraints(schema_def) @@ -921,7 +923,7 @@ def _create_pydantic_model_from_schema( return final_type elif json_type == "array": - items_schema = schema_def.get("items") + items_schema = schema_def.items if items_schema is not None: item_type = self._create_pydantic_model_from_schema( items_schema, f"{model_name}Item", endpoint_type @@ -941,10 +943,8 @@ def _create_pydantic_model_from_schema( return final_type elif json_type == "object": - # Handle complex objects with properties - properties = schema_def.get("properties", {}) - required_fields = set(schema_def.get("required", [])) - # additional_properties = schema_def.get("additionalProperties", True) + properties = schema_def.properties or {} + required_fields = set(schema_def.required or []) if not properties: # Generic object @@ -983,8 +983,6 @@ def _create_pydantic_model_from_schema( # Create the model with proper configuration - # model_config = ConfigDict(extra="allow" if additional_properties else "forbid") - # Create model with fields and config # Note: In Pydantic v2, __config__ is not supported in create_model # Instead, we create the model and then set the config @@ -1000,52 +998,46 @@ def _create_pydantic_model_from_schema( self._model_cache[cache_key] = final_type return final_type - def _extract_field_constraints(self, schema_def: dict[str, Any]) -> dict[str, Any]: - """Extract Pydantic Field constraints from JSON Schema definition.""" - field_kwargs = {} - - # Description - if "description" in schema_def: - field_kwargs["description"] = schema_def["description"] - - # Default value - if "default" in schema_def: - field_kwargs["default"] = schema_def["default"] - - # Examples - if "examples" in schema_def and schema_def["examples"]: - field_kwargs["examples"] = schema_def["examples"] - - # String constraints - if "minLength" in schema_def: - field_kwargs["min_length"] = schema_def["minLength"] - if "maxLength" in schema_def: - field_kwargs["max_length"] = schema_def["maxLength"] - if "pattern" in schema_def: - field_kwargs["pattern"] = schema_def["pattern"] - - # Numeric constraints - if "minimum" in schema_def: - field_kwargs["ge"] = schema_def["minimum"] - if "maximum" in schema_def: - field_kwargs["le"] = schema_def["maximum"] - if "exclusiveMinimum" in schema_def: - field_kwargs["gt"] = schema_def["exclusiveMinimum"] - if "exclusiveMaximum" in schema_def: - field_kwargs["lt"] = schema_def["exclusiveMaximum"] - if "multipleOf" in schema_def: - field_kwargs["multiple_of"] = schema_def["multipleOf"] - - # Array constraints - if "minItems" in schema_def: - field_kwargs["min_length"] = schema_def["minItems"] - if "maxItems" in schema_def: - field_kwargs["max_length"] = schema_def["maxItems"] + def _extract_field_constraints(self, schema_def: TypeDefinitionModel) -> dict[str, Any]: + """Extract Pydantic Field constraints from a type definition.""" + field_kwargs: dict[str, Any] = {} + + if schema_def.description: + field_kwargs["description"] = schema_def.description + + if "default" in schema_def.model_fields_set: + field_kwargs["default"] = schema_def.default + + if schema_def.examples: + field_kwargs["examples"] = schema_def.examples + + if schema_def.minLength is not None: + field_kwargs["min_length"] = schema_def.minLength + if schema_def.maxLength is not None: + field_kwargs["max_length"] = schema_def.maxLength + if schema_def.pattern is not None: + field_kwargs["pattern"] = schema_def.pattern + + if schema_def.minimum is not None: + field_kwargs["ge"] = schema_def.minimum + if schema_def.maximum is not None: + field_kwargs["le"] = schema_def.maximum + if schema_def.exclusiveMinimum is not None: + field_kwargs["gt"] = schema_def.exclusiveMinimum + if schema_def.exclusiveMaximum is not None: + field_kwargs["lt"] = schema_def.exclusiveMaximum + if schema_def.multipleOf is not None: + field_kwargs["multiple_of"] = schema_def.multipleOf + + if schema_def.minItems is not None: + field_kwargs["min_length"] = schema_def.minItems + if schema_def.maxItems is not None: + field_kwargs["max_length"] = schema_def.maxItems return field_kwargs def _create_pydantic_field_annotation( - self, param_def: ParamDefinition, endpoint_type: EndpointType | None = None + self, param_def: TypeDefinitionModel, endpoint_type: EndpointType | None = None ) -> Any: """Create a Pydantic type annotation from parameter definition. @@ -1056,13 +1048,11 @@ def _create_pydantic_field_annotation( Returns: Pydantic type annotation (class or Annotated type) """ - param_name = param_def.get("name", "param") - return self._create_pydantic_model_from_schema( - cast(dict[str, Any], param_def), param_name, endpoint_type - ) + param_name = getattr(param_def, "name", None) or "param" + return self._create_pydantic_model_from_schema(param_def, param_name, endpoint_type) def _json_schema_to_python_type( - self, param_def: ParamDefinition, endpoint_type: EndpointType | None = None + self, param_def: TypeDefinitionModel, endpoint_type: EndpointType | None = None ) -> Any: """Convert JSON Schema type to Python type annotation. @@ -1075,7 +1065,7 @@ def _json_schema_to_python_type( """ return self._create_pydantic_field_annotation(param_def, endpoint_type) - def _create_tool_annotations(self, tool_def: ToolDefinition) -> ToolAnnotations | None: + def _create_tool_annotations(self, tool_def: ToolDefinitionModel) -> ToolAnnotations | None: """Create ToolAnnotations from tool definition. Args: @@ -1084,17 +1074,12 @@ def _create_tool_annotations(self, tool_def: ToolDefinition) -> ToolAnnotations Returns: ToolAnnotations object if annotations are present, None otherwise """ - annotations_data = tool_def.get("annotations", {}) - if not annotations_data: + annotations_model = tool_def.annotations + if not annotations_model: return None - return ToolAnnotations( - title=annotations_data.get("title"), - readOnlyHint=annotations_data.get("readOnlyHint"), - destructiveHint=annotations_data.get("destructiveHint"), - idempotentHint=annotations_data.get("idempotentHint"), - openWorldHint=annotations_data.get("openWorldHint"), - ) + annotations_data = annotations_model.model_dump(mode="python", exclude_unset=True) + return ToolAnnotations(**annotations_data) def _convert_param_type(self, value: Any, param_type: str) -> Any: """Convert parameter value to the correct type based on JSON Schema type. @@ -1189,12 +1174,12 @@ def _build_and_register( self, endpoint_type: EndpointType, endpoint_key: str, # "tool" | "resource" | "prompt" - endpoint_def: ToolDefinition | ResourceDefinition | PromptDefinition, + endpoint_def: ToolDefinitionModel | ResourceDefinitionModel | PromptDefinitionModel, decorator: Any, # self.mcp.tool() | self.mcp.resource(uri) | self.mcp.prompt() log_name: str, # for nice logging ) -> None: # Get parameter definitions - parameters = endpoint_def.get("parameters") or [] + parameters = endpoint_def.parameters or [] # Create function signature with proper Pydantic type annotations # Include Context as the first parameter for accessing session_id @@ -1203,19 +1188,24 @@ def _build_and_register( # Sort parameters so required (no default) come before optional (with default) # This is necessary for valid Python function signatures - required_params = [p for p in parameters if "default" not in p] - optional_params = [p for p in parameters if "default" in p] - sorted_parameters = required_params + optional_params + def has_default(param_model: ParamDefinitionModel) -> bool: + param_dump = param_model.model_dump(mode="python", exclude_unset=True, by_alias=True) + return "default" in param_dump + + sorted_parameters = [p for p in parameters if not has_default(p)] + [ + p for p in parameters if has_default(p) + ] for param in sorted_parameters: - param_name = param["name"] + param_schema = param.model_dump(mode="python", exclude_unset=True, by_alias=True) + param_name = param.name param_type = self._json_schema_to_python_type(param, endpoint_type) param_annotations[param_name] = param_type # Create string representation for makefun with default values - if "default" in param: + if "default" in param_schema: # Parameter has a default value, make it optional in signature - default_value = repr(param["default"]) + default_value = repr(param_schema["default"]) param_signatures.append(f"{param_name}={default_value}") else: # Required parameter @@ -1261,9 +1251,9 @@ async def _body(**kwargs: Any) -> Any: # Determine endpoint name early for use in span and metrics name: str if endpoint_key == "resource": - name = cast(str, endpoint_def.get("uri", "unknown")) + name = cast(str, getattr(endpoint_def, "uri", "unknown")) else: - name = cast(str, endpoint_def.get("name", "unnamed")) + name = cast(str, getattr(endpoint_def, "name", "unnamed")) # Create root span for endpoint execution with all MXCP-specific attributes with traced_operation( @@ -1274,9 +1264,7 @@ async def _body(**kwargs: Any) -> Any: }, ) as span: try: - logger.info( - f"Calling {log_name} {endpoint_def.get('name', endpoint_def.get('uri'))} with: {kwargs}" - ) + logger.info(f"Calling {log_name} {name} with: {kwargs}") if user_context: logger.info( f"Authenticated user: {user_context.username} (provider: {user_context.provider})" @@ -1329,9 +1317,7 @@ async def _body(**kwargs: Any) -> Any: except Exception as e: status = "error" error_msg = str(e) - logger.error( - f"Error executing {log_name} {endpoint_def.get('name', endpoint_def.get('uri'))}:\n{traceback.format_exc()}" - ) + logger.error(f"Error executing {log_name} {name}:\n{traceback.format_exc()}") raise finally: # Calculate duration @@ -1424,9 +1410,9 @@ async def _body(**kwargs: Any) -> Any: # Create function with proper signature and annotations using makefun # ------------------------------------------------------------------- if endpoint_key == "resource": - original_name = cast(str, endpoint_def.get("uri", "unknown")) + original_name = cast(str, getattr(endpoint_def, "uri", "unknown")) else: - original_name = cast(str, endpoint_def.get("name", "unnamed")) + original_name = cast(str, getattr(endpoint_def, "name", "unnamed")) func_name = self.mcp_name_to_py(original_name) # Create the function with proper signature @@ -1436,10 +1422,14 @@ async def _body(**kwargs: Any) -> Any: handler.__annotations__ = param_annotations # Add return type annotation if return schema is defined - return_schema = endpoint_def.get("return") + return_schema = ( + endpoint_def.return_ + if isinstance(endpoint_def, (ToolDefinitionModel, ResourceDefinitionModel)) + else None + ) if return_schema: return_type = self._create_pydantic_model_from_schema( - cast(dict[str, Any], return_schema), f"{original_name}Return", endpoint_type + return_schema, f"{original_name}Return", endpoint_type ) handler.__annotations__["return"] = return_type @@ -1448,7 +1438,7 @@ async def _body(**kwargs: Any) -> Any: decorator(handler) logger.info(f"Registered {log_name}: {original_name} (function: {func_name})") - def _register_tool(self, tool_def: ToolDefinition) -> None: + def _register_tool(self, tool_def: ToolDefinitionModel) -> None: """Register a tool endpoint with MCP. Args: @@ -1462,14 +1452,14 @@ def _register_tool(self, tool_def: ToolDefinition) -> None: "tool", tool_def, decorator=self.mcp.tool( - name=tool_def.get("name"), - description=tool_def.get("description"), + name=tool_def.name, + description=tool_def.description, annotations=annotations, ), log_name="tool", ) - def _register_resource(self, resource_def: ResourceDefinition) -> None: + def _register_resource(self, resource_def: ResourceDefinitionModel) -> None: """Register a resource endpoint with MCP. Args: @@ -1480,15 +1470,15 @@ def _register_resource(self, resource_def: ResourceDefinition) -> None: "resource", resource_def, decorator=self.mcp.resource( - resource_def["uri"], - name=cast(str | None, resource_def.get("name")), - description=resource_def.get("description"), - mime_type=resource_def.get("mime_type"), + resource_def.uri, + name=resource_def.name, + description=resource_def.description, + mime_type=resource_def.mime_type, ), log_name="resource", ) - def _register_prompt(self, prompt_def: PromptDefinition) -> None: + def _register_prompt(self, prompt_def: PromptDefinitionModel) -> None: """Register a prompt endpoint with MCP. Args: @@ -1499,7 +1489,7 @@ def _register_prompt(self, prompt_def: PromptDefinition) -> None: "prompt", prompt_def, decorator=self.mcp.prompt( - name=prompt_def.get("name"), description=prompt_def.get("description") + name=prompt_def.name, description=prompt_def.description ), log_name="prompt", ) @@ -1817,14 +1807,14 @@ def register_endpoints(self) -> None: str(path), self.site_config, self.runtime_environment.execution_engine ) - if validation_result["status"] != "ok": + if validation_result.status != "ok": logger.warning( - f"Skipping invalid endpoint {path}: {validation_result.get('message', 'Unknown error')}" + f"Skipping invalid endpoint {path}: {validation_result.message or 'Unknown error'}" ) self.skipped_endpoints.append( { "path": str(path), - "error": validation_result.get("message", "Unknown error"), + "error": validation_result.message or "Unknown error", } ) continue @@ -1833,27 +1823,21 @@ def register_endpoints(self) -> None: logger.warning(f"Endpoint definition is None for {path}") continue - if "tool" in endpoint_def: - tool_def = endpoint_def.get("tool") - if tool_def: - self._register_tool(tool_def) - logger.info( - f"Registered tool endpoint from {path}: {tool_def.get('name', 'unnamed')}" - ) - elif "resource" in endpoint_def: - resource_def = endpoint_def.get("resource") - if resource_def: - self._register_resource(resource_def) - logger.info( - f"Registered resource endpoint from {path}: {resource_def.get('uri', 'unknown')}" - ) - elif "prompt" in endpoint_def: - prompt_def = endpoint_def.get("prompt") - if prompt_def: - self._register_prompt(prompt_def) - logger.info( - f"Registered prompt endpoint from {path}: {prompt_def.get('name', 'unnamed')}" - ) + if endpoint_def.tool is not None: + self._register_tool(endpoint_def.tool) + logger.info( + f"Registered tool endpoint from {path}: {endpoint_def.tool.name}" + ) + elif endpoint_def.resource is not None: + self._register_resource(endpoint_def.resource) + logger.info( + f"Registered resource endpoint from {path}: {endpoint_def.resource.uri}" + ) + elif endpoint_def.prompt is not None: + self._register_prompt(endpoint_def.prompt) + logger.info( + f"Registered prompt endpoint from {path}: {endpoint_def.prompt.name}" + ) else: logger.warning(f"Unknown endpoint type in {path}: {endpoint_def}") except Exception as e: diff --git a/src/mxcp/server/schemas/common-types-schema-1.json b/src/mxcp/server/schemas/common-types-schema-1.json deleted file mode 100644 index ea52dbf8..00000000 --- a/src/mxcp/server/schemas/common-types-schema-1.json +++ /dev/null @@ -1,171 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "MXCP Common Type Definitions", - "description": "Common type definitions shared across MXCP tool, resource, and prompt schemas", - - "definitions": { - "typeDefinition": { - "type": "object", - "required": ["type"], - "properties": { - "type": { - "type": "string", - "enum": ["string", "number", "integer", "boolean", "array", "object"], - "description": "The data type of the value." - }, - "format": { - "type": "string", - "enum": ["email", "uri", "date", "time", "date-time", "duration", "timestamp"], - "description": "Expected format for string values." - }, - "sensitive": { - "type": "boolean", - "description": "Whether this field contains sensitive data that should be redacted in logs and filtered by policies.", - "default": false - }, - "minLength": { - "type": "integer", - "minimum": 0, - "description": "Minimum string length." - }, - "maxLength": { - "type": "integer", - "minimum": 0, - "description": "Maximum string length." - }, - "minimum": { - "type": "number", - "description": "Minimum value for numbers or integers." - }, - "maximum": { - "type": "number", - "description": "Maximum value for numbers or integers." - }, - "exclusiveMinimum": { - "type": "number", - "description": "Exclusive minimum value for numbers or integers." - }, - "exclusiveMaximum": { - "type": "number", - "description": "Exclusive maximum value for numbers or integers." - }, - "multipleOf": { - "type": "number", - "description": "Value must be a multiple of this for numbers or integers." - }, - "minItems": { - "type": "integer", - "minimum": 0, - "description": "Minimum number of array items." - }, - "maxItems": { - "type": "integer", - "minimum": 0, - "description": "Maximum number of array items." - }, - "uniqueItems": { - "type": "boolean", - "description": "Whether array items must be unique." - }, - "items": { - "$ref": "#/definitions/typeDefinition", - "description": "Schema for items if type is array." - }, - "properties": { - "type": "object", - "description": "Schema for object properties if type is object.", - "additionalProperties": { - "$ref": "#/definitions/typeDefinition" - } - }, - "required": { - "type": "array", - "description": "List of required fields if type is object.", - "items": { "type": "string" } - }, - "additionalProperties": { - "type": "boolean", - "description": "Whether to allow additional properties not defined in the schema. Defaults to true if not specified." - } - } - }, - - "paramDefinition": { - "type": "object", - "required": ["name", "type", "description"], - "properties": { - "name": { - "type": "string", - "description": "Parameter name.", - "pattern": "^[a-zA-Z_][a-zA-Z0-9_]*$", - "minLength": 1 - }, - "description": { - "type": "string", - "description": "What this parameter represents." - }, - "default": { - "description": "Optional default value if none is provided." - }, - "examples": { - "type": "array", - "description": "Example values for this parameter.", - "items": {} - }, - "enum": { - "type": "array", - "description": "List of allowed values.", - "items": {} - } - }, - "allOf": [ - { "$ref": "#/definitions/typeDefinition" } - ] - }, - - "policySet": { - "type": "object", - "description": "Policy definitions for endpoint access control and data filtering.", - "properties": { - "input": { - "type": "array", - "description": "Input policies evaluated before endpoint execution.", - "items": { "$ref": "#/definitions/policyDefinition" } - }, - "output": { - "type": "array", - "description": "Output policies evaluated after endpoint execution.", - "items": { "$ref": "#/definitions/policyDefinition" } - } - }, - "additionalProperties": false - }, - - "policyDefinition": { - "type": "object", - "required": ["condition", "action"], - "description": "A single policy rule definition.", - "properties": { - "condition": { - "type": "string", - "description": "CEL expression that determines when this policy applies." - }, - "action": { - "type": "string", - "enum": ["deny", "filter_fields", "mask_fields", "filter_sensitive_fields"], - "description": "Action to take when the condition is true." - }, - "reason": { - "type": "string", - "description": "Human-readable explanation for the policy action." - }, - "fields": { - "type": "array", - "items": { "type": "string" }, - "description": "List of field names for filter_fields and mask_fields actions." - } - }, - "additionalProperties": false - } - } -} \ No newline at end of file diff --git a/src/mxcp/server/schemas/drift-report-schema-1.json b/src/mxcp/server/schemas/drift-report-schema-1.json deleted file mode 100644 index 56123efb..00000000 --- a/src/mxcp/server/schemas/drift-report-schema-1.json +++ /dev/null @@ -1,145 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "MXCP Drift Report", - "type": "object", - "required": ["version", "generated_at", "baseline_snapshot_path", "current_snapshot_generated_at", "baseline_snapshot_generated_at", "has_drift", "summary", "table_changes", "resource_changes"], - "properties": { - "version": { - "type": "integer", - "description": "Version of the drift report format. Must be 1.", - "enum": [1], - "default": 1 - }, - "generated_at": { - "type": "string", - "format": "date-time", - "description": "ISO 8601 timestamp when the report was generated" - }, - "baseline_snapshot_path": { - "type": "string", - "description": "Path to the baseline snapshot file" - }, - "current_snapshot_generated_at": { - "type": "string", - "format": "date-time", - "description": "Timestamp when the current snapshot was generated" - }, - "baseline_snapshot_generated_at": { - "type": "string", - "format": "date-time", - "description": "Timestamp when the baseline snapshot was generated" - }, - "has_drift": { - "type": "boolean", - "description": "Whether any drift was detected" - }, - "summary": { - "type": "object", - "description": "Summary counts of changes by type", - "properties": { - "tables_added": { "type": "integer" }, - "tables_removed": { "type": "integer" }, - "tables_modified": { "type": "integer" }, - "resources_added": { "type": "integer" }, - "resources_removed": { "type": "integer" }, - "resources_modified": { "type": "integer" } - }, - "additionalProperties": false - }, - "table_changes": { - "type": "array", - "description": "List of table changes detected", - "items": { - "type": "object", - "required": ["name", "change_type"], - "properties": { - "name": { - "type": "string", - "description": "Name of the table" - }, - "change_type": { - "type": "string", - "enum": ["added", "removed", "modified"], - "description": "Type of change" - }, - "columns_added": { - "type": "array", - "description": "Columns that were added", - "items": { - "type": "object", - "required": ["name", "type"], - "properties": { - "name": { "type": "string" }, - "type": { "type": "string" } - } - } - }, - "columns_removed": { - "type": "array", - "description": "Columns that were removed", - "items": { - "type": "object", - "required": ["name", "type"], - "properties": { - "name": { "type": "string" }, - "type": { "type": "string" } - } - } - }, - "columns_modified": { - "type": "array", - "description": "Columns that were modified", - "items": { - "type": "object", - "properties": { - "name": { "type": "string" }, - "old_type": { "type": "string" }, - "new_type": { "type": "string" } - } - } - } - } - } - }, - "resource_changes": { - "type": "array", - "description": "List of resource changes detected", - "items": { - "type": "object", - "required": ["path", "change_type"], - "properties": { - "path": { - "type": "string", - "description": "Path to the resource file" - }, - "endpoint": { - "type": "string", - "description": "Endpoint identifier (e.g., 'tool/name')" - }, - "change_type": { - "type": "string", - "enum": ["added", "removed", "modified"], - "description": "Type of change" - }, - "validation_changed": { - "type": "boolean", - "description": "Whether validation results changed" - }, - "test_results_changed": { - "type": "boolean", - "description": "Whether test results changed" - }, - "definition_changed": { - "type": "boolean", - "description": "Whether endpoint definition changed" - }, - "details": { - "type": "object", - "description": "Specific details about what changed", - "additionalProperties": true - } - } - } - } - } -} \ No newline at end of file diff --git a/src/mxcp/server/schemas/drift-snapshot-schema-1.json b/src/mxcp/server/schemas/drift-snapshot-schema-1.json deleted file mode 100644 index 8b72fc32..00000000 --- a/src/mxcp/server/schemas/drift-snapshot-schema-1.json +++ /dev/null @@ -1,145 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "MXCP Drift Snapshot", - "type": "object", - "required": ["version", "generated_at", "tables", "resources"], - "properties": { - "version": { - "type": "integer", - "description": "Version of the drift snapshot format. Must be 1.", - "enum": [1], - "default": 1 - }, - "generated_at": { - "type": "string", - "format": "date-time", - "description": "ISO 8601 timestamp when the snapshot was generated" - }, - - "tables": { - "type": "array", - "description": "List of tables in the DuckDB catalog", - "items": { - "type": "object", - "required": ["name", "columns"], - "properties": { - "name": { - "type": "string", - "description": "Name of the table" - }, - "columns": { - "type": "array", - "description": "List of columns in the table", - "items": { - "type": "object", - "required": ["name", "type"], - "properties": { - "name": { - "type": "string", - "description": "Name of the column" - }, - "type": { - "type": "string", - "description": "DuckDB data type of the column" - } - } - } - } - } - } - }, - "resources": { - "type": "array", - "description": "List of resources with validation results, test results, and definition", - "items": { - "type": "object", - "required": ["validation_results"], - "properties": { - "validation_results": { - "type": "object", - "required": ["status", "path"], - "properties": { - "status": { - "type": "string", - "enum": ["ok", "error"], - "description": "Validation status" - }, - "path": { - "type": "string", - "description": "Path to the endpoint file (relative to repository root)" - }, - "message": { - "type": "string", - "description": "Error message if validation failed" - } - }, - "description": "Results of validation for the resource" - }, - "test_results": { - "type": "object", - "required": ["status", "tests_run"], - "properties": { - "status": { - "type": "string", - "enum": ["ok", "error", "failed"], - "description": "Test execution status" - }, - "tests_run": { - "type": "integer", - "description": "Number of tests run" - }, - "tests": { - "type": "array", - "description": "List of per-test results", - "items": { - "type": "object", - "required": ["name", "status", "time"], - "properties": { - "name": { - "type": "string", - "description": "Name of the test" - }, - "description": { - "type": "string", - "description": "Description of the test" - }, - "status": { - "type": "string", - "enum": ["passed", "failed", "error"], - "description": "Test status" - }, - "error": { - "type": "string", - "description": "Error message if test failed" - }, - "time": { - "type": "number", - "description": "Time taken to run the test in seconds" - } - } - } - } - }, - "description": "Results of tests for the resource" - }, - "definition": { - "oneOf": [ - { "$ref": "../../endpoints/endpoint_schemas/tool-schema-1.json#/definitions/toolDefinition" }, - { "$ref": "../../endpoints/endpoint_schemas/resource-schema-1.json#/definitions/resourceDefinition" }, - { "$ref": "../../endpoints/endpoint_schemas/prompt-schema-1.json#/definitions/promptDefinition" } - ], - "description": "Endpoint definition" - }, - "metadata": { - "type": "object", - "properties": { - "title": { "type": "string", "description": "Short display title" }, - "description": { "type": "string", "description": "Longer description" } - }, - "description": "Optional metadata for documentation purposes" - } - } - } - } - } -} \ No newline at end of file diff --git a/src/mxcp/server/schemas/eval-schema-1.json b/src/mxcp/server/schemas/eval-schema-1.json deleted file mode 100644 index 8754a6f5..00000000 --- a/src/mxcp/server/schemas/eval-schema-1.json +++ /dev/null @@ -1,111 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "MXCP Eval Suite", - "type": "object", - "required": ["mxcp", "suite", "tests"], - "properties": { - "mxcp": { - "type": "integer", - "description": "Schema version. Must be 1.", - "enum": [1], - "default": 1 - }, - "suite": { - "type": "string", - "description": "Name of the eval suite (e.g., 'churn_checks')", - "pattern": "^[a-zA-Z_][a-zA-Z0-9_]*$" - }, - "description": { - "type": "string", - "description": "Description of what this eval suite tests" - }, - "model": { - "type": "string", - "description": "Optional model to use for this suite (e.g., 'claude-4-opus')", - "enum": [ - "claude-4-opus", - "claude-4-sonnet", - "gpt-4o", - "gpt-4.1" - ] - }, - "tests": { - "type": "array", - "description": "List of eval tests to run", - "items": { - "type": "object", - "required": ["name", "prompt", "assertions"], - "properties": { - "name": { - "type": "string", - "description": "Name of the test", - "pattern": "^[a-zA-Z_][a-zA-Z0-9_]*$" - }, - "description": { - "type": "string", - "description": "What this test is checking" - }, - "prompt": { - "type": "string", - "description": "The prompt to send to the LLM" - }, - "user_context": { - "type": "object", - "description": "Optional user context for this test (e.g., role, permissions)", - "additionalProperties": true - }, - "assertions": { - "type": "object", - "description": "Assertions to validate the LLM's response", - "properties": { - "must_call": { - "type": "array", - "description": "Tools that must be called with specific arguments", - "items": { - "type": "object", - "required": ["tool", "args"], - "properties": { - "tool": { - "type": "string", - "description": "Name of the tool that must be called" - }, - "args": { - "type": "object", - "description": "Expected arguments for the tool call", - "additionalProperties": true - } - }, - "additionalProperties": false - } - }, - "must_not_call": { - "type": "array", - "description": "List of tool names that should NOT be called", - "items": { - "type": "string" - } - }, - "answer_contains": { - "type": "array", - "description": "Strings that must appear in the LLM's answer", - "items": { - "type": "string" - } - }, - "answer_not_contains": { - "type": "array", - "description": "Strings that must NOT appear in the LLM's answer", - "items": { - "type": "string" - } - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - } - }, - "additionalProperties": false -} \ No newline at end of file diff --git a/src/mxcp/server/schemas/prompt-schema-1.json b/src/mxcp/server/schemas/prompt-schema-1.json deleted file mode 100644 index 9b012b97..00000000 --- a/src/mxcp/server/schemas/prompt-schema-1.json +++ /dev/null @@ -1,76 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "MXCP Prompt Definition Schema", - "type": "object", - "required": ["mxcp", "prompt"], - "properties": { - "mxcp": { - "type": "integer", - "description": "Schema version. Must be 1.", - "enum": [1], - "default": 1 - }, - - "prompt": { - "$ref": "#/definitions/promptDefinition", - "description": "Defines an MCP prompt endpoint." - }, - - "metadata": { - "type": "object", - "properties": { - "title": { "type": "string", "description": "Short display title." }, - "description": { "type": "string", "description": "Longer description." } - }, - "description": "Optional metadata for documentation purposes." - } - }, - - "definitions": { - "promptDefinition": { - "type": "object", - "required": ["name"], - "properties": { - "name": { - "type": "string", - "description": "Logical name identifying this prompt.", - "minLength": 1 - }, - "description": { "type": "string", "description": "Description of this prompt." }, - "tags": { - "type": "array", - "items": { "type": "string" }, - "description": "Tags to classify the prompt." - }, - "parameters": { - "type": "array", - "description": "Input parameters used to populate the prompt.", - "items": { "$ref": "common-types-schema-1.json#/definitions/paramDefinition" } - }, - "messages": { - "type": "array", - "description": "List of structured prompt messages forming the full prompt sequence.", - "items": { - "type": "object", - "required": ["prompt"], - "properties": { - "role": { - "type": "string", - "description": "The role of the speaker of the message (e.g. 'user', 'assistant', 'system')." - }, - "type": { - "type": "string", - "description": "The content type of the message (e.g. 'text')." - }, - "prompt": { - "type": "string", - "description": "The templated prompt text (supports Jinja syntax)." - } - }, - "additionalProperties": false - } - } - } - } - } -} \ No newline at end of file diff --git a/src/mxcp/server/schemas/resource-schema-1.json b/src/mxcp/server/schemas/resource-schema-1.json deleted file mode 100644 index 7fc5dd93..00000000 --- a/src/mxcp/server/schemas/resource-schema-1.json +++ /dev/null @@ -1,149 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "MXCP Resource Definition Schema", - "type": "object", - "required": ["mxcp", "resource"], - "properties": { - "mxcp": { - "type": "integer", - "description": "Schema version. Must be 1.", - "enum": [1], - "default": 1 - }, - - "resource": { - "$ref": "#/definitions/resourceDefinition", - "description": "Defines an MCP resource endpoint." - }, - - "metadata": { - "type": "object", - "properties": { - "title": { "type": "string", "description": "Short display title." }, - "description": { "type": "string", "description": "Longer description." } - }, - "description": "Optional metadata for documentation purposes." - } - }, - - "definitions": { - "resourceDefinition": { - "type": "object", - "required": ["uri", "source"], - "properties": { - "uri": { - "type": "string", - "description": "Logical URI identifying this resource.", - "pattern": "^[A-Za-z][A-Za-z0-9+.-]*://(?:[A-Za-z0-9._-]+|\\{[A-Za-z_][A-Za-z0-9_]*\\})(?:/(?:[A-Za-z0-9._-]+|\\{[A-Za-z_][A-Za-z0-9_]*\\}))*$", - "minLength": 1, - "maxLength": 255 - }, - "name": { - "type": "string", - "description": "Name of this resource.", - "minLength": 1 - }, - "description": { "type": "string", "description": "Description of this resource." }, - "tags": { - "type": "array", - "items": { "type": "string" }, - "description": "Tags to classify this resource." - }, - "mime_type": { "type": "string", "description": "MIME type of this resource." }, - "parameters": { - "type": "array", - "description": "Input parameters for this endpoint.", - "items": { "$ref": "common-types-schema-1.json#/definitions/paramDefinition" } - }, - "return": { - "$ref": "common-types-schema-1.json#/definitions/typeDefinition", - "description": "Description of the output schema." - }, - "language": { - "type": "string", - "default": "sql", - "enum": ["sql", "python"], - "description": "The language used to define the logic of this endpoint. 'sql' or 'python'." - }, - "source": { - "type": "object", - "description": "Source for the endpoint logic, either inline or a file reference.", - "oneOf": [ - { "required": ["code"], "not": { "required": ["file"] } }, - { "required": ["file"], "not": { "required": ["code"] } } - ], - "properties": { - "code": { - "type": "string", - "description": "The inline code snippet to execute." - }, - "file": { - "type": "string", - "description": "A relative path to a file containing the code." - } - }, - "additionalProperties": false - }, - "enabled": { "type": "boolean", "default": true, "description": "Whether this endpoint is active." }, - "tests": { - "type": "array", - "description": "Tests to validate this endpoint.", - "items": { - "type": "object", - "required": ["name", "arguments"], - "properties": { - "name": { "type": "string", "description": "Name of the test." }, - "description": { "type": "string", "description": "What the test checks." }, - "arguments": { - "type": "array", - "items": { - "type": "object", - "required": ["key", "value"], - "properties": { - "key": { "type": "string", "description": "Input parameter to pass to test." }, - "value": { "description": "Value of the input parameter to test." } - }, - "additionalProperties": false - } - }, - "result": { "description": "Expected result." }, - "user_context": { - "type": "object", - "description": "User context for policy testing. Can include role, permissions, user_id, etc." - }, - "result_contains": { - "description": "Partial match - result must contain these fields/values. For arrays, checks if array contains this item." - }, - "result_not_contains": { - "type": "array", - "items": { "type": "string" }, - "description": "List of field names that should NOT be present in the result." - }, - "result_contains_item": { - "description": "For array results - at least one array item must match this object/value." - }, - "result_contains_all": { - "type": "array", - "description": "For array results - all these items must be present (any order)." - }, - "result_length": { - "type": "integer", - "minimum": 0, - "description": "For array results - array must have exactly this many items." - }, - "result_contains_text": { - "type": "string", - "description": "For string results - result must contain this substring." - } - }, - "additionalProperties": false - } - }, - "policies": { - "$ref": "common-types-schema-1.json#/definitions/policySet", - "description": "Policy definitions for access control and data filtering." - } - } - } - } -} \ No newline at end of file diff --git a/src/mxcp/server/schemas/tool-schema-1.json b/src/mxcp/server/schemas/tool-schema-1.json deleted file mode 100644 index a03c6279..00000000 --- a/src/mxcp/server/schemas/tool-schema-1.json +++ /dev/null @@ -1,168 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "MXCP Tool Definition Schema", - "type": "object", - "required": ["mxcp", "tool"], - "properties": { - "mxcp": { - "type": "integer", - "description": "Schema version. Must be 1.", - "enum": [1], - "default": 1 - }, - - "tool": { - "$ref": "#/definitions/toolDefinition", - "description": "Defines an MCP tool endpoint." - }, - - "metadata": { - "type": "object", - "properties": { - "title": { "type": "string", "description": "Short display title." }, - "description": { "type": "string", "description": "Longer description." } - }, - "description": "Optional metadata for documentation purposes." - } - }, - - "definitions": { - "toolDefinition": { - "type": "object", - "required": ["name", "source"], - "properties": { - "name": { - "type": "string", - "description": "Name of this tool.", - "minLength": 1 - }, - "description": { "type": "string", "description": "Description of this tool." }, - "tags": { - "type": "array", - "items": { "type": "string" }, - "description": "Tags to classify this tool." - }, - "annotations": { - "type": "object", - "description": "Optional behavioral hints for this tool.", - "properties": { - "title": { - "type": "string", - "description": "Human-readable display title for the tool." - }, - "readOnlyHint": { - "type": "boolean", - "description": "Hint: tool does not modify its environment (side-effect-free)." - }, - "destructiveHint": { - "type": "boolean", - "description": "Hint: tool may perform destructive updates (e.g. delete, overwrite)." - }, - "idempotentHint": { - "type": "boolean", - "description": "Hint: repeated calls with same arguments yield the same result." - }, - "openWorldHint": { - "type": "boolean", - "description": "Hint: tool interacts with external systems or entities (non-closed-world)." - } - }, - "additionalProperties": false - }, - "parameters": { - "type": "array", - "description": "Input parameters for this endpoint.", - "items": { "$ref": "common-types-schema-1.json#/definitions/paramDefinition" } - }, - "return": { - "$ref": "common-types-schema-1.json#/definitions/typeDefinition", - "description": "Description of the output schema." - }, - "language": { - "type": "string", - "default": "sql", - "enum": ["sql", "python"], - "description": "The language used to define the logic of this endpoint. 'sql' or 'python'." - }, - "source": { - "type": "object", - "description": "Source for the endpoint logic, either inline or a file reference.", - "oneOf": [ - { "required": ["code"], "not": { "required": ["file"] } }, - { "required": ["file"], "not": { "required": ["code"] } } - ], - "properties": { - "code": { - "type": "string", - "description": "The inline code snippet to execute." - }, - "file": { - "type": "string", - "description": "A relative path to a file containing the code." - } - }, - "additionalProperties": false - }, - "enabled": { "type": "boolean", "default": true, "description": "Whether this endpoint is active." }, - "tests": { - "type": "array", - "description": "Tests to validate this endpoint.", - "items": { - "type": "object", - "required": ["name", "arguments"], - "properties": { - "name": { "type": "string", "description": "Name of the test." }, - "description": { "type": "string", "description": "What the test checks." }, - "arguments": { - "type": "array", - "items": { - "type": "object", - "required": ["key", "value"], - "properties": { - "key": { "type": "string", "description": "Input parameter to pass to test." }, - "value": { "description": "Value of the input parameter to test." } - }, - "additionalProperties": false - } - }, - "result": { "description": "Expected result." }, - "user_context": { - "type": "object", - "description": "User context for policy testing. Can include role, permissions, user_id, etc." - }, - "result_contains": { - "description": "Partial match - result must contain these fields/values. For arrays, checks if array contains this item." - }, - "result_not_contains": { - "type": "array", - "items": { "type": "string" }, - "description": "List of field names that should NOT be present in the result." - }, - "result_contains_item": { - "description": "For array results - at least one array item must match this object/value." - }, - "result_contains_all": { - "type": "array", - "description": "For array results - all these items must be present (any order)." - }, - "result_length": { - "type": "integer", - "minimum": 0, - "description": "For array results - array must have exactly this many items." - }, - "result_contains_text": { - "type": "string", - "description": "For string results - result must contain this substring." - } - }, - "additionalProperties": false - } - }, - "policies": { - "$ref": "common-types-schema-1.json#/definitions/policySet", - "description": "Policy definitions for access control and data filtering." - } - } - } - } -} \ No newline at end of file diff --git a/src/mxcp/server/services/drift/_types.py b/src/mxcp/server/services/drift/_types.py deleted file mode 100644 index 070fb435..00000000 --- a/src/mxcp/server/services/drift/_types.py +++ /dev/null @@ -1,158 +0,0 @@ -from typing import Any, Literal, Optional, TypedDict - - -class Column(TypedDict): - name: str - type: str - - -class Table(TypedDict): - name: str - columns: list[Column] - - -class TypeDefinition(TypedDict): - type: Literal["string", "number", "integer", "boolean", "array", "object"] - format: Literal["email", "uri", "date", "time", "date-time", "duration", "timestamp"] | None - minLength: int | None - maxLength: int | None - minimum: float | None - maximum: float | None - exclusiveMinimum: float | None - exclusiveMaximum: float | None - multipleOf: float | None - minItems: int | None - maxItems: int | None - uniqueItems: bool | None - items: Optional["TypeDefinition"] - properties: dict[str, "TypeDefinition"] | None - required: list[str] | None - additionalProperties: bool | None - - -class Parameter(TypeDefinition): - name: str - description: str - default: Any | None - examples: list[Any] | None - enum: list[Any] | None - - -class TestArgument(TypedDict): - key: str - value: Any - - -class Test(TypedDict): - name: str - description: str | None - arguments: list[TestArgument] - result: Any | None - - -class Annotations(TypedDict, total=False): - title: str - readOnlyHint: bool - destructiveHint: bool - idempotentHint: bool - openWorldHint: bool - - -class Tool(TypedDict): - name: str - description: str - tags: list[str] | None - annotations: Annotations | None - parameters: list[Parameter] - return_: TypeDefinition - tests: list[Test] | None - - -class Resource(TypedDict): - uri: str - description: str - tags: list[str] | None - mime_type: str | None - parameters: list[Parameter] - return_: TypeDefinition - tests: list[Test] | None - - -class PromptMessage(TypedDict): - role: str | None - type: str | None - prompt: str - - -class Prompt(TypedDict): - name: str - description: str - tags: list[str] | None - parameters: list[Parameter] - messages: list[PromptMessage] - - -class ValidationResults(TypedDict): - status: Literal["ok", "error"] - path: str - message: str | None - - -class TestResult(TypedDict): - name: str - description: str | None - status: Literal["passed", "failed", "error"] - error: str | None - time: float - - -class TestResults(TypedDict): - status: Literal["ok", "error", "failed"] - tests_run: int - tests: list[TestResult] | None - message: str | None - - -class ResourceDefinition(TypedDict): - validation_results: ValidationResults - test_results: TestResults | None - definition: Tool | Resource | Prompt | None - metadata: dict[str, Any] | None - - -class DriftSnapshot(TypedDict): - version: int - generated_at: str - tables: list[Table] - resources: list[ResourceDefinition] - - -# Drift Report Types -class TableChange(TypedDict): - name: str - change_type: Literal["added", "removed", "modified"] - columns_added: list[Column] | None - columns_removed: list[Column] | None - columns_modified: list[dict[str, Any]] | None # old/new column info - - -class ResourceChange(TypedDict): - path: str - endpoint: str | None # endpoint identifier like "tool/name" - change_type: Literal["added", "removed", "modified"] - validation_changed: bool | None - test_results_changed: bool | None - definition_changed: bool | None - details: dict[str, Any] | None # specific change details - - -class DriftReport(TypedDict): - version: int - generated_at: str - baseline_snapshot_path: str - current_snapshot_generated_at: str - baseline_snapshot_generated_at: str - has_drift: bool - summary: dict[str, int] # counts of changes by type - table_changes: list[TableChange] - resource_changes: list[ResourceChange] diff --git a/src/mxcp/server/services/drift/checker.py b/src/mxcp/server/services/drift/checker.py index 7cf8285e..9792ef01 100644 --- a/src/mxcp/server/services/drift/checker.py +++ b/src/mxcp/server/services/drift/checker.py @@ -2,10 +2,14 @@ import logging from datetime import datetime, timezone from pathlib import Path -from typing import Any, cast +from typing import Any + +from pydantic import ValidationError from mxcp.server.core.config.models import SiteConfigModel, UserConfigModel -from mxcp.server.services.drift._types import ( +from mxcp.server.definitions.endpoints.models import EndpointDefinitionModel +from mxcp.server.services.drift.models import ( + ColumnModification, DriftReport, DriftSnapshot, ResourceChange, @@ -32,10 +36,10 @@ def load_and_validate_snapshot(snapshot_path: Path) -> DriftSnapshot: if snapshot_data["version"] != 1: raise ValueError(f"Unsupported snapshot version: {snapshot_data['version']}") - # Note: We skip detailed schema validation for now to avoid complex reference resolution - # The snapshot was already validated when created, so basic structure checks are sufficient - - return cast(DriftSnapshot, snapshot_data) + try: + return DriftSnapshot.model_validate(snapshot_data) + except ValidationError as e: + raise ValueError(f"Invalid drift snapshot: {e}") from e def compare_tables(baseline_tables: list[Table], current_tables: list[Table]) -> list[TableChange]: @@ -43,8 +47,8 @@ def compare_tables(baseline_tables: list[Table], current_tables: list[Table]) -> changes = [] # Create lookup dictionaries - baseline_by_name = {table["name"]: table for table in baseline_tables} - current_by_name = {table["name"]: table for table in current_tables} + baseline_by_name = {table.name: table for table in baseline_tables} + current_by_name = {table.name: table for table in current_tables} # Find added tables for table_name in current_by_name: @@ -53,7 +57,7 @@ def compare_tables(baseline_tables: list[Table], current_tables: list[Table]) -> TableChange( name=table_name, change_type="added", - columns_added=current_by_name[table_name]["columns"], + columns_added=current_by_name[table_name].columns, columns_removed=None, columns_modified=None, ) @@ -67,7 +71,7 @@ def compare_tables(baseline_tables: list[Table], current_tables: list[Table]) -> name=table_name, change_type="removed", columns_added=None, - columns_removed=baseline_by_name[table_name]["columns"], + columns_removed=baseline_by_name[table_name].columns, columns_modified=None, ) ) @@ -79,12 +83,12 @@ def compare_tables(baseline_tables: list[Table], current_tables: list[Table]) -> current_table = current_by_name[table_name] # Compare columns - baseline_cols = {col["name"]: col for col in baseline_table["columns"]} - current_cols = {col["name"]: col for col in current_table["columns"]} + baseline_cols = {col.name: col for col in baseline_table.columns} + current_cols = {col.name: col for col in current_table.columns} - columns_added = [] - columns_removed = [] - columns_modified = [] + columns_added: list[Column] = [] + columns_removed: list[Column] = [] + columns_modified: list[ColumnModification] = [] # Added columns for col_name in current_cols: @@ -101,13 +105,13 @@ def compare_tables(baseline_tables: list[Table], current_tables: list[Table]) -> if col_name in current_cols: baseline_col = baseline_cols[col_name] current_col = current_cols[col_name] - if baseline_col["type"] != current_col["type"]: + if baseline_col.type != current_col.type: columns_modified.append( - { - "name": col_name, - "old_type": baseline_col["type"], - "new_type": current_col["type"], - } + ColumnModification( + name=col_name, + old_type=baseline_col.type, + new_type=current_col.type, + ) ) # If any changes, add to changes list @@ -116,9 +120,9 @@ def compare_tables(baseline_tables: list[Table], current_tables: list[Table]) -> TableChange( name=table_name, change_type="modified", - columns_added=columns_added if columns_added else None, - columns_removed=columns_removed if columns_removed else None, - columns_modified=columns_modified if columns_modified else None, + columns_added=columns_added or None, + columns_removed=columns_removed or None, + columns_modified=columns_modified or None, ) ) @@ -132,14 +136,14 @@ def compare_resources( changes = [] # Create lookup dictionaries by path - baseline_by_path = {res["validation_results"]["path"]: res for res in baseline_resources} - current_by_path = {res["validation_results"]["path"]: res for res in current_resources} + baseline_by_path = {res.validation_results.path: res for res in baseline_resources} + current_by_path = {res.validation_results.path: res for res in current_resources} # Find added resources for path in current_by_path: if path not in baseline_by_path: current_res = current_by_path[path] - endpoint = _extract_endpoint_identifier(current_res.get("definition")) + endpoint = _extract_endpoint_identifier(current_res.definition) changes.append( ResourceChange( path=path, @@ -156,7 +160,7 @@ def compare_resources( for path in baseline_by_path: if path not in current_by_path: baseline_res = baseline_by_path[path] - endpoint = _extract_endpoint_identifier(baseline_res.get("definition")) + endpoint = _extract_endpoint_identifier(baseline_res.definition) changes.append( ResourceChange( path=path, @@ -177,36 +181,33 @@ def compare_resources( # Check for changes validation_changed = _compare_validation_results( - baseline_res["validation_results"], current_res["validation_results"] + baseline_res.validation_results, current_res.validation_results ) test_results_changed = _compare_test_results( - baseline_res.get("test_results"), current_res.get("test_results") + baseline_res.test_results, current_res.test_results ) definition_changed = _compare_definitions( - baseline_res.get("definition"), current_res.get("definition") + baseline_res.definition, current_res.definition ) # If any changes, add to changes list if validation_changed or test_results_changed or definition_changed: - endpoint = _extract_endpoint_identifier(current_res.get("definition")) + endpoint = _extract_endpoint_identifier(current_res.definition) details = {} if validation_changed: details["validation_changes"] = { - "old_status": baseline_res["validation_results"]["status"], - "new_status": current_res["validation_results"]["status"], + "old_status": baseline_res.validation_results.status, + "new_status": current_res.validation_results.status, } if test_results_changed: - baseline_test = baseline_res.get("test_results") if baseline_res else None - current_test = current_res.get("test_results") if current_res else None - details["test_changes"] = cast( - dict[str, Any], - { - "old_status": baseline_test.get("status") if baseline_test else None, - "new_status": current_test.get("status") if current_test else None, - }, - ) + baseline_test = baseline_res.test_results + current_test = current_res.test_results + details["test_changes"] = { + "old_status": baseline_test.status if baseline_test else None, + "new_status": current_test.status if current_test else None, + } changes.append( ResourceChange( @@ -223,40 +224,24 @@ def compare_resources( return changes -def _extract_endpoint_identifier(definition: Any | None) -> str | None: - """Extract endpoint identifier from definition. - - The definition has the structure: - { - "mxcp": 1, - "tool": {"name": "...", ...} # or "resource": {"uri": "...", ...} or "prompt": {"name": "...", ...} - } - """ - if not definition or not isinstance(definition, dict): +def _extract_endpoint_identifier(definition: EndpointDefinitionModel | None) -> str | None: + """Extract endpoint identifier from definition.""" + if not definition: return None - # Check for nested tool/resource/prompt - if "tool" in definition and isinstance(definition["tool"], dict): - return f"tool/{definition['tool'].get('name', 'unnamed')}" - elif "resource" in definition and isinstance(definition["resource"], dict): - return f"resource/{definition['resource'].get('uri', 'unknown')}" - elif "prompt" in definition and isinstance(definition["prompt"], dict): - return f"prompt/{definition['prompt'].get('name', 'unnamed')}" + if definition.tool: + return f"tool/{definition.tool.name}" + if definition.resource: + return f"resource/{definition.resource.uri}" + if definition.prompt: + return f"prompt/{definition.prompt.name}" return None def _compare_validation_results(baseline: ValidationResults, current: ValidationResults) -> bool: """Compare validation results, ignoring path since it should be the same.""" - # Create dict copies to allow modification - baseline_copy = dict(baseline) - current_copy = dict(current) - - # Remove path from comparison since it's the key we're using - baseline_copy.pop("path", None) - current_copy.pop("path", None) - - return baseline_copy != current_copy + return (baseline.status, baseline.message) != (current.status, current.message) def _compare_test_results(baseline: TestResults | None, current: TestResults | None) -> bool: @@ -267,37 +252,37 @@ def _compare_test_results(baseline: TestResults | None, current: TestResults | N return True # Compare status and tests_run - if baseline.get("status") != current.get("status"): + if baseline.status != current.status: return True - if baseline.get("tests_run") != current.get("tests_run"): + if baseline.tests_run != current.tests_run: return True # For detailed test comparison, we could compare individual test results # but for now, we'll just compare the overall structure - baseline_tests = baseline.get("tests") or [] - current_tests = current.get("tests") or [] + baseline_tests = baseline.tests or [] + current_tests = current.tests or [] if len(baseline_tests) != len(current_tests): return True # Compare test names and statuses (simplified comparison) - baseline_test_summary = [(t.get("name"), t.get("status")) for t in baseline_tests if t] - current_test_summary = [(t.get("name"), t.get("status")) for t in current_tests if t] + baseline_test_summary = [(t.name, t.status) for t in baseline_tests if t] + current_test_summary = [(t.name, t.status) for t in current_tests if t] return baseline_test_summary != current_test_summary -def _compare_definitions(baseline: Any | None, current: Any | None) -> bool: +def _compare_definitions( + baseline: EndpointDefinitionModel | None, current: EndpointDefinitionModel | None +) -> bool: """Compare endpoint definitions.""" if baseline is None and current is None: return False if baseline is None or current is None: return True - # For now, do a simple JSON comparison - # In the future, we could implement more sophisticated comparison - # that ignores certain fields or provides more detailed change information - return bool(baseline != current) + # Use BaseModel equality for precise comparison + return baseline != current async def check_drift( @@ -343,19 +328,19 @@ async def check_drift( ) # Compare snapshots - table_changes = compare_tables(baseline_snapshot["tables"], current_snapshot["tables"]) + table_changes = compare_tables(baseline_snapshot.tables, current_snapshot.tables) resource_changes = compare_resources( - baseline_snapshot["resources"], current_snapshot["resources"] + baseline_snapshot.resources, current_snapshot.resources ) # Calculate summary summary = { - "tables_added": len([c for c in table_changes if c["change_type"] == "added"]), - "tables_removed": len([c for c in table_changes if c["change_type"] == "removed"]), - "tables_modified": len([c for c in table_changes if c["change_type"] == "modified"]), - "resources_added": len([c for c in resource_changes if c["change_type"] == "added"]), - "resources_removed": len([c for c in resource_changes if c["change_type"] == "removed"]), - "resources_modified": len([c for c in resource_changes if c["change_type"] == "modified"]), + "tables_added": len([c for c in table_changes if c.change_type == "added"]), + "tables_removed": len([c for c in table_changes if c.change_type == "removed"]), + "tables_modified": len([c for c in table_changes if c.change_type == "modified"]), + "resources_added": len([c for c in resource_changes if c.change_type == "added"]), + "resources_removed": len([c for c in resource_changes if c.change_type == "removed"]), + "resources_modified": len([c for c in resource_changes if c.change_type == "modified"]), } has_drift = any(summary.values()) @@ -365,8 +350,8 @@ async def check_drift( version=1, generated_at=datetime.now(timezone.utc).isoformat(), baseline_snapshot_path=str(baseline_snapshot_path), - current_snapshot_generated_at=current_snapshot["generated_at"], - baseline_snapshot_generated_at=baseline_snapshot["generated_at"], + current_snapshot_generated_at=current_snapshot.generated_at, + baseline_snapshot_generated_at=baseline_snapshot.generated_at, has_drift=has_drift, summary=summary, table_changes=table_changes, diff --git a/src/mxcp/server/services/drift/models.py b/src/mxcp/server/services/drift/models.py new file mode 100644 index 00000000..85f5239e --- /dev/null +++ b/src/mxcp/server/services/drift/models.py @@ -0,0 +1,96 @@ +from __future__ import annotations + +from typing import Any, Literal + +from pydantic import BaseModel, ConfigDict + +from mxcp.server.definitions.endpoints.models import EndpointDefinitionModel + + +class _DriftBaseModel(BaseModel): + """Base model for drift snapshot/report structures.""" + + model_config = ConfigDict(extra="forbid", frozen=True) + + +class Column(_DriftBaseModel): + name: str + type: str + + +class Table(_DriftBaseModel): + name: str + columns: list[Column] + + +class ValidationResults(_DriftBaseModel): + status: Literal["ok", "error"] + path: str + message: str | None = None + + +class TestResult(_DriftBaseModel): + name: str | None = None + description: str | None = None + status: Literal["passed", "failed", "error"] + error: str | None = None + time: float | None = None + + +class TestResults(_DriftBaseModel): + status: Literal["ok", "error", "failed"] + tests_run: int | None = None + tests: list[TestResult] | None = None + message: str | None = None + no_tests: bool | None = None + + +class ResourceDefinition(_DriftBaseModel): + validation_results: ValidationResults + test_results: TestResults | None = None + definition: EndpointDefinitionModel | None = None + metadata: dict[str, Any] | None = None + + +class DriftSnapshot(_DriftBaseModel): + version: Literal[1] = 1 + generated_at: str + tables: list[Table] + resources: list[ResourceDefinition] + + +class ColumnModification(_DriftBaseModel): + name: str + old_type: str + new_type: str + + +class TableChange(_DriftBaseModel): + name: str + change_type: Literal["added", "removed", "modified"] + columns_added: list[Column] | None = None + columns_removed: list[Column] | None = None + columns_modified: list[ColumnModification] | None = None + + +class ResourceChange(_DriftBaseModel): + path: str + endpoint: str | None = None + change_type: Literal["added", "removed", "modified"] + validation_changed: bool | None = None + test_results_changed: bool | None = None + definition_changed: bool | None = None + details: dict[str, Any] | None = None + + +class DriftReport(_DriftBaseModel): + version: Literal[1] = 1 + generated_at: str + baseline_snapshot_path: str + current_snapshot_generated_at: str + baseline_snapshot_generated_at: str + has_drift: bool + summary: dict[str, int] + table_changes: list[TableChange] + resource_changes: list[ResourceChange] + diff --git a/src/mxcp/server/services/drift/snapshot.py b/src/mxcp/server/services/drift/snapshot.py index 3715238d..077644bc 100644 --- a/src/mxcp/server/services/drift/snapshot.py +++ b/src/mxcp/server/services/drift/snapshot.py @@ -2,7 +2,6 @@ import logging from datetime import datetime, timezone from pathlib import Path -from typing import cast import duckdb @@ -12,30 +11,29 @@ from mxcp.server.definitions.endpoints.loader import EndpointLoader from mxcp.server.executor.engine import create_runtime_environment from mxcp.server.executor.runners.test import TestRunner -from mxcp.server.services.drift._types import ( +from mxcp.server.services.drift.models import ( Column, DriftSnapshot, - Prompt, - Resource, ResourceDefinition, Table, + TestResult, TestResults, - Tool, ValidationResults, ) from mxcp.server.services.endpoints.validator import validate_endpoint_payload +from mxcp.server.services.tests.models import TestSuiteResultModel logger = logging.getLogger(__name__) def get_duckdb_tables(conn: duckdb.DuckDBPyConnection) -> list[Table]: """Get list of tables and their columns from DuckDB catalog.""" - tables = [] + tables: list[Table] = [] for table in conn.execute( "SELECT table_name FROM information_schema.tables WHERE table_schema = 'main'" ).fetchall(): table_name = table[0] - columns = [] + columns: list[Column] = [] for col in conn.execute( f"SELECT column_name, data_type FROM information_schema.columns WHERE table_name = '{table_name}'" ).fetchall(): @@ -88,8 +86,6 @@ async def generate_snapshot( if not duckdb_executor: raise RuntimeError("DuckDB executor not found in execution engine") - # Check the type for accessing .session - if not isinstance(duckdb_executor, DuckDBExecutor): raise RuntimeError("SQL executor is not a DuckDB executor") @@ -113,16 +109,16 @@ async def generate_snapshot( relative_path = path.name if error: - error_resource: ResourceDefinition = { - "validation_results": { - "status": "error", - "path": relative_path, - "message": error, - }, - "test_results": None, - "definition": None, - "metadata": None, - } + error_resource = ResourceDefinition( + validation_results=ValidationResults( + status="error", + path=relative_path, + message=error, + ), + test_results=None, + definition=None, + metadata=None, + ) resources.append(error_resource) else: # Determine endpoint type and name @@ -130,18 +126,18 @@ async def generate_snapshot( logger.warning(f"Skipping file {path}: endpoint is None") continue - if endpoint.get("tool") is not None: + if endpoint.tool is not None: endpoint_type = "tool" - tool = endpoint["tool"] - name = tool.get("name", "unnamed") if tool else "unnamed" - elif endpoint.get("resource") is not None: + tool = endpoint.tool + name = tool.name if tool else "unnamed" + elif endpoint.resource is not None: endpoint_type = "resource" - resource = endpoint["resource"] - name = resource.get("uri", "unknown") if resource else "unknown" - elif endpoint.get("prompt") is not None: + resource = endpoint.resource + name = resource.uri if resource else "unknown" + elif endpoint.prompt is not None: endpoint_type = "prompt" - prompt = endpoint["prompt"] - name = prompt.get("name", "unnamed") if prompt else "unnamed" + prompt = endpoint.prompt + name = prompt.name if prompt else "unnamed" else: logger.warning(f"Skipping file {path}: not a valid endpoint") continue @@ -156,14 +152,16 @@ async def generate_snapshot( endpoint_type, name, None ) # Add to snapshot - resource_data: ResourceDefinition = { - "validation_results": cast(ValidationResults, validation_result), - "test_results": cast(TestResults, test_result), - "definition": cast( - Tool | Resource | Prompt | None, endpoint - ), # Store the full endpoint structure - "metadata": endpoint.get("metadata") if endpoint else None, - } + validation_model = ValidationResults.model_validate( + validation_result.model_dump(mode="python", exclude_unset=True) + ) + test_results_model = _convert_suite_to_test_results(test_result) + resource_data = ResourceDefinition( + validation_results=validation_model, + test_results=test_results_model, + definition=endpoint, + metadata=endpoint.metadata if endpoint else None, + ) resources.append(resource_data) if conn is None: raise RuntimeError("DuckDB connection is not available") @@ -176,10 +174,35 @@ async def generate_snapshot( ) if not dry_run: with open(drift_path, "w") as f: - json.dump(snapshot, f, indent=2) + json.dump(snapshot.model_dump(mode="json", exclude_none=True), f, indent=2) logger.info(f"Wrote drift snapshot to {drift_path}") else: - logger.info(f"Would write drift snapshot as {snapshot}") + logger.info( + "Would write drift snapshot as %s", + json.dumps(snapshot.model_dump(mode="json", exclude_none=True)), + ) return snapshot, drift_path finally: runtime_env.shutdown() + + +def _convert_suite_to_test_results(test_suite: TestSuiteResultModel) -> TestResults: + """Convert internal test suite results to the drift snapshot schema.""" + tests = [ + TestResult( + name=test.name, + description=test.description or None, + status=test.status, + error=test.error, + time=test.time, + ) + for test in (test_suite.tests or []) + ] + + return TestResults( + status=test_suite.status, + tests_run=test_suite.tests_run, + tests=tests or None, + message=test_suite.message, + no_tests=test_suite.no_tests or None, + ) diff --git a/src/mxcp/server/services/endpoints/__init__.py b/src/mxcp/server/services/endpoints/__init__.py index 8e7d9d7f..7297ab00 100644 --- a/src/mxcp/server/services/endpoints/__init__.py +++ b/src/mxcp/server/services/endpoints/__init__.py @@ -5,6 +5,10 @@ """ # Import main functions from service module +from .models import ( + EndpointValidationResultModel, + EndpointValidationSummaryModel, +) from .service import ( execute_endpoint, execute_endpoint_with_engine, @@ -24,4 +28,7 @@ # Validation functions "validate_all_endpoints", "validate_endpoint", + # Models + "EndpointValidationResultModel", + "EndpointValidationSummaryModel", ] diff --git a/src/mxcp/server/services/endpoints/models.py b/src/mxcp/server/services/endpoints/models.py new file mode 100644 index 00000000..8f5b3de9 --- /dev/null +++ b/src/mxcp/server/services/endpoints/models.py @@ -0,0 +1,26 @@ +from __future__ import annotations + +from typing import Literal + +from pydantic import BaseModel, ConfigDict + + +class EndpointValidationResultModel(BaseModel): + """Result of validating a single endpoint.""" + + model_config = ConfigDict(extra="forbid", frozen=True) + + status: Literal["ok", "error"] + path: str + message: str | None = None + + +class EndpointValidationSummaryModel(BaseModel): + """Aggregate result for validating multiple endpoints.""" + + model_config = ConfigDict(extra="forbid", frozen=True) + + status: Literal["ok", "error"] + validated: list[EndpointValidationResultModel] + message: str | None = None + diff --git a/src/mxcp/server/services/endpoints/service.py b/src/mxcp/server/services/endpoints/service.py index 021b6923..2580e952 100644 --- a/src/mxcp/server/services/endpoints/service.py +++ b/src/mxcp/server/services/endpoints/service.py @@ -21,7 +21,11 @@ ) from mxcp.server.core.config.models import SiteConfigModel, UserConfigModel from mxcp.server.core.config.site_config import find_repo_root -from mxcp.server.definitions.endpoints._types import PromptDefinition +from mxcp.server.definitions.endpoints.models import ( + EndpointDefinitionModel, + PoliciesDefinitionModel, + PromptDefinitionModel, +) from mxcp.server.definitions.endpoints.loader import EndpointLoader from mxcp.server.executor.engine import create_runtime_environment from mxcp.server.executor.runners.endpoint import ( @@ -32,7 +36,7 @@ logger = logging.getLogger(__name__) -def parse_policies_from_config(policies_config: dict[str, Any] | None) -> PolicySet | None: +def parse_policies_from_config(policies_config: PoliciesDefinitionModel | None) -> PolicySet | None: """Parse policy configuration into PolicySet. This function handles parsing of policy configuration from YAML/JSON format @@ -84,29 +88,26 @@ def parse_policies_from_config(policies_config: dict[str, Any] | None) -> Policy input_policies = [] output_policies = [] - # Parse input policies - for policy_dict in policies_config.get("input", []): - action = PolicyAction(policy_dict["action"]) - policy = PolicyDefinition( - condition=policy_dict["condition"], - action=action, - reason=policy_dict.get("reason"), - fields=policy_dict.get("fields"), + for rule in policies_config.input or []: + input_policies.append( + PolicyDefinition( + condition=rule.condition, + action=PolicyAction(rule.action), + reason=rule.reason, + fields=rule.fields, + ) ) - input_policies.append(policy) - - # Parse output policies - for policy_dict in policies_config.get("output", []): - action = PolicyAction(policy_dict["action"]) - policy = PolicyDefinition( - condition=policy_dict["condition"], - action=action, - reason=policy_dict.get("reason"), - fields=policy_dict.get("fields"), + + for rule in policies_config.output or []: + output_policies.append( + PolicyDefinition( + condition=rule.condition, + action=PolicyAction(rule.action), + reason=rule.reason, + fields=rule.fields, + ) ) - output_policies.append(policy) - # Return PolicySet even for empty dict (this allows for explicit empty config) return PolicySet(input_policies=input_policies, output_policies=output_policies) @@ -219,45 +220,28 @@ async def execute_endpoint_with_engine_and_policy( endpoint_file_path, endpoint_definition = endpoint_result - # endpoint_definition is the raw dict containing the full endpoint structure - # Extract the type-specific data with proper typing policy_enforcer = None + component_dict: dict[str, Any] | None = None if endpoint_type == "tool": - tool_def = endpoint_definition.get("tool") - if not tool_def: - raise ValueError("No tool definition found in endpoint") - - policies_config = tool_def.get("policies") - if policies_config: - policy_set = parse_policies_from_config(cast(dict[str, Any], policies_config)) - if policy_set: - policy_enforcer = PolicyEnforcer(policy_set) - + component = endpoint_definition.tool elif endpoint_type == "resource": - resource_def = endpoint_definition.get("resource") - if not resource_def: - raise ValueError("No resource definition found in endpoint") - - policies_config = resource_def.get("policies") - if policies_config: - policy_set = parse_policies_from_config(cast(dict[str, Any], policies_config)) - if policy_set: - policy_enforcer = PolicyEnforcer(policy_set) - + component = endpoint_definition.resource elif endpoint_type == "prompt": - prompt_def = endpoint_definition.get("prompt") - if not prompt_def: - raise ValueError("No prompt definition found in endpoint") + component = endpoint_definition.prompt + else: + component = None - policies_config = prompt_def.get("policies") - if policies_config: - policy_set = parse_policies_from_config(cast(dict[str, Any], policies_config)) - if policy_set: - policy_enforcer = PolicyEnforcer(policy_set) + if component is None: + raise ValueError(f"No {endpoint_type} definition found in endpoint") - else: - raise ValueError(f"Unknown endpoint type: {endpoint_type}") + component_dict = component.model_dump(mode="python", exclude_unset=True) + + policies_config = getattr(component, "policies", None) + if policies_config: + policy_set = parse_policies_from_config(policies_config) + if policy_set: + policy_enforcer = PolicyEnforcer(policy_set) # Enforce input policies if policy enforcer exists if policy_enforcer: @@ -269,7 +253,7 @@ async def execute_endpoint_with_engine_and_policy( # Dispatch to appropriate execution method based on endpoint type if endpoint_type == "prompt": # We already verified prompt_def exists above - prompt_def = cast(PromptDefinition, endpoint_definition.get("prompt")) + prompt_def = cast(PromptDefinitionModel, endpoint_definition.prompt) result = await execute_prompt_with_validation(prompt_def, params, skip_output_validation) else: result = await execute_code_with_engine( @@ -291,15 +275,8 @@ async def execute_endpoint_with_engine_and_policy( if policy_enforcer: try: # Get the appropriate definition for policy enforcement - if endpoint_type == "tool": - endpoint_def = cast(dict[str, Any], endpoint_definition.get("tool")) - elif endpoint_type == "resource": - endpoint_def = cast(dict[str, Any], endpoint_definition.get("resource")) - else: # prompt - endpoint_def = cast(dict[str, Any], endpoint_definition.get("prompt")) - result, action = policy_enforcer.enforce_output_policies( - user_context, result, endpoint_def + user_context, result, component_dict ) except PolicyEnforcementError as e: raise ValueError(f"Output policy enforcement failed: {e.reason}") from e diff --git a/src/mxcp/server/services/endpoints/validator.py b/src/mxcp/server/services/endpoints/validator.py index f5a3493b..2eaa7d78 100644 --- a/src/mxcp/server/services/endpoints/validator.py +++ b/src/mxcp/server/services/endpoints/validator.py @@ -1,46 +1,48 @@ -import json import re from pathlib import Path -from typing import Any - from jinja2 import Environment, meta -from jsonschema import validate as jsonschema_validate -from referencing import Registry, Resource from mxcp.sdk.executor import ExecutionEngine from mxcp.server.core.config.models import SiteConfigModel from mxcp.server.core.config.site_config import find_repo_root -from mxcp.server.definitions.endpoints._types import EndpointDefinition, ResourceDefinition +from mxcp.server.definitions.endpoints.models import ( + EndpointDefinitionModel, + ResourceDefinitionModel, +) from mxcp.server.definitions.endpoints.loader import EndpointLoader from mxcp.server.definitions.endpoints.utils import get_endpoint_source_code +from mxcp.server.services.endpoints.models import ( + EndpointValidationResultModel, + EndpointValidationSummaryModel, +) RESOURCE_VAR_RE = re.compile(r"{([^{}]+)}") def _validate_resource_uri_vs_params( - res_def: ResourceDefinition, path: Path -) -> dict[str, Any] | None: - uri_params = set(RESOURCE_VAR_RE.findall(res_def["uri"])) - params = res_def.get("parameters") or [] - yaml_params = {p["name"] for p in params} + res_def: ResourceDefinitionModel, path: Path +) -> EndpointValidationResultModel | None: + uri_params = set(RESOURCE_VAR_RE.findall(res_def.uri)) + params = res_def.parameters or [] + yaml_params = {p.name for p in params} extra_in_yaml = yaml_params - uri_params if extra_in_yaml: - return { - "status": "error", - "path": path, - "message": ( + return EndpointValidationResultModel( + status="error", + path=str(path), + message=( f"Resource parameter(s) {sorted(extra_in_yaml)} are not used " - f"in uri '{res_def['uri']}'. Put them in the uri or make a " + f"in uri '{res_def.uri}'. Put them in the uri or make a " f"'tool:' instead." ), - } + ) return None def validate_all_endpoints( site_config: SiteConfigModel, execution_engine: ExecutionEngine -) -> dict[str, Any]: +) -> EndpointValidationSummaryModel: """Validate all endpoints in the repository. Args: @@ -55,31 +57,41 @@ def validate_all_endpoints( loader = EndpointLoader(site_config) endpoints = loader.discover_endpoints() if not endpoints: - return {"status": "error", "message": "No endpoints found"} + return EndpointValidationSummaryModel( + status="error", + validated=[], + message="No endpoints found", + ) - # Validate each endpoint - results = [] + results: list[EndpointValidationResultModel] = [] has_errors = False for path, endpoint, error in endpoints: path_str = str(path) # Convert PosixPath to string if error: - results.append({"status": "error", "path": path_str, "message": error}) + results.append( + EndpointValidationResultModel(status="error", path=path_str, message=error) + ) has_errors = True elif endpoint: result = validate_endpoint_payload(endpoint, path_str, execution_engine) results.append(result) - if result["status"] == "error": + if result.status == "error": has_errors = True else: results.append( - {"status": "error", "path": path_str, "message": "Failed to load endpoint"} + EndpointValidationResultModel( + status="error", path=path_str, message="Failed to load endpoint" + ) ) has_errors = True - return {"status": "error" if has_errors else "ok", "validated": results} + return EndpointValidationSummaryModel( + status="error" if has_errors else "ok", + validated=results, + ) except Exception as e: - return {"status": "error", "message": str(e)} + return EndpointValidationSummaryModel(status="error", validated=[], message=str(e)) def _extract_template_variables(template: str) -> set[str]: @@ -98,269 +110,168 @@ def _extract_template_variables(template: str) -> set[str]: def validate_endpoint_payload( - endpoint: EndpointDefinition, path: str, execution_engine: ExecutionEngine -) -> dict[str, Any]: - """Validate a single endpoint payload. - - Args: - endpoint: The loaded endpoint dictionary - path: Path to the endpoint file (for file operations) - execution_engine: SDK execution engine to use for validation - - Returns: - Dictionary with validation status and details - """ - # Calculate relative path for results + endpoint: EndpointDefinitionModel, path: str, execution_engine: ExecutionEngine +) -> EndpointValidationResultModel: + """Validate a single endpoint payload and return a typed result.""" try: repo_root = find_repo_root() path_obj = Path(path).resolve() relative_path = str(path_obj.relative_to(repo_root)) except ValueError: - # If path is not relative to repo_root, use the filename relative_path = Path(path).name + path_obj = Path(path).resolve() + repo_root = path_obj.parent except Exception: - # If we can't find repo root or resolve path, use filename as fallback relative_path = Path(path).name + path_obj = Path(path).resolve() + repo_root = path_obj.parent try: - # Determine endpoint type first - endpoint_type = None - name = None - for t in ("tool", "resource", "prompt"): - if endpoint.get(t) is not None: - endpoint_type = t - endpoint_def = endpoint[t] - if t == "tool" and endpoint_def: - name = endpoint_def.get("name", "unnamed") - elif t == "resource" and endpoint_def: - name = endpoint_def.get("uri", "unknown") - elif t == "prompt" and endpoint_def: - name = endpoint_def.get("name", "unnamed") - break - - if not endpoint_type or not name: - return { - "status": "error", - "path": relative_path, - "message": "No valid endpoint type (tool/resource/prompt) found", - } - - # Use the appropriate schema based on endpoint type - schema_filename = f"{endpoint_type}-schema-1.json" - schema_path = Path(__file__).parent.parent.parent / "schemas" / schema_filename - with open(schema_path) as schema_file: - schema = json.load(schema_file) - - # Set up registry for cross-file references - schemas_dir = (Path(__file__).parent.parent.parent / "schemas").resolve() - - # Load common schema for registry - common_schema_path = schemas_dir / "common-types-schema-1.json" - with open(common_schema_path) as common_file: - common_schema = json.load(common_file) - - # Create registry with common schema - # The URI needs to match what's expected in the $ref - registry = Registry().with_resource( - uri="common-types-schema-1.json", resource=Resource.from_contents(common_schema) - ) + endpoint_type: str | None = None + component = None + name: str | None = None + + if endpoint.tool is not None: + endpoint_type = "tool" + component = endpoint.tool + name = component.name + elif endpoint.resource is not None: + endpoint_type = "resource" + component = endpoint.resource + name = component.uri + elif endpoint.prompt is not None: + endpoint_type = "prompt" + component = endpoint.prompt + name = component.name + + if not endpoint_type or component is None or not name: + return EndpointValidationResultModel( + status="error", + path=relative_path, + message="No valid endpoint type (tool/resource/prompt) found", + ) - try: - jsonschema_validate(instance=endpoint, schema=schema, registry=registry) - except Exception as e: - return { - "status": "error", - "path": relative_path, - "message": f"Schema validation error: {str(e)}", - } - - # For prompts, validate messages structure and template variables if endpoint_type == "prompt": - prompt_def = endpoint.get("prompt") - if not prompt_def or not prompt_def.get("messages"): - return { - "status": "error", - "path": relative_path, - "message": "No messages found in prompt definition", - } - - messages = prompt_def.get("messages", []) - if not isinstance(messages, list) or not messages: - return { - "status": "error", - "path": relative_path, - "message": "Messages must be a non-empty array", - } - - # Get defined parameters - parameters = prompt_def.get("parameters") or [] - defined_params = {p["name"] for p in parameters if isinstance(p, dict) and "name" in p} - - # Check each message + messages = component.messages or [] + if not messages: + return EndpointValidationResultModel( + status="error", + path=relative_path, + message="No messages found in prompt definition", + ) + + parameters = component.parameters or [] + defined_params = {p.name for p in parameters} + for i, msg in enumerate(messages): - if not isinstance(msg, dict): - return { - "status": "error", - "path": relative_path, - "message": f"Message {i} must be an object", - } - if "prompt" not in msg: - return { - "status": "error", - "path": relative_path, - "message": f"Message {i} missing required 'prompt' field", - } - if not isinstance(msg["prompt"], str): - return { - "status": "error", - "path": relative_path, - "message": f"Message {i} prompt must be a string", - } - - # Extract and validate template variables - template_vars = _extract_template_variables(msg["prompt"]) + template_vars = _extract_template_variables(msg.prompt) undefined_vars = template_vars - defined_params if undefined_vars: - return { - "status": "error", - "path": relative_path, - "message": f"Message {i} uses undefined template variables: {', '.join(sorted(undefined_vars))}", - } + return EndpointValidationResultModel( + status="error", + path=relative_path, + message=( + f"Message {i} uses undefined template variables: " + f"{', '.join(sorted(undefined_vars))}" + ), + ) - return {"status": "ok", "path": relative_path} + return EndpointValidationResultModel(status="ok", path=relative_path) - # For resources, validate URI vs parameters if endpoint_type == "resource": - resource_def = endpoint.get("resource") - if resource_def: - err = _validate_resource_uri_vs_params(resource_def, Path(relative_path)) - if err: - return err - - # Check if this is a Python endpoint - skip SQL validation if so - if endpoint_type == "tool": - endpoint_def = endpoint.get("tool") - elif endpoint_type == "resource": - endpoint_def = endpoint.get("resource") - elif endpoint_type == "prompt": - endpoint_def = endpoint.get("prompt") - else: - endpoint_def = None - - language = endpoint_def.get("language", "sql") if endpoint_def else "sql" + err = _validate_resource_uri_vs_params(component, Path(relative_path)) + if err: + return err + + language = component.language or "sql" if language == "python": - # For Python endpoints, just validate that the source file exists - source = endpoint_def.get("source", {}) if endpoint_def else {} - if not isinstance(source, dict) or "file" not in source: - return { - "status": "error", - "path": relative_path, - "message": "Python endpoints must specify source.file", - } - - # Check if the file exists - file_path = Path(source["file"]) + source = component.source + if not source or source.file is None: + return EndpointValidationResultModel( + status="error", + path=relative_path, + message="Python endpoints must specify source.file", + ) + + file_path = Path(source.file) if not file_path.is_absolute(): file_path = path_obj.parent / file_path if not file_path.exists(): - return { - "status": "error", - "path": relative_path, - "message": f"Python source file not found: {file_path}", - } - - # Python endpoints are valid if they have proper structure and file exists - return {"status": "ok", "path": relative_path} - - # For SQL tools and resources, validate SQL - try: - sql_query = get_endpoint_source_code(endpoint, endpoint_type, path_obj, repo_root) - except Exception as e: - return { - "status": "error", - "path": relative_path, - "message": f"Error resolving source code: {str(e)}", - } - if not sql_query: - return {"status": "error", "path": relative_path, "message": "No SQL query found"} - - # Validate SQL syntax using SDK execution engine - try: - # Determine language based on endpoint type - language = "sql" if endpoint_type in ["tool", "resource"] else "python" - - # Validate source code syntax - validation_result = execution_engine.validate_source(language, sql_query) - if not validation_result.is_valid: - error_message = ( - validation_result.error_message or "Source code syntax validation failed" + return EndpointValidationResultModel( + status="error", + path=relative_path, + message=f"Python source file not found: {file_path}", + ) + + return EndpointValidationResultModel(status="ok", path=relative_path) + + if language == "sql": + try: + sql_query = get_endpoint_source_code(endpoint, endpoint_type, path_obj, repo_root) + except Exception as e: + return EndpointValidationResultModel( + status="error", + path=relative_path, + message=f"Error resolving source code: {str(e)}", + ) + + if not sql_query: + return EndpointValidationResultModel( + status="error", path=relative_path, message="No SQL query found" + ) + + try: + validation_result = execution_engine.validate_source("sql", sql_query) + if not validation_result.is_valid: + error_message = ( + validation_result.error_message or "Source code syntax validation failed" + ) + return EndpointValidationResultModel( + status="error", + path=relative_path, + message=f"Source code syntax validation failed: {error_message}", + ) + + sql_param_names = execution_engine.extract_parameters("sql", sql_query) + except Exception as e: + return EndpointValidationResultModel( + status="error", + path=relative_path, + message=f"Source code validation error: {str(e)}", + ) + + if not isinstance(sql_param_names, list): + sql_param_names = list(sql_param_names) + + yaml_params = component.parameters or [] + yaml_param_names = [p.name for p in yaml_params] + + missing_params = set(sql_param_names) - set(yaml_param_names) + extra_params = set(yaml_param_names) - set(sql_param_names) + if missing_params or extra_params: + return EndpointValidationResultModel( + status="error", + path=relative_path, + message=f"Parameter mismatch: missing={missing_params}, extra={extra_params}", ) - return { - "status": "error", - "path": relative_path, - "message": f"Source code syntax validation failed: {error_message}", - } - - # Extract parameter names using SDK execution engine - sql_param_names = execution_engine.extract_parameters(language, sql_query) - except Exception as e: - return { - "status": "error", - "path": relative_path, - "message": f"Source code validation error: {str(e)}", - } - - # Convert to list if needed (ensure consistent type) - if not isinstance(sql_param_names, list): - sql_param_names = list(sql_param_names) - - # Extract parameters from YAML - if endpoint_def: - yaml_params = endpoint_def.get("parameters") or [] - yaml_param_names = [ - p["name"] for p in yaml_params if isinstance(p, dict) and "name" in p - ] - else: - yaml_params = [] - yaml_param_names = [] - - # Check parameter mapping - missing_params = set(sql_param_names) - set(yaml_param_names) - extra_params = set(yaml_param_names) - set(sql_param_names) - if missing_params or extra_params: - return { - "status": "error", - "path": relative_path, - "message": f"Parameter mismatch: missing={missing_params}, extra={extra_params}", - } - - # Type inference and compatibility check - type_mismatches: list[str] = [] - if isinstance(yaml_params, list): - for yaml_param in yaml_params: - name = yaml_param["name"] - # Skip type checking for now since we can't easily get SQL parameter types - # TODO: Implement proper type inference when DuckDB supports it - pass - - if type_mismatches: - return { - "status": "error", - "path": relative_path, - "message": "Type mismatches: " + ", ".join(type_mismatches), - } - - return {"status": "ok", "path": relative_path} + + return EndpointValidationResultModel(status="ok", path=relative_path) + + return EndpointValidationResultModel( + status="error", + path=relative_path, + message=f"Unsupported language '{language}' for endpoint validation", + ) except Exception as e: - return {"status": "error", "path": relative_path, "message": str(e)} + return EndpointValidationResultModel(status="error", path=relative_path, message=str(e)) def validate_endpoint( path: str, site_config: SiteConfigModel, execution_engine: ExecutionEngine -) -> dict[str, Any]: +) -> EndpointValidationResultModel: """Validate a single endpoint file.""" try: # Use EndpointLoader to properly load and validate the endpoint @@ -372,18 +283,20 @@ def validate_endpoint( for endpoint_path, endpoint, error in all_endpoints: if endpoint_path.resolve() == path_obj: if error: - return {"status": "error", "path": path, "message": error} + return EndpointValidationResultModel(status="error", path=path, message=error) elif endpoint: return validate_endpoint_payload(endpoint, path, execution_engine) else: - return {"status": "error", "path": path, "message": "Failed to load endpoint"} + return EndpointValidationResultModel( + status="error", path=path, message="Failed to load endpoint" + ) # If not found in discovered endpoints, it might not be a valid endpoint file - return { - "status": "error", - "path": path, - "message": "Endpoint file not found or not a valid endpoint", - } + return EndpointValidationResultModel( + status="error", + path=path, + message="Endpoint file not found or not a valid endpoint", + ) except Exception as e: - return {"status": "error", "path": path, "message": str(e)} + return EndpointValidationResultModel(status="error", path=path, message=str(e)) diff --git a/src/mxcp/server/services/evals/service.py b/src/mxcp/server/services/evals/service.py index 506b4a42..1697dc10 100644 --- a/src/mxcp/server/services/evals/service.py +++ b/src/mxcp/server/services/evals/service.py @@ -1,6 +1,6 @@ import logging import time -from typing import Any, cast +from typing import Any from mxcp.sdk.auth import UserContext from mxcp.sdk.evals import ( @@ -14,9 +14,10 @@ from mxcp.sdk.validator import TypeSchema from mxcp.server.core.config.models import SiteConfigModel, UserConfigModel from mxcp.server.core.config.site_config import find_repo_root -from mxcp.server.definitions.endpoints._types import EndpointDefinition +from mxcp.server.definitions.endpoints.models import EndpointDefinitionModel from mxcp.server.definitions.endpoints.loader import EndpointLoader from mxcp.server.definitions.evals.loader import discover_eval_files, load_eval_suite +from mxcp.server.definitions.evals.models import EvalSuiteModel from mxcp.server.executor.engine import create_runtime_environment from mxcp.server.executor.runners.tool import EndpointToolExecutor @@ -62,7 +63,7 @@ def _create_model_config(model: str, user_config: UserConfigModel) -> ModelConfi raise ValueError(f"Unknown model type: {model_type}") -def _load_endpoints(site_config: SiteConfigModel) -> list[EndpointDefinition]: +def _load_endpoints(site_config: SiteConfigModel) -> list[EndpointDefinitionModel]: """Load all available endpoints. Args: @@ -72,15 +73,11 @@ def _load_endpoints(site_config: SiteConfigModel) -> list[EndpointDefinition]: List of endpoint definitions """ loader = EndpointLoader(site_config) - endpoints: list[EndpointDefinition] = [] + endpoints: list[EndpointDefinitionModel] = [] discovered = loader.discover_endpoints() for _path, endpoint_def, error in discovered: - if ( - error is None - and endpoint_def - and (endpoint_def.get("tool") or endpoint_def.get("resource")) - ): + if error is None and endpoint_def and (endpoint_def.tool or endpoint_def.resource): # Only include endpoints that have a tool or resource definition endpoints.append(endpoint_def) @@ -88,7 +85,7 @@ def _load_endpoints(site_config: SiteConfigModel) -> list[EndpointDefinition]: def _convert_endpoints_to_tool_definitions( - endpoints: list[EndpointDefinition], + endpoints: list[EndpointDefinitionModel], ) -> list[ToolDefinition]: """Convert endpoint definitions to ToolDefinition objects for the LLM. @@ -101,72 +98,77 @@ def _convert_endpoints_to_tool_definitions( tool_definitions = [] for endpoint_def in endpoints: - if "tool" in endpoint_def: - tool = endpoint_def["tool"] - if tool: - # Convert parameters - parameters = [] - tool_params = tool.get("parameters") or [] - for param in tool_params: - parameters.append( - ParameterDefinition( - name=param.get("name", ""), - type=param.get("type", "string"), - description=param.get("description", ""), - default=param.get("default"), - required="default" not in param, - ) + if endpoint_def.tool: + tool = endpoint_def.tool + + tool_parameters = [] + for param in tool.parameters or []: + has_default = "default" in param.model_fields_set + tool_parameters.append( + ParameterDefinition( + name=param.name, + type=param.type, + description=param.description or "", + default=param.default if has_default else None, + required=not has_default, ) + ) - # Convert return TypeDefinition to TypeSchema if present - return_type = None - if tool.get("return_"): - return_type = TypeSchema.from_dict(cast(dict[str, Any], tool["return_"])) - - tool_definitions.append( - ToolDefinition( - name=tool["name"], - description=tool.get("description") or "", - parameters=parameters, - return_type=return_type, - annotations=tool.get("annotations") or {}, - tags=tool.get("tags") or [], - ) + return_type = None + if tool.return_: + return_type = TypeSchema.from_dict( + tool.return_.model_dump(mode="python", exclude_unset=True, by_alias=True) ) - elif "resource" in endpoint_def: - resource = endpoint_def["resource"] - if resource: - # Convert parameters - parameters = [] - resource_params = resource.get("parameters") or [] - for param in resource_params: - parameters.append( - ParameterDefinition( - name=param.get("name", ""), - type=param.get("type", "string"), - description=param.get("description", ""), - default=param.get("default"), - required="default" not in param, - ) - ) + annotations = ( + tool.annotations.model_dump(mode="python", exclude_unset=True) + if tool.annotations + else {} + ) + + tool_definitions.append( + ToolDefinition( + name=tool.name, + description=tool.description or "", + parameters=tool_parameters, + return_type=return_type, + annotations=annotations, + tags=tool.tags or [], + ) + ) - # Convert return TypeDefinition to TypeSchema if present - return_type = None - if resource.get("return"): - return_type = TypeSchema.from_dict(cast(dict[str, Any], resource["return"])) - - tool_definitions.append( - ToolDefinition( - name=resource["uri"], - description=resource.get("description") or "", - parameters=parameters, - return_type=return_type, - annotations={}, - tags=resource.get("tags") or [], + elif endpoint_def.resource: + resource = endpoint_def.resource + resource_parameters = [] + for param in resource.parameters or []: + has_default = "default" in param.model_fields_set + resource_parameters.append( + ParameterDefinition( + name=param.name, + type=param.type, + description=param.description or "", + default=param.default if has_default else None, + required=not has_default, ) ) + return_type = None + if resource.return_: + return_type = TypeSchema.from_dict( + resource.return_.model_dump(mode="python", exclude_unset=True, by_alias=True) + ) + + tool_definitions.append( + ToolDefinition( + name=resource.uri, + description=resource.description or "", + parameters=resource_parameters, + return_type=return_type, + annotations={}, + tags=resource.tags or [], + ) + ) + return tool_definitions @@ -199,7 +201,7 @@ async def run_eval_suite( file_path, eval_suite = result # Determine which model to use - model = override_model or eval_suite.get("model") + model = override_model or eval_suite.model if not model: models_config = user_config.models model = models_config.default if models_config else None @@ -227,11 +229,9 @@ async def run_eval_suite( tool_executor = EndpointToolExecutor(engine, endpoints) logger.info(f"Running eval suite: {suite_name} from {file_path}") - logger.info( - f"Suite description: {eval_suite.get('description', 'No description') if eval_suite else 'No description'}" - ) + logger.info(f"Suite description: {eval_suite.description or 'No description'}") logger.info(f"Model: {model}") - logger.info(f"Number of tests: {len(eval_suite.get('tests', []) if eval_suite else [])}") + logger.info(f"Number of tests: {len(eval_suite.tests)}") try: # Create LLM executor with model config, tool definitions, and tool executor @@ -239,103 +239,82 @@ async def run_eval_suite( # Run each test tests = [] - for test in eval_suite.get("tests", []) if eval_suite else []: + for test in eval_suite.tests: test_start = time.time() # Determine user context for this test test_user_context = cli_user_context - if test_user_context is None and "user_context" in test: - # Create UserContext from test definition - test_context_data = test["user_context"] + if test_user_context is None and test.user_context is not None: + test_context_data = test.user_context test_user_context = UserContext( provider="test", - user_id=( - test_context_data.get("user_id", "test_user") - if test_context_data - else "test_user" - ), - username=( - test_context_data.get("username", "test_user") - if test_context_data - else "test_user" - ), - email=test_context_data.get("email") if test_context_data else None, - name=test_context_data.get("name") if test_context_data else None, - avatar_url=test_context_data.get("avatar_url") if test_context_data else None, - raw_profile=test_context_data if test_context_data else {}, + user_id=test_context_data.get("user_id", "test_user"), + username=test_context_data.get("username", "test_user"), + email=test_context_data.get("email"), + name=test_context_data.get("name"), + avatar_url=test_context_data.get("avatar_url"), + raw_profile=test_context_data, ) try: # Execute the prompt response, tool_calls = await executor.execute_prompt( - test["prompt"], user_context=test_user_context + test.prompt, user_context=test_user_context ) # Evaluate assertions failures = [] - assertions = test.get("assertions", {}) + assertions = test.assertions # Check must_call assertions - if assertions and "must_call" in assertions: - must_calls = assertions.get("must_call") - if must_calls: - for expected_call in must_calls: - expected_tool = expected_call["tool"] - expected_args = expected_call.get("args", {}) - - # Check if tool was called with expected args - found = False - for call in tool_calls: - if call["tool"] == expected_tool: - # Check arguments match - actual_args = call.get("arguments", {}) - if all( - actual_args.get(k) == v for k, v in expected_args.items() - ): - found = True - break - - if not found: - failures.append( - f"Expected call to '{expected_tool}' with args {expected_args} not found" - ) + if assertions.must_call: + for expected_call in assertions.must_call: + expected_tool = expected_call.tool + expected_args = expected_call.args or {} + + found = False + for call in tool_calls: + if call["tool"] == expected_tool: + actual_args = call.get("arguments", {}) + if all(actual_args.get(k) == v for k, v in expected_args.items()): + found = True + break + + if not found: + failures.append( + f"Expected call to '{expected_tool}' with args {expected_args} not found" + ) # Check must_not_call assertions - if assertions and "must_not_call" in assertions: - must_not_calls = assertions.get("must_not_call") - if must_not_calls: - for forbidden_tool in must_not_calls: - if any(call["tool"] == forbidden_tool for call in tool_calls): - failures.append( - f"Tool '{forbidden_tool}' was called but should not have been" - ) + if assertions.must_not_call: + for forbidden_tool in assertions.must_not_call: + if any(call["tool"] == forbidden_tool for call in tool_calls): + failures.append( + f"Tool '{forbidden_tool}' was called but should not have been" + ) # Check answer_contains assertions - if assertions and "answer_contains" in assertions: - contains = assertions.get("answer_contains") - if contains: - for expected_text in contains: - if expected_text.lower() not in response.lower(): - failures.append( - f"Expected text '{expected_text}' not found in response" - ) + if assertions.answer_contains: + for expected_text in assertions.answer_contains: + if expected_text.lower() not in response.lower(): + failures.append( + f"Expected text '{expected_text}' not found in response" + ) # Check answer_not_contains assertions - if assertions and "answer_not_contains" in assertions: - not_contains = assertions.get("answer_not_contains") - if not_contains: - for forbidden_text in not_contains: - if forbidden_text.lower() in response.lower(): - failures.append( - f"Forbidden text '{forbidden_text}' found in response" - ) + if assertions.answer_not_contains: + for forbidden_text in assertions.answer_not_contains: + if forbidden_text.lower() in response.lower(): + failures.append( + f"Forbidden text '{forbidden_text}' found in response" + ) test_time = time.time() - test_start tests.append( { - "name": test["name"], - "description": test.get("description"), + "name": test.name, + "description": test.description, "passed": len(failures) == 0, "failures": failures, "time": test_time, @@ -347,8 +326,8 @@ async def run_eval_suite( test_time = time.time() - test_start tests.append( { - "name": test["name"], - "description": test.get("description"), + "name": test.name, + "description": test.description, "passed": False, "error": str(e), "time": test_time, @@ -363,7 +342,7 @@ async def run_eval_suite( return { "suite": suite_name, - "description": eval_suite.get("description"), + "description": eval_suite.description, "model": model, "tests": tests, "all_passed": all_passed, diff --git a/src/mxcp/server/services/tests/__init__.py b/src/mxcp/server/services/tests/__init__.py index 356645df..784c9f80 100644 --- a/src/mxcp/server/services/tests/__init__.py +++ b/src/mxcp/server/services/tests/__init__.py @@ -4,13 +4,17 @@ The main entry point is the service module. """ -# Import main functions from service module -from .service import ( - run_all_tests, - run_tests, +# Re-export typed result models for convenience +from .models import ( + EndpointTestResultModel, + MultiEndpointTestResultsModel, + TestCaseResultModel, + TestSuiteResultModel, ) __all__ = [ - "run_tests", - "run_all_tests", + "TestSuiteResultModel", + "TestCaseResultModel", + "EndpointTestResultModel", + "MultiEndpointTestResultsModel", ] diff --git a/src/mxcp/server/services/tests/models.py b/src/mxcp/server/services/tests/models.py new file mode 100644 index 00000000..e1fa9296 --- /dev/null +++ b/src/mxcp/server/services/tests/models.py @@ -0,0 +1,52 @@ +from __future__ import annotations + +from typing import Literal + +from pydantic import BaseModel, ConfigDict + + +class TestCaseResultModel(BaseModel): + """Represents the outcome of a single endpoint test case.""" + + model_config = ConfigDict(extra="forbid") + + name: str + description: str + status: Literal["passed", "failed", "error"] + error: str | None = None + error_cause: str | None = None + time: float | None = None + + +class TestSuiteResultModel(BaseModel): + """Aggregated result for all tests belonging to one endpoint.""" + + model_config = ConfigDict(extra="forbid") + + status: Literal["ok", "failed", "error"] + tests_run: int + tests: list[TestCaseResultModel] + no_tests: bool = False + message: str | None = None + + +class EndpointTestResultModel(BaseModel): + """Wrapper that associates a suite result with endpoint metadata.""" + + model_config = ConfigDict(extra="forbid") + + endpoint: str + path: str + test_results: TestSuiteResultModel + + +class MultiEndpointTestResultsModel(BaseModel): + """Full report for running tests across multiple endpoints.""" + + model_config = ConfigDict(extra="forbid") + + status: Literal["ok", "failed", "error"] + tests_run: int + endpoints: list[EndpointTestResultModel] + message: str | None = None + diff --git a/src/mxcp/server/services/tests/service.py b/src/mxcp/server/services/tests/service.py index d0cb8b3d..21ba6940 100644 --- a/src/mxcp/server/services/tests/service.py +++ b/src/mxcp/server/services/tests/service.py @@ -1,5 +1,4 @@ import logging -from typing import Any from mxcp.sdk.auth import UserContext from mxcp.server.core.config.models import SiteConfigModel, UserConfigModel @@ -7,6 +6,11 @@ from mxcp.server.definitions.endpoints.loader import EndpointLoader from mxcp.server.executor.engine import create_runtime_environment from mxcp.server.executor.runners.test import TestRunner +from mxcp.server.services.tests.models import ( + EndpointTestResultModel, + MultiEndpointTestResultsModel, + TestSuiteResultModel, +) # Configure logging logging.basicConfig(level=logging.DEBUG) @@ -20,7 +24,7 @@ async def run_all_tests( readonly: bool | None = None, cli_user_context: UserContext | None = None, request_headers: dict[str, str] | None = None, -) -> dict[str, Any]: +) -> MultiEndpointTestResultsModel: """Run tests for all endpoints in the repository (async)""" repo_root = find_repo_root() logger.debug(f"Repository root: {repo_root}") @@ -30,13 +34,16 @@ async def run_all_tests( endpoints = loader.discover_endpoints() logger.debug(f"Found {len(endpoints)} YAML files") - results: dict[str, Any] = {"status": "ok", "tests_run": 0, "endpoints": []} + endpoint_results: list[EndpointTestResultModel] = [] + overall_status: str = "ok" + total_tests_run = 0 # Create runtime environment once for all tests runtime_env = create_runtime_environment(user_config, site_config, profile, readonly=readonly) execution_engine = runtime_env.execution_engine try: + test_runner = TestRunner(user_config, site_config, execution_engine) for file_path, endpoint, error_msg in endpoints: if file_path.name in ["mxcp-site.yml", "mxcp-config.yml"]: continue @@ -50,15 +57,19 @@ async def run_all_tests( relative_path = file_path.name if error_msg is not None: - # This endpoint failed to load - results["endpoints"].append( - { - "endpoint": str(file_path), - "path": relative_path, - "test_results": {"status": "error", "message": error_msg}, - } + endpoint_results.append( + EndpointTestResultModel( + endpoint=str(file_path), + path=relative_path, + test_results=TestSuiteResultModel( + status="error", + tests_run=0, + tests=[], + message=error_msg, + ), + ) ) - results["status"] = "error" + overall_status = "error" continue try: @@ -67,35 +78,20 @@ async def run_all_tests( logger.debug(f"Skipping file {file_path}: endpoint is None") continue - # Determine endpoint type and name - if "tool" in endpoint: + if endpoint.tool is not None: kind = "tool" - tool_def: Any = endpoint.get("tool", {}) - name = ( - tool_def.get("name", "unknown") if isinstance(tool_def, dict) else "unknown" - ) - elif "resource" in endpoint: + name = endpoint.tool.name + elif endpoint.resource is not None: kind = "resource" - resource_def = endpoint.get("resource", {}) - name = ( - resource_def.get("uri", "unknown") - if isinstance(resource_def, dict) - else "unknown" - ) - elif "prompt" in endpoint: + name = endpoint.resource.uri + elif endpoint.prompt is not None: kind = "prompt" - prompt_def = endpoint.get("prompt", {}) - name = ( - prompt_def.get("name", "unknown") - if isinstance(prompt_def, dict) - else "unknown" - ) + name = endpoint.prompt.name else: logger.debug(f"Skipping file {file_path}: not a valid endpoint") continue # Run tests for this endpoint using TestRunner - test_runner = TestRunner(user_config, site_config, execution_engine) test_results = await test_runner.run_tests_for_endpoint( kind, name, @@ -103,35 +99,43 @@ async def run_all_tests( request_headers, ) - # Wrap test results with endpoint context - endpoint_result = { - "endpoint": f"{kind}/{name}", - "path": relative_path, - "test_results": test_results, - } - - results["endpoints"].append(endpoint_result) - results["tests_run"] += test_results.get("tests_run", 0) + endpoint_results.append( + EndpointTestResultModel( + endpoint=f"{kind}/{name}", + path=relative_path, + test_results=test_results, + ) + ) + total_tests_run += test_results.tests_run # Update overall status - if test_results.get("status") == "error": - results["status"] = "error" - elif test_results.get("status") == "failed" and results["status"] != "error": - results["status"] = "failed" + if test_results.status == "error": + overall_status = "error" + elif test_results.status == "failed" and overall_status != "error": + overall_status = "failed" except Exception as e: logger.error(f"Error processing file {file_path}: {str(e)}") - results["endpoints"].append( - { - "endpoint": str(file_path), - "path": relative_path, - "test_results": {"status": "error", "message": str(e)}, - } + endpoint_results.append( + EndpointTestResultModel( + endpoint=str(file_path), + path=relative_path, + test_results=TestSuiteResultModel( + status="error", + tests_run=0, + tests=[], + message=str(e), + ), + ) ) - results["status"] = "error" + overall_status = "error" finally: runtime_env.shutdown() - return results + return MultiEndpointTestResultsModel( + status=overall_status, + tests_run=total_tests_run, + endpoints=endpoint_results, + ) async def run_tests( @@ -143,7 +147,7 @@ async def run_tests( readonly: bool | None = None, cli_user_context: UserContext | None = None, request_headers: dict[str, str] | None = None, -) -> dict[str, Any]: +) -> TestSuiteResultModel: """Run tests for a specific endpoint type and name.""" # Create runtime environment for this single test run runtime_env = create_runtime_environment(user_config, site_config, profile, readonly=readonly) diff --git a/tests/misc/test_no_dict_model_unions.py b/tests/misc/test_no_dict_model_unions.py new file mode 100644 index 00000000..bc324946 --- /dev/null +++ b/tests/misc/test_no_dict_model_unions.py @@ -0,0 +1,23 @@ +from __future__ import annotations + +import re +from pathlib import Path + + +def test_no_dict_model_unions_in_server_code() -> None: + """Ensure server code no longer declares dict|Model type unions.""" + repo_root = Path(__file__).resolve().parents[2] + server_dir = repo_root / "src" / "mxcp" / "server" + pattern = re.compile(r"dict\[str,\s*Any\]\s*\|\s*(?!None\b)[A-Z]") + + violations: list[str] = [] + + for path in server_dir.rglob("*.py"): + text = path.read_text() + for match in pattern.finditer(text): + violations.append(f"{path.relative_to(repo_root)}: {match.group(0)}") + + assert ( + not violations + ), "Disallowed dict|Model unions found:\n" + "\n".join(f"- {entry}" for entry in violations) + diff --git a/tests/sdk/validator/test_schema_comparison.py b/tests/sdk/validator/test_schema_comparison.py index f356e716..48eb87ed 100644 --- a/tests/sdk/validator/test_schema_comparison.py +++ b/tests/sdk/validator/test_schema_comparison.py @@ -3,6 +3,19 @@ import json from pathlib import Path +from mxcp.server.definitions.endpoints.models import ParamDefinitionModel, TypeDefinitionModel + + +def _extract_enum(schema_fragment: dict) -> list[str]: + """Return enum values from schema fragment that may use anyOf wrappers.""" + if "enum" in schema_fragment: + return schema_fragment["enum"] + if "anyOf" in schema_fragment: + for option in schema_fragment["anyOf"]: + if isinstance(option, dict) and "enum" in option: + return option["enum"] + raise KeyError("enum") + class TestSchemaComparison: """Compare validation schema with common-types schema to ensure compatibility.""" @@ -20,24 +33,14 @@ def test_compare_with_common_types(self): / "schemas" / "validation-schema-1.json" ) - common_types_path = ( - Path(__file__).parent.parent.parent.parent - / "src" - / "mxcp" - / "server" - / "schemas" - / "common-types-schema-1.json" - ) with open(validation_schema_path) as f: validation_schema = json.load(f) - with open(common_types_path) as f: - common_types_schema = json.load(f) - # Get type definitions validation_base_type = validation_schema["definitions"]["baseTypeDefinition"]["properties"] - common_type = common_types_schema["definitions"]["typeDefinition"]["properties"] + type_schema = TypeDefinitionModel.model_json_schema() + common_type = type_schema["$defs"]["TypeDefinitionModel"]["properties"] # Check type enum values validation_types = validation_schema["definitions"]["typeEnum"]["enum"] @@ -48,7 +51,7 @@ def test_compare_with_common_types(self): # Check format enum values validation_formats = validation_schema["definitions"]["formatEnum"]["enum"] - common_formats = common_type["format"]["enum"] + common_formats = _extract_enum(common_type["format"]) assert set(validation_formats) == set( common_formats ), f"Format enums differ: {validation_formats} vs {common_formats}" @@ -78,7 +81,7 @@ def test_compare_with_common_types(self): # Compare parameter definitions print("\n=== Parameter Definition Comparison ===") validation_param = validation_schema["definitions"]["parameterSchema"] - common_param = common_types_schema["definitions"]["paramDefinition"] + param_schema = ParamDefinitionModel.model_json_schema() # Extract parameter-specific properties from validation schema validation_param_props = set() @@ -87,7 +90,7 @@ def test_compare_with_common_types(self): validation_param_props.update(item["properties"].keys()) # Get common param properties (excluding type properties already compared) - common_param_props = set(common_param["properties"].keys()) + common_param_props = set(param_schema["properties"].keys()) print(f"Validation parameter properties: {sorted(validation_param_props)}") print(f"Common parameter properties: {sorted(common_param_props)}") @@ -98,7 +101,7 @@ def test_compare_with_common_types(self): if "required" in item: validation_param_required.extend(item["required"]) - common_param_required = common_param.get("required", []) + common_param_required = param_schema.get("required", []) print(f"\nValidation param required: {validation_param_required}") print(f"Common param required: {common_param_required}") @@ -181,33 +184,20 @@ def test_schema_alignment_summary(self): / "schemas" / "validation-schema-1.json" ) - common_types_path = ( - Path(__file__).parent.parent.parent.parent - / "src" - / "mxcp" - / "server" - / "schemas" - / "common-types-schema-1.json" - ) - with open(validation_schema_path) as f: validation_schema = json.load(f) - with open(common_types_path) as f: - common_types_schema = json.load(f) + type_schema = TypeDefinitionModel.model_json_schema() + type_props = type_schema["$defs"]["TypeDefinitionModel"]["properties"] # Verify type enums match exactly validation_types = validation_schema["definitions"]["typeEnum"]["enum"] - common_types = common_types_schema["definitions"]["typeDefinition"]["properties"]["type"][ - "enum" - ] + common_types = type_props["type"]["enum"] assert validation_types == common_types # Verify format enums match exactly validation_formats = validation_schema["definitions"]["formatEnum"]["enum"] - common_formats = common_types_schema["definitions"]["typeDefinition"]["properties"][ - "format" - ]["enum"] + common_formats = _extract_enum(type_props["format"]) assert validation_formats == common_formats print("\n✅ All critical type system components are aligned!") diff --git a/tests/server/fixtures/tester/prompts/valid_prompt.yml b/tests/server/fixtures/tester/prompts/valid_prompt.yml index ea4167e1..8d779c9c 100644 --- a/tests/server/fixtures/tester/prompts/valid_prompt.yml +++ b/tests/server/fixtures/tester/prompts/valid_prompt.yml @@ -21,11 +21,16 @@ prompt: type: string description: Generated text - source: - code: | - SELECT - 'Generated: ' || $context as result - FROM (VALUES (1)) t + messages: + - role: system + type: text + prompt: "You are a SQL assistant that echoes user context." + - role: user + type: text + prompt: "Context: {{context}}" + - role: assistant + type: text + prompt: "Generated: {{context}}" tests: - name: valid context @@ -33,7 +38,8 @@ prompt: arguments: - key: context value: "test context" - result: "Generated: test context" + result: + - name: "test context" - name: with temperature description: Test with temperature parameter @@ -42,4 +48,5 @@ prompt: value: "test context" - key: temperature value: 0.5 - result: "Generated: test context" \ No newline at end of file + result: + - name: "test context" \ No newline at end of file diff --git a/tests/server/test_drift_check.py b/tests/server/test_drift_check.py index 8bb5ccac..543d5cd8 100644 --- a/tests/server/test_drift_check.py +++ b/tests/server/test_drift_check.py @@ -97,12 +97,12 @@ def test_load_and_validate_snapshot_valid(no_changes_repo_path): snapshot_path = no_changes_repo_path / "drift-default.json" snapshot = load_and_validate_snapshot(snapshot_path) - assert snapshot["version"] == 1 - assert "generated_at" in snapshot - assert "tables" in snapshot - assert "resources" in snapshot - assert len(snapshot["resources"]) == 1 - assert snapshot["resources"][0]["validation_results"]["path"] == "tools/hello-world.yml" + assert snapshot.version == 1 + assert snapshot.generated_at + assert snapshot.tables is not None + assert snapshot.resources + assert len(snapshot.resources) == 1 + assert snapshot.resources[0].validation_results.path == "tools/hello-world.yml" def test_load_and_validate_snapshot_missing_file(): @@ -146,11 +146,8 @@ def test_load_and_validate_snapshot_missing_required_field(tmp_path): json.dump(invalid_snapshot, f) - # The current implementation doesn't validate schema strictly, so this test - # just verifies the snapshot loads without error. In a future version, - # we could add strict schema validation. - snapshot = load_and_validate_snapshot(snapshot_path) - assert snapshot["version"] == 1 + with pytest.raises(ValueError, match="Invalid drift snapshot"): + load_and_validate_snapshot(snapshot_path) @pytest.mark.asyncio @@ -170,15 +167,15 @@ async def test_drift_check_no_changes( ) # Verify no drift detected - assert report["has_drift"] is False - assert report["version"] == 1 - assert "generated_at" in report - assert "baseline_snapshot_path" in report - assert "current_snapshot_generated_at" in report - assert "baseline_snapshot_generated_at" in report + assert report.has_drift is False + assert report.version == 1 + assert report.generated_at + assert report.baseline_snapshot_path + assert report.current_snapshot_generated_at + assert report.baseline_snapshot_generated_at # Verify summary shows no changes - summary = report["summary"] + summary = report.summary assert summary["tables_added"] == 0 assert summary["tables_removed"] == 0 assert summary["tables_modified"] == 0 @@ -187,8 +184,8 @@ async def test_drift_check_no_changes( assert summary["resources_modified"] == 0 # Verify no changes in details - assert len(report["table_changes"]) == 0 - assert len(report["resource_changes"]) == 0 + assert len(report.table_changes) == 0 + assert len(report.resource_changes) == 0 finally: os.chdir(original_dir) @@ -215,15 +212,15 @@ async def test_drift_check_has_changes( ) # Verify drift detected - assert report["has_drift"] is True - assert report["version"] == 1 - assert "generated_at" in report - assert "baseline_snapshot_path" in report - assert "current_snapshot_generated_at" in report - assert "baseline_snapshot_generated_at" in report + assert report.has_drift is True + assert report.version == 1 + assert report.generated_at + assert report.baseline_snapshot_path + assert report.current_snapshot_generated_at + assert report.baseline_snapshot_generated_at # Verify summary shows changes - summary = report["summary"] + summary = report.summary assert summary["tables_added"] == 0 # No table changes in this test assert summary["tables_removed"] == 0 assert summary["tables_modified"] == 0 @@ -232,33 +229,31 @@ async def test_drift_check_has_changes( assert summary["resources_modified"] == 1 # hello-world.yml modified # Verify no table changes - assert len(report["table_changes"]) == 0 + assert len(report.table_changes) == 0 # Verify resource changes - assert len(report["resource_changes"]) == 2 + assert len(report.resource_changes) == 2 # Find the added and modified resources - added_resources = [r for r in report["resource_changes"] if r["change_type"] == "added"] - modified_resources = [ - r for r in report["resource_changes"] if r["change_type"] == "modified" - ] + added_resources = [r for r in report.resource_changes if r.change_type == "added"] + modified_resources = [r for r in report.resource_changes if r.change_type == "modified"] assert len(added_resources) == 1 assert len(modified_resources) == 1 # Check added resource (there should be one with bye-world) - bye_world_resources = [r for r in added_resources if "bye-world" in r["path"]] + bye_world_resources = [r for r in added_resources if "bye-world" in r.path] assert len(bye_world_resources) == 1 added_resource = bye_world_resources[0] - assert added_resource["path"] == "tools/bye-world.yml" - assert added_resource["endpoint"] == "tool/bye_world" - assert added_resource["change_type"] == "added" + assert added_resource.path == "tools/bye-world.yml" + assert added_resource.endpoint == "tool/bye_world" + assert added_resource.change_type == "added" # Check modified resource modified_resource = modified_resources[0] - assert modified_resource["path"] == "tools/hello-world.yml" - assert modified_resource["endpoint"] == "tool/hello_world_changed" - assert modified_resource["change_type"] == "modified" + assert modified_resource.path == "tools/hello-world.yml" + assert modified_resource.endpoint == "tool/hello_world_changed" + assert modified_resource.change_type == "modified" finally: os.chdir(original_dir) @@ -291,11 +286,11 @@ async def test_drift_check_with_custom_baseline( ) # Should detect drift since we're comparing has-changes against no-changes baseline - assert report["has_drift"] is True - assert report["baseline_snapshot_path"] == custom_baseline_path + assert report.has_drift is True + assert report.baseline_snapshot_path == custom_baseline_path # Should show resource changes - summary = report["summary"] + summary = report.summary assert summary["resources_added"] == 1 # bye-world.yml added assert summary["resources_modified"] == 1 # hello-world.yml modified @@ -348,23 +343,24 @@ def test_drift_report_structure(no_changes_repo_path): snapshot = load_and_validate_snapshot(snapshot_path) # Verify the snapshot has the expected structure for drift comparison - assert "version" in snapshot - assert "generated_at" in snapshot - assert "tables" in snapshot - assert "resources" in snapshot + assert snapshot.version == 1 + assert snapshot.generated_at + assert snapshot.tables is not None + assert snapshot.resources # Verify resource structure - for resource in snapshot["resources"]: - assert "validation_results" in resource - assert "test_results" in resource - assert "definition" in resource + for resource in snapshot.resources: + assert resource.validation_results + assert resource.test_results is not None + assert resource.definition is not None # Verify validation_results structure - validation = resource["validation_results"] - assert "status" in validation - assert "path" in validation + validation = resource.validation_results + assert validation.status + assert validation.path # Verify test_results structure - test_results = resource["test_results"] - assert "status" in test_results - assert "tests_run" in test_results + test_results = resource.test_results + assert test_results is not None + assert test_results.status + assert test_results.tests_run is not None diff --git a/tests/server/test_endpoint_execution.py b/tests/server/test_endpoint_execution.py index 0788b4a0..de64c625 100644 --- a/tests/server/test_endpoint_execution.py +++ b/tests/server/test_endpoint_execution.py @@ -65,11 +65,11 @@ def test_endpoint_loading(site_config, test_repo_path): assert result is not None endpoint_file_path, endpoint_definition = result assert endpoint_definition is not None - assert "tool" in endpoint_definition - tool_def = endpoint_definition["tool"] - assert tool_def["name"] == "example" - assert "parameters" in tool_def - assert "return" in tool_def + assert endpoint_definition.tool is not None + tool_def = endpoint_definition.tool + assert tool_def.name == "example" + assert tool_def.parameters is not None + assert tool_def.return_ is not None finally: os.chdir(original_dir) @@ -88,14 +88,14 @@ def test_parameter_validation(site_config, test_repo_path): assert result is not None endpoint_file_path, endpoint_definition = result assert endpoint_definition is not None - assert "tool" in endpoint_definition - tool_def = endpoint_definition["tool"] + assert endpoint_definition.tool is not None + tool_def = endpoint_definition.tool assert tool_def is not None # Verify the schema has the expected parameter definitions - parameters = tool_def["parameters"] + parameters = tool_def.parameters or [] assert parameters is not None - param_names = [p["name"] for p in parameters] + param_names = [p.name for p in parameters] assert "name" in param_names assert "age" in param_names assert "is_active" in param_names diff --git a/tests/server/test_evals.py b/tests/server/test_evals.py index ac4d7115..3ea61379 100644 --- a/tests/server/test_evals.py +++ b/tests/server/test_evals.py @@ -3,6 +3,7 @@ import yaml from mxcp.server.definitions.evals.loader import discover_eval_files, load_eval_suite +from mxcp.server.definitions.evals.models import EvalSuiteModel def test_discover_eval_files(tmp_path): @@ -66,7 +67,7 @@ def test_load_eval_suite(): mxcp: 1 suite: test_suite description: "Test suite description" -model: gpt-3.5-turbo +model: gpt-4o tests: - name: test_with_assertions description: "Test with all assertion types" @@ -87,25 +88,26 @@ def test_load_eval_suite(): """ # Parse YAML - suite = yaml.safe_load(content) + suite_data = yaml.safe_load(content) + suite = EvalSuiteModel.model_validate(suite_data) # Validate structure - assert suite["suite"] == "test_suite" - assert suite["description"] == "Test suite description" - assert suite["model"] == "gpt-3.5-turbo" - assert len(suite["tests"]) == 1 + assert suite.suite == "test_suite" + assert suite.description == "Test suite description" + assert suite.model == "gpt-4o" + assert len(suite.tests) == 1 - test = suite["tests"][0] - assert test["name"] == "test_with_assertions" - assert test["prompt"] == "Test prompt" - assert test["user_context"]["role"] == "admin" + test = suite.tests[0] + assert test.name == "test_with_assertions" + assert test.prompt == "Test prompt" + assert test.user_context is not None and test.user_context.get("role") == "admin" - assertions = test["assertions"] - assert len(assertions["must_call"]) == 1 - assert assertions["must_call"][0]["tool"] == "my_tool" - assert assertions["must_not_call"] == ["dangerous_tool"] - assert assertions["answer_contains"] == ["success"] - assert assertions["answer_not_contains"] == ["error"] + assertions = test.assertions + assert assertions.must_call and len(assertions.must_call) == 1 + assert assertions.must_call[0].tool == "my_tool" + assert assertions.must_not_call == ["dangerous_tool"] + assert assertions.answer_contains == ["success"] + assert assertions.answer_not_contains == ["error"] def test_eval_file_validation(tmp_path): @@ -152,7 +154,7 @@ def test_eval_file_validation(tmp_path): try: result = load_eval_suite("valid_suite") assert result is not None - assert result[1]["suite"] == "valid_suite" + assert result[1].suite == "valid_suite" # Load invalid file should fail result = load_eval_suite("invalid_suite") diff --git a/tests/server/test_evals_tool_executor.py b/tests/server/test_evals_tool_executor.py index fee5a4f5..c7ce1f2b 100644 --- a/tests/server/test_evals_tool_executor.py +++ b/tests/server/test_evals_tool_executor.py @@ -6,7 +6,7 @@ from mxcp.sdk.auth import UserContext from mxcp.sdk.executor import ExecutionContext -from mxcp.server.definitions.endpoints._types import EndpointDefinition +from mxcp.server.definitions.endpoints.models import EndpointDefinitionModel, SourceDefinitionModel from mxcp.server.executor.runners.tool import EndpointToolExecutor @@ -48,55 +48,51 @@ def setup_method(self): } ) - self.endpoints: list[EndpointDefinition] = [ - { - "mxcp": "1", - "tool": { - "name": "get_date", - "description": "Get current date", - "parameters": [], - "source": {"code": "SELECT current_date()"}, - }, - "resource": None, - "prompt": None, - "metadata": None, - }, - { - "mxcp": "1", - "tool": { - "name": "calculate", - "description": "Calculate expression", - "parameters": [{"name": "expr", "type": "string"}], - "source": {"code": "return 2 + 2", "language": "python"}, - }, - "resource": None, - "prompt": None, - "metadata": None, - }, - { - "mxcp": "1", - "tool": { - "name": "get_weather", - "description": "Get weather info", - "parameters": [{"name": "location", "type": "string"}], - "source": {"file": "weather.py", "language": "python"}, - }, - "resource": None, - "prompt": None, - "metadata": None, - }, - { - "mxcp": "1", - "tool": None, - "resource": { - "uri": "data://users", - "description": "User data resource", - "parameters": [{"name": "limit", "type": "integer"}], - "source": {"code": "SELECT * FROM users LIMIT $limit"}, - }, - "prompt": None, - "metadata": None, - }, + self.endpoints: list[EndpointDefinitionModel] = [ + EndpointDefinitionModel.model_validate( + { + "mxcp": 1, + "tool": { + "name": "get_date", + "description": "Get current date", + "parameters": [], + "source": {"code": "SELECT current_date()"}, + }, + } + ), + EndpointDefinitionModel.model_validate( + { + "mxcp": 1, + "tool": { + "name": "calculate", + "description": "Calculate expression", + "parameters": [{"name": "expr", "type": "string"}], + "source": {"code": "return 2 + 2", "language": "python"}, + }, + } + ), + EndpointDefinitionModel.model_validate( + { + "mxcp": 1, + "tool": { + "name": "get_weather", + "description": "Get weather info", + "parameters": [{"name": "location", "type": "string"}], + "source": {"file": "weather.py", "language": "python"}, + }, + } + ), + EndpointDefinitionModel.model_validate( + { + "mxcp": 1, + "resource": { + "uri": "data://users", + "description": "User data resource", + "parameters": [{"name": "limit", "type": "integer"}], + "source": {"code": "SELECT * FROM users LIMIT $limit"}, + }, + } + ), ] self.executor = EndpointToolExecutor(self.engine, self.endpoints) @@ -191,72 +187,52 @@ async def test_execute_tool_execution_error(self): @pytest.mark.asyncio async def test_execute_tool_no_source(self): """Test error when endpoint has no source.""" - endpoints_no_source: list[EndpointDefinition] = [ + endpoint = EndpointDefinitionModel.model_validate( { - "mxcp": "1", + "mxcp": 1, "tool": { "name": "broken_tool", "description": "Tool with no source", "parameters": [], - "source": {}, # Empty source + "source": {"code": "SELECT 1"}, }, - "resource": None, - "prompt": None, - "metadata": None, } - ] + ) + # Force the source to be invalid to simulate missing configuration + assert endpoint.tool is not None + object.__setattr__( + endpoint.tool, + "source", + SourceDefinitionModel.model_construct(code=None, file=None), + ) + endpoints_no_source: list[EndpointDefinitionModel] = [endpoint] executor = EndpointToolExecutor(self.engine, endpoints_no_source) with pytest.raises(ValueError) as exc_info: await executor.execute_tool("broken_tool", {}) - assert "No source found for endpoint" in str(exc_info.value) + assert "No source code or file found in source definition" in str(exc_info.value) def test_get_language_inference(self): """Test language inference via endpoint execution.""" # Create endpoints with different language sources - test_endpoints: list[EndpointDefinition] = [ - { - "mxcp": "1", - "tool": { - "name": "python_file_tool", - "source": {"file": "script.py"}, - }, - "resource": None, - "prompt": None, - "metadata": None, - }, - { - "mxcp": "1", - "tool": { - "name": "sql_file_tool", - "source": {"file": "query.sql"}, - }, - "resource": None, - "prompt": None, - "metadata": None, - }, - { - "mxcp": "1", - "tool": { - "name": "explicit_override_tool", - "source": {"file": "script.py", "language": "sql"}, - }, - "resource": None, - "prompt": None, - "metadata": None, - }, - { - "mxcp": "1", - "tool": { - "name": "default_sql_tool", - "source": {"code": "some code"}, - }, - "resource": None, - "prompt": None, - "metadata": None, - }, + test_endpoints: list[EndpointDefinitionModel] = [ + EndpointDefinitionModel.model_validate( + {"mxcp": 1, "tool": {"name": "python_file_tool", "source": {"file": "script.py"}}} + ), + EndpointDefinitionModel.model_validate( + {"mxcp": 1, "tool": {"name": "sql_file_tool", "source": {"file": "query.sql"}}} + ), + EndpointDefinitionModel.model_validate( + { + "mxcp": 1, + "tool": {"name": "explicit_override_tool", "source": {"file": "script.py", "language": "sql"}}, + } + ), + EndpointDefinitionModel.model_validate( + {"mxcp": 1, "tool": {"name": "default_sql_tool", "source": {"code": "some code"}}} + ), ] test_executor = EndpointToolExecutor(self.engine, test_endpoints) diff --git a/tests/server/test_list.py b/tests/server/test_list.py index 501ea790..44af97b0 100644 --- a/tests/server/test_list.py +++ b/tests/server/test_list.py @@ -60,56 +60,55 @@ def test_list_endpoints(test_repo_path, test_config): tool1_path = test_repo_path / "tools" / "tool1.yml" assert str(tool1_path) in endpoint_dict tool1_data = endpoint_dict[str(tool1_path)] - assert "tool" in tool1_data - assert tool1_data["tool"]["name"] == "tool1" + assert tool1_data.tool is not None + assert tool1_data.tool.name == "tool1" # Verify resources directory endpoints resource1_path = test_repo_path / "resources" / "resource1.yml" assert str(resource1_path) in endpoint_dict resource1_data = endpoint_dict[str(resource1_path)] - assert "resource" in resource1_data - assert resource1_data["resource"]["name"] == "resource1" + assert resource1_data.resource is not None + assert resource1_data.resource.name == "resource1" # Verify prompts directory endpoints prompt1_path = test_repo_path / "prompts" / "prompt1.yml" assert str(prompt1_path) in endpoint_dict prompt1_data = endpoint_dict[str(prompt1_path)] - assert "prompt" in prompt1_data - assert prompt1_data["prompt"]["name"] == "prompt1" + assert prompt1_data.prompt is not None + assert prompt1_data.prompt.name == "prompt1" # Verify prompt1 has proper message structure - messages = prompt1_data["prompt"]["messages"] + messages = prompt1_data.prompt.messages assert len(messages) == 2 - assert messages[0]["role"] == "system" - assert messages[0]["type"] == "text" - assert messages[1]["role"] == "user" - assert messages[1]["type"] == "text" + assert messages[0].role == "system" + assert messages[0].type == "text" + assert messages[1].role == "user" + assert messages[1].type == "text" # Verify additional tools tool2_path = test_repo_path / "tools" / "tool2.yml" assert str(tool2_path) in endpoint_dict tool2_data = endpoint_dict[str(tool2_path)] - assert "tool" in tool2_data - assert tool2_data["tool"]["name"] == "tool2" - assert "source" in tool2_data["tool"] - assert "code" in tool2_data["tool"]["source"] + assert tool2_data.tool is not None + assert tool2_data.tool.name == "tool2" + assert tool2_data.tool.source.code is not None # Verify additional prompts prompt2_path = test_repo_path / "prompts" / "prompt2.yml" assert str(prompt2_path) in endpoint_dict prompt2_data = endpoint_dict[str(prompt2_path)] - assert "prompt" in prompt2_data - assert prompt2_data["prompt"]["name"] == "prompt2" + assert prompt2_data.prompt is not None + assert prompt2_data.prompt.name == "prompt2" # Verify prompt2 has proper message structure - messages = prompt2_data["prompt"]["messages"] + messages = prompt2_data.prompt.messages assert len(messages) == 2 - assert messages[0]["role"] == "system" - assert messages[0]["type"] == "text" - assert messages[0]["prompt"] == "You are a helpful assistant in a subfolder." - assert messages[1]["role"] == "user" - assert messages[1]["type"] == "text" - assert messages[1]["prompt"] == "{{message}}" + assert messages[0].role == "system" + assert messages[0].type == "text" + assert messages[0].prompt == "You are a helpful assistant in a subfolder." + assert messages[1].role == "user" + assert messages[1].type == "text" + assert messages[1].prompt == "{{message}}" # Verify disabled endpoint is filtered out during discovery disabled_tool_path = test_repo_path / "tools" / "disabled_tool.yml" @@ -146,34 +145,33 @@ def test_list_endpoints_from_subfolder(test_repo_path, test_config): tool1_path = test_repo_path / "tools" / "tool1.yml" assert str(tool1_path) in endpoint_dict tool1_data = endpoint_dict[str(tool1_path)] - assert "tool" in tool1_data - assert tool1_data["tool"]["name"] == "tool1" + assert tool1_data.tool is not None + assert tool1_data.tool.name == "tool1" # Verify additional tools tool2_path = test_repo_path / "tools" / "tool2.yml" assert str(tool2_path) in endpoint_dict tool2_data = endpoint_dict[str(tool2_path)] - assert "tool" in tool2_data - assert tool2_data["tool"]["name"] == "tool2" - assert "source" in tool2_data["tool"] - assert "code" in tool2_data["tool"]["source"] + assert tool2_data.tool is not None + assert tool2_data.tool.name == "tool2" + assert tool2_data.tool.source.code is not None # Verify additional prompts prompt2_path = test_repo_path / "prompts" / "prompt2.yml" assert str(prompt2_path) in endpoint_dict prompt2_data = endpoint_dict[str(prompt2_path)] - assert "prompt" in prompt2_data - assert prompt2_data["prompt"]["name"] == "prompt2" + assert prompt2_data.prompt is not None + assert prompt2_data.prompt.name == "prompt2" # Verify prompt2 has proper message structure - messages = prompt2_data["prompt"]["messages"] + messages = prompt2_data.prompt.messages assert len(messages) == 2 - assert messages[0]["role"] == "system" - assert messages[0]["type"] == "text" - assert messages[0]["prompt"] == "You are a helpful assistant in a subfolder." - assert messages[1]["role"] == "user" - assert messages[1]["type"] == "text" - assert messages[1]["prompt"] == "{{message}}" + assert messages[0].role == "system" + assert messages[0].type == "text" + assert messages[0].prompt == "You are a helpful assistant in a subfolder." + assert messages[1].role == "user" + assert messages[1].type == "text" + assert messages[1].prompt == "{{message}}" finally: os.chdir(original_dir) diff --git a/tests/server/test_mcp.py b/tests/server/test_mcp.py index e20363c8..e66f26ac 100644 --- a/tests/server/test_mcp.py +++ b/tests/server/test_mcp.py @@ -8,6 +8,11 @@ import aiohttp import pytest +from mxcp.server.definitions.endpoints.models import ( + PromptDefinitionModel, + ResourceDefinitionModel, + ToolDefinitionModel, +) from mxcp.server.interfaces.server.mcp import RAWMCP @@ -78,6 +83,7 @@ def mock_endpoint(): {"name": "array_param", "type": "array"}, {"name": "object_param", "type": "object"}, ], + "source": {"code": "SELECT 1"}, } @@ -124,8 +130,9 @@ def test_convert_param_type_object(mcp_server): def test_register_tool(mcp_server, mock_endpoint): """Test registering a tool endpoint.""" + tool_model = ToolDefinitionModel.model_validate(mock_endpoint) with patch.object(mcp_server.mcp, "tool", return_value=lambda f: f): - mcp_server._register_tool(mock_endpoint) + mcp_server._register_tool(tool_model) def test_register_resource(mcp_server): @@ -133,15 +140,26 @@ def test_register_resource(mcp_server): resource_def = { "uri": "resource://test/resource", "parameters": [{"name": "param1", "type": "string"}], + "source": {"code": "select 1"}, } + resource_model = ResourceDefinitionModel.model_validate(resource_def) with patch.object(mcp_server.mcp, "resource", return_value=lambda f: f): - mcp_server._register_resource(resource_def) + mcp_server._register_resource(resource_model) def test_register_prompt(mcp_server, mock_endpoint): """Test registering a prompt endpoint.""" + prompt_def = { + "name": "test_prompt", + "parameters": [{"name": "name", "type": "string"}], + "messages": [ + {"role": "system", "type": "text", "prompt": "Say hi"}, + {"role": "user", "type": "text", "prompt": "{{name}}"}, + ], + } + prompt_model = PromptDefinitionModel.model_validate(prompt_def) with patch.object(mcp_server.mcp, "prompt", return_value=lambda f: f): - mcp_server._register_prompt(mock_endpoint) + mcp_server._register_prompt(prompt_model) def test_run_http(mcp_server): diff --git a/tests/server/test_runtime.py b/tests/server/test_runtime.py index c3421a94..5a12480a 100644 --- a/tests/server/test_runtime.py +++ b/tests/server/test_runtime.py @@ -17,7 +17,7 @@ from mxcp.server.core.config.site_config import load_site_config from mxcp.server.core.config.user_config import load_user_config -from mxcp.server.services.tests import run_tests +from mxcp.server.services.tests.service import run_tests @pytest.fixture(scope="session", autouse=True) @@ -64,9 +64,9 @@ async def test_runtime_db_execute(runtime_repo_path, site_config, user_config): os.chdir(runtime_repo_path) try: result = await run_tests("tool", "test_db_execute", user_config, site_config, None) - assert result["status"] == "ok" - assert result["tests_run"] == 1 - assert result["tests"][0]["status"] == "passed" + assert result.status == "ok" + assert result.tests_run == 1 + assert result.tests[0].status == "passed" finally: os.chdir(original_dir) @@ -78,9 +78,9 @@ async def test_runtime_config_get_secret(runtime_repo_path, site_config, user_co os.chdir(runtime_repo_path) try: result = await run_tests("tool", "test_get_secret", user_config, site_config, None) - assert result["status"] == "ok" - assert result["tests_run"] == 1 - assert result["tests"][0]["status"] == "passed" + assert result.status == "ok" + assert result.tests_run == 1 + assert result.tests[0].status == "passed" finally: os.chdir(original_dir) @@ -92,9 +92,9 @@ async def test_runtime_config_get_setting(runtime_repo_path, site_config, user_c os.chdir(runtime_repo_path) try: result = await run_tests("tool", "test_get_setting", user_config, site_config, None) - assert result["status"] == "ok" - assert result["tests_run"] == 1 - assert result["tests"][0]["status"] == "passed" + assert result.status == "ok" + assert result.tests_run == 1 + assert result.tests[0].status == "passed" finally: os.chdir(original_dir) @@ -106,9 +106,9 @@ async def test_runtime_config_properties(runtime_repo_path, site_config, user_co os.chdir(runtime_repo_path) try: result = await run_tests("tool", "test_config_properties", user_config, site_config, None) - assert result["status"] == "ok" - assert result["tests_run"] == 1 - assert result["tests"][0]["status"] == "passed" + assert result.status == "ok" + assert result.tests_run == 1 + assert result.tests[0].status == "passed" finally: os.chdir(original_dir) @@ -120,9 +120,9 @@ async def test_runtime_plugins(runtime_repo_path, site_config, user_config): os.chdir(runtime_repo_path) try: result = await run_tests("tool", "test_plugins", user_config, site_config, None) - assert result["status"] == "ok" - assert result["tests_run"] == 1 - assert result["tests"][0]["status"] == "passed" + assert result.status == "ok" + assert result.tests_run == 1 + assert result.tests[0].status == "passed" finally: os.chdir(original_dir) @@ -135,9 +135,9 @@ async def test_runtime_lifecycle_hooks(runtime_repo_path, site_config, user_conf try: # This test verifies that init hooks were called by checking a side effect result = await run_tests("tool", "test_lifecycle_hooks", user_config, site_config, None) - assert result["status"] == "ok" - assert result["tests_run"] == 1 - assert result["tests"][0]["status"] == "passed" + assert result.status == "ok" + assert result.tests_run == 1 + assert result.tests[0].status == "passed" finally: os.chdir(original_dir) @@ -150,8 +150,8 @@ async def test_runtime_error_handling(runtime_repo_path, site_config, user_confi try: # This test should verify proper error messages result = await run_tests("tool", "test_error_handling", user_config, site_config, None) - assert result["status"] == "ok" - assert result["tests_run"] == 1 - assert result["tests"][0]["status"] == "passed" + assert result.status == "ok" + assert result.tests_run == 1 + assert result.tests[0].status == "passed" finally: os.chdir(original_dir) diff --git a/tests/server/test_secrets.py b/tests/server/test_secrets.py index e91ac2c5..19d5bd69 100644 --- a/tests/server/test_secrets.py +++ b/tests/server/test_secrets.py @@ -5,7 +5,7 @@ from mxcp.server.core.config.site_config import load_site_config from mxcp.server.core.config.user_config import load_user_config -from mxcp.server.services.tests import run_all_tests, run_tests +from mxcp.server.services.tests.service import run_all_tests, run_tests @pytest.fixture(scope="session", autouse=True) @@ -51,9 +51,9 @@ async def test_run_secrets_tool(secrets_repo_path, site_config, user_config): os.chdir(secrets_repo_path) try: result = await run_tests("tool", "list_secrets", user_config, site_config, None) - assert result["status"] == "ok" - assert result["tests_run"] == 1 - assert result["tests"][0]["status"] == "passed" + assert result.status == "ok" + assert result.tests_run == 1 + assert result.tests[0].status == "passed" finally: os.chdir(original_dir) @@ -65,18 +65,19 @@ async def test_run_all_secrets_tests(secrets_repo_path, site_config, user_config os.chdir(secrets_repo_path) try: result = await run_all_tests(user_config, site_config, None) - assert result["status"] == "ok" - assert result["tests_run"] > 0 - assert len(result["endpoints"]) > 0 + assert result.status == "ok" + assert result.tests_run > 0 + assert len(result.endpoints) > 0 secrets_endpoint = None - for endpoint in result["endpoints"]: - if "list_secrets" in endpoint["endpoint"]: + for endpoint in result.endpoints: + if "list_secrets" in endpoint.endpoint: secrets_endpoint = endpoint break assert secrets_endpoint is not None - assert secrets_endpoint["test_results"]["status"] == "ok" - assert secrets_endpoint["test_results"]["tests"][0]["status"] == "passed" + assert secrets_endpoint.test_results.status == "ok" + assert secrets_endpoint.test_results.tests is not None + assert secrets_endpoint.test_results.tests[0].status == "passed" finally: os.chdir(original_dir) diff --git a/tests/server/test_tester.py b/tests/server/test_tester.py index d051adda..f27094e0 100644 --- a/tests/server/test_tester.py +++ b/tests/server/test_tester.py @@ -5,7 +5,7 @@ from mxcp.server.core.config.site_config import load_site_config from mxcp.server.core.config.user_config import load_user_config -from mxcp.server.services.tests import run_all_tests, run_tests +from mxcp.server.services.tests.service import run_all_tests, run_tests @pytest.fixture(scope="session", autouse=True) @@ -51,7 +51,7 @@ async def test_run_valid_tool(tester_repo_path, site_config, user_config): os.chdir(tester_repo_path) try: result = await run_tests("tool", "valid_tool", user_config, site_config, None) - assert result["status"] == "ok" + assert result.status == "ok" finally: os.chdir(original_dir) @@ -63,11 +63,11 @@ async def test_run_invalid_tool(tester_repo_path, site_config, user_config): os.chdir(tester_repo_path) try: result = await run_tests("tool", "invalid_tool", user_config, site_config, None) - assert result["status"] == "error" - assert result["tests_run"] == 4 - assert any(test["status"] == "passed" for test in result["tests"]) + assert result.status == "error" + assert result.tests_run == 4 + assert any(test.status == "passed" for test in result.tests) # Check error causes for each error test - error_msgs = [test["error"] for test in result["tests"] if test["status"] == "error"] + error_msgs = [test.error for test in result.tests if test.status == "error"] assert any("Required parameter missing: count" in str(msg) for msg in error_msgs) assert any( "Error validating parameter 'count'" in str(msg) @@ -88,16 +88,16 @@ async def test_run_valid_resource(tester_repo_path, site_config, user_config): result = await run_tests( "resource", "data://valid.resource", user_config, site_config, None ) - assert result["status"] == "error" # Overall status is error because of the failing test - assert result["tests_run"] == 2 + assert result.status == "error" # Overall status is error because of the failing test + assert result.tests_run == 2 assert any( - test["status"] == "passed" for test in result["tests"] + test.status == "passed" for test in result.tests ) # valid filter test should pass assert any( - test["status"] == "error" for test in result["tests"] + test.status == "error" for test in result.tests ) # no filter test should error # Check error cause for the error test - error_msgs = [test["error"] for test in result["tests"] if test["status"] == "error"] + error_msgs = [test.error for test in result.tests if test.status == "error"] assert any("Required parameter missing: filter" in str(msg) for msg in error_msgs) finally: os.chdir(original_dir) @@ -112,9 +112,9 @@ async def test_run_valid_prompt(tester_repo_path, site_config, user_config): result = await run_tests("prompt", "valid_prompt", user_config, site_config, None) # The tests should fail because the prompt returns a transformed string result # but the test expects the raw SQL result format - assert result["status"] == "failed" - assert result["tests_run"] == 2 - assert all(test["status"] == "failed" for test in result["tests"]) + assert result.status == "failed" + assert result.tests_run == 2 + assert all(test.status == "failed" for test in result.tests) finally: os.chdir(original_dir) @@ -126,8 +126,8 @@ async def test_run_nonexistent_endpoint(tester_repo_path, site_config, user_conf os.chdir(tester_repo_path) try: result = await run_tests("tool", "nonexistent", user_config, site_config, None) - assert result["status"] == "error" - assert "Endpoint not found" in result["message"] + assert result.status == "error" + assert result.message is not None and "Endpoint not found" in result.message finally: os.chdir(original_dir) @@ -139,20 +139,20 @@ async def test_run_all_tests(tester_repo_path, site_config, user_config): os.chdir(tester_repo_path) try: result = await run_all_tests(user_config, site_config, None) - assert result["status"] == "error" - assert result["tests_run"] > 0 - assert len(result["endpoints"]) > 0 + assert result.status == "error" + assert result.tests_run > 0 + assert len(result.endpoints) > 0 # Check that we have results for all endpoint types - endpoint_types = {e["endpoint"].split("/")[0] for e in result["endpoints"]} + endpoint_types = {e.endpoint.split("/")[0] for e in result.endpoints} assert "tool" in endpoint_types assert "resource" in endpoint_types assert "prompt" in endpoint_types # Optionally, check that at least one error cause is present in endpoints error_causes = [ - test["error"] - for ep in result["endpoints"] - for test in ep.get("test_results", {}).get("tests", []) - if test["status"] == "error" + test.error + for ep in result.endpoints + for test in (ep.test_results.tests or []) + if test.status == "error" ] assert any( "Required parameter missing" in str(msg) @@ -171,8 +171,8 @@ async def test_run_missing_param_tool(tester_repo_path, site_config, user_config os.chdir(tester_repo_path) try: result = await run_tests("tool", "missing_param_tool", user_config, site_config, None) - assert result["status"] == "error" - assert "Required parameter missing: count" in str(result["tests"][0]["error"]) + assert result.status == "error" + assert "Required parameter missing: count" in str(result.tests[0].error) finally: os.chdir(original_dir) @@ -184,6 +184,6 @@ async def test_run_mismatched_result(tester_repo_path, site_config, user_config) os.chdir(tester_repo_path) try: result = await run_tests("tool", "mismatched_result", user_config, site_config, None) - assert result["status"] == "failed" # Overall status should be failed + assert result.status == "failed" # Overall status should be failed finally: os.chdir(original_dir) diff --git a/tests/server/test_tester_assertions.py b/tests/server/test_tester_assertions.py index e1b3fa59..c09374e9 100644 --- a/tests/server/test_tester_assertions.py +++ b/tests/server/test_tester_assertions.py @@ -5,7 +5,7 @@ from mxcp.server.core.config.site_config import load_site_config from mxcp.server.core.config.user_config import load_user_config -from mxcp.server.services.tests import run_tests +from mxcp.server.services.tests.service import run_tests @pytest.fixture(scope="session", autouse=True) @@ -63,17 +63,17 @@ async def test_policy_assertions(user_config, site_config): results = await run_tests("tool", "test_policy_assertions", user_config, site_config, "test") # All tests should pass - assert results["status"] == "ok" - assert results["tests_run"] == 3 + assert results.status == "ok" + assert results.tests_run == 3 # Check individual test results - tests = results["tests"] - test_names = {test["name"]: test for test in tests} + tests = results.tests + test_names = {test.name: test for test in tests} # All policy-based tests should pass - assert test_names["Admin sees all fields"]["status"] == "passed" - assert test_names["Regular user has filtered fields"]["status"] == "passed" - assert test_names["HR sees SSN but needs permission for phone"]["status"] == "passed" + assert test_names["Admin sees all fields"].status == "passed" + assert test_names["Regular user has filtered fields"].status == "passed" + assert test_names["HR sees SSN but needs permission for phone"].status == "passed" @pytest.mark.asyncio @@ -91,18 +91,18 @@ async def test_object_assertions(user_config, site_config): results = await run_tests("tool", "test_object_assertions", user_config, site_config, "test") # All tests should pass - assert results["status"] == "ok" - assert results["tests_run"] == 4 + assert results.status == "ok" + assert results.tests_run == 4 # Check individual test results - tests = results["tests"] - test_names = {test["name"]: test for test in tests} + tests = results.tests + test_names = {test.name: test for test in tests} # Verify each test passed - assert test_names["Exact object match"]["status"] == "passed" - assert test_names["Partial object match"]["status"] == "passed" - assert test_names["Field exclusion check"]["status"] == "passed" - assert test_names["Combined assertions"]["status"] == "passed" + assert test_names["Exact object match"].status == "passed" + assert test_names["Partial object match"].status == "passed" + assert test_names["Field exclusion check"].status == "passed" + assert test_names["Combined assertions"].status == "passed" @pytest.mark.asyncio @@ -111,19 +111,19 @@ async def test_array_assertions(user_config, site_config): results = await run_tests("tool", "test_array_assertions", user_config, site_config, "test") # All tests should pass - assert results["status"] == "ok" - assert results["tests_run"] == 5 + assert results.status == "ok" + assert results.tests_run == 5 # Check individual test results - tests = results["tests"] - test_names = {test["name"]: test for test in tests} + tests = results.tests + test_names = {test.name: test for test in tests} # Verify each test passed - assert test_names["Array contains specific item"]["status"] == "passed" - assert test_names["Array contains partial match"]["status"] == "passed" - assert test_names["Array contains all specified items"]["status"] == "passed" - assert test_names["Array length check"]["status"] == "passed" - assert test_names["Filtered array length check"]["status"] == "passed" + assert test_names["Array contains specific item"].status == "passed" + assert test_names["Array contains partial match"].status == "passed" + assert test_names["Array contains all specified items"].status == "passed" + assert test_names["Array length check"].status == "passed" + assert test_names["Filtered array length check"].status == "passed" @pytest.mark.asyncio @@ -132,17 +132,17 @@ async def test_string_assertions(user_config, site_config): results = await run_tests("tool", "test_string_assertions", user_config, site_config, "test") # All tests should pass - assert results["status"] == "ok" - assert results["tests_run"] == 3 + assert results.status == "ok" + assert results.tests_run == 3 # Check individual test results - tests = results["tests"] - test_names = {test["name"]: test for test in tests} + tests = results.tests + test_names = {test.name: test for test in tests} # Verify each test passed - assert test_names["String contains text"]["status"] == "passed" - assert test_names["String contains status"]["status"] == "passed" - assert test_names["Exact string match"]["status"] == "passed" + assert test_names["String contains text"].status == "passed" + assert test_names["String contains status"].status == "passed" + assert test_names["Exact string match"].status == "passed" @pytest.mark.asyncio @@ -153,20 +153,20 @@ async def test_result_contains_assertions(user_config, site_config): ) # All tests should pass - assert results["status"] == "ok" - assert results["tests_run"] == 6 + assert results.status == "ok" + assert results.tests_run == 6 # Check individual test results - tests = results["tests"] - test_names = {test["name"]: test for test in tests} + tests = results.tests + test_names = {test.name: test for test in tests} # Verify all tests passed - assert test_names["String array contains banana"]["status"] == "passed" - assert test_names["Number array contains 3"]["status"] == "passed" - assert test_names["Mixed array contains true"]["status"] == "passed" - assert test_names["Mixed array contains null"]["status"] == "passed" - assert test_names["Dict contains name John"]["status"] == "passed" - assert test_names["Dict array contains Bob"]["status"] == "passed" + assert test_names["String array contains banana"].status == "passed" + assert test_names["Number array contains 3"].status == "passed" + assert test_names["Mixed array contains true"].status == "passed" + assert test_names["Mixed array contains null"].status == "passed" + assert test_names["Dict contains name John"].status == "passed" + assert test_names["Dict array contains Bob"].status == "passed" @pytest.mark.asyncio @@ -177,54 +177,60 @@ async def test_result_contains_error_messages(user_config, site_config): ) # All tests should fail (they're designed to) - assert results["status"] == "failed" - assert results["tests_run"] == 7 + assert results.status == "failed" + assert results.tests_run == 7 # Check individual test results and error messages - tests = results["tests"] - test_names = {test["name"]: test for test in tests} + tests = results.tests + test_names = {test.name: test for test in tests} # Test 1: Array missing primitive - assert test_names["Array missing primitive value"]["status"] == "failed" + assert test_names["Array missing primitive value"].status == "failed" assert ( "Array does not contain expected value: grape" - in test_names["Array missing primitive value"]["error"] + in (test_names["Array missing primitive value"].error or "") ) # Test 2: Dict missing field - assert test_names["Dict missing field"]["status"] == "failed" - assert "Expected field 'email' not found in result" in test_names["Dict missing field"]["error"] + assert test_names["Dict missing field"].status == "failed" + assert ( + "Expected field 'email' not found in result" + in (test_names["Dict missing field"].error or "") + ) # Test 3: Dict field wrong value - assert test_names["Dict field wrong value"]["status"] == "failed" - assert "Field 'age' has value 25, expected 30" in test_names["Dict field wrong value"]["error"] + assert test_names["Dict field wrong value"].status == "failed" + assert ( + "Field 'age' has value 25, expected 30" + in (test_names["Dict field wrong value"].error or "") + ) # Test 4: Array of dicts no match - assert test_names["Array of dicts no match"]["status"] == "failed" + assert test_names["Array of dicts no match"].status == "failed" assert ( "No item in array contains the expected fields" - in test_names["Array of dicts no match"]["error"] + in (test_names["Array of dicts no match"].error or "") ) # Test 5: Wrong result type - assert test_names["String result with dict pattern"]["status"] == "failed" + assert test_names["String result with dict pattern"].status == "failed" assert ( "result_contains assertion requires dict or array result" - in test_names["String result with dict pattern"]["error"] + in (test_names["String result with dict pattern"].error or "") ) # Test 6: Empty array - assert test_names["Empty array check"]["status"] == "failed" + assert test_names["Empty array check"].status == "failed" assert ( "Array does not contain expected value: anything" - in test_names["Empty array check"]["error"] + in (test_names["Empty array check"].error or "") ) # Test 7: Number array missing value - assert test_names["Number array missing value"]["status"] == "failed" + assert test_names["Number array missing value"].status == "failed" assert ( "Array does not contain expected value: 10" - in test_names["Number array missing value"]["error"] + in (test_names["Number array missing value"].error or "") ) diff --git a/tests/server/test_validation.py b/tests/server/test_validation.py index 66a12ae2..8676aff8 100644 --- a/tests/server/test_validation.py +++ b/tests/server/test_validation.py @@ -68,8 +68,8 @@ def test_validate_valid_endpoint(validation_repo_path, site_config, test_executi try: endpoint_path = "tools/valid_endpoint.yml" result = validate_endpoint(endpoint_path, site_config, test_execution_engine) - assert result["status"] == "ok" - assert result["path"] == endpoint_path + assert result.status == "ok" + assert result.path == endpoint_path finally: os.chdir(original_dir) @@ -81,8 +81,8 @@ def test_validate_valid_prompt(validation_repo_path, site_config, test_execution try: endpoint_path = "prompts/valid_prompt.yml" result = validate_endpoint(endpoint_path, site_config, test_execution_engine) - assert result["status"] == "ok" - assert result["path"] == endpoint_path + assert result.status == "ok" + assert result.path == endpoint_path finally: os.chdir(original_dir) @@ -94,12 +94,13 @@ def test_validate_invalid_prompt(validation_repo_path, site_config, test_executi try: endpoint_path = "prompts/invalid_prompt.yml" result = validate_endpoint(endpoint_path, site_config, test_execution_engine) - assert result["status"] == "error" - assert "undefined template variables" in result["message"].lower() + assert result.status == "error" + assert result.message is not None + assert "undefined template variables" in result.message.lower() # Check that all undefined variables are mentioned - assert "expertise_level" in result["message"] - assert "complexity" in result["message"] - assert "extra_info" in result["message"] + assert "expertise_level" in result.message + assert "complexity" in result.message + assert "extra_info" in result.message finally: os.chdir(original_dir) @@ -114,9 +115,10 @@ def test_validate_invalid_type(validation_repo_path, site_config, test_execution try: endpoint_path = "endpoints/invalid_type.yml" result = validate_endpoint(endpoint_path, site_config, test_execution_engine) - assert result["status"] == "error" - assert "type mismatches" in result["message"].lower() - assert "user_id" in result["message"] + assert result.status == "error" + assert result.message is not None + assert "type mismatches" in result.message.lower() + assert "user_id" in result.message finally: os.chdir(original_dir) @@ -128,8 +130,12 @@ def test_validate_invalid_parameter_name(validation_repo_path, site_config, test try: endpoint_path = "tools/invalid_parameter_name.yml" result = validate_endpoint(endpoint_path, site_config, test_execution_engine) - assert result["status"] == "error" - assert "'user/id' does not match" in result["message"].lower() + assert result.status == "error" + assert result.message is not None + assert ( + "parameter name must contain only alphanumeric characters or underscores" + in result.message.lower() + ) finally: os.chdir(original_dir) @@ -141,9 +147,10 @@ def test_validate_missing_param(validation_repo_path, site_config, test_executio try: endpoint_path = "tools/missing_param.yml" result = validate_endpoint(endpoint_path, site_config, test_execution_engine) - assert result["status"] == "error" - assert "parameter mismatch" in result["message"].lower() - assert "extra_param" in result["message"] + assert result.status == "error" + assert result.message is not None + assert "parameter mismatch" in result.message.lower() + assert "extra_param" in result.message finally: os.chdir(original_dir) @@ -155,9 +162,9 @@ def test_validate_all_endpoints(validation_repo_path, site_config, test_executio try: result = validate_all_endpoints(site_config, test_execution_engine) # We expect at least one error due to intentionally invalid endpoints - assert result["status"] == "error" + assert result.status == "error" # Check that we have both valid and invalid results - statuses = [r["status"] for r in result["validated"]] + statuses = [r.status for r in result.validated] assert "ok" in statuses assert "error" in statuses finally: @@ -173,8 +180,8 @@ def test_validate_complex_jinja_prompt_valid( try: endpoint_path = "prompts/complex_jinja_prompt.yml" result = validate_endpoint(endpoint_path, site_config, test_execution_engine) - assert result["status"] == "ok" - assert result["path"] == endpoint_path + assert result.status == "ok" + assert result.path == endpoint_path finally: os.chdir(original_dir) @@ -188,13 +195,14 @@ def test_validate_complex_jinja_prompt_invalid( try: endpoint_path = "prompts/invalid_complex_jinja_prompt.yml" result = validate_endpoint(endpoint_path, site_config, test_execution_engine) - assert result["status"] == "error" - assert "undefined template variables" in result["message"].lower() + assert result.status == "error" + assert result.message is not None + assert "undefined template variables" in result.message.lower() # Check that all undefined variables are mentioned - assert "user_type" in result["message"] - assert "username" in result["message"] - assert "items" in result["message"] - assert "item" in result["message"] + assert "user_type" in result.message + assert "username" in result.message + assert "items" in result.message + assert "item" in result.message finally: os.chdir(original_dir) @@ -208,13 +216,14 @@ def test_validate_duplicate_tool_names(validation_repo_path, site_config, test_e result = validate_all_endpoints(site_config, test_execution_engine) # The validation should fail due to duplicate tool names - assert result["status"] == "error" + assert result.status == "error" # Check that the error message mentions duplicate names # Look through all validation results for duplicate name errors duplicate_error_found = False - for validated_result in result.get("validated", []): - if "duplicate" in validated_result.get("message", "").lower(): + for validated_result in result.validated: + message = (validated_result.message or "").lower() + if "duplicate" in message: duplicate_error_found = True break @@ -234,13 +243,13 @@ def test_validate_duplicate_resource_uris(validation_repo_path, site_config, tes result = validate_all_endpoints(site_config, test_execution_engine) # The validation should fail due to duplicate resource URIs - assert result["status"] == "error" + assert result.status == "error" # Check that the error message mentions duplicate URIs # Look through all validation results for duplicate URI errors duplicate_error_found = False - for validated_result in result.get("validated", []): - message = validated_result.get("message", "").lower() + for validated_result in result.validated: + message = (validated_result.message or "").lower() if "duplicate" in message and ( "uri" in message or "test://duplicate.resource" in message ): From 2d0b795b0b4db864277703de260ae6dc2f5cd36f Mon Sep 17 00:00:00 2001 From: Miguel Branco Date: Wed, 26 Nov 2025 08:20:24 +0100 Subject: [PATCH 04/26] Reorganize code --- src/mxcp/server/interfaces/cli/test.py | 111 +++++++++++---------- src/mxcp/server/interfaces/cli/validate.py | 83 ++++++++------- 2 files changed, 98 insertions(+), 96 deletions(-) diff --git a/src/mxcp/server/interfaces/cli/test.py b/src/mxcp/server/interfaces/cli/test.py index 322c3efa..e519c697 100644 --- a/src/mxcp/server/interfaces/cli/test.py +++ b/src/mxcp/server/interfaces/cli/test.py @@ -24,57 +24,48 @@ ) -def format_test_results( - results: TestSuiteResultModel | MultiEndpointTestResultsModel | str, debug: bool = False -) -> str: - """Format test results for human-readable output""" - if isinstance(results, str): - return results - +def _format_single_test_result(result: TestSuiteResultModel, debug: bool) -> str: output = [] - # Single-endpoint test suite - if isinstance(results, TestSuiteResultModel): - endpoint_status = results.status + status = result.status + if status == "ok": + output.append(f"{click.style('✅ All tests passed!', fg='green', bold=True)}") + else: + output.append(f"{click.style('❌ Some tests failed!', fg='red', bold=True)}") - if endpoint_status == "ok": - output.append(f"{click.style('✅ All tests passed!', fg='green', bold=True)}") - else: - output.append(f"{click.style('❌ Some tests failed!', fg='red', bold=True)}") - - if results.message: - output.append(f"{click.style('Error:', fg='red')} {results.message}") - - # Test results - if results.tests: - output.append(f"\n{click.style('📋 Test Results:', fg='cyan', bold=True)}") - for test in results.tests: - test_name = test.name or "Unnamed test" - test_status = test.status - test_time = test.time or 0.0 - - if test_status == "passed": - output.append( - f" {click.style('✓', fg='green')} {click.style(test_name, fg='cyan')} {click.style(f'({test_time:.2f}s)', fg='bright_black')}" - ) - else: - output.append( - f" {click.style('✗', fg='red')} {click.style(test_name, fg='cyan')} {click.style(f'({test_time:.2f}s)', fg='bright_black')}" - ) - if test.error: - output.append(f" {click.style('Error:', fg='red')} {test.error}") - if debug and test.error_cause: - output.append( - f" {click.style('Cause:', fg='yellow')} {test.error_cause}" - ) - - if results.no_tests: - output.append(f"\n{click.style('ℹ️ No tests defined for this endpoint', fg='blue')}") + if result.message: + output.append(f"{click.style('Error:', fg='red')} {result.message}") - return "\n".join(output) + if result.tests: + output.append(f"\n{click.style('📋 Test Results:', fg='cyan', bold=True)}") + for test in result.tests: + test_name = test.name or "Unnamed test" + test_status = test.status + test_time = test.time or 0.0 - # Multiple endpoint tests - new structure with test_results nested - endpoints: list[EndpointTestResultModel] = results.endpoints + if test_status == "passed": + output.append( + f" {click.style('✓', fg='green')} {click.style(test_name, fg='cyan')} {click.style(f'({test_time:.2f}s)', fg='bright_black')}" + ) + else: + output.append( + f" {click.style('✗', fg='red')} {click.style(test_name, fg='cyan')} {click.style(f'({test_time:.2f}s)', fg='bright_black')}" + ) + if test.error: + output.append(f" {click.style('Error:', fg='red')} {test.error}") + if debug and test.error_cause: + output.append(f" {click.style('Cause:', fg='yellow')} {test.error_cause}") + + if result.no_tests: + output.append(f"\n{click.style('ℹ️ No tests defined for this endpoint', fg='blue')}") + + return "\n".join(output) + + +def _format_multi_endpoint_results(report: MultiEndpointTestResultsModel, debug: bool) -> str: + output = [] + + endpoints: list[EndpointTestResultModel] = report.endpoints if not endpoints: output.append(click.style("ℹ️ No endpoints found to test", fg="blue")) output.append(" Create test cases in your endpoint YAML files") @@ -188,6 +179,18 @@ def format_test_results( return "\n".join(output) +def format_test_suite_result(result: TestSuiteResultModel, debug: bool = False) -> str: + """Public helper for formatting a single test suite result.""" + return _format_single_test_result(result, debug) + + +def format_multi_endpoint_results( + report: MultiEndpointTestResultsModel, debug: bool = False +) -> str: + """Public helper for formatting multi-endpoint test reports.""" + return _format_multi_endpoint_results(report, debug) + + @click.command(name="test") @click.argument("endpoint_type", type=click.Choice([t.value for t in EndpointType]), required=False) @click.argument("name", required=False) @@ -383,11 +386,15 @@ async def _test_impl( ) if json_output: - payload = ( - results - if isinstance(results, str) - else results.model_dump(mode="json", exclude_none=True) - ) + if isinstance(results, str): + payload = results + else: + payload = results.model_dump(mode="json", exclude_none=True) output_result(payload, json_output, debug) else: - click.echo(format_test_results(results, debug)) + if isinstance(results, TestSuiteResultModel): + click.echo(format_test_suite_result(results, debug)) + elif isinstance(results, MultiEndpointTestResultsModel): + click.echo(format_multi_endpoint_results(results, debug)) + else: + click.echo(results) diff --git a/src/mxcp/server/interfaces/cli/validate.py b/src/mxcp/server/interfaces/cli/validate.py index da94a996..64344196 100644 --- a/src/mxcp/server/interfaces/cli/validate.py +++ b/src/mxcp/server/interfaces/cli/validate.py @@ -18,41 +18,34 @@ from mxcp.server.services.endpoints.validator import validate_all_endpoints, validate_endpoint -def format_validation_results( - results: EndpointValidationResultModel | EndpointValidationSummaryModel | str, -) -> str: - """Format validation results for human-readable output""" - if isinstance(results, str): - return results - +def _format_validation_result(result: EndpointValidationResultModel) -> str: output = [] - # Overall status - if isinstance(results, EndpointValidationResultModel): - status = results.status - path = results.path - message = results.message or "" + status = result.status + path = result.path + message = result.message or "" - if status == "ok": - output.append(f"{click.style('✅ Validation passed!', fg='green', bold=True)}") - output.append(f"\n{click.style('📄 File:', fg='cyan')} {path}") - else: - output.append(f"{click.style('❌ Validation failed!', fg='red', bold=True)}") - output.append(f"\n{click.style('📄 File:', fg='cyan')} {path}") - if message: - # Handle multi-line error messages with proper indentation - lines = message.split("\n") - first_line = lines[0] - output.append(f"{click.style('Error:', fg='red')} {first_line}") - # Indent subsequent lines to align under the error message - for line in lines[1:]: - if line.strip(): # Only add non-empty lines - output.append(f"{line}") - return "\n".join(output) + if status == "ok": + output.append(f"{click.style('✅ Validation passed!', fg='green', bold=True)}") + output.append(f"\n{click.style('📄 File:', fg='cyan')} {path}") + else: + output.append(f"{click.style('❌ Validation failed!', fg='red', bold=True)}") + output.append(f"\n{click.style('📄 File:', fg='cyan')} {path}") + if message: + lines = message.split("\n") + first_line = lines[0] + output.append(f"{click.style('Error:', fg='red')} {first_line}") + for line in lines[1:]: + if line.strip(): + output.append(line) + + return "\n".join(output) - # Multiple endpoint validation - assert isinstance(results, EndpointValidationSummaryModel) - validated = results.validated + +def _format_validation_summary(summary: EndpointValidationSummaryModel) -> str: + output = [] + + validated = summary.validated if not validated: output.append(click.style("ℹ️ No endpoints found to validate", fg="blue")) output.append( @@ -60,11 +53,9 @@ def format_validation_results( ) return "\n".join(output) - # Count valid and failed endpoints valid_count = sum(1 for r in validated if r.status == "ok") failed_count = len(validated) - valid_count - # Header output.append(f"\n{click.style('🔍 Validation Results', fg='cyan', bold=True)}") output.append(f" Validated {click.style(str(len(validated)), fg='yellow')} endpoint files") @@ -73,47 +64,38 @@ def format_validation_results( if failed_count > 0: output.append(f" • {click.style(f'{failed_count} failed', fg='red')}") - # Group by status valid_endpoints = [] failed_endpoints = [] for result in validated: path = result.path message = result.message or "" - if result.status == "ok": valid_endpoints.append((path, message)) else: failed_endpoints.append((path, message)) - # Show failed endpoints first if failed_endpoints: output.append(f"\n{click.style('❌ Failed validation:', fg='red', bold=True)}") sorted_failed = sorted(failed_endpoints) for i, (path, message) in enumerate(sorted_failed): output.append(f" {click.style('✗', fg='red')} {path}") if message: - # Handle multi-line error messages with proper indentation - # Strip trailing whitespace to ensure consistent spacing clean_message = message.rstrip() lines = clean_message.split("\n") first_line = lines[0] output.append(f" {click.style('Error:', fg='red')} {first_line}") - # Indent subsequent lines to align under the error message for line in lines[1:]: - if line.strip(): # Only add non-empty lines + if line.strip(): output.append(f" {line}") - # Add consistent blank line between errors (except after the last one) if i < len(sorted_failed) - 1: output.append("") - # Then show valid endpoints if valid_endpoints: output.append(f"\n{click.style('✅ Passed validation:', fg='green', bold=True)}") for path, _ in sorted(valid_endpoints): output.append(f" {click.style('✓', fg='green')} {path}") - # Summary message if failed_count == 0: output.append(f"\n{click.style('🎉 All endpoints are valid!', fg='green', bold=True)}") else: @@ -124,6 +106,14 @@ def format_validation_results( return "\n".join(output) +def format_validation_result(result: EndpointValidationResultModel) -> str: + return _format_validation_result(result) + + +def format_validation_summary(summary: EndpointValidationSummaryModel) -> str: + return _format_validation_summary(summary) + + @click.command(name="validate") @click.argument("endpoint", required=False) @click.option("--profile", help="Profile name to use") @@ -198,7 +188,12 @@ def validate( ) output_result(payload, json_output, debug) else: - click.echo(format_validation_results(result)) + if isinstance(result, EndpointValidationResultModel): + click.echo(format_validation_result(result)) + elif isinstance(result, EndpointValidationSummaryModel): + click.echo(format_validation_summary(result)) + else: + click.echo(result) finally: runtime_env.shutdown() From acad52b81f93d56c2b0d7023d4c150d329b28fb7 Mon Sep 17 00:00:00 2001 From: Miguel Branco Date: Wed, 26 Nov 2025 08:53:11 +0100 Subject: [PATCH 05/26] Fix formatting --- src/mxcp/runtime/__init__.py | 61 +++++-------------- src/mxcp/server/admin/endpoints/endpoints.py | 40 +++++++----- src/mxcp/server/admin/service.py | 6 +- .../server/definitions/endpoints/loader.py | 5 +- .../server/definitions/endpoints/models.py | 24 ++++---- .../server/definitions/endpoints/utils.py | 4 +- src/mxcp/server/definitions/evals/loader.py | 2 +- src/mxcp/server/definitions/evals/models.py | 16 ++--- src/mxcp/server/executor/runners/endpoint.py | 7 +-- src/mxcp/server/executor/runners/test.py | 14 +++-- src/mxcp/server/interfaces/cli/lint.py | 34 ++++++----- src/mxcp/server/interfaces/cli/list.py | 2 +- src/mxcp/server/interfaces/cli/test.py | 6 +- src/mxcp/server/interfaces/cli/validate.py | 1 + src/mxcp/server/interfaces/server/mcp.py | 13 ++-- src/mxcp/server/services/drift/checker.py | 7 +-- src/mxcp/server/services/drift/models.py | 1 - src/mxcp/server/services/endpoints/models.py | 1 - src/mxcp/server/services/endpoints/service.py | 9 +-- .../server/services/endpoints/validator.py | 28 ++++++--- src/mxcp/server/services/evals/service.py | 9 +-- src/mxcp/server/services/tests/models.py | 1 - tests/misc/test_no_dict_model_unions.py | 7 +-- tests/server/test_evals_tool_executor.py | 5 +- tests/server/test_tester.py | 4 +- tests/server/test_tester_assertions.py | 35 +++++------ 26 files changed, 163 insertions(+), 179 deletions(-) diff --git a/src/mxcp/runtime/__init__.py b/src/mxcp/runtime/__init__.py index e7065c02..0ad32c3b 100644 --- a/src/mxcp/runtime/__init__.py +++ b/src/mxcp/runtime/__init__.py @@ -60,42 +60,25 @@ def get_secret(self, name: str) -> dict[str, Any] | None: "No execution context available - function not called from MXCP executor" ) - site_config = context.get("site_config") - user_config = context.get("user_config") + site_config = cast(SiteConfigModel | None, context.get("site_config")) + user_config = cast(UserConfigModel | None, context.get("user_config")) if not user_config or not site_config: return None - # Get project and profile from site config - if isinstance(site_config, SiteConfigModel): - project = site_config.project - profile = site_config.profile - else: - project = site_config.get("project") - profile = site_config.get("profile") + project = site_config.project + profile = site_config.profile if not project or not profile: return None - if isinstance(user_config, UserConfigModel): - project_config = user_config.projects.get(project) - profile_config = project_config.profiles.get(profile) if project_config else None - secrets = profile_config.secrets if profile_config else [] - for secret in secrets: - if secret.name == name: - return secret.parameters - return None - - try: - project_config = user_config["projects"][project] - profile_config = project_config["profiles"][profile] - secrets = profile_config.get("secrets", []) - for secret in secrets: - if secret.get("name") == name: - return cast(dict[str, Any], secret.get("parameters", {})) - return None - except (KeyError, TypeError): - return None + project_config = user_config.projects.get(project) + profile_config = project_config.profiles.get(profile) if project_config else None + secrets = profile_config.secrets if profile_config else [] + for secret in secrets: + if secret.name == name: + return secret.parameters + return None def get_setting(self, key: str, default: Any = None) -> Any: """Get configuration setting from site config.""" @@ -105,15 +88,11 @@ def get_setting(self, key: str, default: Any = None) -> Any: "No execution context available - function not called from MXCP executor" ) - site_config = context.get("site_config") + site_config = cast(SiteConfigModel | None, context.get("site_config")) if not site_config: return default - raw_config = ( - site_config.model_dump(mode="python") - if isinstance(site_config, SiteConfigModel) - else site_config - ) + raw_config = site_config.model_dump(mode="python") # Support nested key access (e.g., "dbt.enabled") if "." in key: @@ -135,24 +114,16 @@ def user_config(self) -> UserConfigModel | None: if not context: return None - user_config = context.get("user_config") - if isinstance(user_config, UserConfigModel): - return user_config - if user_config is None: - return None - return UserConfigModel.model_validate(user_config) + return cast(UserConfigModel | None, context.get("user_config")) @property - def site_config(self) -> Any | None: + def site_config(self) -> SiteConfigModel | None: """Access full site configuration (typically a SiteConfigModel).""" context = get_execution_context() if not context: return None - site_config = context.get("site_config") - if site_config is None: - return None - return site_config + return cast(SiteConfigModel | None, context.get("site_config")) class PluginsProxy: diff --git a/src/mxcp/server/admin/endpoints/endpoints.py b/src/mxcp/server/admin/endpoints/endpoints.py index c45fdff8..d35d8952 100644 --- a/src/mxcp/server/admin/endpoints/endpoints.py +++ b/src/mxcp/server/admin/endpoints/endpoints.py @@ -10,6 +10,12 @@ from fastapi import APIRouter, HTTPException +from mxcp.server.definitions.endpoints.models import ( + PromptDefinitionModel, + ResourceDefinitionModel, + ToolDefinitionModel, +) + from ..models import EndpointListResponse, EndpointMetadata from ..service import AdminService @@ -72,29 +78,33 @@ async def list_endpoints() -> EndpointListResponse: endpoint_type: Literal["tool", "resource", "prompt"] | None = None enabled = True + name: str | None + description: str | None + language: str | None + if endpoint_def.tool is not None: + tool_def: ToolDefinitionModel = endpoint_def.tool endpoint_type = "tool" - endpoint_data = endpoint_def.tool - name = endpoint_data.name - description = endpoint_data.description - language = endpoint_data.language - enabled = endpoint_data.enabled + name = tool_def.name + description = tool_def.description + language = tool_def.language + enabled = tool_def.enabled elif endpoint_def.resource is not None: + resource_def: ResourceDefinitionModel = endpoint_def.resource endpoint_type = "resource" - endpoint_data = endpoint_def.resource - name = endpoint_data.name or endpoint_data.uri - description = endpoint_data.description - language = endpoint_data.language - enabled = endpoint_data.enabled + name = resource_def.name or resource_def.uri + description = resource_def.description + language = resource_def.language + enabled = resource_def.enabled elif endpoint_def.prompt is not None: + prompt_def: PromptDefinitionModel = endpoint_def.prompt endpoint_type = "prompt" - endpoint_data = endpoint_def.prompt - name = endpoint_data.name - description = endpoint_data.description + name = prompt_def.name + description = prompt_def.description language = None - enabled = endpoint_data.enabled + enabled = prompt_def.enabled else: - endpoint_data = None + endpoint_type = None name = None description = None language = None diff --git a/src/mxcp/server/admin/service.py b/src/mxcp/server/admin/service.py index 4c3db5f5..72a41b2d 100644 --- a/src/mxcp/server/admin/service.py +++ b/src/mxcp/server/admin/service.py @@ -11,7 +11,6 @@ from typing import TYPE_CHECKING, Any from mxcp.sdk.audit.backends.noop import NoOpAuditBackend -from mxcp.server.core.config.models import SiteConfigModel from mxcp.server.definitions.endpoints.models import EndpointDefinitionModel from .models import ConfigResponse, EndpointCounts, Features @@ -84,10 +83,7 @@ def get_config_snapshot(self) -> ConfigResponse: Reads public fields directly - no intermediate types needed. """ site_config_obj = self._server.site_config - if isinstance(site_config_obj, SiteConfigModel): - project_name = site_config_obj.project - else: - project_name = site_config_obj.get("project") + project_name = site_config_obj.project return ConfigResponse( project=project_name, profile=self._server.profile_name, diff --git a/src/mxcp/server/definitions/endpoints/loader.py b/src/mxcp/server/definitions/endpoints/loader.py index 959b5c53..373e81f1 100644 --- a/src/mxcp/server/definitions/endpoints/loader.py +++ b/src/mxcp/server/definitions/endpoints/loader.py @@ -1,7 +1,6 @@ import logging from dataclasses import dataclass from pathlib import Path -from typing import Any import yaml from pydantic import ValidationError @@ -276,7 +275,9 @@ def load_endpoint( if endpoint_obj is None: continue - identifier = endpoint_obj.uri if endpoint_type == "resource" else endpoint_obj.name + identifier = ( + endpoint_obj.uri if endpoint_type == "resource" else endpoint_obj.name + ) if identifier != name: continue diff --git a/src/mxcp/server/definitions/endpoints/models.py b/src/mxcp/server/definitions/endpoints/models.py index ccb592af..dd5910dd 100644 --- a/src/mxcp/server/definitions/endpoints/models.py +++ b/src/mxcp/server/definitions/endpoints/models.py @@ -1,5 +1,6 @@ from __future__ import annotations +from contextlib import suppress from typing import Any, Literal from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator @@ -14,9 +15,9 @@ class DefinitionModel(BaseModel): class TypeDefinitionModel(DefinitionModel): name: str | None = None type: Literal["string", "number", "integer", "boolean", "array", "object"] - format: ( - Literal["email", "uri", "date", "time", "date-time", "duration", "timestamp"] | None - ) = None + format: Literal["email", "uri", "date", "time", "date-time", "duration", "timestamp"] | None = ( + None + ) description: str | None = None default: Any | None = None examples: list[Any] | None = None @@ -51,7 +52,9 @@ def validate_name(cls, value: str) -> str: if not value[0].isalpha() and value[0] != "_": raise ValueError("Parameter name must start with a letter or underscore") if not all(ch.isalnum() or ch == "_" for ch in value): - raise ValueError("Parameter name must contain only alphanumeric characters or underscores") + raise ValueError( + "Parameter name must contain only alphanumeric characters or underscores" + ) return value @@ -61,7 +64,7 @@ class SourceDefinitionModel(DefinitionModel): language: Literal["sql", "python"] | None = None @model_validator(mode="after") - def validate_source(self) -> "SourceDefinitionModel": + def validate_source(self) -> SourceDefinitionModel: if bool(self.code) == bool(self.file): raise ValueError("Source must provide exactly one of 'code' or 'file'") return self @@ -166,25 +169,24 @@ def coerce_mxcp(cls, data: Any) -> Any: if isinstance(data, dict) and "mxcp" in data: value = data["mxcp"] if isinstance(value, str): - try: + with suppress(ValueError): data["mxcp"] = int(float(value)) - except ValueError: - pass return data @model_validator(mode="after") - def ensure_endpoint_type(self) -> "EndpointDefinitionModel": + def ensure_endpoint_type(self) -> EndpointDefinitionModel: present = [ bool(self.tool), bool(self.resource), bool(self.prompt), ] if not any(present): - raise ValueError("Endpoint definition must include a tool, resource, or prompt definition") + raise ValueError( + "Endpoint definition must include a tool, resource, or prompt definition" + ) if sum(1 for flag in present if flag) > 1: raise ValueError("Endpoint definition must contain exactly one endpoint type") return self TypeDefinitionModel.model_rebuild() - diff --git a/src/mxcp/server/definitions/endpoints/utils.py b/src/mxcp/server/definitions/endpoints/utils.py index 57670c25..c9ca9cbf 100644 --- a/src/mxcp/server/definitions/endpoints/utils.py +++ b/src/mxcp/server/definitions/endpoints/utils.py @@ -129,7 +129,9 @@ def resolve_file_path(file_path: str, endpoint_file_path: Path, repo_root: Path) return endpoint_file_path.parent / source_path -def get_endpoint_name_or_uri(endpoint_definition: EndpointDefinitionModel, endpoint_type: str) -> str: +def get_endpoint_name_or_uri( + endpoint_definition: EndpointDefinitionModel, endpoint_type: str +) -> str: """Get the name or URI identifier for an endpoint. Args: diff --git a/src/mxcp/server/definitions/evals/loader.py b/src/mxcp/server/definitions/evals/loader.py index 6c771115..dfdf16a5 100644 --- a/src/mxcp/server/definitions/evals/loader.py +++ b/src/mxcp/server/definitions/evals/loader.py @@ -38,7 +38,7 @@ def discover_eval_files( - error_message: Error message if loading failed, None if successful """ base_path = find_repo_root() - results: list[tuple[Path, EvalSuite | None, str | None]] = [] + results: list[tuple[Path, EvalSuiteModel | None, str | None]] = [] # Determine the evals directory if site_config: diff --git a/src/mxcp/server/definitions/evals/models.py b/src/mxcp/server/definitions/evals/models.py index cb9aed8f..78e489a0 100644 --- a/src/mxcp/server/definitions/evals/models.py +++ b/src/mxcp/server/definitions/evals/models.py @@ -1,8 +1,9 @@ from __future__ import annotations +from contextlib import suppress from typing import Any, Literal -from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator +from pydantic import BaseModel, ConfigDict, field_validator, model_validator class EvalBaseModel(BaseModel): @@ -38,7 +39,9 @@ def validate_name(cls, value: str) -> str: if not value[0].isalpha() and value[0] != "_": raise ValueError("Eval test name must start with a letter or underscore") if not all(ch.isalnum() or ch == "_" for ch in value): - raise ValueError("Eval test name must contain only alphanumeric characters or underscores") + raise ValueError( + "Eval test name must contain only alphanumeric characters or underscores" + ) return value @@ -57,7 +60,9 @@ def validate_suite(cls, value: str) -> str: if not value[0].isalpha() and value[0] != "_": raise ValueError("Eval suite name must start with a letter or underscore") if not all(ch.isalnum() or ch == "_" for ch in value): - raise ValueError("Eval suite name must contain only alphanumeric characters or underscores") + raise ValueError( + "Eval suite name must contain only alphanumeric characters or underscores" + ) return value @model_validator(mode="before") @@ -66,9 +71,6 @@ def coerce_mxcp(cls, data: Any) -> Any: if isinstance(data, dict) and "mxcp" in data: value = data["mxcp"] if isinstance(value, str): - try: + with suppress(ValueError): data["mxcp"] = int(float(value)) - except ValueError: - pass return data - diff --git a/src/mxcp/server/executor/runners/endpoint.py b/src/mxcp/server/executor/runners/endpoint.py index 1410c9e8..3faea907 100644 --- a/src/mxcp/server/executor/runners/endpoint.py +++ b/src/mxcp/server/executor/runners/endpoint.py @@ -7,7 +7,7 @@ import logging from pathlib import Path -from typing import TYPE_CHECKING, Any, Optional, cast +from typing import TYPE_CHECKING, Any, Optional from jinja2 import Template @@ -58,10 +58,7 @@ async def execute_prompt_with_validation( validated_params = params.copy() for param_def in param_defs: name = param_def.name - if ( - name not in validated_params - and "default" in param_def.model_fields_set - ): + if name not in validated_params and "default" in param_def.model_fields_set: validated_params[name] = param_def.default # Template rendering with validated parameters diff --git a/src/mxcp/server/executor/runners/test.py b/src/mxcp/server/executor/runners/test.py index e20f3c64..683d4ad0 100644 --- a/src/mxcp/server/executor/runners/test.py +++ b/src/mxcp/server/executor/runners/test.py @@ -8,18 +8,20 @@ import json import logging import time -from typing import Any +from typing import Any, Literal import numpy as np from mxcp.sdk.auth import UserContext from mxcp.sdk.executor import ExecutionEngine from mxcp.server.core.config.models import SiteConfigModel, UserConfigModel +from mxcp.server.definitions.endpoints.loader import EndpointLoader from mxcp.server.definitions.endpoints.models import ( EndpointDefinitionModel, + ResourceDefinitionModel, TestDefinitionModel, + ToolDefinitionModel, ) -from mxcp.server.definitions.endpoints.loader import EndpointLoader from mxcp.server.services.endpoints import execute_endpoint_with_engine from mxcp.server.services.tests.models import ( TestCaseResultModel, @@ -116,11 +118,13 @@ async def run_tests_for_endpoint( has_failed = True # Determine overall status - status = "ok" + status: Literal["ok", "failed", "error"] if has_error: status = "error" elif has_failed: status = "failed" + else: + status = "ok" logger.info(f"Final test status: {status}") return TestSuiteResultModel( @@ -195,7 +199,7 @@ async def _run_single_test( # Compare with expected results passed, error_msg = compare_results(normalized_result, test_def) - status = "passed" if passed else "failed" + status: Literal["passed", "failed"] = "passed" if passed else "failed" error = error_msg if not passed else None if not passed: @@ -239,7 +243,7 @@ def _extract_column_names( """Extract column names from endpoint return schema.""" columns: list[str] = [] - component = None + component: ToolDefinitionModel | ResourceDefinitionModel | None = None if endpoint_type == "tool": component = endpoint_def.tool elif endpoint_type == "resource": diff --git a/src/mxcp/server/interfaces/cli/lint.py b/src/mxcp/server/interfaces/cli/lint.py index 6a987a77..41917095 100644 --- a/src/mxcp/server/interfaces/cli/lint.py +++ b/src/mxcp/server/interfaces/cli/lint.py @@ -7,14 +7,15 @@ from mxcp.server.core.config.analytics import track_command_with_timing from mxcp.server.core.config.site_config import find_repo_root, load_site_config from mxcp.server.core.config.user_config import load_user_config +from mxcp.server.definitions.endpoints.loader import EndpointLoader from mxcp.server.definitions.endpoints.models import ( EndpointDefinitionModel, ParamDefinitionModel, - TestDefinitionModel, + PromptDefinitionModel, + ResourceDefinitionModel, ToolDefinitionModel, TypeDefinitionModel, ) -from mxcp.server.definitions.endpoints.loader import EndpointLoader from mxcp.server.interfaces.cli.utils import ( configure_logging_from_config, output_error, @@ -155,7 +156,9 @@ def lint_return_type( if return_def.type == "array" and return_def.items is not None: lint_nested_type(return_def.items, f"{endpoint_type}.return.items", issues, path) elif return_def.type == "object" and return_def.properties: - lint_object_properties(return_def.properties, f"{endpoint_type}.return.properties", issues, path) + lint_object_properties( + return_def.properties, f"{endpoint_type}.return.properties", issues, path + ) def lint_nested_type( @@ -210,7 +213,7 @@ def lint_endpoint(path: Path, endpoint: EndpointDefinitionModel) -> list[LintIss """Lint a single endpoint for missing metadata.""" issues: list[LintIssueModel] = [] - definition = None + definition: ToolDefinitionModel | ResourceDefinitionModel | PromptDefinitionModel | None = None endpoint_type: str | None = None if endpoint.tool is not None: @@ -285,15 +288,15 @@ def lint_endpoint(path: Path, endpoint: EndpointDefinitionModel) -> list[LintIss annotations = tool_def.annotations has_annotations = bool(annotations and annotations.model_fields_set) if not has_annotations: - issues.append( - LintIssueModel( - severity="info", - path=str(path), - location=f"{endpoint_type}.annotations", - message="Tool has no behavioral annotations", - suggestion="Consider adding annotations like readOnlyHint, idempotentHint to help LLMs use the tool safely", - ) + issues.append( + LintIssueModel( + severity="info", + path=str(path), + location=f"{endpoint_type}.annotations", + message="Tool has no behavioral annotations", + suggestion="Consider adding annotations like readOnlyHint, idempotentHint to help LLMs use the tool safely", ) + ) return issues @@ -317,7 +320,8 @@ def format_lint_results_as_text(report: LintReportModel) -> str: sum(1 for i in file_report.issues if i.severity == "error") for file_report in report.files ) warning_count = sum( - sum(1 for i in file_report.issues if i.severity == "warning") for file_report in report.files + sum(1 for i in file_report.issues if i.severity == "warning") + for file_report in report.files ) info_count = sum( sum(1 for i in file_report.issues if i.severity == "info") for file_report in report.files @@ -485,9 +489,7 @@ def lint(profile: str, json_output: bool, debug: bool, severity: str) -> None: issues = [i for i in issues if i.severity == "info"] if issues: - file_reports.append( - LintFileReportModel(path=str(path), issues=issues) - ) + file_reports.append(LintFileReportModel(path=str(path), issues=issues)) else: clean_paths.append(str(path)) diff --git a/src/mxcp/server/interfaces/cli/list.py b/src/mxcp/server/interfaces/cli/list.py index 09207a4d..38ba07bb 100644 --- a/src/mxcp/server/interfaces/cli/list.py +++ b/src/mxcp/server/interfaces/cli/list.py @@ -6,8 +6,8 @@ from mxcp.server.core.config.analytics import track_command_with_timing from mxcp.server.core.config.site_config import find_repo_root, load_site_config from mxcp.server.core.config.user_config import load_user_config -from mxcp.server.definitions.endpoints.models import EndpointDefinitionModel from mxcp.server.definitions.endpoints.loader import EndpointLoader +from mxcp.server.definitions.endpoints.models import EndpointDefinitionModel from mxcp.server.interfaces.cli.utils import ( configure_logging_from_config, output_error, diff --git a/src/mxcp/server/interfaces/cli/test.py b/src/mxcp/server/interfaces/cli/test.py index e519c697..4f59af2d 100644 --- a/src/mxcp/server/interfaces/cli/test.py +++ b/src/mxcp/server/interfaces/cli/test.py @@ -1,6 +1,7 @@ import asyncio import json from pathlib import Path + import click from mxcp.sdk.auth import UserContext @@ -15,13 +16,12 @@ output_result, resolve_profile, ) -from mxcp.server.services.tests.service import run_all_tests, run_tests from mxcp.server.services.tests.models import ( EndpointTestResultModel, MultiEndpointTestResultsModel, - TestCaseResultModel, TestSuiteResultModel, ) +from mxcp.server.services.tests.service import run_all_tests, run_tests def _format_single_test_result(result: TestSuiteResultModel, debug: bool) -> str: @@ -364,6 +364,8 @@ async def _test_impl( if not isinstance(headers, dict): raise click.BadParameter("Request headers must be a JSON object") + results: TestSuiteResultModel | MultiEndpointTestResultsModel | str + if endpoint_type and name: results = await run_tests( endpoint_type, diff --git a/src/mxcp/server/interfaces/cli/validate.py b/src/mxcp/server/interfaces/cli/validate.py index 64344196..8b999eb9 100644 --- a/src/mxcp/server/interfaces/cli/validate.py +++ b/src/mxcp/server/interfaces/cli/validate.py @@ -175,6 +175,7 @@ def validate( execution_engine = runtime_env.execution_engine try: + result: EndpointValidationResultModel | EndpointValidationSummaryModel | str if endpoint: result = validate_endpoint(endpoint, site_config, execution_engine) else: diff --git a/src/mxcp/server/interfaces/server/mcp.py b/src/mxcp/server/interfaces/server/mcp.py index 9d131b87..95f31d8c 100644 --- a/src/mxcp/server/interfaces/server/mcp.py +++ b/src/mxcp/server/interfaces/server/mcp.py @@ -49,15 +49,14 @@ from mxcp.server.core.refs.external import ExternalRefTracker from mxcp.server.core.reload import ReloadManager, ReloadRequest from mxcp.server.core.telemetry import configure_telemetry_from_config, shutdown_telemetry +from mxcp.server.definitions.endpoints.loader import EndpointLoader from mxcp.server.definitions.endpoints.models import ( - EndpointDefinitionModel, ParamDefinitionModel, PromptDefinitionModel, ResourceDefinitionModel, ToolDefinitionModel, TypeDefinitionModel, ) -from mxcp.server.definitions.endpoints.loader import EndpointLoader from mxcp.server.definitions.endpoints.utils import EndpointType from mxcp.server.executor.engine import RuntimeEnvironment, create_runtime_environment from mxcp.server.interfaces.cli.utils import ( @@ -1424,7 +1423,7 @@ async def _body(**kwargs: Any) -> Any: # Add return type annotation if return schema is defined return_schema = ( endpoint_def.return_ - if isinstance(endpoint_def, (ToolDefinitionModel, ResourceDefinitionModel)) + if isinstance(endpoint_def, ToolDefinitionModel | ResourceDefinitionModel) else None ) if return_schema: @@ -1488,9 +1487,7 @@ def _register_prompt(self, prompt_def: PromptDefinitionModel) -> None: EndpointType.PROMPT, "prompt", prompt_def, - decorator=self.mcp.prompt( - name=prompt_def.name, description=prompt_def.description - ), + decorator=self.mcp.prompt(name=prompt_def.name, description=prompt_def.description), log_name="prompt", ) @@ -1825,9 +1822,7 @@ def register_endpoints(self) -> None: if endpoint_def.tool is not None: self._register_tool(endpoint_def.tool) - logger.info( - f"Registered tool endpoint from {path}: {endpoint_def.tool.name}" - ) + logger.info(f"Registered tool endpoint from {path}: {endpoint_def.tool.name}") elif endpoint_def.resource is not None: self._register_resource(endpoint_def.resource) logger.info( diff --git a/src/mxcp/server/services/drift/checker.py b/src/mxcp/server/services/drift/checker.py index 9792ef01..03185d24 100644 --- a/src/mxcp/server/services/drift/checker.py +++ b/src/mxcp/server/services/drift/checker.py @@ -9,6 +9,7 @@ from mxcp.server.core.config.models import SiteConfigModel, UserConfigModel from mxcp.server.definitions.endpoints.models import EndpointDefinitionModel from mxcp.server.services.drift.models import ( + Column, ColumnModification, DriftReport, DriftSnapshot, @@ -195,7 +196,7 @@ def compare_resources( # If any changes, add to changes list if validation_changed or test_results_changed or definition_changed: endpoint = _extract_endpoint_identifier(current_res.definition) - details = {} + details: dict[str, Any] = {} if validation_changed: details["validation_changes"] = { "old_status": baseline_res.validation_results.status, @@ -329,9 +330,7 @@ async def check_drift( # Compare snapshots table_changes = compare_tables(baseline_snapshot.tables, current_snapshot.tables) - resource_changes = compare_resources( - baseline_snapshot.resources, current_snapshot.resources - ) + resource_changes = compare_resources(baseline_snapshot.resources, current_snapshot.resources) # Calculate summary summary = { diff --git a/src/mxcp/server/services/drift/models.py b/src/mxcp/server/services/drift/models.py index 85f5239e..3c7d37c3 100644 --- a/src/mxcp/server/services/drift/models.py +++ b/src/mxcp/server/services/drift/models.py @@ -93,4 +93,3 @@ class DriftReport(_DriftBaseModel): summary: dict[str, int] table_changes: list[TableChange] resource_changes: list[ResourceChange] - diff --git a/src/mxcp/server/services/endpoints/models.py b/src/mxcp/server/services/endpoints/models.py index 8f5b3de9..d10644ad 100644 --- a/src/mxcp/server/services/endpoints/models.py +++ b/src/mxcp/server/services/endpoints/models.py @@ -23,4 +23,3 @@ class EndpointValidationSummaryModel(BaseModel): status: Literal["ok", "error"] validated: list[EndpointValidationResultModel] message: str | None = None - diff --git a/src/mxcp/server/services/endpoints/service.py b/src/mxcp/server/services/endpoints/service.py index 2580e952..d41a5d0f 100644 --- a/src/mxcp/server/services/endpoints/service.py +++ b/src/mxcp/server/services/endpoints/service.py @@ -21,12 +21,13 @@ ) from mxcp.server.core.config.models import SiteConfigModel, UserConfigModel from mxcp.server.core.config.site_config import find_repo_root +from mxcp.server.definitions.endpoints.loader import EndpointLoader from mxcp.server.definitions.endpoints.models import ( - EndpointDefinitionModel, PoliciesDefinitionModel, PromptDefinitionModel, + ResourceDefinitionModel, + ToolDefinitionModel, ) -from mxcp.server.definitions.endpoints.loader import EndpointLoader from mxcp.server.executor.engine import create_runtime_environment from mxcp.server.executor.runners.endpoint import ( execute_code_with_engine, @@ -222,6 +223,7 @@ async def execute_endpoint_with_engine_and_policy( policy_enforcer = None component_dict: dict[str, Any] | None = None + component: ToolDefinitionModel | ResourceDefinitionModel | PromptDefinitionModel | None = None if endpoint_type == "tool": component = endpoint_definition.tool @@ -252,8 +254,7 @@ async def execute_endpoint_with_engine_and_policy( # Dispatch to appropriate execution method based on endpoint type if endpoint_type == "prompt": - # We already verified prompt_def exists above - prompt_def = cast(PromptDefinitionModel, endpoint_definition.prompt) + prompt_def = cast(PromptDefinitionModel, component) result = await execute_prompt_with_validation(prompt_def, params, skip_output_validation) else: result = await execute_code_with_engine( diff --git a/src/mxcp/server/services/endpoints/validator.py b/src/mxcp/server/services/endpoints/validator.py index 2eaa7d78..9d126404 100644 --- a/src/mxcp/server/services/endpoints/validator.py +++ b/src/mxcp/server/services/endpoints/validator.py @@ -1,15 +1,19 @@ import re from pathlib import Path +from typing import cast + from jinja2 import Environment, meta from mxcp.sdk.executor import ExecutionEngine from mxcp.server.core.config.models import SiteConfigModel from mxcp.server.core.config.site_config import find_repo_root +from mxcp.server.definitions.endpoints.loader import EndpointLoader from mxcp.server.definitions.endpoints.models import ( EndpointDefinitionModel, + PromptDefinitionModel, ResourceDefinitionModel, + ToolDefinitionModel, ) -from mxcp.server.definitions.endpoints.loader import EndpointLoader from mxcp.server.definitions.endpoints.utils import get_endpoint_source_code from mxcp.server.services.endpoints.models import ( EndpointValidationResultModel, @@ -128,7 +132,9 @@ def validate_endpoint_payload( try: endpoint_type: str | None = None - component = None + component: ToolDefinitionModel | ResourceDefinitionModel | PromptDefinitionModel | None = ( + None + ) name: str | None = None if endpoint.tool is not None: @@ -152,7 +158,8 @@ def validate_endpoint_payload( ) if endpoint_type == "prompt": - messages = component.messages or [] + prompt_def = cast(PromptDefinitionModel, component) + messages = prompt_def.messages or [] if not messages: return EndpointValidationResultModel( status="error", @@ -160,7 +167,7 @@ def validate_endpoint_payload( message="No messages found in prompt definition", ) - parameters = component.parameters or [] + parameters = prompt_def.parameters or [] defined_params = {p.name for p in parameters} for i, msg in enumerate(messages): @@ -178,15 +185,20 @@ def validate_endpoint_payload( return EndpointValidationResultModel(status="ok", path=relative_path) + executable_component: ToolDefinitionModel | ResourceDefinitionModel if endpoint_type == "resource": - err = _validate_resource_uri_vs_params(component, Path(relative_path)) + resource_def = cast(ResourceDefinitionModel, component) + err = _validate_resource_uri_vs_params(resource_def, Path(relative_path)) if err: return err + executable_component = resource_def + else: + executable_component = cast(ToolDefinitionModel, component) - language = component.language or "sql" + language = executable_component.language or "sql" if language == "python": - source = component.source + source = executable_component.source if not source or source.file is None: return EndpointValidationResultModel( status="error", @@ -245,7 +257,7 @@ def validate_endpoint_payload( if not isinstance(sql_param_names, list): sql_param_names = list(sql_param_names) - yaml_params = component.parameters or [] + yaml_params = executable_component.parameters or [] yaml_param_names = [p.name for p in yaml_params] missing_params = set(sql_param_names) - set(yaml_param_names) diff --git a/src/mxcp/server/services/evals/service.py b/src/mxcp/server/services/evals/service.py index 1697dc10..0530fc5f 100644 --- a/src/mxcp/server/services/evals/service.py +++ b/src/mxcp/server/services/evals/service.py @@ -14,10 +14,9 @@ from mxcp.sdk.validator import TypeSchema from mxcp.server.core.config.models import SiteConfigModel, UserConfigModel from mxcp.server.core.config.site_config import find_repo_root -from mxcp.server.definitions.endpoints.models import EndpointDefinitionModel from mxcp.server.definitions.endpoints.loader import EndpointLoader +from mxcp.server.definitions.endpoints.models import EndpointDefinitionModel from mxcp.server.definitions.evals.loader import discover_eval_files, load_eval_suite -from mxcp.server.definitions.evals.models import EvalSuiteModel from mxcp.server.executor.engine import create_runtime_environment from mxcp.server.executor.runners.tool import EndpointToolExecutor @@ -305,9 +304,7 @@ async def run_eval_suite( if assertions.answer_not_contains: for forbidden_text in assertions.answer_not_contains: if forbidden_text.lower() in response.lower(): - failures.append( - f"Forbidden text '{forbidden_text}' found in response" - ) + failures.append(f"Forbidden text '{forbidden_text}' found in response") test_time = time.time() - test_start @@ -390,7 +387,7 @@ async def run_all_evals( else: if eval_suite is None: continue - suite_name = eval_suite.get("suite", "unnamed") + suite_name = eval_suite.suite or "unnamed" # Run the suite result = await run_eval_suite( suite_name, user_config, site_config, profile, cli_user_context, override_model diff --git a/src/mxcp/server/services/tests/models.py b/src/mxcp/server/services/tests/models.py index e1fa9296..a7d43357 100644 --- a/src/mxcp/server/services/tests/models.py +++ b/src/mxcp/server/services/tests/models.py @@ -49,4 +49,3 @@ class MultiEndpointTestResultsModel(BaseModel): tests_run: int endpoints: list[EndpointTestResultModel] message: str | None = None - diff --git a/tests/misc/test_no_dict_model_unions.py b/tests/misc/test_no_dict_model_unions.py index bc324946..ca4d84b1 100644 --- a/tests/misc/test_no_dict_model_unions.py +++ b/tests/misc/test_no_dict_model_unions.py @@ -17,7 +17,6 @@ def test_no_dict_model_unions_in_server_code() -> None: for match in pattern.finditer(text): violations.append(f"{path.relative_to(repo_root)}: {match.group(0)}") - assert ( - not violations - ), "Disallowed dict|Model unions found:\n" + "\n".join(f"- {entry}" for entry in violations) - + assert not violations, "Disallowed dict|Model unions found:\n" + "\n".join( + f"- {entry}" for entry in violations + ) diff --git a/tests/server/test_evals_tool_executor.py b/tests/server/test_evals_tool_executor.py index c7ce1f2b..15daf3e0 100644 --- a/tests/server/test_evals_tool_executor.py +++ b/tests/server/test_evals_tool_executor.py @@ -227,7 +227,10 @@ def test_get_language_inference(self): EndpointDefinitionModel.model_validate( { "mxcp": 1, - "tool": {"name": "explicit_override_tool", "source": {"file": "script.py", "language": "sql"}}, + "tool": { + "name": "explicit_override_tool", + "source": {"file": "script.py", "language": "sql"}, + }, } ), EndpointDefinitionModel.model_validate( diff --git a/tests/server/test_tester.py b/tests/server/test_tester.py index f27094e0..254aebdd 100644 --- a/tests/server/test_tester.py +++ b/tests/server/test_tester.py @@ -93,9 +93,7 @@ async def test_run_valid_resource(tester_repo_path, site_config, user_config): assert any( test.status == "passed" for test in result.tests ) # valid filter test should pass - assert any( - test.status == "error" for test in result.tests - ) # no filter test should error + assert any(test.status == "error" for test in result.tests) # no filter test should error # Check error cause for the error test error_msgs = [test.error for test in result.tests if test.status == "error"] assert any("Required parameter missing: filter" in str(msg) for msg in error_msgs) diff --git a/tests/server/test_tester_assertions.py b/tests/server/test_tester_assertions.py index c09374e9..5d71865b 100644 --- a/tests/server/test_tester_assertions.py +++ b/tests/server/test_tester_assertions.py @@ -186,51 +186,44 @@ async def test_result_contains_error_messages(user_config, site_config): # Test 1: Array missing primitive assert test_names["Array missing primitive value"].status == "failed" - assert ( - "Array does not contain expected value: grape" - in (test_names["Array missing primitive value"].error or "") + assert "Array does not contain expected value: grape" in ( + test_names["Array missing primitive value"].error or "" ) # Test 2: Dict missing field assert test_names["Dict missing field"].status == "failed" - assert ( - "Expected field 'email' not found in result" - in (test_names["Dict missing field"].error or "") + assert "Expected field 'email' not found in result" in ( + test_names["Dict missing field"].error or "" ) # Test 3: Dict field wrong value assert test_names["Dict field wrong value"].status == "failed" - assert ( - "Field 'age' has value 25, expected 30" - in (test_names["Dict field wrong value"].error or "") + assert "Field 'age' has value 25, expected 30" in ( + test_names["Dict field wrong value"].error or "" ) # Test 4: Array of dicts no match assert test_names["Array of dicts no match"].status == "failed" - assert ( - "No item in array contains the expected fields" - in (test_names["Array of dicts no match"].error or "") + assert "No item in array contains the expected fields" in ( + test_names["Array of dicts no match"].error or "" ) # Test 5: Wrong result type assert test_names["String result with dict pattern"].status == "failed" - assert ( - "result_contains assertion requires dict or array result" - in (test_names["String result with dict pattern"].error or "") + assert "result_contains assertion requires dict or array result" in ( + test_names["String result with dict pattern"].error or "" ) # Test 6: Empty array assert test_names["Empty array check"].status == "failed" - assert ( - "Array does not contain expected value: anything" - in (test_names["Empty array check"].error or "") + assert "Array does not contain expected value: anything" in ( + test_names["Empty array check"].error or "" ) # Test 7: Number array missing value assert test_names["Number array missing value"].status == "failed" - assert ( - "Array does not contain expected value: 10" - in (test_names["Number array missing value"].error or "") + assert "Array does not contain expected value: 10" in ( + test_names["Number array missing value"].error or "" ) From 3d55eb58566739a460ee6abe4b119f223a020a48 Mon Sep 17 00:00:00 2001 From: Miguel Branco Date: Wed, 26 Nov 2025 13:43:33 +0100 Subject: [PATCH 06/26] Ensure mxcp.runtime remains backward compat --- pydantic-migration-plan.md | 132 ------------------ src/mxcp/runtime/__init__.py | 60 +++++--- src/mxcp/server/core/config/models.py | 13 +- src/mxcp/server/core/config/parsers.py | 4 +- src/mxcp/server/executor/runners/endpoint.py | 4 +- src/mxcp/server/interfaces/server/mcp.py | 8 +- src/mxcp/server/services/endpoints/service.py | 31 +++- tests/server/test_integration.py | 4 +- 8 files changed, 92 insertions(+), 164 deletions(-) delete mode 100644 pydantic-migration-plan.md diff --git a/pydantic-migration-plan.md b/pydantic-migration-plan.md deleted file mode 100644 index 045ad6d0..00000000 --- a/pydantic-migration-plan.md +++ /dev/null @@ -1,132 +0,0 @@ -# Pydantic v2 Migration Strategy - -This document describes a staged plan to converge all MXCP configuration and definition models on Pydantic v2. The focus is on incremental, low-risk rollouts—starting with site configuration (the “root” dependency) and expanding to additional schemas over time. - ---- - -## Guiding Principles -- **Single source of truth**: Pydantic models (with `model_json_schema()` exports if needed) become the canonical definition. Legacy JSON Schema files are deleted only after their equivalents can be auto-generated. -- **Immutable consumer view**: Once validated, configs should be immutable (`frozen=True`) to discourage incidental mutation. Derived values and defaults live inside model validators. -- **Bridge period**: Loader functions may return both models and dicts temporarily to avoid flag days. Downstream modules gradually adopt model instances and drop `.get()` patterns. -- **Tight feedback**: Each phase ships with targeted tests (unit + integration) that exercise both success and failure scenarios, including environment interpolation. -- **No CLI regression**: Existing CLI commands (`mxcp log`, `mxcp log-cleanup`, etc.) must keep current names, options, and output. - ---- - -## Phase 0 – Preparation -1. **Inventory + ownership** - - Confirm all call sites of `SiteConfig` / `UserConfig` TypedDicts and jsonschema validation to understand migration blast radius. - - Establish owners for each schema domain (site, user, endpoints, prompts, evals) so follow-on phases stay staffed. -2. **Decide on doc export** - - If external tooling still needs JSON Schema files, agree on generating them via `BaseModel.model_json_schema()` to avoid drift. -3. **Define common utilities** - - Draft helpers for repo-relative path resolution, environment overrides, and profile-specific defaults so they can be reused by later phases. - -Deliverables: short design doc for the `SiteConfigModel` API surface, agreement on schema export strategy, and tickets for each migration phase. - ---- - -## Phase 1 – Site Configuration (root dependency) -1. **Model definition** - - Create `mxcp/server/core/config/models.py` (new names, e.g., `SiteConfigModel`, `SitePathsConfigModel`, etc.). - - Encode current JSON schema semantics via field types, `Annotated` constraints, and default values in `Field(...)`. - - Add `model_validator(mode="after")` hooks for derived defaults (DuckDB path, drift/audit files, env overrides). Centralize the logic currently found in `_apply_defaults`. - - Set `model_config = ConfigDict(extra="forbid", frozen=True, use_enum_values=True)` to block stray keys and deliver immutable instances. -2. **Loader bridge** - - Update `load_site_config` to: - - Parse YAML → dict - - Run legacy JSON Schema validation (optional, behind a feature flag) for a release or two - - Instantiate `SiteConfigModel` - - Return both the model and `model_dump()` (e.g., `(model, model_dump)` or maintain current signature while storing the model internally) - - Emit warnings when downstream code accesses dict interfaces so we can track migration progress. -3. **Adopt model consumers** - - Prioritize modules closest to configuration loading (e.g., `mxcp/server/services/endpoints/service.py`, `.../dbt/runner.py`, `.../executor/engine.py`) to accept the model. Replace `.get()`/`["key"]` operations with dot access. - - Update tests that rely on plain dict fixtures to instantiate the model instead, or call `model_dump()` explicitly when dicts are required for serialization. -4. **Validation & regression** - - Add new unit tests for `SiteConfigModel` covering: missing sections, repo-root derived paths, env overrides, invalid values, immutability. - - Run `uv run pytest tests/server/test_site_config*.py` (create if needed) plus existing integration suites touched by config loading. -5. **Cleanup** - - Once all runtime code uses the model (no dict consumers remain), delete the `SiteConfig` TypedDict and JSON schema, remove `jsonschema` dependency from this path, and simplify loader return values to just the model. - -Exit criteria: `load_site_config` returns only `SiteConfigModel`, no dict-style access in server modules, and JSON schema file `mxcp-site-schema-1.json` is removed (or auto-generated from the model). - ---- - -## Phase 2 – User Configuration -**Prerequisite**: Site config consumers operate purely on `SiteConfigModel`. - -1. **Model definition** - - Introduce `UserConfigModel` and nested components, mirroring the existing schema plus resolver-specific defaults (vault, 1password, transport, telemetry, etc.). - - Capture dependencies on site config explicitly where required (e.g., default generation uses `SiteConfigModel` values). -2. **Resolver integration** - - Ensure interpolation (`interpolate_all` / `interpolate_selective`) happens before model validation. Any exceptions raised should reference model fields for clarity. -3. **Loader bridge + adoption** - - Mirror the site-config strategy: loader returns both model and dict until consumers are migrated. - - Update CLI initialization and executor setup to consume the model. Replace `TypedDict` imports globally. -4. **Testing & cleanup** - - Expand `tests/server/test_user_config.py` to instantiate the new model, covering env/file/vault/1password references, persistence defaults, telemetry toggles, etc. - - Remove `mxcp-config-schema-1.json` and the `UserConfig` TypedDict once adoption is complete. - -Exit criteria: `load_user_config` returns only `UserConfigModel`, resolver pipeline validated via tests, and no modules mutate the user config post-validation. - ---- - -## Phase 3 – Definition Schemas (endpoints, prompts, resources, evals) -1. **Prioritize endpoints** - - Replace `jsonschema` validation in `EndpointLoader` with Pydantic models (`ToolDefinitionModel`, `PromptDefinitionModel`, etc.). - - These models should encapsulate enabled/disabled logic, URI/name validation, and cross-reference checks. Consider using `RootModel[List[ToolDefinitionModel]]` for directory scanning utilities. -2. **Prompts / resources / evals** - - Repeat the pattern for each schema-heavy component, unifying shared blocks (e.g., `AuditConfigModel`, `LLMModelConfigModel`) to reduce duplication. -3. **Schema export** - - If we still need JSON artifacts for external tooling, generate them from the Pydantic models during packaging (e.g., via a build step that writes to `dist/schemas/`). -4. **Testing** - - Add targeted tests for each loader that ensure invalid definitions trigger `ValidationError` with helpful messages, and that derived defaults (enabled flags, templated names) behave as expected. - -Exit criteria: All schema validations in `mxcp/server/schemas/` are backed by Pydantic models, and `jsonschema` is no longer required in runtime dependencies. - ---- - -## Phase 4 – Final Cleanup & Enforcement -1. **Remove legacy artifacts** - - Delete remaining JSON schema files and any helper utilities that existed solely for `jsonschema`. - - Drop `types-jsonschema` stubs and related dev dependencies if unused elsewhere. -2. **Static analysis** - - Enable mypy/pyright rules (or custom scripts) to flag residual `.get()`/`dict[...]` usage on config objects, ensuring future code sticks to typed models. -3. **Documentation** - - Update user-facing docs (e.g., `docs/guides/configuration.md`) to reference the new schema definitions or the auto-generated JSON schema exports. -4. **Performance & regression review** - - Profile configuration loading to ensure Pydantic validation doesn’t introduce noticeable latency. - - Confirm CLI commands and server startups produce identical behavior and logs. - -Exit criteria: All configs/definitions rely on Pydantic, legacy schemas are gone (or auto-generated), type checking enforces model usage, and jsonschema dependency is fully removed. - ---- - -## Risk Mitigation & Tooling -- **Gradual rollout**: Each phase should ship behind internal feature flags or environment toggles to allow canary testing. -- **Telemetry**: Add temporary counters/logging to measure how often dict fallbacks are used during the bridge period. -- **Backwards compatibility**: Because configs are local files, ensure we support `model_validate` with clear errors that match (or improve upon) current jsonschema messages. -- **Contributor guidance**: Add a short section to `docs/guides/configuration.md` explaining how to add fields to the models, including where defaults/validators live. - ---- - -## Tracking -Create one epic per phase with child tasks for: -- Model definition + validators -- Loader updates -- Consumer migrations (grouped by package) -- Test coverage -- Schema removal / dependency cleanup - -Progress through the phases only after the previous phase meets its exit criteria to avoid overlapping risk areas. This ensures we maintain a fully typed, immutable configuration surface before tackling downstream definitions. - ---- - -## Post-Migration Cleanup (Completed) - -- **Structured outputs everywhere** – Test runner, endpoint validation, and CLI lint now emit dedicated Pydantic models. Sanitizers such as `_sanitize_test_results` were removed, and JSON output is derived via `model_dump()` only at the final serialization boundary. -- **No more dict↔model unions in server code** – Inputs are validated upfront (e.g., policy parsing, schema helpers), and a regression test (`tests/misc/test_no_dict_model_unions.py`) guards against reintroducing `dict[str, Any] | Model` type hints. Union uses remain only in SDK-facing code where dicts are part of the public contract. -- **Metadata remains optional by design** – The models preserve existing semantics, while lint rules continue to warn when authors omit descriptions/tests/examples. This matches the long-standing behavior without forcing breaking schema changes. - -With these tightening steps and the automated guard in place, the Pydantic migration is fully complete. - diff --git a/src/mxcp/runtime/__init__.py b/src/mxcp/runtime/__init__.py index 0ad32c3b..6f99dade 100644 --- a/src/mxcp/runtime/__init__.py +++ b/src/mxcp/runtime/__init__.py @@ -13,6 +13,11 @@ Internal APIs (not for user code): - _set_global_runtime(): Set the global DuckDB runtime for init hooks - _get_global_runtime(): Get the global DuckDB runtime +Runtime compatibility note: + The public ``config.site_config`` and ``config.user_config`` accessors continue + to expose plain ``dict`` objects for backward compatibility. Internally we + convert those dictionaries to Pydantic models as needed, but user code can + keep using the legacy dictionary-style access without changes. """ import logging @@ -20,7 +25,6 @@ from typing import Any, cast from mxcp.sdk.executor.context import get_execution_context -from mxcp.server.core.config.models import SiteConfigModel, UserConfigModel logger = logging.getLogger(__name__) @@ -60,24 +64,44 @@ def get_secret(self, name: str) -> dict[str, Any] | None: "No execution context available - function not called from MXCP executor" ) - site_config = cast(SiteConfigModel | None, context.get("site_config")) - user_config = cast(UserConfigModel | None, context.get("user_config")) + site_config = cast(dict[str, Any] | None, context.get("site_config")) + user_config = cast(dict[str, Any] | None, context.get("user_config")) if not user_config or not site_config: return None - project = site_config.project - profile = site_config.profile + project = site_config.get("project") + profile = site_config.get("profile") if not project or not profile: return None - project_config = user_config.projects.get(project) - profile_config = project_config.profiles.get(profile) if project_config else None - secrets = profile_config.secrets if profile_config else [] + projects = user_config.get("projects") + if not isinstance(projects, dict): + return None + + project_config = projects.get(project) + if not isinstance(project_config, dict): + return None + + profiles = project_config.get("profiles") + if not isinstance(profiles, dict): + return None + + profile_config = profiles.get(profile) + if not isinstance(profile_config, dict): + return None + + secrets = profile_config.get("secrets", []) + if not isinstance(secrets, list): + return None + for secret in secrets: - if secret.name == name: - return secret.parameters + if not isinstance(secret, dict): + continue + if secret.get("name") == name: + params = secret.get("parameters") + return params if isinstance(params, dict) else {} return None def get_setting(self, key: str, default: Any = None) -> Any: @@ -88,11 +112,11 @@ def get_setting(self, key: str, default: Any = None) -> Any: "No execution context available - function not called from MXCP executor" ) - site_config = cast(SiteConfigModel | None, context.get("site_config")) + site_config = cast(dict[str, Any] | None, context.get("site_config")) if not site_config: return default - raw_config = site_config.model_dump(mode="python") + raw_config = site_config # Support nested key access (e.g., "dbt.enabled") if "." in key: @@ -108,22 +132,22 @@ def get_setting(self, key: str, default: Any = None) -> Any: return raw_config.get(key, default) @property - def user_config(self) -> UserConfigModel | None: - """Access full user configuration.""" + def user_config(self) -> dict[str, Any] | None: + """Access full user configuration as the legacy dictionary structure.""" context = get_execution_context() if not context: return None - return cast(UserConfigModel | None, context.get("user_config")) + return cast(dict[str, Any] | None, context.get("user_config")) @property - def site_config(self) -> SiteConfigModel | None: - """Access full site configuration (typically a SiteConfigModel).""" + def site_config(self) -> dict[str, Any] | None: + """Access full site configuration as the legacy dictionary structure.""" context = get_execution_context() if not context: return None - return cast(SiteConfigModel | None, context.get("site_config")) + return cast(dict[str, Any] | None, context.get("site_config")) class PluginsProxy: diff --git a/src/mxcp/server/core/config/models.py b/src/mxcp/server/core/config/models.py index c320685b..98622b4c 100644 --- a/src/mxcp/server/core/config/models.py +++ b/src/mxcp/server/core/config/models.py @@ -213,7 +213,11 @@ def _apply_active_profile_defaults(self, info: ValidationInfo) -> SiteConfigMode update={"duckdb": duckdb, "drift": drift, "audit": audit} ) - return self.model_copy(update={"profiles": profiles}) + object.__setattr__(self, "profiles", profiles) + updated_fields = set(getattr(self, "__pydantic_fields_set__", set())) + updated_fields.add("profiles") + object.__setattr__(self, "__pydantic_fields_set__", updated_fields) + return self @field_serializer("extensions") def _serialize_extensions( @@ -377,7 +381,12 @@ def _apply_defaults(self) -> UserAuthConfigModel: persistence = self.persistence if provider != "none" and persistence is None: persistence = UserAuthPersistenceConfigModel() - return self.model_copy(update={"provider": provider, "persistence": persistence}) + object.__setattr__(self, "provider", provider) + object.__setattr__(self, "persistence", persistence) + updated_fields = set(getattr(self, "__pydantic_fields_set__", set())) + updated_fields.update({"provider", "persistence"}) + object.__setattr__(self, "__pydantic_fields_set__", updated_fields) + return self class UserModelConfigModel(BaseModel): diff --git a/src/mxcp/server/core/config/parsers.py b/src/mxcp/server/core/config/parsers.py index 0acb061d..f3e9385f 100644 --- a/src/mxcp/server/core/config/parsers.py +++ b/src/mxcp/server/core/config/parsers.py @@ -127,8 +127,8 @@ def execution_context_for_init_hooks( try: if user_config and site_config: context = ExecutionContext() - context.set("user_config", user_config) - context.set("site_config", site_config) + context.set("user_config", user_config.model_dump(mode="python", exclude_unset=True)) + context.set("site_config", site_config.model_dump(mode="python", exclude_unset=True)) if duckdb_runtime: context.set("duckdb_runtime", duckdb_runtime) token = set_execution_context(context) diff --git a/src/mxcp/server/executor/runners/endpoint.py b/src/mxcp/server/executor/runners/endpoint.py index 3faea907..6214d2ac 100644 --- a/src/mxcp/server/executor/runners/endpoint.py +++ b/src/mxcp/server/executor/runners/endpoint.py @@ -111,8 +111,8 @@ async def execute_code_with_engine( execution_context = ExecutionContext(user_context=user_context) # Populate context with data that runtime module expects - execution_context.set("user_config", user_config) - execution_context.set("site_config", site_config) + execution_context.set("user_config", user_config.model_dump(mode="python", exclude_unset=True)) + execution_context.set("site_config", site_config.model_dump(mode="python", exclude_unset=True)) if server_ref: execution_context.set("_mxcp_server", server_ref) # Add HTTP headers diff --git a/src/mxcp/server/interfaces/server/mcp.py b/src/mxcp/server/interfaces/server/mcp.py index 95f31d8c..93362bfe 100644 --- a/src/mxcp/server/interfaces/server/mcp.py +++ b/src/mxcp/server/interfaces/server/mcp.py @@ -1783,8 +1783,12 @@ async def _execute_sql( raise RuntimeError("Execution engine not initialized") execution_context = ExecutionContext(user_context=user_context) - execution_context.set("user_config", self.user_config) - execution_context.set("site_config", self.site_config) + execution_context.set( + "user_config", self.user_config.model_dump(mode="python", exclude_unset=True) + ) + execution_context.set( + "site_config", self.site_config.model_dump(mode="python", exclude_unset=True) + ) return await self.runtime_environment.execution_engine.execute( language="sql", diff --git a/src/mxcp/server/services/endpoints/service.py b/src/mxcp/server/services/endpoints/service.py index d41a5d0f..e4859775 100644 --- a/src/mxcp/server/services/endpoints/service.py +++ b/src/mxcp/server/services/endpoints/service.py @@ -5,6 +5,7 @@ """ import logging +from collections.abc import Mapping from typing import TYPE_CHECKING, Any, Optional, cast if TYPE_CHECKING: @@ -169,13 +170,29 @@ async def execute_endpoint( runtime_env.shutdown() +def _ensure_user_config_model( + user_config: UserConfigModel | Mapping[str, Any], +) -> UserConfigModel: + if isinstance(user_config, UserConfigModel): + return user_config + return UserConfigModel.model_validate(user_config) + + +def _ensure_site_config_model( + site_config: SiteConfigModel | Mapping[str, Any], +) -> SiteConfigModel: + if isinstance(site_config, SiteConfigModel): + return site_config + return SiteConfigModel.model_validate(site_config) + + async def execute_endpoint_with_engine_and_policy( endpoint_type: str, name: str, params: dict[str, Any], request_headers: dict[str, str] | None, - user_config: UserConfigModel, - site_config: SiteConfigModel, + user_config: UserConfigModel | Mapping[str, Any], + site_config: SiteConfigModel | Mapping[str, Any], execution_engine: ExecutionEngine, skip_output_validation: bool = False, user_context: UserContext | None = None, @@ -207,13 +224,17 @@ async def execute_endpoint_with_engine_and_policy( RuntimeError: If execution fails """ + # Normalize configs (callers may pass dicts) + site_config_model = _ensure_site_config_model(site_config) + user_config_model = _ensure_user_config_model(user_config) + # Find repository root repo_root = find_repo_root() if not repo_root: raise ValueError("Could not find repository root (no mxcp-site.yml found)") # Load the endpoint using EndpointLoader - loader = EndpointLoader(site_config) + loader = EndpointLoader(site_config_model) endpoint_result = loader.load_endpoint(endpoint_type, name) if not endpoint_result: @@ -265,8 +286,8 @@ async def execute_endpoint_with_engine_and_policy( params, execution_engine, skip_output_validation, - user_config, - site_config, + user_config_model, + site_config_model, user_context, server_ref, request_headers, diff --git a/tests/server/test_integration.py b/tests/server/test_integration.py index 7567b8c0..5d65952a 100644 --- a/tests/server/test_integration.py +++ b/tests/server/test_integration.py @@ -541,7 +541,9 @@ def trigger_reload(message: str = "test") -> dict: # Get DuckDB file path site_config = config.site_config - db_path = Path(site_config.profiles["default"].duckdb.path) + profile = site_config["profiles"]["default"] + duckdb_cfg = profile["duckdb"] + db_path = Path(duckdb_cfg["path"]) if not db_path.is_absolute(): db_path = Path.cwd() / db_path From 9335a33b356e3ef88436edbeb54cde6986b8e4f4 Mon Sep 17 00:00:00 2001 From: Miguel Branco Date: Wed, 26 Nov 2025 13:53:40 +0100 Subject: [PATCH 07/26] Fix --- src/mxcp/runtime/__init__.py | 4 +-- .../fixtures/runtime/python/runtime_tests.py | 27 +++++++------------ 2 files changed, 11 insertions(+), 20 deletions(-) diff --git a/src/mxcp/runtime/__init__.py b/src/mxcp/runtime/__init__.py index 6f99dade..7746cad4 100644 --- a/src/mxcp/runtime/__init__.py +++ b/src/mxcp/runtime/__init__.py @@ -133,7 +133,7 @@ def get_setting(self, key: str, default: Any = None) -> Any: @property def user_config(self) -> dict[str, Any] | None: - """Access full user configuration as the legacy dictionary structure.""" + """Access full user configuration.""" context = get_execution_context() if not context: return None @@ -142,7 +142,7 @@ def user_config(self) -> dict[str, Any] | None: @property def site_config(self) -> dict[str, Any] | None: - """Access full site configuration as the legacy dictionary structure.""" + """Access full site configuration.""" context = get_execution_context() if not context: return None diff --git a/tests/server/fixtures/runtime/python/runtime_tests.py b/tests/server/fixtures/runtime/python/runtime_tests.py index e17e13b1..b45b2bbb 100644 --- a/tests/server/fixtures/runtime/python/runtime_tests.py +++ b/tests/server/fixtures/runtime/python/runtime_tests.py @@ -134,25 +134,16 @@ def test_config_properties() -> dict: user_cfg = config.user_config site_cfg = config.site_config - # Verify we can access nested values + # Runtime proxies should return plain dicts for backward compatibility + assert isinstance(user_cfg, dict) + assert isinstance(site_cfg, dict) + try: - # From user config - if hasattr(user_cfg, "model_dump"): - user_cfg_dict = user_cfg.model_dump(mode="python") - else: - user_cfg_dict = user_cfg - project_name = list(user_cfg_dict["projects"].keys())[0] - secrets_count = len( - user_cfg_dict["projects"]["runtime_test"]["profiles"]["default"]["secrets"] - ) - - # From site config - if hasattr(site_cfg, "model_dump"): - site_cfg_dict = site_cfg.model_dump(mode="python") - else: - site_cfg_dict = site_cfg - site_project = site_cfg_dict["project"] - site_secrets = site_cfg_dict["secrets"] + project_name = list(user_cfg["projects"].keys())[0] + secrets_count = len(user_cfg["projects"]["runtime_test"]["profiles"]["default"]["secrets"]) + + site_project = site_cfg["project"] + site_secrets = site_cfg["secrets"] access_works = True except Exception: From 8cf26092f88385b67587694fcccec0701fa0eff4 Mon Sep 17 00:00:00 2001 From: Alex Zerntev Date: Fri, 28 Nov 2025 10:46:36 +0200 Subject: [PATCH 08/26] initial implementation --- docs/guides/configuration.md | 27 +- docs/guides/quality.md | 61 +- docs/reference/cli.md | 4 +- src/mxcp/sdk/evals/_types.py | 1 + src/mxcp/sdk/evals/executor.py | 677 ++++++++++++-------- src/mxcp/server/core/config/models.py | 1 + src/mxcp/server/definitions/evals/models.py | 1 + src/mxcp/server/executor/runners/tool.py | 62 +- src/mxcp/server/interfaces/cli/evals.py | 6 +- src/mxcp/server/services/evals/service.py | 94 ++- tests/sdk/evals/test_executor.py | 350 +++------- tests/server/test_evals_tool_executor.py | 200 ++++-- tests/server/test_user_config.py | 33 + 13 files changed, 896 insertions(+), 621 deletions(-) diff --git a/docs/guides/configuration.md b/docs/guides/configuration.md index fd660da0..1640a818 100644 --- a/docs/guides/configuration.md +++ b/docs/guides/configuration.md @@ -640,9 +640,30 @@ models: - **models**: Dictionary of model configurations - **type**: Either "claude" or "openai" - **api_key**: API key (you can use environment variables references) - - **base_url**: Custom API endpoint (optional, for OpenAI-compatible services) - - **timeout**: Request timeout in seconds - - **max_retries**: Number of retries on failure +- **base_url**: Custom API endpoint (optional, for OpenAI-compatible services) +- **timeout**: Request timeout in seconds +- **max_retries**: Number of retries on failure +- **options**: Extra provider-specific options forwarded to the model (e.g. `thinking: false`) + +Example with mixed providers and options: + +```yaml +models: + default: "gpt-4o" + models: + gpt-4o: + type: "openai" + api_key: "${OPENAI_API_KEY}" + timeout: 45 + options: + reasoning: "fast" + claude-4-sonnet: + type: "claude" + api_key: "${ANTHROPIC_API_KEY}" + timeout: 30 + options: + thinking: false +``` For more information on using evals, see the [LLM Evaluation section](quality.md#llm-evaluation-evals) in the Quality & Testing Guide. diff --git a/docs/guides/quality.md b/docs/guides/quality.md index 9b732c4f..0cf4608d 100644 --- a/docs/guides/quality.md +++ b/docs/guides/quality.md @@ -819,6 +819,7 @@ tests: - tool: get_churn_score args: customer_id: "ABC" + expected_answer: "The customer is high risk of churn" answer_contains: - "risk" - "churn" @@ -880,6 +881,64 @@ answer_not_contains: - "unauthorized" ``` +#### `expected_answer` +Checks the model's final answer against an expected answer using the LLM as a grader. The grader +returns `correct`, `wrong`, or `partially correct` plus a short comment. + +```yaml +expected_answer: "The customer is high risk of churn" +``` + +### Complete Eval Example + +```yaml +# faq-evals.yml +mxcp: 1 +suite: faq_checks +description: "Make sure the assistant answers FAQs accurately and uses tools when needed" +model: gpt-4o + +tests: + - name: tool_usage_for_price_lookup + prompt: "What's the current price for SKU-1234?" + assertions: + must_call: + - tool: get_product_price + args: + sku: "SKU-1234" + answer_contains: + - "price" + + - name: expected_answer_grading + prompt: "What are your support hours?" + assertions: + expected_answer: "Our support team is available Monday to Friday, 9am-5pm local time." + answer_contains: + - "Monday" + - "Friday" +``` + +### Model Configuration Example + +Add models to your user config (`~/.mxcp/config.yml`) so evals know which providers to call: + +```yaml +models: + default: "claude-4-sonnet" + models: + claude-4-sonnet: + type: "claude" + api_key: "${ANTHROPIC_API_KEY}" + timeout: 30 + gpt-4o: + type: "openai" + api_key: "${OPENAI_API_KEY}" + base_url: "https://api.openai.com/v1" + timeout: 45 + options: + reasoning: "fast" # forwarded to the provider as-is +``` + ### Running Evals ```bash @@ -1037,4 +1096,4 @@ Well-tested endpoints with rich metadata provide: - Faster debugging - Safe AI interactions -Remember: LLMs perform best when they clearly understand what your endpoints do, how to use them, and what to expect in return! \ No newline at end of file +Remember: LLMs perform best when they clearly understand what your endpoints do, how to use them, and what to expect in return! diff --git a/docs/reference/cli.md b/docs/reference/cli.md index 7ef974a0..0b23368c 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -340,7 +340,7 @@ The evals command executes evaluation tests that verify LLM behavior. Unlike reg 1. Send prompts to an LLM 2. Verify the LLM calls appropriate tools -3. Check that responses contain expected information +3. Check that responses contain expected information (including `expected_answer` grading) 4. Ensure safety by verifying destructive operations aren't called inappropriately Eval files should have the suffix `-evals.yml` or `.evals.yml` and are automatically discovered in your repository. @@ -624,4 +624,4 @@ The following environment variables can be used to configure MXCP: - `MXCP_TELEMETRY_TRACING_CONSOLE`: Enable console trace export for debugging (`true`/`false`) - `MXCP_TELEMETRY_METRICS_INTERVAL`: Metrics export interval in seconds (default: `60`) -For more details on environment variables and their usage, see the [Configuration Guide](../guides/configuration.md) and [Observability Guide](../guides/observability.md). \ No newline at end of file +For more details on environment variables and their usage, see the [Configuration Guide](../guides/configuration.md) and [Observability Guide](../guides/observability.md). diff --git a/src/mxcp/sdk/evals/_types.py b/src/mxcp/sdk/evals/_types.py index 55afc8e2..b842055e 100644 --- a/src/mxcp/sdk/evals/_types.py +++ b/src/mxcp/sdk/evals/_types.py @@ -18,6 +18,7 @@ class ModelConfig(ABC): name: str api_key: str + options: dict[str, Any] = field(default_factory=dict) @abstractmethod def get_type(self) -> str: diff --git a/src/mxcp/sdk/evals/executor.py b/src/mxcp/sdk/evals/executor.py index 8e6e5b3f..3b038ac0 100644 --- a/src/mxcp/sdk/evals/executor.py +++ b/src/mxcp/sdk/evals/executor.py @@ -1,15 +1,19 @@ -"""Core LLM executor for MXCP SDK Evals. +"""Agent-style LLM executor for MXCP evals. -This module provides the main LLMExecutor class that handles LLM orchestration -and tool calling, with tool execution delegated to external implementations. +This implementation builds pydantic-based tool schemas from MXCP tool +definitions and drives a lightweight agent loop that lets the model call +tools, execute them, and return a final answer plus tool history. """ +from __future__ import annotations + import json import logging -import re +from dataclasses import dataclass, field from typing import Any, Protocol, cast import httpx +from pydantic import BaseModel, ValidationError, create_model from mxcp.sdk.auth import UserContext @@ -19,70 +23,45 @@ class ToolExecutor(Protocol): - """Protocol for tool execution strategies. - - Different contexts can implement this protocol to provide their own - tool execution logic (e.g., using ExecutionEngine, HTTP APIs, mocks, etc.). - """ + """Protocol for tool execution strategies.""" async def execute_tool( self, tool_name: str, arguments: dict[str, Any], user_context: UserContext | None = None ) -> Any: - """Execute a tool and return the result. + ... - Args: - tool_name: Name of the tool to execute - arguments: Arguments to pass to the tool - user_context: Optional user context for execution - Returns: - Result of tool execution +@dataclass +class LLMToolCall: + id: str | None + tool: str + arguments: dict[str, Any] + + +@dataclass +class ToolCallRecord: + id: str | None + tool: str + arguments: dict[str, Any] + result: Any | None = None + error: str | None = None + + +@dataclass +class LLMResponse: + content: str + tool_calls: list[LLMToolCall] + raw_message: dict[str, Any] | None = None - Raises: - Exception: If tool execution fails - """ - ... + +@dataclass +class AgentResult: + answer: str + tool_calls: list[ToolCallRecord] = field(default_factory=list) class LLMExecutor: - """Core LLM executor focused on LLM orchestration and tool calling. - - This class handles: - - LLM API interactions (Claude, OpenAI, etc.) - - Tool call extraction from LLM responses - - Multi-turn conversations with tool results - - Prompt formatting for different model types - - Tool execution is delegated to an external ToolExecutor implementation, - making this class highly testable and reusable across different contexts. - - Example usage: - >>> # Create tool definitions (metadata only) - >>> tools = [ - ... ToolDefinition( - ... name="get_weather", - ... description="Get current weather for a location", - ... parameters=[ - ... ParameterDefinition(name="location", type="string", description="City name") - ... ] - ... ) - ... ] - >>> - >>> # Create model config - >>> model = ClaudeConfig(name="claude-3-haiku", api_key="...") - >>> - >>> # Create tool executor (implemented by context) - >>> tool_executor = MyToolExecutor(...) - >>> - >>> # Create LLM executor - >>> executor = LLMExecutor(model, tools, tool_executor) - >>> - >>> # Execute a prompt - >>> response, tool_calls = await executor.execute_prompt( - ... "What's the weather in Paris?", - ... user_context=user_context - ... ) - """ + """Pydantic-based agent loop with tool support.""" def __init__( self, @@ -90,219 +69,320 @@ def __init__( available_tools: list[ToolDefinition], tool_executor: ToolExecutor, ): - """Initialize LLM executor. - - Args: - model_config: Configuration for the LLM model (Claude, OpenAI, etc.) - available_tools: List of tool definitions available to the LLM - tool_executor: Implementation for executing tools - """ self.model_config = model_config self.available_tools = available_tools self.tool_executor = tool_executor + self.model_type = model_config.get_type() + + self._tool_models = self._build_tool_models(available_tools) + self._openai_tools, self._anthropic_tools = self._build_tool_schemas(available_tools) + self.system_prompt = self._build_system_prompt(available_tools) logger.info( - f"LLM executor initialized with model: {model_config.name} ({model_config.get_type()})" + "LLM executor initialized with model %s (%s) and %d tools", + model_config.name, + self.model_type, + len(available_tools), ) - logger.info(f"Available tools: {len(available_tools)}") - - def _format_tools_for_prompt(self) -> str: - """Format all available tools for inclusion in the prompt.""" - if not self.available_tools: - return "No tools available." - - tool_sections = [] - for tool in self.available_tools: - tool_sections.append(tool.to_prompt_format()) - - return "=== AVAILABLE TOOLS ===\n\n" + "\n\n".join(tool_sections) - - def _get_model_prompt( - self, user_prompt: str, conversation_history: list[dict[str, str]] | None = None - ) -> str: - """Get model-specific prompt format""" - available_tools = self._format_tools_for_prompt() - model_type = self.model_config.get_type() - - if model_type == "claude": - return self._get_claude_prompt(user_prompt, available_tools, conversation_history) - elif model_type == "openai": - return self._get_openai_prompt(user_prompt, available_tools, conversation_history) - else: - return self._get_default_prompt(user_prompt, available_tools, conversation_history) - - def _get_claude_prompt( - self, - user_prompt: str, - available_tools: str, - conversation_history: list[dict[str, str]] | None = None, - ) -> str: - """Claude-specific prompt format""" - system_prompt = f"""You are a helpful assistant with access to the following tools: - -{available_tools} - -To use a tool, respond with a JSON object: -{{"tool": "tool_name", "arguments": {{"param": "value"}}}} - -For multiple tool calls, use an array: -[{{"tool": "tool1", "arguments": {{}}}}, {{"tool": "tool2", "arguments": {{}}}}] - -Only output JSON when calling tools. Otherwise respond with regular text.""" - - messages = [] - if conversation_history: - for msg in conversation_history: - messages.append(f"{msg['role']}: {msg['content']}") - messages.append(f"Human: {user_prompt}") - - return system_prompt + "\n\n" + "\n\n".join(messages) - - def _get_openai_prompt( - self, - user_prompt: str, - available_tools: str, - conversation_history: list[dict[str, str]] | None = None, - ) -> str: - """OpenAI-specific prompt format""" - system_prompt = f"""You are a helpful assistant with access to the following tools: - -{available_tools} - -To use a tool, respond with a JSON object: -{{"tool": "tool_name", "arguments": {{"param": "value"}}}} - -For multiple tool calls, use an array: -[{{"tool": "tool1", "arguments": {{}}}}, {{"tool": "tool2", "arguments": {{}}}}] - -Only output JSON when calling tools. Otherwise respond with regular text.""" - - messages = [] - if conversation_history: - for msg in conversation_history: - messages.append(f"{msg['role']}: {msg['content']}") - messages.append(f"User: {user_prompt}") - - return system_prompt + "\n\n" + "\n\n".join(messages) - - def _get_default_prompt( - self, - user_prompt: str, - available_tools: str, - conversation_history: list[dict[str, str]] | None = None, - ) -> str: - """Default prompt format""" - return self._get_claude_prompt(user_prompt, available_tools, conversation_history) async def execute_prompt( - self, prompt: str, user_context: UserContext | None = None - ) -> tuple[str, list[dict[str, Any]]]: - """Execute a prompt and return the response and tool calls made. - - Args: - prompt: The user prompt to execute - user_context: Optional user context for tool execution - - Returns: - Tuple of (final_response, list_of_tool_calls_made) - """ - conversation_history: list[dict[str, Any]] = [] - tool_calls_made: list[dict[str, Any]] = [] - max_iterations = 10 # Prevent infinite loops - - for _iteration in range(max_iterations): - # Get model-specific prompt - full_prompt = self._get_model_prompt(prompt, conversation_history) - - # Call the LLM - response = await self._call_llm(full_prompt) - - # Check if response contains tool calls - tool_calls = self._extract_tool_calls(response) - - if not tool_calls: - # No more tool calls, return final response - return response, tool_calls_made - - # Execute tool calls - tool_results = [] - for tool_call in tool_calls: - tool_calls_made.append(tool_call) - - try: - tool_name = tool_call["tool"] - arguments = tool_call.get("arguments", {}) - - # Execute the tool using external executor - result = await self.tool_executor.execute_tool( - tool_name, arguments, user_context + self, prompt: str, user_context: UserContext | None = None, max_turns: int = 10 + ) -> AgentResult: + """Run the agent loop for a prompt.""" + messages = self._initial_messages(prompt) + history: list[ToolCallRecord] = [] + + for _ in range(max_turns): + llm_response = await self._call_llm(messages, use_tools=True) + + # If the model wants to call tools, execute them and continue. + if llm_response.tool_calls: + self._append_assistant_message(messages, llm_response) + for call in llm_response.tool_calls: + validated_args = self._validate_tool_arguments(call.tool, call.arguments) + record = ToolCallRecord( + id=call.id, tool=call.tool, arguments=validated_args or {} ) + try: + result = await self.tool_executor.execute_tool( + call.tool, validated_args, user_context + ) + record.result = result + except Exception as exc: # noqa: BLE001 + record.error = str(exc) + + history.append(record) + messages.append(self._tool_result_message(record)) + continue - tool_results.append({"tool": tool_name, "result": result}) + # No tool calls: final answer. + self._append_assistant_message(messages, llm_response) + return AgentResult(answer=llm_response.content, tool_calls=history) + + logger.warning("Max agent turns reached without a final answer") + return AgentResult(answer=llm_response.content, tool_calls=history) + + async def evaluate_expected_answer(self, answer: str, expected_answer: str) -> dict[str, str]: + """Ask the model to grade an answer against an expected value.""" + grader_system = ( + "You grade semantic equivalence between a candidate answer and an expected answer. " + "Focus on meaning, not wording or punctuation. Treat rephrasings, casing, and " + "minor formatting differences as correct if the meaning matches. Use 'partially correct' " + "only when the meaning overlaps but is incomplete or slightly off. " + "Return concise JSON with keys result (correct|wrong|partially correct), comment, and reasoning." + ) + grader_prompt = ( + "Compare the candidate answer to the expected answer (semantic match, not exact string).\n" + "Candidate answer:\n" + f"{answer}\n\n" + "Expected answer:\n" + f"{expected_answer}\n\n" + "Respond with JSON like " + '{"result":"correct|wrong|partially correct","comment":"short","reasoning":"short"}' + ) - except Exception as e: - tool_results.append({"tool": tool_call.get("tool", "unknown"), "error": str(e)}) + messages = self._initial_messages(grader_prompt, system_override=grader_system) + llm_response = await self._call_llm(messages, use_tools=False, system_override=grader_system) + return self._parse_grade_response(llm_response.content) + + def _build_system_prompt(self, tools: list[ToolDefinition]) -> str: + if not tools: + return "You are an AI assistant. If no tools are suitable, answer directly." + + tool_names = ", ".join(tool.name for tool in tools) + return ( + "You are an MXCP agent. Use the provided tools when they help answer the user. " + "Call tools only with JSON arguments that match their schema. " + "Tools available: " + f"{tool_names}. " + "When no tool fits, answer directly and concisely." + ) - # Add tool results to conversation - conversation_history.append({"role": "assistant", "content": response}) - conversation_history.append( - {"role": "system", "content": f"Tool results: {json.dumps(tool_results)}"} - ) + def _initial_messages( + self, prompt: str, system_override: str | None = None + ) -> list[dict[str, Any]]: + system_prompt = system_override or self.system_prompt + if self.model_type == "openai": + return [{"role": "system", "content": system_prompt}, {"role": "user", "content": prompt}] + + # Anthropic/Claude style keeps system separate; we include it in the API call. + return [{"role": "user", "content": [{"type": "text", "text": prompt}]}] + + def _append_assistant_message( + self, messages: list[dict[str, Any]], response: LLMResponse + ) -> None: + if self.model_type == "openai": + messages.append(response.raw_message or {"role": "assistant", "content": response.content}) + return + + # Claude/Anthropic + messages.append( + response.raw_message + or {"role": "assistant", "content": [{"type": "text", "text": response.content}]} + ) - # Continue conversation with tool results - prompt = "Please incorporate the tool results into your response." + def _build_tool_models( + self, tools: list[ToolDefinition] + ) -> dict[str, type[BaseModel]]: + models: dict[str, type[BaseModel]] = {} + + for tool in tools: + fields: dict[str, tuple[Any, Any]] = {} + for param in tool.parameters: + py_type = self._map_param_type(param.type) + default = param.default if param.default is not None else None + if param.required and default is None: + fields[param.name] = (py_type, ...) + else: + fields[param.name] = (py_type, default) + + models[tool.name] = create_model(f"{tool.name}_Args", **fields) # type: ignore[arg-type] + + return models + + def _build_tool_schemas( + self, tools: list[ToolDefinition] + ) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]: + openai_tools: list[dict[str, Any]] = [] + anthropic_tools: list[dict[str, Any]] = [] + + for tool in tools: + schema = self._tool_json_schema(tool) + openai_tools.append( + { + "type": "function", + "function": { + "name": tool.name, + "description": tool.description, + "parameters": schema, + }, + } + ) + anthropic_tools.append( + { + "name": tool.name, + "description": tool.description, + "input_schema": schema, + } + ) - # If we reach here, we hit the max iterations - return response, tool_calls_made + return openai_tools, anthropic_tools + + def _tool_json_schema(self, tool: ToolDefinition) -> dict[str, Any]: + properties: dict[str, Any] = {} + required: list[str] = [] + + for param in tool.parameters: + schema: dict[str, Any] = {"type": self._map_type_string(param.type)} + if param.description: + schema["description"] = param.description + if param.default is not None: + schema["default"] = param.default + + properties[param.name] = schema + if param.required: + required.append(param.name) + + return {"type": "object", "properties": properties, "required": required} + + def _map_param_type(self, param_type: str) -> Any: + mapping: dict[str, Any] = { + "string": str, + "integer": int, + "number": float, + "boolean": bool, + "object": dict[str, Any], + "array": list[Any], + } + return mapping.get(param_type.lower(), Any) + + def _map_type_string(self, param_type: str) -> str: + mapping = { + "string": "string", + "integer": "integer", + "number": "number", + "boolean": "boolean", + "object": "object", + "array": "array", + } + return mapping.get(param_type.lower(), "string") + + def _validate_tool_arguments( + self, tool_name: str, arguments: dict[str, Any] + ) -> dict[str, Any]: + model = self._tool_models.get(tool_name) + if not model: + return arguments - def _extract_tool_calls(self, response: str) -> list[dict[str, Any]]: - """Extract tool calls from LLM response""" try: - # Try to parse as JSON (single tool call) - tool_call = json.loads(response.strip()) - if isinstance(tool_call, dict) and "tool" in tool_call: - return [tool_call] - elif isinstance(tool_call, list): - # Multiple tool calls - return [tc for tc in tool_call if isinstance(tc, dict) and "tool" in tc] - except json.JSONDecodeError: - pass - - # If not pure JSON, look for JSON in the response - - json_pattern = r'\{[^}]*"tool"[^}]*\}' - matches = re.findall(json_pattern, response) + return cast(dict[str, Any], model.model_validate(arguments).model_dump()) + except ValidationError as exc: + raise ValueError(f"Invalid arguments for tool '{tool_name}': {exc}") from exc + + def _tool_result_message(self, record: ToolCallRecord) -> dict[str, Any]: + payload = record.result + if record.error: + payload = {"error": record.error} + + content = self._serialize_result(payload) + + if self.model_type == "openai": + return { + "role": "tool", + "tool_call_id": record.id or record.tool, + "name": record.tool, + "content": content, + } + + return { + "role": "user", + "content": [ + { + "type": "tool_result", + "tool_use_id": record.id or record.tool, + "content": content, + } + ], + } + + def _serialize_result(self, result: Any) -> str: + if isinstance(result, str): + return result + if result is None: + return "null" + if isinstance(result, (int, float, bool)): + return json.dumps(result) + try: + return json.dumps(result) + except TypeError: + return str(result) + def _extract_openai_response(self, data: dict[str, Any]) -> LLMResponse: + message = data["choices"][0]["message"] + content = message.get("content") or "" tool_calls = [] - for match in matches: + + for call in message.get("tool_calls", []) or []: + args_raw = call.get("function", {}).get("arguments", "{}") try: - tool_call = json.loads(match) - if "tool" in tool_call: - tool_calls.append(tool_call) + args = json.loads(args_raw) except json.JSONDecodeError: - continue - - return tool_calls - - async def _call_llm(self, prompt: str) -> str: - """Call the actual LLM API using the configured model""" - - # Log the full prompt in debug mode - logger.debug(f"=== LLM Request to {self.model_config.name} ===") - logger.debug(f"Full prompt:\n{prompt}") - logger.debug("=== End of prompt ===") + args = {} + tool_calls.append( + LLMToolCall(id=call.get("id"), tool=call["function"]["name"], arguments=args) + ) - model_type = self.model_config.get_type() + return LLMResponse(content=content, tool_calls=tool_calls, raw_message=message) - if model_type == "claude": - return await self._call_claude(prompt) - elif model_type == "openai": - return await self._call_openai(prompt) - else: - raise ValueError(f"Unknown model type: {model_type}") + def _extract_anthropic_response(self, data: dict[str, Any]) -> LLMResponse: + content_blocks = data.get("content", []) + tool_calls = [] + text_parts = [] + + for block in content_blocks: + if block.get("type") == "tool_use": + tool_calls.append( + LLMToolCall( + id=block.get("id"), + tool=block.get("name"), + arguments=block.get("input") or {}, + ) + ) + elif block.get("type") == "text": + text_parts.append(block.get("text") or "") + + return LLMResponse( + content="\n".join(text_parts).strip(), + tool_calls=tool_calls, + raw_message={"role": "assistant", "content": content_blocks}, + ) - async def _call_claude(self, prompt: str) -> str: - """Call Claude API""" + async def _call_llm( + self, + messages: list[dict[str, Any]], + use_tools: bool = True, + system_override: str | None = None, + ) -> LLMResponse: + if self.model_type == "claude": + return await self._call_claude(messages, use_tools, system_override) + if self.model_type == "openai": + return await self._call_openai(messages, use_tools) + raise ValueError(f"Unknown model type: {self.model_type}") + + async def _call_claude( + self, messages: list[dict[str, Any]], use_tools: bool, system_override: str | None + ) -> LLMResponse: + payload: dict[str, Any] = { + "model": self.model_config.name, + "system": system_override or self.system_prompt, + "messages": messages, + "max_output_tokens": 4096, + } + + if use_tools and self._anthropic_tools: + payload["tools"] = self._anthropic_tools + + payload.update(self.model_config.options or {}) async with httpx.AsyncClient() as client: response = await client.post( @@ -312,26 +392,35 @@ async def _call_claude(self, prompt: str) -> str: "anthropic-version": "2023-06-01", "content-type": "application/json", }, - json={ - "model": self.model_config.name, - "messages": [{"role": "user", "content": prompt}], - "max_tokens": 4096, - }, + json=payload, timeout=self.model_config.timeout, ) - response.raise_for_status() + try: + response.raise_for_status() + except httpx.HTTPStatusError as exc: + body = (exc.response.text or "").strip() + snippet = body[:500] if body else "No response body" + raise ValueError( + f"Claude API call failed ({exc.response.status_code} {exc.response.reason_phrase}): " + f"{snippet}" + ) from exc + data = response.json() + logger.debug("Claude response: %s", data) + return self._extract_anthropic_response(data) - # Log response in debug mode - logger.debug(f"=== LLM Response from {self.model_config.name} ===") - logger.debug(f"Response: {data['content'][0]['text'][:500]}...") # First 500 chars - logger.debug("=== End of response ===") + async def _call_openai(self, messages: list[dict[str, Any]], use_tools: bool) -> LLMResponse: + payload: dict[str, Any] = { + "model": self.model_config.name, + "messages": messages, + } - return cast(str, data["content"][0]["text"]) + if use_tools and self._openai_tools: + payload["tools"] = self._openai_tools + payload["tool_choice"] = "auto" - async def _call_openai(self, prompt: str) -> str: - """Call OpenAI API""" + payload.update(self.model_config.options or {}) async with httpx.AsyncClient() as client: response = await client.post( @@ -340,25 +429,57 @@ async def _call_openai(self, prompt: str) -> str: "Authorization": f"Bearer {self.model_config.api_key}", "Content-Type": "application/json", }, - json={ - "model": self.model_config.name, - "messages": [ - {"role": "system", "content": "You are a helpful assistant."}, - {"role": "user", "content": prompt}, - ], - "max_tokens": 4096, - }, + json=payload, timeout=self.model_config.timeout, ) - response.raise_for_status() - data = response.json() + try: + response.raise_for_status() + except httpx.HTTPStatusError as exc: + body = (exc.response.text or "").strip() + snippet = body[:500] if body else "No response body" + raise ValueError( + f"OpenAI API call failed ({exc.response.status_code} {exc.response.reason_phrase}): " + f"{snippet}" + ) from exc - # Log response in debug mode - logger.debug(f"=== LLM Response from {self.model_config.name} ===") - logger.debug( - f"Response: {data['choices'][0]['message']['content'][:500]}..." - ) # First 500 chars - logger.debug("=== End of response ===") + data = response.json() + logger.debug("OpenAI response: %s", data) + return self._extract_openai_response(data) - return cast(str, data["choices"][0]["message"]["content"]) + def _parse_grade_response(self, content: str) -> dict[str, str]: + def _parse(obj: str) -> dict[str, str] | None: + try: + data = json.loads(obj) + except json.JSONDecodeError: + return None + if not isinstance(data, dict): + return None + result = str(data.get("result", "unknown")).lower() + comment = str(data.get("comment", "") or "").strip() + reasoning = str(data.get("reasoning", "") or "").strip() + return {"result": result, "comment": comment, "reasoning": reasoning} + + # Try direct parse + parsed = _parse(content) + if parsed: + return parsed + + # Strip markdown code fences + trimmed = content.strip() + if trimmed.startswith("```") and "```" in trimmed[3:]: + inner = trimmed.strip("`") + parsed = _parse(inner) + if parsed: + return parsed + + # Extract first JSON object from the text + start = trimmed.find("{") + end = trimmed.rfind("}") + if start != -1 and end != -1 and end > start: + parsed = _parse(trimmed[start : end + 1]) + if parsed: + return parsed + + logger.debug("Failed to parse grade JSON: %s", content) + return {"result": "unknown", "comment": trimmed[:200], "reasoning": ""} diff --git a/src/mxcp/server/core/config/models.py b/src/mxcp/server/core/config/models.py index 98622b4c..c2a3439a 100644 --- a/src/mxcp/server/core/config/models.py +++ b/src/mxcp/server/core/config/models.py @@ -397,6 +397,7 @@ class UserModelConfigModel(BaseModel): base_url: str | None = None timeout: int | None = None max_retries: int | None = None + options: dict[str, Any] = Field(default_factory=dict) class UserModelsConfigModel(BaseModel): diff --git a/src/mxcp/server/definitions/evals/models.py b/src/mxcp/server/definitions/evals/models.py index 78e489a0..0c822df2 100644 --- a/src/mxcp/server/definitions/evals/models.py +++ b/src/mxcp/server/definitions/evals/models.py @@ -22,6 +22,7 @@ class EvalAssertionsModel(EvalBaseModel): must_not_call: list[str] | None = None answer_contains: list[str] | None = None answer_not_contains: list[str] | None = None + expected_answer: str | None = None class EvalTestModel(EvalBaseModel): diff --git a/src/mxcp/server/executor/runners/tool.py b/src/mxcp/server/executor/runners/tool.py index 48b9f65d..dacd6014 100644 --- a/src/mxcp/server/executor/runners/tool.py +++ b/src/mxcp/server/executor/runners/tool.py @@ -6,12 +6,14 @@ """ import logging +from pathlib import Path from typing import Any from mxcp.sdk.auth import UserContext from mxcp.sdk.executor import ExecutionContext, ExecutionEngine from mxcp.server.definitions.endpoints.models import EndpointDefinitionModel from mxcp.server.definitions.endpoints.utils import detect_language_from_source, extract_source_info +from mxcp.server.core.config.site_config import find_repo_root logger = logging.getLogger(__name__) @@ -41,7 +43,7 @@ class EndpointToolExecutor: >>> llm_executor = LLMExecutor(model_config, tool_definitions, tool_executor) """ - def __init__(self, engine: ExecutionEngine, endpoints: list[EndpointDefinitionModel]): + def __init__(self, engine: ExecutionEngine, endpoints: list[tuple[EndpointDefinitionModel, Path]]): """Initialize the endpoint tool executor. Args: @@ -49,15 +51,15 @@ def __init__(self, engine: ExecutionEngine, endpoints: list[EndpointDefinitionMo endpoints: List of endpoint definitions """ self.engine = engine - self.endpoints = endpoints + self.endpoints = [ep for ep, _ in endpoints] # Create lookup map for faster tool resolution - self._tool_map: dict[str, EndpointDefinitionModel] = {} - for endpoint_def in endpoints: + self._tool_map: dict[str, tuple[EndpointDefinitionModel, Path]] = {} + for endpoint_def, path in endpoints: if endpoint_def.tool: - self._tool_map[endpoint_def.tool.name] = endpoint_def + self._tool_map[endpoint_def.tool.name] = (endpoint_def, path) elif endpoint_def.resource: - self._tool_map[endpoint_def.resource.uri] = endpoint_def + self._tool_map[endpoint_def.resource.uri] = (endpoint_def, path) logger.info(f"EndpointToolExecutor initialized with {len(endpoints)} endpoints") @@ -79,17 +81,18 @@ async def execute_tool( Exception: If execution fails """ # Find the endpoint - endpoint_def = self._tool_map.get(tool_name) - if not endpoint_def: + entry = self._tool_map.get(tool_name) + if not entry: available_tools = list(self._tool_map.keys()) raise ValueError(f"Tool '{tool_name}' not found. Available tools: {available_tools}") + endpoint_def, endpoint_path = entry # Create execution context context = ExecutionContext(user_context=user_context) # Determine the source code and language - source_info = self._get_source_code(endpoint_def, tool_name) - language = self._get_language(endpoint_def, tool_name, source_info) + source_info, source_path = self._get_source_code(endpoint_def, endpoint_path, tool_name) + language = self._get_language(endpoint_def, tool_name, source_path) logger.debug(f"Executing tool '{tool_name}' with language '{language}'") @@ -106,8 +109,10 @@ async def execute_tool( logger.error(f"Tool '{tool_name}' execution failed: {e}") raise - def _get_source_code(self, endpoint_def: EndpointDefinitionModel, tool_name: str) -> str: - """Extract source code from endpoint definition.""" + def _get_source_code( + self, endpoint_def: EndpointDefinitionModel, endpoint_path: Path, tool_name: str + ) -> tuple[str, str | None]: + """Extract source code from endpoint definition, loading files when needed.""" # Get the tool or resource definition source = None if endpoint_def.tool: @@ -119,10 +124,37 @@ def _get_source_code(self, endpoint_def: EndpointDefinitionModel, tool_name: str raise ValueError(f"No source found for endpoint '{tool_name}'") source_type, source_value = extract_source_info(source) - return source_value + if source_type == "file": + relative_path = Path(source_value) + candidates = [] + if not relative_path.is_absolute(): + candidates.append((endpoint_path.parent / relative_path).resolve()) + try: + repo_root = find_repo_root() + except FileNotFoundError: + repo_root = Path.cwd() + candidates.append((repo_root / relative_path).resolve()) + else: + candidates.append(relative_path.resolve()) + + source_path = next((c for c in candidates if c.exists()), None) + + if not source_path: + # Report first candidate for clarity + candidate_msg = candidates[0] if candidates else relative_path + raise ValueError( + f"Source file not found for endpoint '{tool_name}': {candidate_msg}" + ) + + try: + return source_path.read_text(), str(source_path) + except Exception as exc: # noqa: BLE001 + raise ValueError(f"Failed to read source file for endpoint '{tool_name}': {exc}") from exc + + return source_value, None def _get_language( - self, endpoint_def: EndpointDefinitionModel, tool_name: str, source_info: str + self, endpoint_def: EndpointDefinitionModel, tool_name: str, source_path: str | None ) -> str: """Determine the programming language for the endpoint.""" # Get the tool or resource definition @@ -135,4 +167,4 @@ def _get_language( if not source: raise ValueError(f"No source found for endpoint '{tool_name}'") - return detect_language_from_source(source, source_info) + return detect_language_from_source(source, source_path) diff --git a/src/mxcp/server/interfaces/cli/evals.py b/src/mxcp/server/interfaces/cli/evals.py index 08d68369..2f2b545f 100644 --- a/src/mxcp/server/interfaces/cli/evals.py +++ b/src/mxcp/server/interfaces/cli/evals.py @@ -168,10 +168,8 @@ def format_eval_results(results: dict[str, Any], debug: bool = False) -> str: output.append( f" {click.style('✗', fg='red')} {test['name']} {click.style(f'({test_time:.2f}s)', fg='bright_black')}" ) - if test.get("error") and debug: - output.append( - f" {click.style('Error:', fg='red')} {test['error']}" - ) + if test.get("error"): + output.append(f" {click.style('Error:', fg='red')} {test['error']}") for failure in test.get("failures", []): output.append(f" {click.style('💡', fg='yellow')} {failure}") diff --git a/src/mxcp/server/services/evals/service.py b/src/mxcp/server/services/evals/service.py index 0530fc5f..1cde0028 100644 --- a/src/mxcp/server/services/evals/service.py +++ b/src/mxcp/server/services/evals/service.py @@ -1,5 +1,6 @@ import logging import time +from pathlib import Path from typing import Any from mxcp.sdk.auth import UserContext @@ -46,6 +47,7 @@ def _create_model_config(model: str, user_config: UserConfigModel) -> ModelConfi model_type = model_config.type api_key = model_config.api_key + options = model_config.options or {} if not api_key: raise ValueError(f"No API key configured for model '{model}'") @@ -53,38 +55,42 @@ def _create_model_config(model: str, user_config: UserConfigModel) -> ModelConfi if model_type == "claude": base_url = model_config.base_url or "https://api.anthropic.com" timeout = model_config.timeout or 30 - return ClaudeConfig(name=model, api_key=api_key, base_url=base_url, timeout=timeout) + return ClaudeConfig( + name=model, api_key=api_key, base_url=base_url, timeout=timeout, options=options + ) elif model_type == "openai": base_url = model_config.base_url or "https://api.openai.com/v1" timeout = model_config.timeout or 30 - return OpenAIConfig(name=model, api_key=api_key, base_url=base_url, timeout=timeout) + return OpenAIConfig( + name=model, api_key=api_key, base_url=base_url, timeout=timeout, options=options + ) else: raise ValueError(f"Unknown model type: {model_type}") -def _load_endpoints(site_config: SiteConfigModel) -> list[EndpointDefinitionModel]: +def _load_endpoints(site_config: SiteConfigModel) -> list[tuple[EndpointDefinitionModel, Path]]: """Load all available endpoints. Args: site_config: Site configuration for endpoint discovery Returns: - List of endpoint definitions + List of (endpoint definition, file path) """ loader = EndpointLoader(site_config) - endpoints: list[EndpointDefinitionModel] = [] + endpoints: list[tuple[EndpointDefinitionModel, Path]] = [] discovered = loader.discover_endpoints() - for _path, endpoint_def, error in discovered: + for path, endpoint_def, error in discovered: if error is None and endpoint_def and (endpoint_def.tool or endpoint_def.resource): # Only include endpoints that have a tool or resource definition - endpoints.append(endpoint_def) + endpoints.append((endpoint_def, path)) return endpoints def _convert_endpoints_to_tool_definitions( - endpoints: list[EndpointDefinitionModel], + endpoints: list[tuple[EndpointDefinitionModel, Path]], ) -> list[ToolDefinition]: """Convert endpoint definitions to ToolDefinition objects for the LLM. @@ -96,7 +102,7 @@ def _convert_endpoints_to_tool_definitions( """ tool_definitions = [] - for endpoint_def in endpoints: + for endpoint_def, _endpoint_path in endpoints: if endpoint_def.tool: tool = endpoint_def.tool @@ -171,6 +177,15 @@ def _convert_endpoints_to_tool_definitions( return tool_definitions +def _compact_text(*parts: str, max_length: int | None = 240) -> str: + """Join parts, collapse whitespace, and optionally truncate for display.""" + text = " ".join(p.strip() for p in parts if p).strip() + text = " ".join(text.split()) + if max_length and len(text) > max_length: + return text[: max_length - 3] + "..." + return text + + async def run_eval_suite( suite_name: str, user_config: UserConfigModel, @@ -257,13 +272,26 @@ async def run_eval_suite( try: # Execute the prompt - response, tool_calls = await executor.execute_prompt( + agent_result = await executor.execute_prompt( test.prompt, user_context=test_user_context ) + response = agent_result.answer + tool_calls = agent_result.tool_calls + # Evaluate assertions failures = [] assertions = test.assertions + evaluation: dict[str, Any] | None = None + + # Surface tool execution errors directly + for call in tool_calls: + if call.error: + failures.append( + _compact_text( + f"Tool '{call.tool}' failed: {call.error}", max_length=None + ) + ) # Check must_call assertions if assertions.must_call: @@ -273,8 +301,8 @@ async def run_eval_suite( found = False for call in tool_calls: - if call["tool"] == expected_tool: - actual_args = call.get("arguments", {}) + if call.tool == expected_tool: + actual_args = call.arguments or {} if all(actual_args.get(k) == v for k, v in expected_args.items()): found = True break @@ -287,7 +315,7 @@ async def run_eval_suite( # Check must_not_call assertions if assertions.must_not_call: for forbidden_tool in assertions.must_not_call: - if any(call["tool"] == forbidden_tool for call in tool_calls): + if any(call.tool == forbidden_tool for call in tool_calls): failures.append( f"Tool '{forbidden_tool}' was called but should not have been" ) @@ -306,6 +334,24 @@ async def run_eval_suite( if forbidden_text.lower() in response.lower(): failures.append(f"Forbidden text '{forbidden_text}' found in response") + if assertions.expected_answer: + evaluation = await executor.evaluate_expected_answer( + response, assertions.expected_answer + ) + grade = (evaluation.get("result") or "").lower() + comment = evaluation.get("comment") or "Model answer did not match expected" + reasoning = evaluation.get("reasoning") or "" + detail = _compact_text( + f"LLM answer: {response}", + f"Expected: {assertions.expected_answer}", + f"Grade: {grade or 'unknown'}", + f"Comment: {comment}", + f"Reasoning: {reasoning or 'n/a'}", + max_length=None, # show the full detail in output + ) + if grade != "correct": + failures.append(detail) + test_time = time.time() - test_start tests.append( @@ -315,7 +361,21 @@ async def run_eval_suite( "passed": len(failures) == 0, "failures": failures, "time": test_time, - "details": {"response": response, "tool_calls": tool_calls}, + "details": { + "response": response, + "tool_calls": [ + { + "id": call.id, + "tool": call.tool, + "arguments": call.arguments, + "result": call.result, + "error": call.error, + } + for call in tool_calls + ], + "expected_answer": assertions.expected_answer, + "expected_answer_evaluation": evaluation, + }, } ) @@ -399,8 +459,10 @@ async def run_all_evals( except Exception: relative_path = str(file_path) - # Map new result structure to old structure for backward compatibility - all_passed = result.get("summary", {}).get("failed", 1) == 0 if result else False + # Determine pass/fail + all_passed = bool(result.get("all_passed")) + if not all_passed and result.get("summary"): + all_passed = result["summary"].get("failed", 1) == 0 suites.append( { diff --git a/tests/sdk/evals/test_executor.py b/tests/sdk/evals/test_executor.py index 461084b4..a6368830 100644 --- a/tests/sdk/evals/test_executor.py +++ b/tests/sdk/evals/test_executor.py @@ -1,4 +1,4 @@ -"""Tests for mxcp.sdk.evals.executor module.""" +"""Tests for the agent-style LLM executor.""" from typing import Any from unittest.mock import AsyncMock @@ -6,14 +6,9 @@ import pytest from mxcp.sdk.auth import UserContext -from mxcp.sdk.evals import ( - ClaudeConfig, - LLMExecutor, - OpenAIConfig, - ParameterDefinition, - ToolDefinition, -) -from mxcp.sdk.validator import TypeSchema +from mxcp.sdk.evals import ClaudeConfig, ParameterDefinition, ToolDefinition +from mxcp.sdk.evals.executor import AgentResult, LLMExecutor, LLMResponse, LLMToolCall +import httpx class MockToolExecutor: @@ -21,280 +16,147 @@ class MockToolExecutor: def __init__(self, responses: dict[str, Any] | None = None): self.responses = responses or {} - self.calls = [] + self.calls: list[dict[str, Any]] = [] async def execute_tool( self, tool_name: str, arguments: dict[str, Any], user_context: UserContext | None = None ) -> Any: - """Mock tool execution that records calls and returns predefined responses.""" self.calls.append( {"tool_name": tool_name, "arguments": arguments, "user_context": user_context} ) if tool_name in self.responses: - result = self.responses[tool_name] - if isinstance(result, Exception): - raise result - return result + value = self.responses[tool_name] + if isinstance(value, Exception): + raise value + return value - return f"Mock result for {tool_name}" + return {"echo": arguments} class TestLLMExecutor: - """Test cases for LLMExecutor.""" - - def setup_method(self): - """Set up test fixtures.""" - self.model_config = ClaudeConfig(name="claude-3-haiku", api_key="test-key") - + def setup_method(self) -> None: + self.model_config = ClaudeConfig(name="claude-test", api_key="key") self.tools = [ ToolDefinition( name="get_weather", - description="Get current weather for a location", - parameters=[ - ParameterDefinition(name="location", type="string", description="City name") - ], - ), - ToolDefinition( - name="calculate", - description="Perform mathematical calculations", - parameters=[ - ParameterDefinition( - name="expression", - type="string", - description="Mathematical expression to evaluate", - ) - ], - ), + description="Weather lookup", + parameters=[ParameterDefinition(name="location", type="string", required=True)], + ) ] - - self.tool_executor = MockToolExecutor( - {"get_weather": {"temperature": 22, "condition": "sunny"}, "calculate": 42} - ) - + self.tool_executor = MockToolExecutor({"get_weather": {"temperature": 20}}) self.executor = LLMExecutor(self.model_config, self.tools, self.tool_executor) - def test_initialization(self): - """Test LLMExecutor initialization.""" - assert self.executor.model_config == self.model_config - assert self.executor.available_tools == self.tools - assert self.executor.tool_executor == self.tool_executor - - def test_format_tools_for_prompt(self): - """Test tool formatting for prompts.""" - formatted = self.executor._format_tools_for_prompt() - - assert "=== AVAILABLE TOOLS ===" in formatted - assert "Tool: get_weather" in formatted - assert "Tool: calculate" in formatted - assert "Description: Get current weather for a location" in formatted - assert "location (string): City name" in formatted - - def test_format_tools_for_prompt_empty(self): - """Test tool formatting with no tools.""" - executor = LLMExecutor(self.model_config, [], self.tool_executor) - formatted = executor._format_tools_for_prompt() - assert formatted == "No tools available." - - def test_get_claude_prompt(self): - """Test Claude-specific prompt formatting.""" - prompt = self.executor._get_claude_prompt( - "What's the weather in Paris?", "Mock tools", None - ) - - assert "You are a helpful assistant" in prompt - assert "Mock tools" in prompt - assert "Human: What's the weather in Paris?" in prompt - assert '{"tool": "tool_name"' in prompt - - def test_get_openai_prompt(self): - """Test OpenAI-specific prompt formatting.""" - prompt = self.executor._get_openai_prompt("Calculate 2+2", "Mock tools", None) - - assert "You are a helpful assistant" in prompt - assert "Mock tools" in prompt - assert "User: Calculate 2+2" in prompt - assert '{"tool": "tool_name"' in prompt - - def test_extract_tool_calls_single(self): - """Test extraction of single tool call.""" - response = '{"tool": "get_weather", "arguments": {"location": "Paris"}}' - calls = self.executor._extract_tool_calls(response) - - assert len(calls) == 1 - assert calls[0]["tool"] == "get_weather" - assert calls[0]["arguments"]["location"] == "Paris" - - def test_extract_tool_calls_multiple(self): - """Test extraction of multiple tool calls.""" - response = '[{"tool": "get_weather", "arguments": {"location": "Paris"}}, {"tool": "calculate", "arguments": {"expression": "2+2"}}]' - calls = self.executor._extract_tool_calls(response) - - assert len(calls) == 2 - assert calls[0]["tool"] == "get_weather" - assert calls[1]["tool"] == "calculate" - - def test_extract_tool_calls_none(self): - """Test extraction when no tool calls present.""" - response = "The weather in Paris is sunny and 22 degrees." - calls = self.executor._extract_tool_calls(response) - - assert len(calls) == 0 - - def test_extract_tool_calls_invalid_json(self): - """Test extraction with invalid JSON.""" - response = "Invalid JSON {tool: get_weather}" - calls = self.executor._extract_tool_calls(response) - - assert len(calls) == 0 - @pytest.mark.asyncio - async def test_execute_prompt_no_tools(self): - """Test prompt execution without tool calls.""" - # Mock the LLM call to return a simple response - self.executor._call_llm = AsyncMock(return_value="Hello! I'm a helpful assistant.") + async def test_execute_prompt_no_tools(self) -> None: + """Returns final answer when no tool calls are present.""" + self.executor._call_llm = AsyncMock( # type: ignore[assignment] + return_value=LLMResponse(content="Hello!", tool_calls=[]) + ) - response, tool_calls = await self.executor.execute_prompt("Hello") + result = await self.executor.execute_prompt("Hi") - assert response == "Hello! I'm a helpful assistant." - assert len(tool_calls) == 0 - assert len(self.tool_executor.calls) == 0 + assert isinstance(result, AgentResult) + assert result.answer == "Hello!" + assert result.tool_calls == [] + assert self.tool_executor.calls == [] @pytest.mark.asyncio - async def test_execute_prompt_with_tools(self): - """Test prompt execution with tool calls.""" - # Mock LLM to first return tool call, then final response - self.executor._call_llm = AsyncMock( - side_effect=[ - '{"tool": "get_weather", "arguments": {"location": "Paris"}}', - "The weather in Paris is sunny and 22 degrees.", - ] + async def test_execute_prompt_with_tool_call(self) -> None: + """Executes tool calls and returns final answer.""" + first = LLMResponse( + content="", + tool_calls=[LLMToolCall(id="1", tool="get_weather", arguments={"location": "Paris"})], ) + second = LLMResponse(content="Sunny", tool_calls=[]) + self.executor._call_llm = AsyncMock(side_effect=[first, second]) # type: ignore[assignment] - user_context = UserContext(provider="test", user_id="test-user", username="testuser") - - response, tool_calls = await self.executor.execute_prompt( - "What's the weather in Paris?", user_context=user_context - ) + user_ctx = UserContext(provider="test", user_id="u1", username="user") - assert response == "The weather in Paris is sunny and 22 degrees." - assert len(tool_calls) == 1 - assert tool_calls[0]["tool"] == "get_weather" - assert tool_calls[0]["arguments"]["location"] == "Paris" + result = await self.executor.execute_prompt("Weather?", user_context=user_ctx) - # Verify tool executor was called correctly - assert len(self.tool_executor.calls) == 1 - call = self.tool_executor.calls[0] - assert call["tool_name"] == "get_weather" - assert call["arguments"]["location"] == "Paris" - assert call["user_context"] == user_context + assert result.answer == "Sunny" + assert len(result.tool_calls) == 1 + call = result.tool_calls[0] + assert call.tool == "get_weather" + assert call.arguments["location"] == "Paris" + assert call.result == {"temperature": 20} + assert call.error is None + assert self.tool_executor.calls[0]["user_context"] == user_ctx @pytest.mark.asyncio - async def test_execute_prompt_tool_error(self): - """Test prompt execution when tool execution fails.""" - # Configure tool executor to raise an error - self.tool_executor.responses["get_weather"] = ValueError("Tool failed") - - # Mock LLM to return tool call, then final response - self.executor._call_llm = AsyncMock( - side_effect=[ - '{"tool": "get_weather", "arguments": {"location": "Paris"}}', - "I'm sorry, I couldn't get the weather information.", - ] + async def test_execute_prompt_tool_error(self) -> None: + """Records tool errors without failing the loop.""" + self.tool_executor.responses["get_weather"] = ValueError("boom") + first = LLMResponse( + content="", + tool_calls=[LLMToolCall(id="t1", tool="get_weather", arguments={"location": "Rome"})], ) + second = LLMResponse(content="Could not fetch", tool_calls=[]) + self.executor._call_llm = AsyncMock(side_effect=[first, second]) # type: ignore[assignment] - response, tool_calls = await self.executor.execute_prompt("What's the weather in Paris?") + result = await self.executor.execute_prompt("Weather?") - assert response == "I'm sorry, I couldn't get the weather information." - assert len(tool_calls) == 1 - - # Verify the LLM received the tool error in the conversation - assert self.executor._call_llm.call_count == 2 + assert result.answer == "Could not fetch" + assert len(result.tool_calls) == 1 + assert result.tool_calls[0].error == "boom" @pytest.mark.asyncio - async def test_execute_prompt_max_iterations(self): - """Test that max iterations prevents infinite loops.""" - # Mock LLM to always return tool calls - self.executor._call_llm = AsyncMock( - return_value='{"tool": "get_weather", "arguments": {"location": "Paris"}}' - ) - - response, tool_calls = await self.executor.execute_prompt("Weather?") - - # Should hit max iterations (10) and return the last response - assert len(tool_calls) == 10 - assert self.executor._call_llm.call_count == 10 - - -class TestToolDefinition: - """Test cases for ToolDefinition.""" - - def test_to_prompt_format_basic(self): - """Test basic tool formatting.""" - tool = ToolDefinition(name="test_tool", description="A test tool") - - formatted = tool.to_prompt_format() - assert "Tool: test_tool" in formatted - assert "Description: A test tool" in formatted - assert "Parameters: None" in formatted - - def test_to_prompt_format_with_parameters(self): - """Test tool formatting with parameters.""" - tool = ToolDefinition( - name="calculator", - description="Perform calculations", - parameters=[ - ParameterDefinition( - name="expression", type="string", description="Math expression", default="0" - ), - ParameterDefinition(name="precision", type="integer", description="Decimal places"), - ], - return_type=TypeSchema(type="number", description="Result"), - tags=["math", "utility"], - ) + async def test_execute_prompt_max_turns(self) -> None: + """Stops after max turns when the model keeps calling tools.""" + + async def _loop_response(*_: Any, **__: Any) -> LLMResponse: + return LLMResponse( + content="", + tool_calls=[ + LLMToolCall(id=None, tool="get_weather", arguments={"location": "Paris"}) + ], + ) - formatted = tool.to_prompt_format() - assert "Tool: calculator" in formatted - assert "Description: Perform calculations" in formatted - assert "expression (string) [default: 0]: Math expression" in formatted - assert "precision (integer): Decimal places" in formatted - assert "Returns: number - Result" in formatted - assert "Tags: math, utility" in formatted + self.executor._call_llm = AsyncMock(side_effect=_loop_response) # type: ignore[assignment] + result = await self.executor.execute_prompt("Weather?") -class TestModelConfigs: - """Test cases for model configurations.""" + assert len(result.tool_calls) == 10 + assert result.answer == "" - def test_claude_config(self): - """Test Claude configuration.""" - config = ClaudeConfig( - name="claude-3-haiku", api_key="test-key", base_url="https://api.custom.com", timeout=60 + def test_parse_grade_response(self) -> None: + """Parses grading JSON with fallbacks.""" + parsed = self.executor._parse_grade_response( # type: ignore[attr-defined] + '{"result":"correct","comment":"ok","reasoning":"short"}' ) + assert parsed["result"] == "correct" + assert parsed["comment"] == "ok" - assert config.get_type() == "claude" - assert config.name == "claude-3-haiku" - assert config.api_key == "test-key" - assert config.base_url == "https://api.custom.com" - assert config.timeout == 60 - - def test_openai_config(self): - """Test OpenAI configuration.""" - config = OpenAIConfig( - name="gpt-4", api_key="test-key", base_url="https://api.custom.com", timeout=45 - ) + fallback = self.executor._parse_grade_response("not json") # type: ignore[attr-defined] + assert fallback["result"] == "unknown" - assert config.get_type() == "openai" - assert config.name == "gpt-4" - assert config.api_key == "test-key" - assert config.base_url == "https://api.custom.com" - assert config.timeout == 45 - - def test_config_defaults(self): - """Test default values for configs.""" - claude = ClaudeConfig(name="claude", api_key="key") - assert claude.base_url == "https://api.anthropic.com" - assert claude.timeout == 30 - - openai = OpenAIConfig(name="gpt", api_key="key") - assert openai.base_url == "https://api.openai.com/v1" - assert openai.timeout == 30 + @pytest.mark.asyncio + async def test_openai_http_error_includes_body(self, monkeypatch) -> None: + """HTTP errors should include status and body for easier debugging.""" + + async def fake_post(*args: Any, **kwargs: Any): # noqa: ANN401 + return httpx.Response( + status_code=400, + request=httpx.Request("POST", "https://api.openai.com/v1/chat/completions"), + text="bad request details", + ) + + monkeypatch.setattr(httpx.AsyncClient, "post", fake_post) + + with pytest.raises(ValueError) as excinfo: + await self.executor._call_openai([], use_tools=False) # type: ignore[attr-defined] + + message = str(excinfo.value) + assert "OpenAI API call failed" in message + assert "400" in message + assert "bad request details" in message + + def test_parse_grade_response_code_fence(self) -> None: + """Parses grading JSON even when wrapped in code fences.""" + fenced = """```json + {"result":"partially correct","comment":"ok","reasoning":"short"} + ```""" + parsed = self.executor._parse_grade_response(fenced) # type: ignore[attr-defined] + assert parsed["result"] == "partially correct" + assert parsed["comment"] == "ok" diff --git a/tests/server/test_evals_tool_executor.py b/tests/server/test_evals_tool_executor.py index 15daf3e0..87a740b9 100644 --- a/tests/server/test_evals_tool_executor.py +++ b/tests/server/test_evals_tool_executor.py @@ -1,5 +1,6 @@ """Tests for EndpointToolExecutor integration.""" +import os from typing import Any import pytest @@ -8,6 +9,7 @@ from mxcp.sdk.executor import ExecutionContext from mxcp.server.definitions.endpoints.models import EndpointDefinitionModel, SourceDefinitionModel from mxcp.server.executor.runners.tool import EndpointToolExecutor +from pathlib import Path class MockExecutionEngine: @@ -48,50 +50,62 @@ def setup_method(self): } ) - self.endpoints: list[EndpointDefinitionModel] = [ - EndpointDefinitionModel.model_validate( - { - "mxcp": 1, - "tool": { - "name": "get_date", - "description": "Get current date", - "parameters": [], - "source": {"code": "SELECT current_date()"}, - }, - } + self.endpoints: list[tuple[EndpointDefinitionModel, Path]] = [ + ( + EndpointDefinitionModel.model_validate( + { + "mxcp": 1, + "tool": { + "name": "get_date", + "description": "Get current date", + "parameters": [], + "source": {"code": "SELECT current_date()"}, + }, + } + ), + Path("endpoints/get_date.yml"), ), - EndpointDefinitionModel.model_validate( - { - "mxcp": 1, - "tool": { - "name": "calculate", - "description": "Calculate expression", - "parameters": [{"name": "expr", "type": "string"}], - "source": {"code": "return 2 + 2", "language": "python"}, - }, - } + ( + EndpointDefinitionModel.model_validate( + { + "mxcp": 1, + "tool": { + "name": "calculate", + "description": "Calculate expression", + "parameters": [{"name": "expr", "type": "string"}], + "source": {"code": "return 2 + 2", "language": "python"}, + }, + } + ), + Path("endpoints/calculate.yml"), ), - EndpointDefinitionModel.model_validate( - { - "mxcp": 1, - "tool": { - "name": "get_weather", - "description": "Get weather info", - "parameters": [{"name": "location", "type": "string"}], - "source": {"file": "weather.py", "language": "python"}, - }, - } + ( + EndpointDefinitionModel.model_validate( + { + "mxcp": 1, + "tool": { + "name": "get_weather", + "description": "Get weather info", + "parameters": [{"name": "location", "type": "string"}], + "source": {"code": "weather.py", "language": "python"}, + }, + } + ), + Path("endpoints/get_weather.yml"), ), - EndpointDefinitionModel.model_validate( - { - "mxcp": 1, - "resource": { - "uri": "data://users", - "description": "User data resource", - "parameters": [{"name": "limit", "type": "integer"}], - "source": {"code": "SELECT * FROM users LIMIT $limit"}, - }, - } + ( + EndpointDefinitionModel.model_validate( + { + "mxcp": 1, + "resource": { + "uri": "data://users", + "description": "User data resource", + "parameters": [{"name": "limit", "type": "integer"}], + "source": {"code": "SELECT * FROM users LIMIT $limit"}, + }, + } + ), + Path("endpoints/users.yml"), ), ] @@ -100,7 +114,7 @@ def setup_method(self): def test_initialization(self): """Test EndpointToolExecutor initialization.""" assert self.executor.engine == self.engine - assert self.executor.endpoints == self.endpoints + assert len(self.executor.endpoints) == len(self.endpoints) assert len(self.executor._tool_map) == 4 assert "get_date" in self.executor._tool_map assert "data://users" in self.executor._tool_map @@ -139,6 +153,8 @@ async def test_execute_tool_with_language(self): @pytest.mark.asyncio async def test_execute_tool_with_file(self): """Test executing a tool with file reference.""" + tmp_file = Path("weather.py") + tmp_file.write_text("weather.py") result = await self.executor.execute_tool("get_weather", {"location": "Paris"}) assert result == {"temperature": 22, "condition": "sunny"} @@ -149,6 +165,8 @@ async def test_execute_tool_with_file(self): assert call["language"] == "python" assert call["source_code"] == "weather.py" assert call["params"] == {"location": "Paris"} + if tmp_file.exists(): + tmp_file.unlink() @pytest.mark.asyncio async def test_execute_resource(self): @@ -205,7 +223,9 @@ async def test_execute_tool_no_source(self): "source", SourceDefinitionModel.model_construct(code=None, file=None), ) - endpoints_no_source: list[EndpointDefinitionModel] = [endpoint] + endpoints_no_source: list[tuple[EndpointDefinitionModel, Path]] = [ + (endpoint, Path("endpoints/broken.yml")) + ] executor = EndpointToolExecutor(self.engine, endpoints_no_source) @@ -217,24 +237,36 @@ async def test_execute_tool_no_source(self): def test_get_language_inference(self): """Test language inference via endpoint execution.""" # Create endpoints with different language sources - test_endpoints: list[EndpointDefinitionModel] = [ - EndpointDefinitionModel.model_validate( - {"mxcp": 1, "tool": {"name": "python_file_tool", "source": {"file": "script.py"}}} + test_endpoints: list[tuple[EndpointDefinitionModel, Path]] = [ + ( + EndpointDefinitionModel.model_validate( + {"mxcp": 1, "tool": {"name": "python_file_tool", "source": {"file": "script.py"}}} + ), + Path("endpoints/python.yml"), ), - EndpointDefinitionModel.model_validate( - {"mxcp": 1, "tool": {"name": "sql_file_tool", "source": {"file": "query.sql"}}} + ( + EndpointDefinitionModel.model_validate( + {"mxcp": 1, "tool": {"name": "sql_file_tool", "source": {"file": "query.sql"}}} + ), + Path("endpoints/sql.yml"), ), - EndpointDefinitionModel.model_validate( - { - "mxcp": 1, - "tool": { - "name": "explicit_override_tool", - "source": {"file": "script.py", "language": "sql"}, - }, - } + ( + EndpointDefinitionModel.model_validate( + { + "mxcp": 1, + "tool": { + "name": "explicit_override_tool", + "source": {"file": "script.py", "language": "sql"}, + }, + } + ), + Path("endpoints/override.yml"), ), - EndpointDefinitionModel.model_validate( - {"mxcp": 1, "tool": {"name": "default_sql_tool", "source": {"code": "some code"}}} + ( + EndpointDefinitionModel.model_validate( + {"mxcp": 1, "tool": {"name": "default_sql_tool", "source": {"code": "some code"}}} + ), + Path("endpoints/default.yml"), ), ] @@ -245,3 +277,55 @@ def test_get_language_inference(self): assert "sql_file_tool" in test_executor._tool_map assert "explicit_override_tool" in test_executor._tool_map assert "default_sql_tool" in test_executor._tool_map + + @pytest.mark.asyncio + async def test_execute_tool_loads_file_content(self, tmp_path, monkeypatch): + """Ensure file-based sources are read and executed with their content.""" + sql_dir = tmp_path / "sql" + sql_dir.mkdir() + sql_file = sql_dir / "hello.sql" + sql_file.write_text("select 1 as val;") + + # Provide mxcp-site.yml so find_repo_root() resolves to tmp_path + (tmp_path / "mxcp-site.yml").write_text("mxcp: 1\nproject: test\nprofile: default\n") + monkeypatch.chdir(tmp_path) + + endpoint = ( + EndpointDefinitionModel.model_validate( + {"mxcp": 1, "tool": {"name": "hello_tool", "source": {"file": "sql/hello.sql"}}} + ), + Path("endpoints/hello.yml"), + ) + + engine = MockExecutionEngine({"select 1 as val;": {"val": 1}}) + executor = EndpointToolExecutor(engine, [endpoint]) + + result = await executor.execute_tool("hello_tool", {}) + + assert result == {"val": 1} + assert engine.calls[0]["source_code"] == "select 1 as val;" + + @pytest.mark.asyncio + async def test_execute_tool_loads_relative_parent_path(self, tmp_path, monkeypatch): + """Relative paths with '..' should resolve correctly.""" + (tmp_path / "mxcp-site.yml").write_text("mxcp: 1\nproject: test\nprofile: default\n") + sql_dir = tmp_path.parent / "shared-sql" + sql_dir.mkdir(exist_ok=True) + sql_file = sql_dir / "hi.sql" + sql_file.write_text("select 2 as val;") + + # endpoint references ../shared-sql/hi.sql relative to repo root + endpoint = ( + EndpointDefinitionModel.model_validate( + {"mxcp": 1, "tool": {"name": "hi_tool", "source": {"file": "../shared-sql/hi.sql"}}} + ), + Path("endpoints/hi.yml"), + ) + + engine = MockExecutionEngine({"select 2 as val;": {"val": 2}}) + monkeypatch.chdir(tmp_path) + executor = EndpointToolExecutor(engine, [endpoint]) + + result = await executor.execute_tool("hi_tool", {}) + + assert result == {"val": 2} diff --git a/tests/server/test_user_config.py b/tests/server/test_user_config.py index c211ba3a..0f2f8bb8 100644 --- a/tests/server/test_user_config.py +++ b/tests/server/test_user_config.py @@ -402,3 +402,36 @@ def test_load_without_resolving_refs(tmp_path): # Clean up the secret file if secret_file.exists(): secret_file.unlink() + + +def test_model_options_allowed(tmp_path): + """Ensure model options field is accepted in user config.""" + config_path = tmp_path / "config.yml" + config_content = """ + mxcp: 1 + models: + default: "gpt-4o" + models: + gpt-4o: + type: "openai" + api_key: "${OPENAI_API_KEY}" + options: + reasoning: "fast" + projects: + test_project: + profiles: + dev: {} + """ + config_path.write_text(config_content) + + os.environ["MXCP_CONFIG"] = str(config_path) + os.environ["OPENAI_API_KEY"] = "secret" + + site_config = make_site_config("test_project", "dev") + + user_config = load_user_config(site_config).model_dump(mode="python") + model_cfg = user_config["models"]["models"]["gpt-4o"] + assert model_cfg["options"]["reasoning"] == "fast" + + del os.environ["MXCP_CONFIG"] + del os.environ["OPENAI_API_KEY"] From ce05585970bff39ac167fe35549ba1f27230abbc Mon Sep 17 00:00:00 2001 From: Alex Zerntev Date: Fri, 28 Nov 2025 15:06:35 +0200 Subject: [PATCH 09/26] fixed a bug --- src/mxcp/server/executor/runners/tool.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/mxcp/server/executor/runners/tool.py b/src/mxcp/server/executor/runners/tool.py index dacd6014..0b770111 100644 --- a/src/mxcp/server/executor/runners/tool.py +++ b/src/mxcp/server/executor/runners/tool.py @@ -127,13 +127,19 @@ def _get_source_code( if source_type == "file": relative_path = Path(source_value) candidates = [] + # Resolve endpoint path against repo root (or CWD fallback) to handle relative paths + try: + base_root = find_repo_root() + except FileNotFoundError: + base_root = Path.cwd() + + endpoint_path_abs = ( + endpoint_path if endpoint_path.is_absolute() else (base_root / endpoint_path) + ).resolve() + if not relative_path.is_absolute(): - candidates.append((endpoint_path.parent / relative_path).resolve()) - try: - repo_root = find_repo_root() - except FileNotFoundError: - repo_root = Path.cwd() - candidates.append((repo_root / relative_path).resolve()) + candidates.append((endpoint_path_abs.parent / relative_path).resolve()) + candidates.append((base_root / relative_path).resolve()) else: candidates.append(relative_path.resolve()) From 86490b93179d8b08239763337fa7602b735cc9c2 Mon Sep 17 00:00:00 2001 From: Alex Zerntev Date: Fri, 28 Nov 2025 15:21:31 +0200 Subject: [PATCH 10/26] fixes --- src/mxcp/sdk/evals/executor.py | 32 +- src/mxcp/server/executor/runners/tool.py | 10 +- src/mxcp/server/interfaces/cli/evals.py | 4 +- tests/server/test_evals_tool_executor.py | 10 +- uv.lock | 3592 +++++++++++----------- 5 files changed, 1832 insertions(+), 1816 deletions(-) diff --git a/src/mxcp/sdk/evals/executor.py b/src/mxcp/sdk/evals/executor.py index 3b038ac0..d82b476a 100644 --- a/src/mxcp/sdk/evals/executor.py +++ b/src/mxcp/sdk/evals/executor.py @@ -27,8 +27,7 @@ class ToolExecutor(Protocol): async def execute_tool( self, tool_name: str, arguments: dict[str, Any], user_context: UserContext | None = None - ) -> Any: - ... + ) -> Any: ... @dataclass @@ -142,7 +141,9 @@ async def evaluate_expected_answer(self, answer: str, expected_answer: str) -> d ) messages = self._initial_messages(grader_prompt, system_override=grader_system) - llm_response = await self._call_llm(messages, use_tools=False, system_override=grader_system) + llm_response = await self._call_llm( + messages, use_tools=False, system_override=grader_system + ) return self._parse_grade_response(llm_response.content) def _build_system_prompt(self, tools: list[ToolDefinition]) -> str: @@ -163,7 +164,10 @@ def _initial_messages( ) -> list[dict[str, Any]]: system_prompt = system_override or self.system_prompt if self.model_type == "openai": - return [{"role": "system", "content": system_prompt}, {"role": "user", "content": prompt}] + return [ + {"role": "system", "content": system_prompt}, + {"role": "user", "content": prompt}, + ] # Anthropic/Claude style keeps system separate; we include it in the API call. return [{"role": "user", "content": [{"type": "text", "text": prompt}]}] @@ -172,7 +176,9 @@ def _append_assistant_message( self, messages: list[dict[str, Any]], response: LLMResponse ) -> None: if self.model_type == "openai": - messages.append(response.raw_message or {"role": "assistant", "content": response.content}) + messages.append( + response.raw_message or {"role": "assistant", "content": response.content} + ) return # Claude/Anthropic @@ -181,13 +187,11 @@ def _append_assistant_message( or {"role": "assistant", "content": [{"type": "text", "text": response.content}]} ) - def _build_tool_models( - self, tools: list[ToolDefinition] - ) -> dict[str, type[BaseModel]]: + def _build_tool_models(self, tools: list[ToolDefinition]) -> dict[str, type[BaseModel]]: models: dict[str, type[BaseModel]] = {} for tool in tools: - fields: dict[str, tuple[Any, Any]] = {} + fields: dict[str, Any] = {} for param in tool.parameters: py_type = self._map_param_type(param.type) default = param.default if param.default is not None else None @@ -196,7 +200,7 @@ def _build_tool_models( else: fields[param.name] = (py_type, default) - models[tool.name] = create_model(f"{tool.name}_Args", **fields) # type: ignore[arg-type] + models[tool.name] = create_model(f"{tool.name}_Args", **fields) return models @@ -267,15 +271,13 @@ def _map_type_string(self, param_type: str) -> str: } return mapping.get(param_type.lower(), "string") - def _validate_tool_arguments( - self, tool_name: str, arguments: dict[str, Any] - ) -> dict[str, Any]: + def _validate_tool_arguments(self, tool_name: str, arguments: dict[str, Any]) -> dict[str, Any]: model = self._tool_models.get(tool_name) if not model: return arguments try: - return cast(dict[str, Any], model.model_validate(arguments).model_dump()) + return model.model_validate(arguments).model_dump() except ValidationError as exc: raise ValueError(f"Invalid arguments for tool '{tool_name}': {exc}") from exc @@ -310,7 +312,7 @@ def _serialize_result(self, result: Any) -> str: return result if result is None: return "null" - if isinstance(result, (int, float, bool)): + if isinstance(result, int | float | bool): return json.dumps(result) try: return json.dumps(result) diff --git a/src/mxcp/server/executor/runners/tool.py b/src/mxcp/server/executor/runners/tool.py index 0b770111..17d22e2a 100644 --- a/src/mxcp/server/executor/runners/tool.py +++ b/src/mxcp/server/executor/runners/tool.py @@ -11,9 +11,9 @@ from mxcp.sdk.auth import UserContext from mxcp.sdk.executor import ExecutionContext, ExecutionEngine +from mxcp.server.core.config.site_config import find_repo_root from mxcp.server.definitions.endpoints.models import EndpointDefinitionModel from mxcp.server.definitions.endpoints.utils import detect_language_from_source, extract_source_info -from mxcp.server.core.config.site_config import find_repo_root logger = logging.getLogger(__name__) @@ -43,7 +43,9 @@ class EndpointToolExecutor: >>> llm_executor = LLMExecutor(model_config, tool_definitions, tool_executor) """ - def __init__(self, engine: ExecutionEngine, endpoints: list[tuple[EndpointDefinitionModel, Path]]): + def __init__( + self, engine: ExecutionEngine, endpoints: list[tuple[EndpointDefinitionModel, Path]] + ): """Initialize the endpoint tool executor. Args: @@ -155,7 +157,9 @@ def _get_source_code( try: return source_path.read_text(), str(source_path) except Exception as exc: # noqa: BLE001 - raise ValueError(f"Failed to read source file for endpoint '{tool_name}': {exc}") from exc + raise ValueError( + f"Failed to read source file for endpoint '{tool_name}': {exc}" + ) from exc return source_value, None diff --git a/src/mxcp/server/interfaces/cli/evals.py b/src/mxcp/server/interfaces/cli/evals.py index 2f2b545f..2508f344 100644 --- a/src/mxcp/server/interfaces/cli/evals.py +++ b/src/mxcp/server/interfaces/cli/evals.py @@ -169,7 +169,9 @@ def format_eval_results(results: dict[str, Any], debug: bool = False) -> str: f" {click.style('✗', fg='red')} {test['name']} {click.style(f'({test_time:.2f}s)', fg='bright_black')}" ) if test.get("error"): - output.append(f" {click.style('Error:', fg='red')} {test['error']}") + output.append( + f" {click.style('Error:', fg='red')} {test['error']}" + ) for failure in test.get("failures", []): output.append(f" {click.style('💡', fg='yellow')} {failure}") diff --git a/tests/server/test_evals_tool_executor.py b/tests/server/test_evals_tool_executor.py index 8d8277ea..27e2e661 100644 --- a/tests/server/test_evals_tool_executor.py +++ b/tests/server/test_evals_tool_executor.py @@ -229,7 +229,10 @@ async def test_execute_tool_no_source(self): test_endpoints: list[tuple[EndpointDefinitionModel, Path]] = [ ( EndpointDefinitionModel.model_validate( - {"mxcp": 1, "tool": {"name": "python_file_tool", "source": {"file": "script.py"}}} + { + "mxcp": 1, + "tool": {"name": "python_file_tool", "source": {"file": "script.py"}}, + } ), Path("endpoints/python.yml"), ), @@ -253,7 +256,10 @@ async def test_execute_tool_no_source(self): ), ( EndpointDefinitionModel.model_validate( - {"mxcp": 1, "tool": {"name": "default_sql_tool", "source": {"code": "some code"}}} + { + "mxcp": 1, + "tool": {"name": "default_sql_tool", "source": {"code": "some code"}}, + } ), Path("endpoints/default.yml"), ), diff --git a/uv.lock b/uv.lock index 88ddc21f..e296f405 100644 --- a/uv.lock +++ b/uv.lock @@ -1,4 +1,5 @@ version = 1 +revision = 3 requires-python = ">=3.10" resolution-markers = [ "python_full_version >= '3.12'", @@ -19,18 +20,18 @@ dependencies = [ { name = "pytimeparse" }, { name = "tzdata", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/29/77/6f5df1c68bf056f5fdefc60ccc616303c6211e71cd6033c830c12735f605/agate-1.9.1.tar.gz", hash = "sha256:bc60880c2ee59636a2a80cd8603d63f995be64526abf3cbba12f00767bcd5b3d", size = 202303 } +sdist = { url = "https://files.pythonhosted.org/packages/29/77/6f5df1c68bf056f5fdefc60ccc616303c6211e71cd6033c830c12735f605/agate-1.9.1.tar.gz", hash = "sha256:bc60880c2ee59636a2a80cd8603d63f995be64526abf3cbba12f00767bcd5b3d", size = 202303, upload-time = "2023-12-21T20:05:24.316Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/53/89b197cb472a3175d73384761a3413fd58e6b65a794c1102d148b8de87bd/agate-1.9.1-py2.py3-none-any.whl", hash = "sha256:1cf329510b3dde07c4ad1740b7587c9c679abc3dcd92bb1107eabc10c2e03c50", size = 95085 }, + { url = "https://files.pythonhosted.org/packages/d1/53/89b197cb472a3175d73384761a3413fd58e6b65a794c1102d148b8de87bd/agate-1.9.1-py2.py3-none-any.whl", hash = "sha256:1cf329510b3dde07c4ad1740b7587c9c679abc3dcd92bb1107eabc10c2e03c50", size = 95085, upload-time = "2023-12-21T20:05:21.954Z" }, ] [[package]] name = "aiohappyeyeballs" version = "2.6.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/26/30/f84a107a9c4331c14b2b586036f40965c128aa4fee4dda5d3d51cb14ad54/aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558", size = 22760 } +sdist = { url = "https://files.pythonhosted.org/packages/26/30/f84a107a9c4331c14b2b586036f40965c128aa4fee4dda5d3d51cb14ad54/aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558", size = 22760, upload-time = "2025-03-12T01:42:48.764Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8", size = 15265 }, + { url = "https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8", size = 15265, upload-time = "2025-03-12T01:42:47.083Z" }, ] [[package]] @@ -47,76 +48,76 @@ dependencies = [ { name = "propcache" }, { name = "yarl" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e6/0b/e39ad954107ebf213a2325038a3e7a506be3d98e1435e1f82086eec4cde2/aiohttp-3.12.14.tar.gz", hash = "sha256:6e06e120e34d93100de448fd941522e11dafa78ef1a893c179901b7d66aa29f2", size = 7822921 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0c/88/f161f429f9de391eee6a5c2cffa54e2ecd5b7122ae99df247f7734dfefcb/aiohttp-3.12.14-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:906d5075b5ba0dd1c66fcaaf60eb09926a9fef3ca92d912d2a0bbdbecf8b1248", size = 702641 }, - { url = "https://files.pythonhosted.org/packages/fe/b5/24fa382a69a25d242e2baa3e56d5ea5227d1b68784521aaf3a1a8b34c9a4/aiohttp-3.12.14-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c875bf6fc2fd1a572aba0e02ef4e7a63694778c5646cdbda346ee24e630d30fb", size = 479005 }, - { url = "https://files.pythonhosted.org/packages/09/67/fda1bc34adbfaa950d98d934a23900918f9d63594928c70e55045838c943/aiohttp-3.12.14-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fbb284d15c6a45fab030740049d03c0ecd60edad9cd23b211d7e11d3be8d56fd", size = 466781 }, - { url = "https://files.pythonhosted.org/packages/36/96/3ce1ea96d3cf6928b87cfb8cdd94650367f5c2f36e686a1f5568f0f13754/aiohttp-3.12.14-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38e360381e02e1a05d36b223ecab7bc4a6e7b5ab15760022dc92589ee1d4238c", size = 1648841 }, - { url = "https://files.pythonhosted.org/packages/be/04/ddea06cb4bc7d8db3745cf95e2c42f310aad485ca075bd685f0e4f0f6b65/aiohttp-3.12.14-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:aaf90137b5e5d84a53632ad95ebee5c9e3e7468f0aab92ba3f608adcb914fa95", size = 1622896 }, - { url = "https://files.pythonhosted.org/packages/73/66/63942f104d33ce6ca7871ac6c1e2ebab48b88f78b2b7680c37de60f5e8cd/aiohttp-3.12.14-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e532a25e4a0a2685fa295a31acf65e027fbe2bea7a4b02cdfbbba8a064577663", size = 1695302 }, - { url = "https://files.pythonhosted.org/packages/20/00/aab615742b953f04b48cb378ee72ada88555b47b860b98c21c458c030a23/aiohttp-3.12.14-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eab9762c4d1b08ae04a6c77474e6136da722e34fdc0e6d6eab5ee93ac29f35d1", size = 1737617 }, - { url = "https://files.pythonhosted.org/packages/d6/4f/ef6d9f77225cf27747368c37b3d69fac1f8d6f9d3d5de2d410d155639524/aiohttp-3.12.14-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abe53c3812b2899889a7fca763cdfaeee725f5be68ea89905e4275476ffd7e61", size = 1642282 }, - { url = "https://files.pythonhosted.org/packages/37/e1/e98a43c15aa52e9219a842f18c59cbae8bbe2d50c08d298f17e9e8bafa38/aiohttp-3.12.14-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5760909b7080aa2ec1d320baee90d03b21745573780a072b66ce633eb77a8656", size = 1582406 }, - { url = "https://files.pythonhosted.org/packages/71/5c/29c6dfb49323bcdb0239bf3fc97ffcf0eaf86d3a60426a3287ec75d67721/aiohttp-3.12.14-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:02fcd3f69051467bbaa7f84d7ec3267478c7df18d68b2e28279116e29d18d4f3", size = 1626255 }, - { url = "https://files.pythonhosted.org/packages/79/60/ec90782084090c4a6b459790cfd8d17be2c5662c9c4b2d21408b2f2dc36c/aiohttp-3.12.14-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:4dcd1172cd6794884c33e504d3da3c35648b8be9bfa946942d353b939d5f1288", size = 1637041 }, - { url = "https://files.pythonhosted.org/packages/22/89/205d3ad30865c32bc472ac13f94374210745b05bd0f2856996cb34d53396/aiohttp-3.12.14-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:224d0da41355b942b43ad08101b1b41ce633a654128ee07e36d75133443adcda", size = 1612494 }, - { url = "https://files.pythonhosted.org/packages/48/ae/2f66edaa8bd6db2a4cba0386881eb92002cdc70834e2a93d1d5607132c7e/aiohttp-3.12.14-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e387668724f4d734e865c1776d841ed75b300ee61059aca0b05bce67061dcacc", size = 1692081 }, - { url = "https://files.pythonhosted.org/packages/08/3a/fa73bfc6e21407ea57f7906a816f0dc73663d9549da703be05dbd76d2dc3/aiohttp-3.12.14-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:dec9cde5b5a24171e0b0a4ca064b1414950904053fb77c707efd876a2da525d8", size = 1715318 }, - { url = "https://files.pythonhosted.org/packages/e3/b3/751124b8ceb0831c17960d06ee31a4732cb4a6a006fdbfa1153d07c52226/aiohttp-3.12.14-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bbad68a2af4877cc103cd94af9160e45676fc6f0c14abb88e6e092b945c2c8e3", size = 1643660 }, - { url = "https://files.pythonhosted.org/packages/81/3c/72477a1d34edb8ab8ce8013086a41526d48b64f77e381c8908d24e1c18f5/aiohttp-3.12.14-cp310-cp310-win32.whl", hash = "sha256:ee580cb7c00bd857b3039ebca03c4448e84700dc1322f860cf7a500a6f62630c", size = 428289 }, - { url = "https://files.pythonhosted.org/packages/a2/c4/8aec4ccf1b822ec78e7982bd5cf971113ecce5f773f04039c76a083116fc/aiohttp-3.12.14-cp310-cp310-win_amd64.whl", hash = "sha256:cf4f05b8cea571e2ccc3ca744e35ead24992d90a72ca2cf7ab7a2efbac6716db", size = 451328 }, - { url = "https://files.pythonhosted.org/packages/53/e1/8029b29316971c5fa89cec170274582619a01b3d82dd1036872acc9bc7e8/aiohttp-3.12.14-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f4552ff7b18bcec18b60a90c6982049cdb9dac1dba48cf00b97934a06ce2e597", size = 709960 }, - { url = "https://files.pythonhosted.org/packages/96/bd/4f204cf1e282041f7b7e8155f846583b19149e0872752711d0da5e9cc023/aiohttp-3.12.14-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8283f42181ff6ccbcf25acaae4e8ab2ff7e92b3ca4a4ced73b2c12d8cd971393", size = 482235 }, - { url = "https://files.pythonhosted.org/packages/d6/0f/2a580fcdd113fe2197a3b9df30230c7e85bb10bf56f7915457c60e9addd9/aiohttp-3.12.14-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:040afa180ea514495aaff7ad34ec3d27826eaa5d19812730fe9e529b04bb2179", size = 470501 }, - { url = "https://files.pythonhosted.org/packages/38/78/2c1089f6adca90c3dd74915bafed6d6d8a87df5e3da74200f6b3a8b8906f/aiohttp-3.12.14-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b413c12f14c1149f0ffd890f4141a7471ba4b41234fe4fd4a0ff82b1dc299dbb", size = 1740696 }, - { url = "https://files.pythonhosted.org/packages/4a/c8/ce6c7a34d9c589f007cfe064da2d943b3dee5aabc64eaecd21faf927ab11/aiohttp-3.12.14-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:1d6f607ce2e1a93315414e3d448b831238f1874b9968e1195b06efaa5c87e245", size = 1689365 }, - { url = "https://files.pythonhosted.org/packages/18/10/431cd3d089de700756a56aa896faf3ea82bee39d22f89db7ddc957580308/aiohttp-3.12.14-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:565e70d03e924333004ed101599902bba09ebb14843c8ea39d657f037115201b", size = 1788157 }, - { url = "https://files.pythonhosted.org/packages/fa/b2/26f4524184e0f7ba46671c512d4b03022633bcf7d32fa0c6f1ef49d55800/aiohttp-3.12.14-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4699979560728b168d5ab63c668a093c9570af2c7a78ea24ca5212c6cdc2b641", size = 1827203 }, - { url = "https://files.pythonhosted.org/packages/e0/30/aadcdf71b510a718e3d98a7bfeaea2396ac847f218b7e8edb241b09bd99a/aiohttp-3.12.14-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad5fdf6af93ec6c99bf800eba3af9a43d8bfd66dce920ac905c817ef4a712afe", size = 1729664 }, - { url = "https://files.pythonhosted.org/packages/67/7f/7ccf11756ae498fdedc3d689a0c36ace8fc82f9d52d3517da24adf6e9a74/aiohttp-3.12.14-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4ac76627c0b7ee0e80e871bde0d376a057916cb008a8f3ffc889570a838f5cc7", size = 1666741 }, - { url = "https://files.pythonhosted.org/packages/6b/4d/35ebc170b1856dd020c92376dbfe4297217625ef4004d56587024dc2289c/aiohttp-3.12.14-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:798204af1180885651b77bf03adc903743a86a39c7392c472891649610844635", size = 1715013 }, - { url = "https://files.pythonhosted.org/packages/7b/24/46dc0380146f33e2e4aa088b92374b598f5bdcde1718c77e8d1a0094f1a4/aiohttp-3.12.14-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:4f1205f97de92c37dd71cf2d5bcfb65fdaed3c255d246172cce729a8d849b4da", size = 1710172 }, - { url = "https://files.pythonhosted.org/packages/2f/0a/46599d7d19b64f4d0fe1b57bdf96a9a40b5c125f0ae0d8899bc22e91fdce/aiohttp-3.12.14-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:76ae6f1dd041f85065d9df77c6bc9c9703da9b5c018479d20262acc3df97d419", size = 1690355 }, - { url = "https://files.pythonhosted.org/packages/08/86/b21b682e33d5ca317ef96bd21294984f72379454e689d7da584df1512a19/aiohttp-3.12.14-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a194ace7bc43ce765338ca2dfb5661489317db216ea7ea700b0332878b392cab", size = 1783958 }, - { url = "https://files.pythonhosted.org/packages/4f/45/f639482530b1396c365f23c5e3b1ae51c9bc02ba2b2248ca0c855a730059/aiohttp-3.12.14-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:16260e8e03744a6fe3fcb05259eeab8e08342c4c33decf96a9dad9f1187275d0", size = 1804423 }, - { url = "https://files.pythonhosted.org/packages/7e/e5/39635a9e06eed1d73671bd4079a3caf9cf09a49df08490686f45a710b80e/aiohttp-3.12.14-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8c779e5ebbf0e2e15334ea404fcce54009dc069210164a244d2eac8352a44b28", size = 1717479 }, - { url = "https://files.pythonhosted.org/packages/51/e1/7f1c77515d369b7419c5b501196526dad3e72800946c0099594c1f0c20b4/aiohttp-3.12.14-cp311-cp311-win32.whl", hash = "sha256:a289f50bf1bd5be227376c067927f78079a7bdeccf8daa6a9e65c38bae14324b", size = 427907 }, - { url = "https://files.pythonhosted.org/packages/06/24/a6bf915c85b7a5b07beba3d42b3282936b51e4578b64a51e8e875643c276/aiohttp-3.12.14-cp311-cp311-win_amd64.whl", hash = "sha256:0b8a69acaf06b17e9c54151a6c956339cf46db4ff72b3ac28516d0f7068f4ced", size = 452334 }, - { url = "https://files.pythonhosted.org/packages/c3/0d/29026524e9336e33d9767a1e593ae2b24c2b8b09af7c2bd8193762f76b3e/aiohttp-3.12.14-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a0ecbb32fc3e69bc25efcda7d28d38e987d007096cbbeed04f14a6662d0eee22", size = 701055 }, - { url = "https://files.pythonhosted.org/packages/0a/b8/a5e8e583e6c8c1056f4b012b50a03c77a669c2e9bf012b7cf33d6bc4b141/aiohttp-3.12.14-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0400f0ca9bb3e0b02f6466421f253797f6384e9845820c8b05e976398ac1d81a", size = 475670 }, - { url = "https://files.pythonhosted.org/packages/29/e8/5202890c9e81a4ec2c2808dd90ffe024952e72c061729e1d49917677952f/aiohttp-3.12.14-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a56809fed4c8a830b5cae18454b7464e1529dbf66f71c4772e3cfa9cbec0a1ff", size = 468513 }, - { url = "https://files.pythonhosted.org/packages/23/e5/d11db8c23d8923d3484a27468a40737d50f05b05eebbb6288bafcb467356/aiohttp-3.12.14-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27f2e373276e4755691a963e5d11756d093e346119f0627c2d6518208483fb6d", size = 1715309 }, - { url = "https://files.pythonhosted.org/packages/53/44/af6879ca0eff7a16b1b650b7ea4a827301737a350a464239e58aa7c387ef/aiohttp-3.12.14-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:ca39e433630e9a16281125ef57ece6817afd1d54c9f1bf32e901f38f16035869", size = 1697961 }, - { url = "https://files.pythonhosted.org/packages/bb/94/18457f043399e1ec0e59ad8674c0372f925363059c276a45a1459e17f423/aiohttp-3.12.14-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9c748b3f8b14c77720132b2510a7d9907a03c20ba80f469e58d5dfd90c079a1c", size = 1753055 }, - { url = "https://files.pythonhosted.org/packages/26/d9/1d3744dc588fafb50ff8a6226d58f484a2242b5dd93d8038882f55474d41/aiohttp-3.12.14-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0a568abe1b15ce69d4cc37e23020720423f0728e3cb1f9bcd3f53420ec3bfe7", size = 1799211 }, - { url = "https://files.pythonhosted.org/packages/73/12/2530fb2b08773f717ab2d249ca7a982ac66e32187c62d49e2c86c9bba9b4/aiohttp-3.12.14-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9888e60c2c54eaf56704b17feb558c7ed6b7439bca1e07d4818ab878f2083660", size = 1718649 }, - { url = "https://files.pythonhosted.org/packages/b9/34/8d6015a729f6571341a311061b578e8b8072ea3656b3d72329fa0faa2c7c/aiohttp-3.12.14-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3006a1dc579b9156de01e7916d38c63dc1ea0679b14627a37edf6151bc530088", size = 1634452 }, - { url = "https://files.pythonhosted.org/packages/ff/4b/08b83ea02595a582447aeb0c1986792d0de35fe7a22fb2125d65091cbaf3/aiohttp-3.12.14-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:aa8ec5c15ab80e5501a26719eb48a55f3c567da45c6ea5bb78c52c036b2655c7", size = 1695511 }, - { url = "https://files.pythonhosted.org/packages/b5/66/9c7c31037a063eec13ecf1976185c65d1394ded4a5120dd5965e3473cb21/aiohttp-3.12.14-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:39b94e50959aa07844c7fe2206b9f75d63cc3ad1c648aaa755aa257f6f2498a9", size = 1716967 }, - { url = "https://files.pythonhosted.org/packages/ba/02/84406e0ad1acb0fb61fd617651ab6de760b2d6a31700904bc0b33bd0894d/aiohttp-3.12.14-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:04c11907492f416dad9885d503fbfc5dcb6768d90cad8639a771922d584609d3", size = 1657620 }, - { url = "https://files.pythonhosted.org/packages/07/53/da018f4013a7a179017b9a274b46b9a12cbeb387570f116964f498a6f211/aiohttp-3.12.14-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:88167bd9ab69bb46cee91bd9761db6dfd45b6e76a0438c7e884c3f8160ff21eb", size = 1737179 }, - { url = "https://files.pythonhosted.org/packages/49/e8/ca01c5ccfeaafb026d85fa4f43ceb23eb80ea9c1385688db0ef322c751e9/aiohttp-3.12.14-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:791504763f25e8f9f251e4688195e8b455f8820274320204f7eafc467e609425", size = 1765156 }, - { url = "https://files.pythonhosted.org/packages/22/32/5501ab525a47ba23c20613e568174d6c63aa09e2caa22cded5c6ea8e3ada/aiohttp-3.12.14-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2785b112346e435dd3a1a67f67713a3fe692d288542f1347ad255683f066d8e0", size = 1724766 }, - { url = "https://files.pythonhosted.org/packages/06/af/28e24574801fcf1657945347ee10df3892311c2829b41232be6089e461e7/aiohttp-3.12.14-cp312-cp312-win32.whl", hash = "sha256:15f5f4792c9c999a31d8decf444e79fcfd98497bf98e94284bf390a7bb8c1729", size = 422641 }, - { url = "https://files.pythonhosted.org/packages/98/d5/7ac2464aebd2eecac38dbe96148c9eb487679c512449ba5215d233755582/aiohttp-3.12.14-cp312-cp312-win_amd64.whl", hash = "sha256:3b66e1a182879f579b105a80d5c4bd448b91a57e8933564bf41665064796a338", size = 449316 }, - { url = "https://files.pythonhosted.org/packages/06/48/e0d2fa8ac778008071e7b79b93ab31ef14ab88804d7ba71b5c964a7c844e/aiohttp-3.12.14-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:3143a7893d94dc82bc409f7308bc10d60285a3cd831a68faf1aa0836c5c3c767", size = 695471 }, - { url = "https://files.pythonhosted.org/packages/8d/e7/f73206afa33100804f790b71092888f47df65fd9a4cd0e6800d7c6826441/aiohttp-3.12.14-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3d62ac3d506cef54b355bd34c2a7c230eb693880001dfcda0bf88b38f5d7af7e", size = 473128 }, - { url = "https://files.pythonhosted.org/packages/df/e2/4dd00180be551a6e7ee979c20fc7c32727f4889ee3fd5b0586e0d47f30e1/aiohttp-3.12.14-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:48e43e075c6a438937c4de48ec30fa8ad8e6dfef122a038847456bfe7b947b63", size = 465426 }, - { url = "https://files.pythonhosted.org/packages/de/dd/525ed198a0bb674a323e93e4d928443a680860802c44fa7922d39436b48b/aiohttp-3.12.14-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:077b4488411a9724cecc436cbc8c133e0d61e694995b8de51aaf351c7578949d", size = 1704252 }, - { url = "https://files.pythonhosted.org/packages/d8/b1/01e542aed560a968f692ab4fc4323286e8bc4daae83348cd63588e4f33e3/aiohttp-3.12.14-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d8c35632575653f297dcbc9546305b2c1133391089ab925a6a3706dfa775ccab", size = 1685514 }, - { url = "https://files.pythonhosted.org/packages/b3/06/93669694dc5fdabdc01338791e70452d60ce21ea0946a878715688d5a191/aiohttp-3.12.14-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6b8ce87963f0035c6834b28f061df90cf525ff7c9b6283a8ac23acee6502afd4", size = 1737586 }, - { url = "https://files.pythonhosted.org/packages/a5/3a/18991048ffc1407ca51efb49ba8bcc1645961f97f563a6c480cdf0286310/aiohttp-3.12.14-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0a2cf66e32a2563bb0766eb24eae7e9a269ac0dc48db0aae90b575dc9583026", size = 1786958 }, - { url = "https://files.pythonhosted.org/packages/30/a8/81e237f89a32029f9b4a805af6dffc378f8459c7b9942712c809ff9e76e5/aiohttp-3.12.14-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdea089caf6d5cde975084a884c72d901e36ef9c2fd972c9f51efbbc64e96fbd", size = 1709287 }, - { url = "https://files.pythonhosted.org/packages/8c/e3/bd67a11b0fe7fc12c6030473afd9e44223d456f500f7cf526dbaa259ae46/aiohttp-3.12.14-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8a7865f27db67d49e81d463da64a59365ebd6b826e0e4847aa111056dcb9dc88", size = 1622990 }, - { url = "https://files.pythonhosted.org/packages/83/ba/e0cc8e0f0d9ce0904e3cf2d6fa41904e379e718a013c721b781d53dcbcca/aiohttp-3.12.14-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0ab5b38a6a39781d77713ad930cb5e7feea6f253de656a5f9f281a8f5931b086", size = 1676015 }, - { url = "https://files.pythonhosted.org/packages/d8/b3/1e6c960520bda094c48b56de29a3d978254637ace7168dd97ddc273d0d6c/aiohttp-3.12.14-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:9b3b15acee5c17e8848d90a4ebc27853f37077ba6aec4d8cb4dbbea56d156933", size = 1707678 }, - { url = "https://files.pythonhosted.org/packages/0a/19/929a3eb8c35b7f9f076a462eaa9830b32c7f27d3395397665caa5e975614/aiohttp-3.12.14-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e4c972b0bdaac167c1e53e16a16101b17c6d0ed7eac178e653a07b9f7fad7151", size = 1650274 }, - { url = "https://files.pythonhosted.org/packages/22/e5/81682a6f20dd1b18ce3d747de8eba11cbef9b270f567426ff7880b096b48/aiohttp-3.12.14-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7442488b0039257a3bdbc55f7209587911f143fca11df9869578db6c26feeeb8", size = 1726408 }, - { url = "https://files.pythonhosted.org/packages/8c/17/884938dffaa4048302985483f77dfce5ac18339aad9b04ad4aaa5e32b028/aiohttp-3.12.14-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:f68d3067eecb64c5e9bab4a26aa11bd676f4c70eea9ef6536b0a4e490639add3", size = 1759879 }, - { url = "https://files.pythonhosted.org/packages/95/78/53b081980f50b5cf874359bde707a6eacd6c4be3f5f5c93937e48c9d0025/aiohttp-3.12.14-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f88d3704c8b3d598a08ad17d06006cb1ca52a1182291f04979e305c8be6c9758", size = 1708770 }, - { url = "https://files.pythonhosted.org/packages/ed/91/228eeddb008ecbe3ffa6c77b440597fdf640307162f0c6488e72c5a2d112/aiohttp-3.12.14-cp313-cp313-win32.whl", hash = "sha256:a3c99ab19c7bf375c4ae3debd91ca5d394b98b6089a03231d4c580ef3c2ae4c5", size = 421688 }, - { url = "https://files.pythonhosted.org/packages/66/5f/8427618903343402fdafe2850738f735fd1d9409d2a8f9bcaae5e630d3ba/aiohttp-3.12.14-cp313-cp313-win_amd64.whl", hash = "sha256:3f8aad695e12edc9d571f878c62bedc91adf30c760c8632f09663e5f564f4baa", size = 448098 }, +sdist = { url = "https://files.pythonhosted.org/packages/e6/0b/e39ad954107ebf213a2325038a3e7a506be3d98e1435e1f82086eec4cde2/aiohttp-3.12.14.tar.gz", hash = "sha256:6e06e120e34d93100de448fd941522e11dafa78ef1a893c179901b7d66aa29f2", size = 7822921, upload-time = "2025-07-10T13:05:33.968Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/88/f161f429f9de391eee6a5c2cffa54e2ecd5b7122ae99df247f7734dfefcb/aiohttp-3.12.14-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:906d5075b5ba0dd1c66fcaaf60eb09926a9fef3ca92d912d2a0bbdbecf8b1248", size = 702641, upload-time = "2025-07-10T13:02:38.98Z" }, + { url = "https://files.pythonhosted.org/packages/fe/b5/24fa382a69a25d242e2baa3e56d5ea5227d1b68784521aaf3a1a8b34c9a4/aiohttp-3.12.14-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c875bf6fc2fd1a572aba0e02ef4e7a63694778c5646cdbda346ee24e630d30fb", size = 479005, upload-time = "2025-07-10T13:02:42.714Z" }, + { url = "https://files.pythonhosted.org/packages/09/67/fda1bc34adbfaa950d98d934a23900918f9d63594928c70e55045838c943/aiohttp-3.12.14-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fbb284d15c6a45fab030740049d03c0ecd60edad9cd23b211d7e11d3be8d56fd", size = 466781, upload-time = "2025-07-10T13:02:44.639Z" }, + { url = "https://files.pythonhosted.org/packages/36/96/3ce1ea96d3cf6928b87cfb8cdd94650367f5c2f36e686a1f5568f0f13754/aiohttp-3.12.14-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38e360381e02e1a05d36b223ecab7bc4a6e7b5ab15760022dc92589ee1d4238c", size = 1648841, upload-time = "2025-07-10T13:02:46.356Z" }, + { url = "https://files.pythonhosted.org/packages/be/04/ddea06cb4bc7d8db3745cf95e2c42f310aad485ca075bd685f0e4f0f6b65/aiohttp-3.12.14-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:aaf90137b5e5d84a53632ad95ebee5c9e3e7468f0aab92ba3f608adcb914fa95", size = 1622896, upload-time = "2025-07-10T13:02:48.422Z" }, + { url = "https://files.pythonhosted.org/packages/73/66/63942f104d33ce6ca7871ac6c1e2ebab48b88f78b2b7680c37de60f5e8cd/aiohttp-3.12.14-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e532a25e4a0a2685fa295a31acf65e027fbe2bea7a4b02cdfbbba8a064577663", size = 1695302, upload-time = "2025-07-10T13:02:50.078Z" }, + { url = "https://files.pythonhosted.org/packages/20/00/aab615742b953f04b48cb378ee72ada88555b47b860b98c21c458c030a23/aiohttp-3.12.14-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eab9762c4d1b08ae04a6c77474e6136da722e34fdc0e6d6eab5ee93ac29f35d1", size = 1737617, upload-time = "2025-07-10T13:02:52.123Z" }, + { url = "https://files.pythonhosted.org/packages/d6/4f/ef6d9f77225cf27747368c37b3d69fac1f8d6f9d3d5de2d410d155639524/aiohttp-3.12.14-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abe53c3812b2899889a7fca763cdfaeee725f5be68ea89905e4275476ffd7e61", size = 1642282, upload-time = "2025-07-10T13:02:53.899Z" }, + { url = "https://files.pythonhosted.org/packages/37/e1/e98a43c15aa52e9219a842f18c59cbae8bbe2d50c08d298f17e9e8bafa38/aiohttp-3.12.14-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5760909b7080aa2ec1d320baee90d03b21745573780a072b66ce633eb77a8656", size = 1582406, upload-time = "2025-07-10T13:02:55.515Z" }, + { url = "https://files.pythonhosted.org/packages/71/5c/29c6dfb49323bcdb0239bf3fc97ffcf0eaf86d3a60426a3287ec75d67721/aiohttp-3.12.14-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:02fcd3f69051467bbaa7f84d7ec3267478c7df18d68b2e28279116e29d18d4f3", size = 1626255, upload-time = "2025-07-10T13:02:57.343Z" }, + { url = "https://files.pythonhosted.org/packages/79/60/ec90782084090c4a6b459790cfd8d17be2c5662c9c4b2d21408b2f2dc36c/aiohttp-3.12.14-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:4dcd1172cd6794884c33e504d3da3c35648b8be9bfa946942d353b939d5f1288", size = 1637041, upload-time = "2025-07-10T13:02:59.008Z" }, + { url = "https://files.pythonhosted.org/packages/22/89/205d3ad30865c32bc472ac13f94374210745b05bd0f2856996cb34d53396/aiohttp-3.12.14-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:224d0da41355b942b43ad08101b1b41ce633a654128ee07e36d75133443adcda", size = 1612494, upload-time = "2025-07-10T13:03:00.618Z" }, + { url = "https://files.pythonhosted.org/packages/48/ae/2f66edaa8bd6db2a4cba0386881eb92002cdc70834e2a93d1d5607132c7e/aiohttp-3.12.14-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e387668724f4d734e865c1776d841ed75b300ee61059aca0b05bce67061dcacc", size = 1692081, upload-time = "2025-07-10T13:03:02.154Z" }, + { url = "https://files.pythonhosted.org/packages/08/3a/fa73bfc6e21407ea57f7906a816f0dc73663d9549da703be05dbd76d2dc3/aiohttp-3.12.14-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:dec9cde5b5a24171e0b0a4ca064b1414950904053fb77c707efd876a2da525d8", size = 1715318, upload-time = "2025-07-10T13:03:04.322Z" }, + { url = "https://files.pythonhosted.org/packages/e3/b3/751124b8ceb0831c17960d06ee31a4732cb4a6a006fdbfa1153d07c52226/aiohttp-3.12.14-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bbad68a2af4877cc103cd94af9160e45676fc6f0c14abb88e6e092b945c2c8e3", size = 1643660, upload-time = "2025-07-10T13:03:06.406Z" }, + { url = "https://files.pythonhosted.org/packages/81/3c/72477a1d34edb8ab8ce8013086a41526d48b64f77e381c8908d24e1c18f5/aiohttp-3.12.14-cp310-cp310-win32.whl", hash = "sha256:ee580cb7c00bd857b3039ebca03c4448e84700dc1322f860cf7a500a6f62630c", size = 428289, upload-time = "2025-07-10T13:03:08.274Z" }, + { url = "https://files.pythonhosted.org/packages/a2/c4/8aec4ccf1b822ec78e7982bd5cf971113ecce5f773f04039c76a083116fc/aiohttp-3.12.14-cp310-cp310-win_amd64.whl", hash = "sha256:cf4f05b8cea571e2ccc3ca744e35ead24992d90a72ca2cf7ab7a2efbac6716db", size = 451328, upload-time = "2025-07-10T13:03:10.146Z" }, + { url = "https://files.pythonhosted.org/packages/53/e1/8029b29316971c5fa89cec170274582619a01b3d82dd1036872acc9bc7e8/aiohttp-3.12.14-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f4552ff7b18bcec18b60a90c6982049cdb9dac1dba48cf00b97934a06ce2e597", size = 709960, upload-time = "2025-07-10T13:03:11.936Z" }, + { url = "https://files.pythonhosted.org/packages/96/bd/4f204cf1e282041f7b7e8155f846583b19149e0872752711d0da5e9cc023/aiohttp-3.12.14-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8283f42181ff6ccbcf25acaae4e8ab2ff7e92b3ca4a4ced73b2c12d8cd971393", size = 482235, upload-time = "2025-07-10T13:03:14.118Z" }, + { url = "https://files.pythonhosted.org/packages/d6/0f/2a580fcdd113fe2197a3b9df30230c7e85bb10bf56f7915457c60e9addd9/aiohttp-3.12.14-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:040afa180ea514495aaff7ad34ec3d27826eaa5d19812730fe9e529b04bb2179", size = 470501, upload-time = "2025-07-10T13:03:16.153Z" }, + { url = "https://files.pythonhosted.org/packages/38/78/2c1089f6adca90c3dd74915bafed6d6d8a87df5e3da74200f6b3a8b8906f/aiohttp-3.12.14-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b413c12f14c1149f0ffd890f4141a7471ba4b41234fe4fd4a0ff82b1dc299dbb", size = 1740696, upload-time = "2025-07-10T13:03:18.4Z" }, + { url = "https://files.pythonhosted.org/packages/4a/c8/ce6c7a34d9c589f007cfe064da2d943b3dee5aabc64eaecd21faf927ab11/aiohttp-3.12.14-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:1d6f607ce2e1a93315414e3d448b831238f1874b9968e1195b06efaa5c87e245", size = 1689365, upload-time = "2025-07-10T13:03:20.629Z" }, + { url = "https://files.pythonhosted.org/packages/18/10/431cd3d089de700756a56aa896faf3ea82bee39d22f89db7ddc957580308/aiohttp-3.12.14-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:565e70d03e924333004ed101599902bba09ebb14843c8ea39d657f037115201b", size = 1788157, upload-time = "2025-07-10T13:03:22.44Z" }, + { url = "https://files.pythonhosted.org/packages/fa/b2/26f4524184e0f7ba46671c512d4b03022633bcf7d32fa0c6f1ef49d55800/aiohttp-3.12.14-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4699979560728b168d5ab63c668a093c9570af2c7a78ea24ca5212c6cdc2b641", size = 1827203, upload-time = "2025-07-10T13:03:24.628Z" }, + { url = "https://files.pythonhosted.org/packages/e0/30/aadcdf71b510a718e3d98a7bfeaea2396ac847f218b7e8edb241b09bd99a/aiohttp-3.12.14-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad5fdf6af93ec6c99bf800eba3af9a43d8bfd66dce920ac905c817ef4a712afe", size = 1729664, upload-time = "2025-07-10T13:03:26.412Z" }, + { url = "https://files.pythonhosted.org/packages/67/7f/7ccf11756ae498fdedc3d689a0c36ace8fc82f9d52d3517da24adf6e9a74/aiohttp-3.12.14-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4ac76627c0b7ee0e80e871bde0d376a057916cb008a8f3ffc889570a838f5cc7", size = 1666741, upload-time = "2025-07-10T13:03:28.167Z" }, + { url = "https://files.pythonhosted.org/packages/6b/4d/35ebc170b1856dd020c92376dbfe4297217625ef4004d56587024dc2289c/aiohttp-3.12.14-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:798204af1180885651b77bf03adc903743a86a39c7392c472891649610844635", size = 1715013, upload-time = "2025-07-10T13:03:30.018Z" }, + { url = "https://files.pythonhosted.org/packages/7b/24/46dc0380146f33e2e4aa088b92374b598f5bdcde1718c77e8d1a0094f1a4/aiohttp-3.12.14-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:4f1205f97de92c37dd71cf2d5bcfb65fdaed3c255d246172cce729a8d849b4da", size = 1710172, upload-time = "2025-07-10T13:03:31.821Z" }, + { url = "https://files.pythonhosted.org/packages/2f/0a/46599d7d19b64f4d0fe1b57bdf96a9a40b5c125f0ae0d8899bc22e91fdce/aiohttp-3.12.14-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:76ae6f1dd041f85065d9df77c6bc9c9703da9b5c018479d20262acc3df97d419", size = 1690355, upload-time = "2025-07-10T13:03:34.754Z" }, + { url = "https://files.pythonhosted.org/packages/08/86/b21b682e33d5ca317ef96bd21294984f72379454e689d7da584df1512a19/aiohttp-3.12.14-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a194ace7bc43ce765338ca2dfb5661489317db216ea7ea700b0332878b392cab", size = 1783958, upload-time = "2025-07-10T13:03:36.53Z" }, + { url = "https://files.pythonhosted.org/packages/4f/45/f639482530b1396c365f23c5e3b1ae51c9bc02ba2b2248ca0c855a730059/aiohttp-3.12.14-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:16260e8e03744a6fe3fcb05259eeab8e08342c4c33decf96a9dad9f1187275d0", size = 1804423, upload-time = "2025-07-10T13:03:38.504Z" }, + { url = "https://files.pythonhosted.org/packages/7e/e5/39635a9e06eed1d73671bd4079a3caf9cf09a49df08490686f45a710b80e/aiohttp-3.12.14-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8c779e5ebbf0e2e15334ea404fcce54009dc069210164a244d2eac8352a44b28", size = 1717479, upload-time = "2025-07-10T13:03:40.158Z" }, + { url = "https://files.pythonhosted.org/packages/51/e1/7f1c77515d369b7419c5b501196526dad3e72800946c0099594c1f0c20b4/aiohttp-3.12.14-cp311-cp311-win32.whl", hash = "sha256:a289f50bf1bd5be227376c067927f78079a7bdeccf8daa6a9e65c38bae14324b", size = 427907, upload-time = "2025-07-10T13:03:41.801Z" }, + { url = "https://files.pythonhosted.org/packages/06/24/a6bf915c85b7a5b07beba3d42b3282936b51e4578b64a51e8e875643c276/aiohttp-3.12.14-cp311-cp311-win_amd64.whl", hash = "sha256:0b8a69acaf06b17e9c54151a6c956339cf46db4ff72b3ac28516d0f7068f4ced", size = 452334, upload-time = "2025-07-10T13:03:43.485Z" }, + { url = "https://files.pythonhosted.org/packages/c3/0d/29026524e9336e33d9767a1e593ae2b24c2b8b09af7c2bd8193762f76b3e/aiohttp-3.12.14-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a0ecbb32fc3e69bc25efcda7d28d38e987d007096cbbeed04f14a6662d0eee22", size = 701055, upload-time = "2025-07-10T13:03:45.59Z" }, + { url = "https://files.pythonhosted.org/packages/0a/b8/a5e8e583e6c8c1056f4b012b50a03c77a669c2e9bf012b7cf33d6bc4b141/aiohttp-3.12.14-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0400f0ca9bb3e0b02f6466421f253797f6384e9845820c8b05e976398ac1d81a", size = 475670, upload-time = "2025-07-10T13:03:47.249Z" }, + { url = "https://files.pythonhosted.org/packages/29/e8/5202890c9e81a4ec2c2808dd90ffe024952e72c061729e1d49917677952f/aiohttp-3.12.14-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a56809fed4c8a830b5cae18454b7464e1529dbf66f71c4772e3cfa9cbec0a1ff", size = 468513, upload-time = "2025-07-10T13:03:49.377Z" }, + { url = "https://files.pythonhosted.org/packages/23/e5/d11db8c23d8923d3484a27468a40737d50f05b05eebbb6288bafcb467356/aiohttp-3.12.14-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27f2e373276e4755691a963e5d11756d093e346119f0627c2d6518208483fb6d", size = 1715309, upload-time = "2025-07-10T13:03:51.556Z" }, + { url = "https://files.pythonhosted.org/packages/53/44/af6879ca0eff7a16b1b650b7ea4a827301737a350a464239e58aa7c387ef/aiohttp-3.12.14-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:ca39e433630e9a16281125ef57ece6817afd1d54c9f1bf32e901f38f16035869", size = 1697961, upload-time = "2025-07-10T13:03:53.511Z" }, + { url = "https://files.pythonhosted.org/packages/bb/94/18457f043399e1ec0e59ad8674c0372f925363059c276a45a1459e17f423/aiohttp-3.12.14-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9c748b3f8b14c77720132b2510a7d9907a03c20ba80f469e58d5dfd90c079a1c", size = 1753055, upload-time = "2025-07-10T13:03:55.368Z" }, + { url = "https://files.pythonhosted.org/packages/26/d9/1d3744dc588fafb50ff8a6226d58f484a2242b5dd93d8038882f55474d41/aiohttp-3.12.14-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0a568abe1b15ce69d4cc37e23020720423f0728e3cb1f9bcd3f53420ec3bfe7", size = 1799211, upload-time = "2025-07-10T13:03:57.216Z" }, + { url = "https://files.pythonhosted.org/packages/73/12/2530fb2b08773f717ab2d249ca7a982ac66e32187c62d49e2c86c9bba9b4/aiohttp-3.12.14-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9888e60c2c54eaf56704b17feb558c7ed6b7439bca1e07d4818ab878f2083660", size = 1718649, upload-time = "2025-07-10T13:03:59.469Z" }, + { url = "https://files.pythonhosted.org/packages/b9/34/8d6015a729f6571341a311061b578e8b8072ea3656b3d72329fa0faa2c7c/aiohttp-3.12.14-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3006a1dc579b9156de01e7916d38c63dc1ea0679b14627a37edf6151bc530088", size = 1634452, upload-time = "2025-07-10T13:04:01.698Z" }, + { url = "https://files.pythonhosted.org/packages/ff/4b/08b83ea02595a582447aeb0c1986792d0de35fe7a22fb2125d65091cbaf3/aiohttp-3.12.14-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:aa8ec5c15ab80e5501a26719eb48a55f3c567da45c6ea5bb78c52c036b2655c7", size = 1695511, upload-time = "2025-07-10T13:04:04.165Z" }, + { url = "https://files.pythonhosted.org/packages/b5/66/9c7c31037a063eec13ecf1976185c65d1394ded4a5120dd5965e3473cb21/aiohttp-3.12.14-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:39b94e50959aa07844c7fe2206b9f75d63cc3ad1c648aaa755aa257f6f2498a9", size = 1716967, upload-time = "2025-07-10T13:04:06.132Z" }, + { url = "https://files.pythonhosted.org/packages/ba/02/84406e0ad1acb0fb61fd617651ab6de760b2d6a31700904bc0b33bd0894d/aiohttp-3.12.14-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:04c11907492f416dad9885d503fbfc5dcb6768d90cad8639a771922d584609d3", size = 1657620, upload-time = "2025-07-10T13:04:07.944Z" }, + { url = "https://files.pythonhosted.org/packages/07/53/da018f4013a7a179017b9a274b46b9a12cbeb387570f116964f498a6f211/aiohttp-3.12.14-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:88167bd9ab69bb46cee91bd9761db6dfd45b6e76a0438c7e884c3f8160ff21eb", size = 1737179, upload-time = "2025-07-10T13:04:10.182Z" }, + { url = "https://files.pythonhosted.org/packages/49/e8/ca01c5ccfeaafb026d85fa4f43ceb23eb80ea9c1385688db0ef322c751e9/aiohttp-3.12.14-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:791504763f25e8f9f251e4688195e8b455f8820274320204f7eafc467e609425", size = 1765156, upload-time = "2025-07-10T13:04:12.029Z" }, + { url = "https://files.pythonhosted.org/packages/22/32/5501ab525a47ba23c20613e568174d6c63aa09e2caa22cded5c6ea8e3ada/aiohttp-3.12.14-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2785b112346e435dd3a1a67f67713a3fe692d288542f1347ad255683f066d8e0", size = 1724766, upload-time = "2025-07-10T13:04:13.961Z" }, + { url = "https://files.pythonhosted.org/packages/06/af/28e24574801fcf1657945347ee10df3892311c2829b41232be6089e461e7/aiohttp-3.12.14-cp312-cp312-win32.whl", hash = "sha256:15f5f4792c9c999a31d8decf444e79fcfd98497bf98e94284bf390a7bb8c1729", size = 422641, upload-time = "2025-07-10T13:04:16.018Z" }, + { url = "https://files.pythonhosted.org/packages/98/d5/7ac2464aebd2eecac38dbe96148c9eb487679c512449ba5215d233755582/aiohttp-3.12.14-cp312-cp312-win_amd64.whl", hash = "sha256:3b66e1a182879f579b105a80d5c4bd448b91a57e8933564bf41665064796a338", size = 449316, upload-time = "2025-07-10T13:04:18.289Z" }, + { url = "https://files.pythonhosted.org/packages/06/48/e0d2fa8ac778008071e7b79b93ab31ef14ab88804d7ba71b5c964a7c844e/aiohttp-3.12.14-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:3143a7893d94dc82bc409f7308bc10d60285a3cd831a68faf1aa0836c5c3c767", size = 695471, upload-time = "2025-07-10T13:04:20.124Z" }, + { url = "https://files.pythonhosted.org/packages/8d/e7/f73206afa33100804f790b71092888f47df65fd9a4cd0e6800d7c6826441/aiohttp-3.12.14-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3d62ac3d506cef54b355bd34c2a7c230eb693880001dfcda0bf88b38f5d7af7e", size = 473128, upload-time = "2025-07-10T13:04:21.928Z" }, + { url = "https://files.pythonhosted.org/packages/df/e2/4dd00180be551a6e7ee979c20fc7c32727f4889ee3fd5b0586e0d47f30e1/aiohttp-3.12.14-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:48e43e075c6a438937c4de48ec30fa8ad8e6dfef122a038847456bfe7b947b63", size = 465426, upload-time = "2025-07-10T13:04:24.071Z" }, + { url = "https://files.pythonhosted.org/packages/de/dd/525ed198a0bb674a323e93e4d928443a680860802c44fa7922d39436b48b/aiohttp-3.12.14-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:077b4488411a9724cecc436cbc8c133e0d61e694995b8de51aaf351c7578949d", size = 1704252, upload-time = "2025-07-10T13:04:26.049Z" }, + { url = "https://files.pythonhosted.org/packages/d8/b1/01e542aed560a968f692ab4fc4323286e8bc4daae83348cd63588e4f33e3/aiohttp-3.12.14-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d8c35632575653f297dcbc9546305b2c1133391089ab925a6a3706dfa775ccab", size = 1685514, upload-time = "2025-07-10T13:04:28.186Z" }, + { url = "https://files.pythonhosted.org/packages/b3/06/93669694dc5fdabdc01338791e70452d60ce21ea0946a878715688d5a191/aiohttp-3.12.14-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6b8ce87963f0035c6834b28f061df90cf525ff7c9b6283a8ac23acee6502afd4", size = 1737586, upload-time = "2025-07-10T13:04:30.195Z" }, + { url = "https://files.pythonhosted.org/packages/a5/3a/18991048ffc1407ca51efb49ba8bcc1645961f97f563a6c480cdf0286310/aiohttp-3.12.14-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0a2cf66e32a2563bb0766eb24eae7e9a269ac0dc48db0aae90b575dc9583026", size = 1786958, upload-time = "2025-07-10T13:04:32.482Z" }, + { url = "https://files.pythonhosted.org/packages/30/a8/81e237f89a32029f9b4a805af6dffc378f8459c7b9942712c809ff9e76e5/aiohttp-3.12.14-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdea089caf6d5cde975084a884c72d901e36ef9c2fd972c9f51efbbc64e96fbd", size = 1709287, upload-time = "2025-07-10T13:04:34.493Z" }, + { url = "https://files.pythonhosted.org/packages/8c/e3/bd67a11b0fe7fc12c6030473afd9e44223d456f500f7cf526dbaa259ae46/aiohttp-3.12.14-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8a7865f27db67d49e81d463da64a59365ebd6b826e0e4847aa111056dcb9dc88", size = 1622990, upload-time = "2025-07-10T13:04:36.433Z" }, + { url = "https://files.pythonhosted.org/packages/83/ba/e0cc8e0f0d9ce0904e3cf2d6fa41904e379e718a013c721b781d53dcbcca/aiohttp-3.12.14-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0ab5b38a6a39781d77713ad930cb5e7feea6f253de656a5f9f281a8f5931b086", size = 1676015, upload-time = "2025-07-10T13:04:38.958Z" }, + { url = "https://files.pythonhosted.org/packages/d8/b3/1e6c960520bda094c48b56de29a3d978254637ace7168dd97ddc273d0d6c/aiohttp-3.12.14-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:9b3b15acee5c17e8848d90a4ebc27853f37077ba6aec4d8cb4dbbea56d156933", size = 1707678, upload-time = "2025-07-10T13:04:41.275Z" }, + { url = "https://files.pythonhosted.org/packages/0a/19/929a3eb8c35b7f9f076a462eaa9830b32c7f27d3395397665caa5e975614/aiohttp-3.12.14-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e4c972b0bdaac167c1e53e16a16101b17c6d0ed7eac178e653a07b9f7fad7151", size = 1650274, upload-time = "2025-07-10T13:04:43.483Z" }, + { url = "https://files.pythonhosted.org/packages/22/e5/81682a6f20dd1b18ce3d747de8eba11cbef9b270f567426ff7880b096b48/aiohttp-3.12.14-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7442488b0039257a3bdbc55f7209587911f143fca11df9869578db6c26feeeb8", size = 1726408, upload-time = "2025-07-10T13:04:45.577Z" }, + { url = "https://files.pythonhosted.org/packages/8c/17/884938dffaa4048302985483f77dfce5ac18339aad9b04ad4aaa5e32b028/aiohttp-3.12.14-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:f68d3067eecb64c5e9bab4a26aa11bd676f4c70eea9ef6536b0a4e490639add3", size = 1759879, upload-time = "2025-07-10T13:04:47.663Z" }, + { url = "https://files.pythonhosted.org/packages/95/78/53b081980f50b5cf874359bde707a6eacd6c4be3f5f5c93937e48c9d0025/aiohttp-3.12.14-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f88d3704c8b3d598a08ad17d06006cb1ca52a1182291f04979e305c8be6c9758", size = 1708770, upload-time = "2025-07-10T13:04:49.944Z" }, + { url = "https://files.pythonhosted.org/packages/ed/91/228eeddb008ecbe3ffa6c77b440597fdf640307162f0c6488e72c5a2d112/aiohttp-3.12.14-cp313-cp313-win32.whl", hash = "sha256:a3c99ab19c7bf375c4ae3debd91ca5d394b98b6089a03231d4c580ef3c2ae4c5", size = 421688, upload-time = "2025-07-10T13:04:51.993Z" }, + { url = "https://files.pythonhosted.org/packages/66/5f/8427618903343402fdafe2850738f735fd1d9409d2a8f9bcaae5e630d3ba/aiohttp-3.12.14-cp313-cp313-win_amd64.whl", hash = "sha256:3f8aad695e12edc9d571f878c62bedc91adf30c760c8632f09663e5f564f4baa", size = 448098, upload-time = "2025-07-10T13:04:53.999Z" }, ] [[package]] @@ -127,9 +128,9 @@ dependencies = [ { name = "aiohttp" }, { name = "packaging" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/de/03/532bbc645bdebcf3b6af3b25d46655259d66ce69abba7720b71ebfabbade/aioresponses-0.7.8.tar.gz", hash = "sha256:b861cdfe5dc58f3b8afac7b0a6973d5d7b2cb608dd0f6253d16b8ee8eaf6df11", size = 40253 } +sdist = { url = "https://files.pythonhosted.org/packages/de/03/532bbc645bdebcf3b6af3b25d46655259d66ce69abba7720b71ebfabbade/aioresponses-0.7.8.tar.gz", hash = "sha256:b861cdfe5dc58f3b8afac7b0a6973d5d7b2cb608dd0f6253d16b8ee8eaf6df11", size = 40253, upload-time = "2025-01-19T18:14:03.222Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/12/b7/584157e43c98aa89810bc2f7099e7e01c728ecf905a66cf705106009228f/aioresponses-0.7.8-py2.py3-none-any.whl", hash = "sha256:b73bd4400d978855e55004b23a3a84cb0f018183bcf066a85ad392800b5b9a94", size = 12518 }, + { url = "https://files.pythonhosted.org/packages/12/b7/584157e43c98aa89810bc2f7099e7e01c728ecf905a66cf705106009228f/aioresponses-0.7.8-py2.py3-none-any.whl", hash = "sha256:b73bd4400d978855e55004b23a3a84cb0f018183bcf066a85ad392800b5b9a94", size = 12518, upload-time = "2025-01-19T18:13:59.633Z" }, ] [[package]] @@ -140,27 +141,27 @@ dependencies = [ { name = "frozenlist" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/61/62/06741b579156360248d1ec624842ad0edf697050bbaf7c3e46394e106ad1/aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7", size = 25007 } +sdist = { url = "https://files.pythonhosted.org/packages/61/62/06741b579156360248d1ec624842ad0edf697050bbaf7c3e46394e106ad1/aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7", size = 25007, upload-time = "2025-07-03T22:54:43.528Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490 }, + { url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" }, ] [[package]] name = "annotated-doc" version = "0.0.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d7/a6/dc46877b911e40c00d395771ea710d5e77b6de7bacd5fdcd78d70cc5a48f/annotated_doc-0.0.3.tar.gz", hash = "sha256:e18370014c70187422c33e945053ff4c286f453a984eba84d0dbfa0c935adeda", size = 5535 } +sdist = { url = "https://files.pythonhosted.org/packages/d7/a6/dc46877b911e40c00d395771ea710d5e77b6de7bacd5fdcd78d70cc5a48f/annotated_doc-0.0.3.tar.gz", hash = "sha256:e18370014c70187422c33e945053ff4c286f453a984eba84d0dbfa0c935adeda", size = 5535, upload-time = "2025-10-24T14:57:10.718Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/02/b7/cf592cb5de5cb3bade3357f8d2cf42bf103bbe39f459824b4939fd212911/annotated_doc-0.0.3-py3-none-any.whl", hash = "sha256:348ec6664a76f1fd3be81f43dffbee4c7e8ce931ba71ec67cc7f4ade7fbbb580", size = 5488 }, + { url = "https://files.pythonhosted.org/packages/02/b7/cf592cb5de5cb3bade3357f8d2cf42bf103bbe39f459824b4939fd212911/annotated_doc-0.0.3-py3-none-any.whl", hash = "sha256:348ec6664a76f1fd3be81f43dffbee4c7e8ce931ba71ec67cc7f4ade7fbbb580", size = 5488, upload-time = "2025-10-24T14:57:09.462Z" }, ] [[package]] name = "annotated-types" version = "0.7.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 }, + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, ] [[package]] @@ -173,54 +174,54 @@ dependencies = [ { name = "sniffio" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949 } +sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949, upload-time = "2025-03-17T00:02:54.77Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916 }, + { url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916, upload-time = "2025-03-17T00:02:52.713Z" }, ] [[package]] name = "async-timeout" version = "5.0.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a5/ae/136395dfbfe00dfc94da3f3e136d0b13f394cba8f4841120e34226265780/async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3", size = 9274 } +sdist = { url = "https://files.pythonhosted.org/packages/a5/ae/136395dfbfe00dfc94da3f3e136d0b13f394cba8f4841120e34226265780/async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3", size = 9274, upload-time = "2024-11-06T16:41:39.6Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fe/ba/e2081de779ca30d473f21f5b30e0e737c438205440784c7dfc81efc2b029/async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c", size = 6233 }, + { url = "https://files.pythonhosted.org/packages/fe/ba/e2081de779ca30d473f21f5b30e0e737c438205440784c7dfc81efc2b029/async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c", size = 6233, upload-time = "2024-11-06T16:41:37.9Z" }, ] [[package]] name = "attrs" version = "25.3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032 } +sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032, upload-time = "2025-03-13T11:10:22.779Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815 }, + { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815, upload-time = "2025-03-13T11:10:21.14Z" }, ] [[package]] name = "babel" version = "2.17.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852 } +sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852, upload-time = "2025-02-01T15:17:41.026Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537 }, + { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537, upload-time = "2025-02-01T15:17:37.39Z" }, ] [[package]] name = "backoff" version = "2.2.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/47/d7/5bbeb12c44d7c4f2fb5b56abce497eb5ed9f34d85701de869acedd602619/backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba", size = 17001 } +sdist = { url = "https://files.pythonhosted.org/packages/47/d7/5bbeb12c44d7c4f2fb5b56abce497eb5ed9f34d85701de869acedd602619/backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba", size = 17001, upload-time = "2022-10-05T19:19:32.061Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/df/73/b6e24bd22e6720ca8ee9a85a0c4a2971af8497d8f3193fa05390cbd46e09/backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8", size = 15148 }, + { url = "https://files.pythonhosted.org/packages/df/73/b6e24bd22e6720ca8ee9a85a0c4a2971af8497d8f3193fa05390cbd46e09/backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8", size = 15148, upload-time = "2022-10-05T19:19:30.546Z" }, ] [[package]] name = "backports-tarfile" version = "1.2.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/86/72/cd9b395f25e290e633655a100af28cb253e4393396264a98bd5f5951d50f/backports_tarfile-1.2.0.tar.gz", hash = "sha256:d75e02c268746e1b8144c278978b6e98e85de6ad16f8e4b0844a154557eca991", size = 86406 } +sdist = { url = "https://files.pythonhosted.org/packages/86/72/cd9b395f25e290e633655a100af28cb253e4393396264a98bd5f5951d50f/backports_tarfile-1.2.0.tar.gz", hash = "sha256:d75e02c268746e1b8144c278978b6e98e85de6ad16f8e4b0844a154557eca991", size = 86406, upload-time = "2024-05-28T17:01:54.731Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b9/fa/123043af240e49752f1c4bd24da5053b6bd00cad78c2be53c0d1e8b975bc/backports.tarfile-1.2.0-py3-none-any.whl", hash = "sha256:77e284d754527b01fb1e6fa8a1afe577858ebe4e9dad8919e34c862cb399bc34", size = 30181 }, + { url = "https://files.pythonhosted.org/packages/b9/fa/123043af240e49752f1c4bd24da5053b6bd00cad78c2be53c0d1e8b975bc/backports.tarfile-1.2.0-py3-none-any.whl", hash = "sha256:77e284d754527b01fb1e6fa8a1afe577858ebe4e9dad8919e34c862cb399bc34", size = 30181, upload-time = "2024-05-28T17:01:53.112Z" }, ] [[package]] @@ -236,25 +237,25 @@ dependencies = [ { name = "tomli", marker = "python_full_version < '3.11'" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/94/49/26a7b0f3f35da4b5a65f081943b7bcd22d7002f5f0fb8098ec1ff21cb6ef/black-25.1.0.tar.gz", hash = "sha256:33496d5cd1222ad73391352b4ae8da15253c5de89b93a80b3e2c8d9a19ec2666", size = 649449 } +sdist = { url = "https://files.pythonhosted.org/packages/94/49/26a7b0f3f35da4b5a65f081943b7bcd22d7002f5f0fb8098ec1ff21cb6ef/black-25.1.0.tar.gz", hash = "sha256:33496d5cd1222ad73391352b4ae8da15253c5de89b93a80b3e2c8d9a19ec2666", size = 649449, upload-time = "2025-01-29T04:15:40.373Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4d/3b/4ba3f93ac8d90410423fdd31d7541ada9bcee1df32fb90d26de41ed40e1d/black-25.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:759e7ec1e050a15f89b770cefbf91ebee8917aac5c20483bc2d80a6c3a04df32", size = 1629419 }, - { url = "https://files.pythonhosted.org/packages/b4/02/0bde0485146a8a5e694daed47561785e8b77a0466ccc1f3e485d5ef2925e/black-25.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e519ecf93120f34243e6b0054db49c00a35f84f195d5bce7e9f5cfc578fc2da", size = 1461080 }, - { url = "https://files.pythonhosted.org/packages/52/0e/abdf75183c830eaca7589144ff96d49bce73d7ec6ad12ef62185cc0f79a2/black-25.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:055e59b198df7ac0b7efca5ad7ff2516bca343276c466be72eb04a3bcc1f82d7", size = 1766886 }, - { url = "https://files.pythonhosted.org/packages/dc/a6/97d8bb65b1d8a41f8a6736222ba0a334db7b7b77b8023ab4568288f23973/black-25.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:db8ea9917d6f8fc62abd90d944920d95e73c83a5ee3383493e35d271aca872e9", size = 1419404 }, - { url = "https://files.pythonhosted.org/packages/7e/4f/87f596aca05c3ce5b94b8663dbfe242a12843caaa82dd3f85f1ffdc3f177/black-25.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a39337598244de4bae26475f77dda852ea00a93bd4c728e09eacd827ec929df0", size = 1614372 }, - { url = "https://files.pythonhosted.org/packages/e7/d0/2c34c36190b741c59c901e56ab7f6e54dad8df05a6272a9747ecef7c6036/black-25.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96c1c7cd856bba8e20094e36e0f948718dc688dba4a9d78c3adde52b9e6c2299", size = 1442865 }, - { url = "https://files.pythonhosted.org/packages/21/d4/7518c72262468430ead45cf22bd86c883a6448b9eb43672765d69a8f1248/black-25.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bce2e264d59c91e52d8000d507eb20a9aca4a778731a08cfff7e5ac4a4bb7096", size = 1749699 }, - { url = "https://files.pythonhosted.org/packages/58/db/4f5beb989b547f79096e035c4981ceb36ac2b552d0ac5f2620e941501c99/black-25.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:172b1dbff09f86ce6f4eb8edf9dede08b1fce58ba194c87d7a4f1a5aa2f5b3c2", size = 1428028 }, - { url = "https://files.pythonhosted.org/packages/83/71/3fe4741df7adf015ad8dfa082dd36c94ca86bb21f25608eb247b4afb15b2/black-25.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4b60580e829091e6f9238c848ea6750efed72140b91b048770b64e74fe04908b", size = 1650988 }, - { url = "https://files.pythonhosted.org/packages/13/f3/89aac8a83d73937ccd39bbe8fc6ac8860c11cfa0af5b1c96d081facac844/black-25.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e2978f6df243b155ef5fa7e558a43037c3079093ed5d10fd84c43900f2d8ecc", size = 1453985 }, - { url = "https://files.pythonhosted.org/packages/6f/22/b99efca33f1f3a1d2552c714b1e1b5ae92efac6c43e790ad539a163d1754/black-25.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b48735872ec535027d979e8dcb20bf4f70b5ac75a8ea99f127c106a7d7aba9f", size = 1783816 }, - { url = "https://files.pythonhosted.org/packages/18/7e/a27c3ad3822b6f2e0e00d63d58ff6299a99a5b3aee69fa77cd4b0076b261/black-25.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:ea0213189960bda9cf99be5b8c8ce66bb054af5e9e861249cd23471bd7b0b3ba", size = 1440860 }, - { url = "https://files.pythonhosted.org/packages/98/87/0edf98916640efa5d0696e1abb0a8357b52e69e82322628f25bf14d263d1/black-25.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8f0b18a02996a836cc9c9c78e5babec10930862827b1b724ddfe98ccf2f2fe4f", size = 1650673 }, - { url = "https://files.pythonhosted.org/packages/52/e5/f7bf17207cf87fa6e9b676576749c6b6ed0d70f179a3d812c997870291c3/black-25.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:afebb7098bfbc70037a053b91ae8437c3857482d3a690fefc03e9ff7aa9a5fd3", size = 1453190 }, - { url = "https://files.pythonhosted.org/packages/e3/ee/adda3d46d4a9120772fae6de454c8495603c37c4c3b9c60f25b1ab6401fe/black-25.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:030b9759066a4ee5e5aca28c3c77f9c64789cdd4de8ac1df642c40b708be6171", size = 1782926 }, - { url = "https://files.pythonhosted.org/packages/cc/64/94eb5f45dcb997d2082f097a3944cfc7fe87e071907f677e80788a2d7b7a/black-25.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:a22f402b410566e2d1c950708c77ebf5ebd5d0d88a6a2e87c86d9fb48afa0d18", size = 1442613 }, - { url = "https://files.pythonhosted.org/packages/09/71/54e999902aed72baf26bca0d50781b01838251a462612966e9fc4891eadd/black-25.1.0-py3-none-any.whl", hash = "sha256:95e8176dae143ba9097f351d174fdaf0ccd29efb414b362ae3fd72bf0f710717", size = 207646 }, + { url = "https://files.pythonhosted.org/packages/4d/3b/4ba3f93ac8d90410423fdd31d7541ada9bcee1df32fb90d26de41ed40e1d/black-25.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:759e7ec1e050a15f89b770cefbf91ebee8917aac5c20483bc2d80a6c3a04df32", size = 1629419, upload-time = "2025-01-29T05:37:06.642Z" }, + { url = "https://files.pythonhosted.org/packages/b4/02/0bde0485146a8a5e694daed47561785e8b77a0466ccc1f3e485d5ef2925e/black-25.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e519ecf93120f34243e6b0054db49c00a35f84f195d5bce7e9f5cfc578fc2da", size = 1461080, upload-time = "2025-01-29T05:37:09.321Z" }, + { url = "https://files.pythonhosted.org/packages/52/0e/abdf75183c830eaca7589144ff96d49bce73d7ec6ad12ef62185cc0f79a2/black-25.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:055e59b198df7ac0b7efca5ad7ff2516bca343276c466be72eb04a3bcc1f82d7", size = 1766886, upload-time = "2025-01-29T04:18:24.432Z" }, + { url = "https://files.pythonhosted.org/packages/dc/a6/97d8bb65b1d8a41f8a6736222ba0a334db7b7b77b8023ab4568288f23973/black-25.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:db8ea9917d6f8fc62abd90d944920d95e73c83a5ee3383493e35d271aca872e9", size = 1419404, upload-time = "2025-01-29T04:19:04.296Z" }, + { url = "https://files.pythonhosted.org/packages/7e/4f/87f596aca05c3ce5b94b8663dbfe242a12843caaa82dd3f85f1ffdc3f177/black-25.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a39337598244de4bae26475f77dda852ea00a93bd4c728e09eacd827ec929df0", size = 1614372, upload-time = "2025-01-29T05:37:11.71Z" }, + { url = "https://files.pythonhosted.org/packages/e7/d0/2c34c36190b741c59c901e56ab7f6e54dad8df05a6272a9747ecef7c6036/black-25.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96c1c7cd856bba8e20094e36e0f948718dc688dba4a9d78c3adde52b9e6c2299", size = 1442865, upload-time = "2025-01-29T05:37:14.309Z" }, + { url = "https://files.pythonhosted.org/packages/21/d4/7518c72262468430ead45cf22bd86c883a6448b9eb43672765d69a8f1248/black-25.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bce2e264d59c91e52d8000d507eb20a9aca4a778731a08cfff7e5ac4a4bb7096", size = 1749699, upload-time = "2025-01-29T04:18:17.688Z" }, + { url = "https://files.pythonhosted.org/packages/58/db/4f5beb989b547f79096e035c4981ceb36ac2b552d0ac5f2620e941501c99/black-25.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:172b1dbff09f86ce6f4eb8edf9dede08b1fce58ba194c87d7a4f1a5aa2f5b3c2", size = 1428028, upload-time = "2025-01-29T04:18:51.711Z" }, + { url = "https://files.pythonhosted.org/packages/83/71/3fe4741df7adf015ad8dfa082dd36c94ca86bb21f25608eb247b4afb15b2/black-25.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4b60580e829091e6f9238c848ea6750efed72140b91b048770b64e74fe04908b", size = 1650988, upload-time = "2025-01-29T05:37:16.707Z" }, + { url = "https://files.pythonhosted.org/packages/13/f3/89aac8a83d73937ccd39bbe8fc6ac8860c11cfa0af5b1c96d081facac844/black-25.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e2978f6df243b155ef5fa7e558a43037c3079093ed5d10fd84c43900f2d8ecc", size = 1453985, upload-time = "2025-01-29T05:37:18.273Z" }, + { url = "https://files.pythonhosted.org/packages/6f/22/b99efca33f1f3a1d2552c714b1e1b5ae92efac6c43e790ad539a163d1754/black-25.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b48735872ec535027d979e8dcb20bf4f70b5ac75a8ea99f127c106a7d7aba9f", size = 1783816, upload-time = "2025-01-29T04:18:33.823Z" }, + { url = "https://files.pythonhosted.org/packages/18/7e/a27c3ad3822b6f2e0e00d63d58ff6299a99a5b3aee69fa77cd4b0076b261/black-25.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:ea0213189960bda9cf99be5b8c8ce66bb054af5e9e861249cd23471bd7b0b3ba", size = 1440860, upload-time = "2025-01-29T04:19:12.944Z" }, + { url = "https://files.pythonhosted.org/packages/98/87/0edf98916640efa5d0696e1abb0a8357b52e69e82322628f25bf14d263d1/black-25.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8f0b18a02996a836cc9c9c78e5babec10930862827b1b724ddfe98ccf2f2fe4f", size = 1650673, upload-time = "2025-01-29T05:37:20.574Z" }, + { url = "https://files.pythonhosted.org/packages/52/e5/f7bf17207cf87fa6e9b676576749c6b6ed0d70f179a3d812c997870291c3/black-25.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:afebb7098bfbc70037a053b91ae8437c3857482d3a690fefc03e9ff7aa9a5fd3", size = 1453190, upload-time = "2025-01-29T05:37:22.106Z" }, + { url = "https://files.pythonhosted.org/packages/e3/ee/adda3d46d4a9120772fae6de454c8495603c37c4c3b9c60f25b1ab6401fe/black-25.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:030b9759066a4ee5e5aca28c3c77f9c64789cdd4de8ac1df642c40b708be6171", size = 1782926, upload-time = "2025-01-29T04:18:58.564Z" }, + { url = "https://files.pythonhosted.org/packages/cc/64/94eb5f45dcb997d2082f097a3944cfc7fe87e071907f677e80788a2d7b7a/black-25.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:a22f402b410566e2d1c950708c77ebf5ebd5d0d88a6a2e87c86d9fb48afa0d18", size = 1442613, upload-time = "2025-01-29T04:19:27.63Z" }, + { url = "https://files.pythonhosted.org/packages/09/71/54e999902aed72baf26bca0d50781b01838251a462612966e9fc4891eadd/black-25.1.0-py3-none-any.whl", hash = "sha256:95e8176dae143ba9097f351d174fdaf0ccd29efb414b362ae3fd72bf0f710717", size = 207646, upload-time = "2025-01-29T04:15:38.082Z" }, ] [[package]] @@ -268,9 +269,9 @@ dependencies = [ { name = "pyproject-hooks" }, { name = "tomli", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7d/46/aeab111f8e06793e4f0e421fcad593d547fb8313b50990f31681ee2fb1ad/build-1.2.2.post1.tar.gz", hash = "sha256:b36993e92ca9375a219c99e606a122ff365a760a2d4bba0caa09bd5278b608b7", size = 46701 } +sdist = { url = "https://files.pythonhosted.org/packages/7d/46/aeab111f8e06793e4f0e421fcad593d547fb8313b50990f31681ee2fb1ad/build-1.2.2.post1.tar.gz", hash = "sha256:b36993e92ca9375a219c99e606a122ff365a760a2d4bba0caa09bd5278b608b7", size = 46701, upload-time = "2024-10-06T17:22:25.251Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/84/c2/80633736cd183ee4a62107413def345f7e6e3c01563dbca1417363cf957e/build-1.2.2.post1-py3-none-any.whl", hash = "sha256:1d61c0887fa860c01971625baae8bdd338e517b836a2f70dd1f7aa3a6b2fc5b5", size = 22950 }, + { url = "https://files.pythonhosted.org/packages/84/c2/80633736cd183ee4a62107413def345f7e6e3c01563dbca1417363cf957e/build-1.2.2.post1-py3-none-any.whl", hash = "sha256:1d61c0887fa860c01971625baae8bdd338e517b836a2f70dd1f7aa3a6b2fc5b5", size = 22950, upload-time = "2024-10-06T17:22:23.299Z" }, ] [[package]] @@ -283,18 +284,18 @@ dependencies = [ { name = "pyyaml" }, { name = "types-pyyaml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/92/7a/1a8a1138aa800f85a57a68b40ac905d88d325692a18f62146eee28ff8728/cel_python-0.3.0.tar.gz", hash = "sha256:0fb25e0bdf68e738621f1438ecb53f2d2f4e06a48591d6844febe7a1a86e4bfe", size = 67291 } +sdist = { url = "https://files.pythonhosted.org/packages/92/7a/1a8a1138aa800f85a57a68b40ac905d88d325692a18f62146eee28ff8728/cel_python-0.3.0.tar.gz", hash = "sha256:0fb25e0bdf68e738621f1438ecb53f2d2f4e06a48591d6844febe7a1a86e4bfe", size = 67291, upload-time = "2025-05-31T11:58:26.481Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bd/a4/b282e20c07224f4e68cf6c0e9d2c227161b611809bd4970a93974261ff48/cel_python-0.3.0-py3-none-any.whl", hash = "sha256:7db7460f1deed505146f87b94253af80422090ae2a18ea74abca41ff09a9edae", size = 72044 }, + { url = "https://files.pythonhosted.org/packages/bd/a4/b282e20c07224f4e68cf6c0e9d2c227161b611809bd4970a93974261ff48/cel_python-0.3.0-py3-none-any.whl", hash = "sha256:7db7460f1deed505146f87b94253af80422090ae2a18ea74abca41ff09a9edae", size = 72044, upload-time = "2025-05-31T11:58:25.085Z" }, ] [[package]] name = "certifi" version = "2025.7.9" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/de/8a/c729b6b60c66a38f590c4e774decc4b2ec7b0576be8f1aa984a53ffa812a/certifi-2025.7.9.tar.gz", hash = "sha256:c1d2ec05395148ee10cf672ffc28cd37ea0ab0d99f9cc74c43e588cbd111b079", size = 160386 } +sdist = { url = "https://files.pythonhosted.org/packages/de/8a/c729b6b60c66a38f590c4e774decc4b2ec7b0576be8f1aa984a53ffa812a/certifi-2025.7.9.tar.gz", hash = "sha256:c1d2ec05395148ee10cf672ffc28cd37ea0ab0d99f9cc74c43e588cbd111b079", size = 160386, upload-time = "2025-07-09T02:13:58.874Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/66/f3/80a3f974c8b535d394ff960a11ac20368e06b736da395b551a49ce950cce/certifi-2025.7.9-py3-none-any.whl", hash = "sha256:d842783a14f8fdd646895ac26f719a061408834473cfc10203f6a575beb15d39", size = 159230 }, + { url = "https://files.pythonhosted.org/packages/66/f3/80a3f974c8b535d394ff960a11ac20368e06b736da395b551a49ce950cce/certifi-2025.7.9-py3-none-any.whl", hash = "sha256:d842783a14f8fdd646895ac26f719a061408834473cfc10203f6a575beb15d39", size = 159230, upload-time = "2025-07-09T02:13:57.007Z" }, ] [[package]] @@ -304,99 +305,99 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pycparser" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/de/cc/4635c320081c78d6ffc2cab0a76025b691a91204f4aa317d568ff9280a2d/cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382", size = 426024 }, - { url = "https://files.pythonhosted.org/packages/b6/7b/3b2b250f3aab91abe5f8a51ada1b717935fdaec53f790ad4100fe2ec64d1/cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702", size = 448188 }, - { url = "https://files.pythonhosted.org/packages/d3/48/1b9283ebbf0ec065148d8de05d647a986c5f22586b18120020452fff8f5d/cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3", size = 455571 }, - { url = "https://files.pythonhosted.org/packages/40/87/3b8452525437b40f39ca7ff70276679772ee7e8b394934ff60e63b7b090c/cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6", size = 436687 }, - { url = "https://files.pythonhosted.org/packages/8d/fb/4da72871d177d63649ac449aec2e8a29efe0274035880c7af59101ca2232/cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17", size = 446211 }, - { url = "https://files.pythonhosted.org/packages/ab/a0/62f00bcb411332106c02b663b26f3545a9ef136f80d5df746c05878f8c4b/cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8", size = 461325 }, - { url = "https://files.pythonhosted.org/packages/36/83/76127035ed2e7e27b0787604d99da630ac3123bfb02d8e80c633f218a11d/cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e", size = 438784 }, - { url = "https://files.pythonhosted.org/packages/21/81/a6cd025db2f08ac88b901b745c163d884641909641f9b826e8cb87645942/cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be", size = 461564 }, - { url = "https://files.pythonhosted.org/packages/94/dd/a3f0118e688d1b1a57553da23b16bdade96d2f9bcda4d32e7d2838047ff7/cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4", size = 445259 }, - { url = "https://files.pythonhosted.org/packages/2e/ea/70ce63780f096e16ce8588efe039d3c4f91deb1dc01e9c73a287939c79a6/cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41", size = 469200 }, - { url = "https://files.pythonhosted.org/packages/1c/a0/a4fa9f4f781bda074c3ddd57a572b060fa0df7655d2a4247bbe277200146/cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1", size = 477235 }, - { url = "https://files.pythonhosted.org/packages/62/12/ce8710b5b8affbcdd5c6e367217c242524ad17a02fe5beec3ee339f69f85/cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6", size = 459721 }, - { url = "https://files.pythonhosted.org/packages/ff/6b/d45873c5e0242196f042d555526f92aa9e0c32355a1be1ff8c27f077fd37/cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d", size = 467242 }, - { url = "https://files.pythonhosted.org/packages/1a/52/d9a0e523a572fbccf2955f5abe883cfa8bcc570d7faeee06336fbd50c9fc/cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6", size = 477999 }, - { url = "https://files.pythonhosted.org/packages/44/74/f2a2460684a1a2d00ca799ad880d54652841a780c4c97b87754f660c7603/cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f", size = 454242 }, - { url = "https://files.pythonhosted.org/packages/f8/4a/34599cac7dfcd888ff54e801afe06a19c17787dfd94495ab0c8d35fe99fb/cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b", size = 478604 }, - { url = "https://files.pythonhosted.org/packages/cc/b6/db007700f67d151abadf508cbfd6a1884f57eab90b1bb985c4c8c02b0f28/cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", size = 454803 }, - { url = "https://files.pythonhosted.org/packages/1a/df/f8d151540d8c200eb1c6fba8cd0dfd40904f1b0682ea705c36e6c2e97ab3/cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5", size = 478850 }, - { url = "https://files.pythonhosted.org/packages/28/c0/b31116332a547fd2677ae5b78a2ef662dfc8023d67f41b2a83f7c2aa78b1/cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff", size = 485729 }, - { url = "https://files.pythonhosted.org/packages/91/2b/9a1ddfa5c7f13cab007a2c9cc295b70fbbda7cb10a286aa6810338e60ea1/cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99", size = 471256 }, - { url = "https://files.pythonhosted.org/packages/b2/d5/da47df7004cb17e4955df6a43d14b3b4ae77737dff8bf7f8f333196717bf/cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93", size = 479424 }, - { url = "https://files.pythonhosted.org/packages/0b/ac/2a28bcf513e93a219c8a4e8e125534f4f6db03e3179ba1c45e949b76212c/cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3", size = 484568 }, - { url = "https://files.pythonhosted.org/packages/d4/38/ca8a4f639065f14ae0f1d9751e70447a261f1a30fa7547a828ae08142465/cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8", size = 488736 }, - { url = "https://files.pythonhosted.org/packages/0e/2d/eab2e858a91fdff70533cab61dcff4a1f55ec60425832ddfdc9cd36bc8af/cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", size = 454792 }, - { url = "https://files.pythonhosted.org/packages/75/b2/fbaec7c4455c604e29388d55599b99ebcc250a60050610fadde58932b7ee/cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", size = 478893 }, - { url = "https://files.pythonhosted.org/packages/4f/b7/6e4a2162178bf1935c336d4da8a9352cccab4d3a5d7914065490f08c0690/cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", size = 485810 }, - { url = "https://files.pythonhosted.org/packages/c7/8a/1d0e4a9c26e54746dc08c2c6c037889124d4f59dffd853a659fa545f1b40/cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", size = 471200 }, - { url = "https://files.pythonhosted.org/packages/26/9f/1aab65a6c0db35f43c4d1b4f580e8df53914310afc10ae0397d29d697af4/cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", size = 479447 }, - { url = "https://files.pythonhosted.org/packages/5f/e4/fb8b3dd8dc0e98edf1135ff067ae070bb32ef9d509d6cb0f538cd6f7483f/cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", size = 484358 }, - { url = "https://files.pythonhosted.org/packages/f1/47/d7145bf2dc04684935d57d67dff9d6d795b2ba2796806bb109864be3a151/cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", size = 488469 }, +sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621, upload-time = "2024-09-04T20:45:21.852Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/cc/4635c320081c78d6ffc2cab0a76025b691a91204f4aa317d568ff9280a2d/cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382", size = 426024, upload-time = "2024-09-04T20:43:34.186Z" }, + { url = "https://files.pythonhosted.org/packages/b6/7b/3b2b250f3aab91abe5f8a51ada1b717935fdaec53f790ad4100fe2ec64d1/cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702", size = 448188, upload-time = "2024-09-04T20:43:36.286Z" }, + { url = "https://files.pythonhosted.org/packages/d3/48/1b9283ebbf0ec065148d8de05d647a986c5f22586b18120020452fff8f5d/cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3", size = 455571, upload-time = "2024-09-04T20:43:38.586Z" }, + { url = "https://files.pythonhosted.org/packages/40/87/3b8452525437b40f39ca7ff70276679772ee7e8b394934ff60e63b7b090c/cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6", size = 436687, upload-time = "2024-09-04T20:43:40.084Z" }, + { url = "https://files.pythonhosted.org/packages/8d/fb/4da72871d177d63649ac449aec2e8a29efe0274035880c7af59101ca2232/cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17", size = 446211, upload-time = "2024-09-04T20:43:41.526Z" }, + { url = "https://files.pythonhosted.org/packages/ab/a0/62f00bcb411332106c02b663b26f3545a9ef136f80d5df746c05878f8c4b/cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8", size = 461325, upload-time = "2024-09-04T20:43:43.117Z" }, + { url = "https://files.pythonhosted.org/packages/36/83/76127035ed2e7e27b0787604d99da630ac3123bfb02d8e80c633f218a11d/cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e", size = 438784, upload-time = "2024-09-04T20:43:45.256Z" }, + { url = "https://files.pythonhosted.org/packages/21/81/a6cd025db2f08ac88b901b745c163d884641909641f9b826e8cb87645942/cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be", size = 461564, upload-time = "2024-09-04T20:43:46.779Z" }, + { url = "https://files.pythonhosted.org/packages/94/dd/a3f0118e688d1b1a57553da23b16bdade96d2f9bcda4d32e7d2838047ff7/cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4", size = 445259, upload-time = "2024-09-04T20:43:56.123Z" }, + { url = "https://files.pythonhosted.org/packages/2e/ea/70ce63780f096e16ce8588efe039d3c4f91deb1dc01e9c73a287939c79a6/cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41", size = 469200, upload-time = "2024-09-04T20:43:57.891Z" }, + { url = "https://files.pythonhosted.org/packages/1c/a0/a4fa9f4f781bda074c3ddd57a572b060fa0df7655d2a4247bbe277200146/cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1", size = 477235, upload-time = "2024-09-04T20:44:00.18Z" }, + { url = "https://files.pythonhosted.org/packages/62/12/ce8710b5b8affbcdd5c6e367217c242524ad17a02fe5beec3ee339f69f85/cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6", size = 459721, upload-time = "2024-09-04T20:44:01.585Z" }, + { url = "https://files.pythonhosted.org/packages/ff/6b/d45873c5e0242196f042d555526f92aa9e0c32355a1be1ff8c27f077fd37/cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d", size = 467242, upload-time = "2024-09-04T20:44:03.467Z" }, + { url = "https://files.pythonhosted.org/packages/1a/52/d9a0e523a572fbccf2955f5abe883cfa8bcc570d7faeee06336fbd50c9fc/cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6", size = 477999, upload-time = "2024-09-04T20:44:05.023Z" }, + { url = "https://files.pythonhosted.org/packages/44/74/f2a2460684a1a2d00ca799ad880d54652841a780c4c97b87754f660c7603/cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f", size = 454242, upload-time = "2024-09-04T20:44:06.444Z" }, + { url = "https://files.pythonhosted.org/packages/f8/4a/34599cac7dfcd888ff54e801afe06a19c17787dfd94495ab0c8d35fe99fb/cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b", size = 478604, upload-time = "2024-09-04T20:44:08.206Z" }, + { url = "https://files.pythonhosted.org/packages/cc/b6/db007700f67d151abadf508cbfd6a1884f57eab90b1bb985c4c8c02b0f28/cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", size = 454803, upload-time = "2024-09-04T20:44:15.231Z" }, + { url = "https://files.pythonhosted.org/packages/1a/df/f8d151540d8c200eb1c6fba8cd0dfd40904f1b0682ea705c36e6c2e97ab3/cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5", size = 478850, upload-time = "2024-09-04T20:44:17.188Z" }, + { url = "https://files.pythonhosted.org/packages/28/c0/b31116332a547fd2677ae5b78a2ef662dfc8023d67f41b2a83f7c2aa78b1/cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff", size = 485729, upload-time = "2024-09-04T20:44:18.688Z" }, + { url = "https://files.pythonhosted.org/packages/91/2b/9a1ddfa5c7f13cab007a2c9cc295b70fbbda7cb10a286aa6810338e60ea1/cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99", size = 471256, upload-time = "2024-09-04T20:44:20.248Z" }, + { url = "https://files.pythonhosted.org/packages/b2/d5/da47df7004cb17e4955df6a43d14b3b4ae77737dff8bf7f8f333196717bf/cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93", size = 479424, upload-time = "2024-09-04T20:44:21.673Z" }, + { url = "https://files.pythonhosted.org/packages/0b/ac/2a28bcf513e93a219c8a4e8e125534f4f6db03e3179ba1c45e949b76212c/cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3", size = 484568, upload-time = "2024-09-04T20:44:23.245Z" }, + { url = "https://files.pythonhosted.org/packages/d4/38/ca8a4f639065f14ae0f1d9751e70447a261f1a30fa7547a828ae08142465/cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8", size = 488736, upload-time = "2024-09-04T20:44:24.757Z" }, + { url = "https://files.pythonhosted.org/packages/0e/2d/eab2e858a91fdff70533cab61dcff4a1f55ec60425832ddfdc9cd36bc8af/cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", size = 454792, upload-time = "2024-09-04T20:44:32.01Z" }, + { url = "https://files.pythonhosted.org/packages/75/b2/fbaec7c4455c604e29388d55599b99ebcc250a60050610fadde58932b7ee/cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", size = 478893, upload-time = "2024-09-04T20:44:33.606Z" }, + { url = "https://files.pythonhosted.org/packages/4f/b7/6e4a2162178bf1935c336d4da8a9352cccab4d3a5d7914065490f08c0690/cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", size = 485810, upload-time = "2024-09-04T20:44:35.191Z" }, + { url = "https://files.pythonhosted.org/packages/c7/8a/1d0e4a9c26e54746dc08c2c6c037889124d4f59dffd853a659fa545f1b40/cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", size = 471200, upload-time = "2024-09-04T20:44:36.743Z" }, + { url = "https://files.pythonhosted.org/packages/26/9f/1aab65a6c0db35f43c4d1b4f580e8df53914310afc10ae0397d29d697af4/cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", size = 479447, upload-time = "2024-09-04T20:44:38.492Z" }, + { url = "https://files.pythonhosted.org/packages/5f/e4/fb8b3dd8dc0e98edf1135ff067ae070bb32ef9d509d6cb0f538cd6f7483f/cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", size = 484358, upload-time = "2024-09-04T20:44:40.046Z" }, + { url = "https://files.pythonhosted.org/packages/f1/47/d7145bf2dc04684935d57d67dff9d6d795b2ba2796806bb109864be3a151/cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", size = 488469, upload-time = "2024-09-04T20:44:41.616Z" }, ] [[package]] name = "charset-normalizer" version = "3.4.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e4/33/89c2ced2b67d1c2a61c19c6751aa8902d46ce3dacb23600a283619f5a12d/charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63", size = 126367 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/95/28/9901804da60055b406e1a1c5ba7aac1276fb77f1dde635aabfc7fd84b8ab/charset_normalizer-3.4.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c48ed483eb946e6c04ccbe02c6b4d1d48e51944b6db70f697e089c193404941", size = 201818 }, - { url = "https://files.pythonhosted.org/packages/d9/9b/892a8c8af9110935e5adcbb06d9c6fe741b6bb02608c6513983048ba1a18/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2d318c11350e10662026ad0eb71bb51c7812fc8590825304ae0bdd4ac283acd", size = 144649 }, - { url = "https://files.pythonhosted.org/packages/7b/a5/4179abd063ff6414223575e008593861d62abfc22455b5d1a44995b7c101/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9cbfacf36cb0ec2897ce0ebc5d08ca44213af24265bd56eca54bee7923c48fd6", size = 155045 }, - { url = "https://files.pythonhosted.org/packages/3b/95/bc08c7dfeddd26b4be8c8287b9bb055716f31077c8b0ea1cd09553794665/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18dd2e350387c87dabe711b86f83c9c78af772c748904d372ade190b5c7c9d4d", size = 147356 }, - { url = "https://files.pythonhosted.org/packages/a8/2d/7a5b635aa65284bf3eab7653e8b4151ab420ecbae918d3e359d1947b4d61/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8075c35cd58273fee266c58c0c9b670947c19df5fb98e7b66710e04ad4e9ff86", size = 149471 }, - { url = "https://files.pythonhosted.org/packages/ae/38/51fc6ac74251fd331a8cfdb7ec57beba8c23fd5493f1050f71c87ef77ed0/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5bf4545e3b962767e5c06fe1738f951f77d27967cb2caa64c28be7c4563e162c", size = 151317 }, - { url = "https://files.pythonhosted.org/packages/b7/17/edee1e32215ee6e9e46c3e482645b46575a44a2d72c7dfd49e49f60ce6bf/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a6ab32f7210554a96cd9e33abe3ddd86732beeafc7a28e9955cdf22ffadbab0", size = 146368 }, - { url = "https://files.pythonhosted.org/packages/26/2c/ea3e66f2b5f21fd00b2825c94cafb8c326ea6240cd80a91eb09e4a285830/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b33de11b92e9f75a2b545d6e9b6f37e398d86c3e9e9653c4864eb7e89c5773ef", size = 154491 }, - { url = "https://files.pythonhosted.org/packages/52/47/7be7fa972422ad062e909fd62460d45c3ef4c141805b7078dbab15904ff7/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8755483f3c00d6c9a77f490c17e6ab0c8729e39e6390328e42521ef175380ae6", size = 157695 }, - { url = "https://files.pythonhosted.org/packages/2f/42/9f02c194da282b2b340f28e5fb60762de1151387a36842a92b533685c61e/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:68a328e5f55ec37c57f19ebb1fdc56a248db2e3e9ad769919a58672958e8f366", size = 154849 }, - { url = "https://files.pythonhosted.org/packages/67/44/89cacd6628f31fb0b63201a618049be4be2a7435a31b55b5eb1c3674547a/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:21b2899062867b0e1fde9b724f8aecb1af14f2778d69aacd1a5a1853a597a5db", size = 150091 }, - { url = "https://files.pythonhosted.org/packages/1f/79/4b8da9f712bc079c0f16b6d67b099b0b8d808c2292c937f267d816ec5ecc/charset_normalizer-3.4.2-cp310-cp310-win32.whl", hash = "sha256:e8082b26888e2f8b36a042a58307d5b917ef2b1cacab921ad3323ef91901c71a", size = 98445 }, - { url = "https://files.pythonhosted.org/packages/7d/d7/96970afb4fb66497a40761cdf7bd4f6fca0fc7bafde3a84f836c1f57a926/charset_normalizer-3.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:f69a27e45c43520f5487f27627059b64aaf160415589230992cec34c5e18a509", size = 105782 }, - { url = "https://files.pythonhosted.org/packages/05/85/4c40d00dcc6284a1c1ad5de5e0996b06f39d8232f1031cd23c2f5c07ee86/charset_normalizer-3.4.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2", size = 198794 }, - { url = "https://files.pythonhosted.org/packages/41/d9/7a6c0b9db952598e97e93cbdfcb91bacd89b9b88c7c983250a77c008703c/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645", size = 142846 }, - { url = "https://files.pythonhosted.org/packages/66/82/a37989cda2ace7e37f36c1a8ed16c58cf48965a79c2142713244bf945c89/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd", size = 153350 }, - { url = "https://files.pythonhosted.org/packages/df/68/a576b31b694d07b53807269d05ec3f6f1093e9545e8607121995ba7a8313/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8", size = 145657 }, - { url = "https://files.pythonhosted.org/packages/92/9b/ad67f03d74554bed3aefd56fe836e1623a50780f7c998d00ca128924a499/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f", size = 147260 }, - { url = "https://files.pythonhosted.org/packages/a6/e6/8aebae25e328160b20e31a7e9929b1578bbdc7f42e66f46595a432f8539e/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7", size = 149164 }, - { url = "https://files.pythonhosted.org/packages/8b/f2/b3c2f07dbcc248805f10e67a0262c93308cfa149a4cd3d1fe01f593e5fd2/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9", size = 144571 }, - { url = "https://files.pythonhosted.org/packages/60/5b/c3f3a94bc345bc211622ea59b4bed9ae63c00920e2e8f11824aa5708e8b7/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544", size = 151952 }, - { url = "https://files.pythonhosted.org/packages/e2/4d/ff460c8b474122334c2fa394a3f99a04cf11c646da895f81402ae54f5c42/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82", size = 155959 }, - { url = "https://files.pythonhosted.org/packages/a2/2b/b964c6a2fda88611a1fe3d4c400d39c66a42d6c169c924818c848f922415/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0", size = 153030 }, - { url = "https://files.pythonhosted.org/packages/59/2e/d3b9811db26a5ebf444bc0fa4f4be5aa6d76fc6e1c0fd537b16c14e849b6/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5", size = 148015 }, - { url = "https://files.pythonhosted.org/packages/90/07/c5fd7c11eafd561bb51220d600a788f1c8d77c5eef37ee49454cc5c35575/charset_normalizer-3.4.2-cp311-cp311-win32.whl", hash = "sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a", size = 98106 }, - { url = "https://files.pythonhosted.org/packages/a8/05/5e33dbef7e2f773d672b6d79f10ec633d4a71cd96db6673625838a4fd532/charset_normalizer-3.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28", size = 105402 }, - { url = "https://files.pythonhosted.org/packages/d7/a4/37f4d6035c89cac7930395a35cc0f1b872e652eaafb76a6075943754f095/charset_normalizer-3.4.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7", size = 199936 }, - { url = "https://files.pythonhosted.org/packages/ee/8a/1a5e33b73e0d9287274f899d967907cd0bf9c343e651755d9307e0dbf2b3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3", size = 143790 }, - { url = "https://files.pythonhosted.org/packages/66/52/59521f1d8e6ab1482164fa21409c5ef44da3e9f653c13ba71becdd98dec3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a", size = 153924 }, - { url = "https://files.pythonhosted.org/packages/86/2d/fb55fdf41964ec782febbf33cb64be480a6b8f16ded2dbe8db27a405c09f/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214", size = 146626 }, - { url = "https://files.pythonhosted.org/packages/8c/73/6ede2ec59bce19b3edf4209d70004253ec5f4e319f9a2e3f2f15601ed5f7/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a", size = 148567 }, - { url = "https://files.pythonhosted.org/packages/09/14/957d03c6dc343c04904530b6bef4e5efae5ec7d7990a7cbb868e4595ee30/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd", size = 150957 }, - { url = "https://files.pythonhosted.org/packages/0d/c8/8174d0e5c10ccebdcb1b53cc959591c4c722a3ad92461a273e86b9f5a302/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981", size = 145408 }, - { url = "https://files.pythonhosted.org/packages/58/aa/8904b84bc8084ac19dc52feb4f5952c6df03ffb460a887b42615ee1382e8/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c", size = 153399 }, - { url = "https://files.pythonhosted.org/packages/c2/26/89ee1f0e264d201cb65cf054aca6038c03b1a0c6b4ae998070392a3ce605/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b", size = 156815 }, - { url = "https://files.pythonhosted.org/packages/fd/07/68e95b4b345bad3dbbd3a8681737b4338ff2c9df29856a6d6d23ac4c73cb/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d", size = 154537 }, - { url = "https://files.pythonhosted.org/packages/77/1a/5eefc0ce04affb98af07bc05f3bac9094513c0e23b0562d64af46a06aae4/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f", size = 149565 }, - { url = "https://files.pythonhosted.org/packages/37/a0/2410e5e6032a174c95e0806b1a6585eb21e12f445ebe239fac441995226a/charset_normalizer-3.4.2-cp312-cp312-win32.whl", hash = "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c", size = 98357 }, - { url = "https://files.pythonhosted.org/packages/6c/4f/c02d5c493967af3eda9c771ad4d2bbc8df6f99ddbeb37ceea6e8716a32bc/charset_normalizer-3.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e", size = 105776 }, - { url = "https://files.pythonhosted.org/packages/ea/12/a93df3366ed32db1d907d7593a94f1fe6293903e3e92967bebd6950ed12c/charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0", size = 199622 }, - { url = "https://files.pythonhosted.org/packages/04/93/bf204e6f344c39d9937d3c13c8cd5bbfc266472e51fc8c07cb7f64fcd2de/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf", size = 143435 }, - { url = "https://files.pythonhosted.org/packages/22/2a/ea8a2095b0bafa6c5b5a55ffdc2f924455233ee7b91c69b7edfcc9e02284/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e", size = 153653 }, - { url = "https://files.pythonhosted.org/packages/b6/57/1b090ff183d13cef485dfbe272e2fe57622a76694061353c59da52c9a659/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1", size = 146231 }, - { url = "https://files.pythonhosted.org/packages/e2/28/ffc026b26f441fc67bd21ab7f03b313ab3fe46714a14b516f931abe1a2d8/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c", size = 148243 }, - { url = "https://files.pythonhosted.org/packages/c0/0f/9abe9bd191629c33e69e47c6ef45ef99773320e9ad8e9cb08b8ab4a8d4cb/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691", size = 150442 }, - { url = "https://files.pythonhosted.org/packages/67/7c/a123bbcedca91d5916c056407f89a7f5e8fdfce12ba825d7d6b9954a1a3c/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0", size = 145147 }, - { url = "https://files.pythonhosted.org/packages/ec/fe/1ac556fa4899d967b83e9893788e86b6af4d83e4726511eaaad035e36595/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b", size = 153057 }, - { url = "https://files.pythonhosted.org/packages/2b/ff/acfc0b0a70b19e3e54febdd5301a98b72fa07635e56f24f60502e954c461/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff", size = 156454 }, - { url = "https://files.pythonhosted.org/packages/92/08/95b458ce9c740d0645feb0e96cea1f5ec946ea9c580a94adfe0b617f3573/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b", size = 154174 }, - { url = "https://files.pythonhosted.org/packages/78/be/8392efc43487ac051eee6c36d5fbd63032d78f7728cb37aebcc98191f1ff/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148", size = 149166 }, - { url = "https://files.pythonhosted.org/packages/44/96/392abd49b094d30b91d9fbda6a69519e95802250b777841cf3bda8fe136c/charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7", size = 98064 }, - { url = "https://files.pythonhosted.org/packages/e9/b0/0200da600134e001d91851ddc797809e2fe0ea72de90e09bec5a2fbdaccb/charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980", size = 105641 }, - { url = "https://files.pythonhosted.org/packages/20/94/c5790835a017658cbfabd07f3bfb549140c3ac458cfc196323996b10095a/charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", size = 52626 }, +sdist = { url = "https://files.pythonhosted.org/packages/e4/33/89c2ced2b67d1c2a61c19c6751aa8902d46ce3dacb23600a283619f5a12d/charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63", size = 126367, upload-time = "2025-05-02T08:34:42.01Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/28/9901804da60055b406e1a1c5ba7aac1276fb77f1dde635aabfc7fd84b8ab/charset_normalizer-3.4.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c48ed483eb946e6c04ccbe02c6b4d1d48e51944b6db70f697e089c193404941", size = 201818, upload-time = "2025-05-02T08:31:46.725Z" }, + { url = "https://files.pythonhosted.org/packages/d9/9b/892a8c8af9110935e5adcbb06d9c6fe741b6bb02608c6513983048ba1a18/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2d318c11350e10662026ad0eb71bb51c7812fc8590825304ae0bdd4ac283acd", size = 144649, upload-time = "2025-05-02T08:31:48.889Z" }, + { url = "https://files.pythonhosted.org/packages/7b/a5/4179abd063ff6414223575e008593861d62abfc22455b5d1a44995b7c101/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9cbfacf36cb0ec2897ce0ebc5d08ca44213af24265bd56eca54bee7923c48fd6", size = 155045, upload-time = "2025-05-02T08:31:50.757Z" }, + { url = "https://files.pythonhosted.org/packages/3b/95/bc08c7dfeddd26b4be8c8287b9bb055716f31077c8b0ea1cd09553794665/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18dd2e350387c87dabe711b86f83c9c78af772c748904d372ade190b5c7c9d4d", size = 147356, upload-time = "2025-05-02T08:31:52.634Z" }, + { url = "https://files.pythonhosted.org/packages/a8/2d/7a5b635aa65284bf3eab7653e8b4151ab420ecbae918d3e359d1947b4d61/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8075c35cd58273fee266c58c0c9b670947c19df5fb98e7b66710e04ad4e9ff86", size = 149471, upload-time = "2025-05-02T08:31:56.207Z" }, + { url = "https://files.pythonhosted.org/packages/ae/38/51fc6ac74251fd331a8cfdb7ec57beba8c23fd5493f1050f71c87ef77ed0/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5bf4545e3b962767e5c06fe1738f951f77d27967cb2caa64c28be7c4563e162c", size = 151317, upload-time = "2025-05-02T08:31:57.613Z" }, + { url = "https://files.pythonhosted.org/packages/b7/17/edee1e32215ee6e9e46c3e482645b46575a44a2d72c7dfd49e49f60ce6bf/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a6ab32f7210554a96cd9e33abe3ddd86732beeafc7a28e9955cdf22ffadbab0", size = 146368, upload-time = "2025-05-02T08:31:59.468Z" }, + { url = "https://files.pythonhosted.org/packages/26/2c/ea3e66f2b5f21fd00b2825c94cafb8c326ea6240cd80a91eb09e4a285830/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b33de11b92e9f75a2b545d6e9b6f37e398d86c3e9e9653c4864eb7e89c5773ef", size = 154491, upload-time = "2025-05-02T08:32:01.219Z" }, + { url = "https://files.pythonhosted.org/packages/52/47/7be7fa972422ad062e909fd62460d45c3ef4c141805b7078dbab15904ff7/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8755483f3c00d6c9a77f490c17e6ab0c8729e39e6390328e42521ef175380ae6", size = 157695, upload-time = "2025-05-02T08:32:03.045Z" }, + { url = "https://files.pythonhosted.org/packages/2f/42/9f02c194da282b2b340f28e5fb60762de1151387a36842a92b533685c61e/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:68a328e5f55ec37c57f19ebb1fdc56a248db2e3e9ad769919a58672958e8f366", size = 154849, upload-time = "2025-05-02T08:32:04.651Z" }, + { url = "https://files.pythonhosted.org/packages/67/44/89cacd6628f31fb0b63201a618049be4be2a7435a31b55b5eb1c3674547a/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:21b2899062867b0e1fde9b724f8aecb1af14f2778d69aacd1a5a1853a597a5db", size = 150091, upload-time = "2025-05-02T08:32:06.719Z" }, + { url = "https://files.pythonhosted.org/packages/1f/79/4b8da9f712bc079c0f16b6d67b099b0b8d808c2292c937f267d816ec5ecc/charset_normalizer-3.4.2-cp310-cp310-win32.whl", hash = "sha256:e8082b26888e2f8b36a042a58307d5b917ef2b1cacab921ad3323ef91901c71a", size = 98445, upload-time = "2025-05-02T08:32:08.66Z" }, + { url = "https://files.pythonhosted.org/packages/7d/d7/96970afb4fb66497a40761cdf7bd4f6fca0fc7bafde3a84f836c1f57a926/charset_normalizer-3.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:f69a27e45c43520f5487f27627059b64aaf160415589230992cec34c5e18a509", size = 105782, upload-time = "2025-05-02T08:32:10.46Z" }, + { url = "https://files.pythonhosted.org/packages/05/85/4c40d00dcc6284a1c1ad5de5e0996b06f39d8232f1031cd23c2f5c07ee86/charset_normalizer-3.4.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2", size = 198794, upload-time = "2025-05-02T08:32:11.945Z" }, + { url = "https://files.pythonhosted.org/packages/41/d9/7a6c0b9db952598e97e93cbdfcb91bacd89b9b88c7c983250a77c008703c/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645", size = 142846, upload-time = "2025-05-02T08:32:13.946Z" }, + { url = "https://files.pythonhosted.org/packages/66/82/a37989cda2ace7e37f36c1a8ed16c58cf48965a79c2142713244bf945c89/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd", size = 153350, upload-time = "2025-05-02T08:32:15.873Z" }, + { url = "https://files.pythonhosted.org/packages/df/68/a576b31b694d07b53807269d05ec3f6f1093e9545e8607121995ba7a8313/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8", size = 145657, upload-time = "2025-05-02T08:32:17.283Z" }, + { url = "https://files.pythonhosted.org/packages/92/9b/ad67f03d74554bed3aefd56fe836e1623a50780f7c998d00ca128924a499/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f", size = 147260, upload-time = "2025-05-02T08:32:18.807Z" }, + { url = "https://files.pythonhosted.org/packages/a6/e6/8aebae25e328160b20e31a7e9929b1578bbdc7f42e66f46595a432f8539e/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7", size = 149164, upload-time = "2025-05-02T08:32:20.333Z" }, + { url = "https://files.pythonhosted.org/packages/8b/f2/b3c2f07dbcc248805f10e67a0262c93308cfa149a4cd3d1fe01f593e5fd2/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9", size = 144571, upload-time = "2025-05-02T08:32:21.86Z" }, + { url = "https://files.pythonhosted.org/packages/60/5b/c3f3a94bc345bc211622ea59b4bed9ae63c00920e2e8f11824aa5708e8b7/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544", size = 151952, upload-time = "2025-05-02T08:32:23.434Z" }, + { url = "https://files.pythonhosted.org/packages/e2/4d/ff460c8b474122334c2fa394a3f99a04cf11c646da895f81402ae54f5c42/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82", size = 155959, upload-time = "2025-05-02T08:32:24.993Z" }, + { url = "https://files.pythonhosted.org/packages/a2/2b/b964c6a2fda88611a1fe3d4c400d39c66a42d6c169c924818c848f922415/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0", size = 153030, upload-time = "2025-05-02T08:32:26.435Z" }, + { url = "https://files.pythonhosted.org/packages/59/2e/d3b9811db26a5ebf444bc0fa4f4be5aa6d76fc6e1c0fd537b16c14e849b6/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5", size = 148015, upload-time = "2025-05-02T08:32:28.376Z" }, + { url = "https://files.pythonhosted.org/packages/90/07/c5fd7c11eafd561bb51220d600a788f1c8d77c5eef37ee49454cc5c35575/charset_normalizer-3.4.2-cp311-cp311-win32.whl", hash = "sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a", size = 98106, upload-time = "2025-05-02T08:32:30.281Z" }, + { url = "https://files.pythonhosted.org/packages/a8/05/5e33dbef7e2f773d672b6d79f10ec633d4a71cd96db6673625838a4fd532/charset_normalizer-3.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28", size = 105402, upload-time = "2025-05-02T08:32:32.191Z" }, + { url = "https://files.pythonhosted.org/packages/d7/a4/37f4d6035c89cac7930395a35cc0f1b872e652eaafb76a6075943754f095/charset_normalizer-3.4.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7", size = 199936, upload-time = "2025-05-02T08:32:33.712Z" }, + { url = "https://files.pythonhosted.org/packages/ee/8a/1a5e33b73e0d9287274f899d967907cd0bf9c343e651755d9307e0dbf2b3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3", size = 143790, upload-time = "2025-05-02T08:32:35.768Z" }, + { url = "https://files.pythonhosted.org/packages/66/52/59521f1d8e6ab1482164fa21409c5ef44da3e9f653c13ba71becdd98dec3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a", size = 153924, upload-time = "2025-05-02T08:32:37.284Z" }, + { url = "https://files.pythonhosted.org/packages/86/2d/fb55fdf41964ec782febbf33cb64be480a6b8f16ded2dbe8db27a405c09f/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214", size = 146626, upload-time = "2025-05-02T08:32:38.803Z" }, + { url = "https://files.pythonhosted.org/packages/8c/73/6ede2ec59bce19b3edf4209d70004253ec5f4e319f9a2e3f2f15601ed5f7/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a", size = 148567, upload-time = "2025-05-02T08:32:40.251Z" }, + { url = "https://files.pythonhosted.org/packages/09/14/957d03c6dc343c04904530b6bef4e5efae5ec7d7990a7cbb868e4595ee30/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd", size = 150957, upload-time = "2025-05-02T08:32:41.705Z" }, + { url = "https://files.pythonhosted.org/packages/0d/c8/8174d0e5c10ccebdcb1b53cc959591c4c722a3ad92461a273e86b9f5a302/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981", size = 145408, upload-time = "2025-05-02T08:32:43.709Z" }, + { url = "https://files.pythonhosted.org/packages/58/aa/8904b84bc8084ac19dc52feb4f5952c6df03ffb460a887b42615ee1382e8/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c", size = 153399, upload-time = "2025-05-02T08:32:46.197Z" }, + { url = "https://files.pythonhosted.org/packages/c2/26/89ee1f0e264d201cb65cf054aca6038c03b1a0c6b4ae998070392a3ce605/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b", size = 156815, upload-time = "2025-05-02T08:32:48.105Z" }, + { url = "https://files.pythonhosted.org/packages/fd/07/68e95b4b345bad3dbbd3a8681737b4338ff2c9df29856a6d6d23ac4c73cb/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d", size = 154537, upload-time = "2025-05-02T08:32:49.719Z" }, + { url = "https://files.pythonhosted.org/packages/77/1a/5eefc0ce04affb98af07bc05f3bac9094513c0e23b0562d64af46a06aae4/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f", size = 149565, upload-time = "2025-05-02T08:32:51.404Z" }, + { url = "https://files.pythonhosted.org/packages/37/a0/2410e5e6032a174c95e0806b1a6585eb21e12f445ebe239fac441995226a/charset_normalizer-3.4.2-cp312-cp312-win32.whl", hash = "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c", size = 98357, upload-time = "2025-05-02T08:32:53.079Z" }, + { url = "https://files.pythonhosted.org/packages/6c/4f/c02d5c493967af3eda9c771ad4d2bbc8df6f99ddbeb37ceea6e8716a32bc/charset_normalizer-3.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e", size = 105776, upload-time = "2025-05-02T08:32:54.573Z" }, + { url = "https://files.pythonhosted.org/packages/ea/12/a93df3366ed32db1d907d7593a94f1fe6293903e3e92967bebd6950ed12c/charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0", size = 199622, upload-time = "2025-05-02T08:32:56.363Z" }, + { url = "https://files.pythonhosted.org/packages/04/93/bf204e6f344c39d9937d3c13c8cd5bbfc266472e51fc8c07cb7f64fcd2de/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf", size = 143435, upload-time = "2025-05-02T08:32:58.551Z" }, + { url = "https://files.pythonhosted.org/packages/22/2a/ea8a2095b0bafa6c5b5a55ffdc2f924455233ee7b91c69b7edfcc9e02284/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e", size = 153653, upload-time = "2025-05-02T08:33:00.342Z" }, + { url = "https://files.pythonhosted.org/packages/b6/57/1b090ff183d13cef485dfbe272e2fe57622a76694061353c59da52c9a659/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1", size = 146231, upload-time = "2025-05-02T08:33:02.081Z" }, + { url = "https://files.pythonhosted.org/packages/e2/28/ffc026b26f441fc67bd21ab7f03b313ab3fe46714a14b516f931abe1a2d8/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c", size = 148243, upload-time = "2025-05-02T08:33:04.063Z" }, + { url = "https://files.pythonhosted.org/packages/c0/0f/9abe9bd191629c33e69e47c6ef45ef99773320e9ad8e9cb08b8ab4a8d4cb/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691", size = 150442, upload-time = "2025-05-02T08:33:06.418Z" }, + { url = "https://files.pythonhosted.org/packages/67/7c/a123bbcedca91d5916c056407f89a7f5e8fdfce12ba825d7d6b9954a1a3c/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0", size = 145147, upload-time = "2025-05-02T08:33:08.183Z" }, + { url = "https://files.pythonhosted.org/packages/ec/fe/1ac556fa4899d967b83e9893788e86b6af4d83e4726511eaaad035e36595/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b", size = 153057, upload-time = "2025-05-02T08:33:09.986Z" }, + { url = "https://files.pythonhosted.org/packages/2b/ff/acfc0b0a70b19e3e54febdd5301a98b72fa07635e56f24f60502e954c461/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff", size = 156454, upload-time = "2025-05-02T08:33:11.814Z" }, + { url = "https://files.pythonhosted.org/packages/92/08/95b458ce9c740d0645feb0e96cea1f5ec946ea9c580a94adfe0b617f3573/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b", size = 154174, upload-time = "2025-05-02T08:33:13.707Z" }, + { url = "https://files.pythonhosted.org/packages/78/be/8392efc43487ac051eee6c36d5fbd63032d78f7728cb37aebcc98191f1ff/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148", size = 149166, upload-time = "2025-05-02T08:33:15.458Z" }, + { url = "https://files.pythonhosted.org/packages/44/96/392abd49b094d30b91d9fbda6a69519e95802250b777841cf3bda8fe136c/charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7", size = 98064, upload-time = "2025-05-02T08:33:17.06Z" }, + { url = "https://files.pythonhosted.org/packages/e9/b0/0200da600134e001d91851ddc797809e2fe0ea72de90e09bec5a2fbdaccb/charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980", size = 105641, upload-time = "2025-05-02T08:33:18.753Z" }, + { url = "https://files.pythonhosted.org/packages/20/94/c5790835a017658cbfabd07f3bfb549140c3ac458cfc196323996b10095a/charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", size = 52626, upload-time = "2025-05-02T08:34:40.053Z" }, ] [[package]] @@ -406,82 +407,82 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342 } +sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342, upload-time = "2025-05-20T23:19:49.832Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215 }, + { url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215, upload-time = "2025-05-20T23:19:47.796Z" }, ] [[package]] name = "colorama" version = "0.4.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] [[package]] name = "coverage" version = "7.9.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/04/b7/c0465ca253df10a9e8dae0692a4ae6e9726d245390aaef92360e1d6d3832/coverage-7.9.2.tar.gz", hash = "sha256:997024fa51e3290264ffd7492ec97d0690293ccd2b45a6cd7d82d945a4a80c8b", size = 813556 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a1/0d/5c2114fd776c207bd55068ae8dc1bef63ecd1b767b3389984a8e58f2b926/coverage-7.9.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:66283a192a14a3854b2e7f3418d7db05cdf411012ab7ff5db98ff3b181e1f912", size = 212039 }, - { url = "https://files.pythonhosted.org/packages/cf/ad/dc51f40492dc2d5fcd31bb44577bc0cc8920757d6bc5d3e4293146524ef9/coverage-7.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4e01d138540ef34fcf35c1aa24d06c3de2a4cffa349e29a10056544f35cca15f", size = 212428 }, - { url = "https://files.pythonhosted.org/packages/a2/a3/55cb3ff1b36f00df04439c3993d8529193cdf165a2467bf1402539070f16/coverage-7.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f22627c1fe2745ee98d3ab87679ca73a97e75ca75eb5faee48660d060875465f", size = 241534 }, - { url = "https://files.pythonhosted.org/packages/eb/c9/a8410b91b6be4f6e9c2e9f0dce93749b6b40b751d7065b4410bf89cb654b/coverage-7.9.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b1c2d8363247b46bd51f393f86c94096e64a1cf6906803fa8d5a9d03784bdbf", size = 239408 }, - { url = "https://files.pythonhosted.org/packages/ff/c4/6f3e56d467c612b9070ae71d5d3b114c0b899b5788e1ca3c93068ccb7018/coverage-7.9.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c10c882b114faf82dbd33e876d0cbd5e1d1ebc0d2a74ceef642c6152f3f4d547", size = 240552 }, - { url = "https://files.pythonhosted.org/packages/fd/20/04eda789d15af1ce79bce5cc5fd64057c3a0ac08fd0576377a3096c24663/coverage-7.9.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:de3c0378bdf7066c3988d66cd5232d161e933b87103b014ab1b0b4676098fa45", size = 240464 }, - { url = "https://files.pythonhosted.org/packages/a9/5a/217b32c94cc1a0b90f253514815332d08ec0812194a1ce9cca97dda1cd20/coverage-7.9.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:1e2f097eae0e5991e7623958a24ced3282676c93c013dde41399ff63e230fcf2", size = 239134 }, - { url = "https://files.pythonhosted.org/packages/34/73/1d019c48f413465eb5d3b6898b6279e87141c80049f7dbf73fd020138549/coverage-7.9.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:28dc1f67e83a14e7079b6cea4d314bc8b24d1aed42d3582ff89c0295f09b181e", size = 239405 }, - { url = "https://files.pythonhosted.org/packages/49/6c/a2beca7aa2595dad0c0d3f350382c381c92400efe5261e2631f734a0e3fe/coverage-7.9.2-cp310-cp310-win32.whl", hash = "sha256:bf7d773da6af9e10dbddacbf4e5cab13d06d0ed93561d44dae0188a42c65be7e", size = 214519 }, - { url = "https://files.pythonhosted.org/packages/fc/c8/91e5e4a21f9a51e2c7cdd86e587ae01a4fcff06fc3fa8cde4d6f7cf68df4/coverage-7.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:0c0378ba787681ab1897f7c89b415bd56b0b2d9a47e5a3d8dc0ea55aac118d6c", size = 215400 }, - { url = "https://files.pythonhosted.org/packages/39/40/916786453bcfafa4c788abee4ccd6f592b5b5eca0cd61a32a4e5a7ef6e02/coverage-7.9.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a7a56a2964a9687b6aba5b5ced6971af308ef6f79a91043c05dd4ee3ebc3e9ba", size = 212152 }, - { url = "https://files.pythonhosted.org/packages/9f/66/cc13bae303284b546a030762957322bbbff1ee6b6cb8dc70a40f8a78512f/coverage-7.9.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:123d589f32c11d9be7fe2e66d823a236fe759b0096f5db3fb1b75b2fa414a4fa", size = 212540 }, - { url = "https://files.pythonhosted.org/packages/0f/3c/d56a764b2e5a3d43257c36af4a62c379df44636817bb5f89265de4bf8bd7/coverage-7.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:333b2e0ca576a7dbd66e85ab402e35c03b0b22f525eed82681c4b866e2e2653a", size = 245097 }, - { url = "https://files.pythonhosted.org/packages/b1/46/bd064ea8b3c94eb4ca5d90e34d15b806cba091ffb2b8e89a0d7066c45791/coverage-7.9.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:326802760da234baf9f2f85a39e4a4b5861b94f6c8d95251f699e4f73b1835dc", size = 242812 }, - { url = "https://files.pythonhosted.org/packages/43/02/d91992c2b29bc7afb729463bc918ebe5f361be7f1daae93375a5759d1e28/coverage-7.9.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19e7be4cfec248df38ce40968c95d3952fbffd57b400d4b9bb580f28179556d2", size = 244617 }, - { url = "https://files.pythonhosted.org/packages/b7/4f/8fadff6bf56595a16d2d6e33415841b0163ac660873ed9a4e9046194f779/coverage-7.9.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0b4a4cb73b9f2b891c1788711408ef9707666501ba23684387277ededab1097c", size = 244263 }, - { url = "https://files.pythonhosted.org/packages/9b/d2/e0be7446a2bba11739edb9f9ba4eff30b30d8257370e237418eb44a14d11/coverage-7.9.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:2c8937fa16c8c9fbbd9f118588756e7bcdc7e16a470766a9aef912dd3f117dbd", size = 242314 }, - { url = "https://files.pythonhosted.org/packages/9d/7d/dcbac9345000121b8b57a3094c2dfcf1ccc52d8a14a40c1d4bc89f936f80/coverage-7.9.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:42da2280c4d30c57a9b578bafd1d4494fa6c056d4c419d9689e66d775539be74", size = 242904 }, - { url = "https://files.pythonhosted.org/packages/41/58/11e8db0a0c0510cf31bbbdc8caf5d74a358b696302a45948d7c768dfd1cf/coverage-7.9.2-cp311-cp311-win32.whl", hash = "sha256:14fa8d3da147f5fdf9d298cacc18791818f3f1a9f542c8958b80c228320e90c6", size = 214553 }, - { url = "https://files.pythonhosted.org/packages/3a/7d/751794ec8907a15e257136e48dc1021b1f671220ecccfd6c4eaf30802714/coverage-7.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:549cab4892fc82004f9739963163fd3aac7a7b0df430669b75b86d293d2df2a7", size = 215441 }, - { url = "https://files.pythonhosted.org/packages/62/5b/34abcedf7b946c1c9e15b44f326cb5b0da852885312b30e916f674913428/coverage-7.9.2-cp311-cp311-win_arm64.whl", hash = "sha256:c2667a2b913e307f06aa4e5677f01a9746cd08e4b35e14ebcde6420a9ebb4c62", size = 213873 }, - { url = "https://files.pythonhosted.org/packages/53/d7/7deefc6fd4f0f1d4c58051f4004e366afc9e7ab60217ac393f247a1de70a/coverage-7.9.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ae9eb07f1cfacd9cfe8eaee6f4ff4b8a289a668c39c165cd0c8548484920ffc0", size = 212344 }, - { url = "https://files.pythonhosted.org/packages/95/0c/ee03c95d32be4d519e6a02e601267769ce2e9a91fc8faa1b540e3626c680/coverage-7.9.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9ce85551f9a1119f02adc46d3014b5ee3f765deac166acf20dbb851ceb79b6f3", size = 212580 }, - { url = "https://files.pythonhosted.org/packages/8b/9f/826fa4b544b27620086211b87a52ca67592622e1f3af9e0a62c87aea153a/coverage-7.9.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8f6389ac977c5fb322e0e38885fbbf901743f79d47f50db706e7644dcdcb6e1", size = 246383 }, - { url = "https://files.pythonhosted.org/packages/7f/b3/4477aafe2a546427b58b9c540665feff874f4db651f4d3cb21b308b3a6d2/coverage-7.9.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff0d9eae8cdfcd58fe7893b88993723583a6ce4dfbfd9f29e001922544f95615", size = 243400 }, - { url = "https://files.pythonhosted.org/packages/f8/c2/efffa43778490c226d9d434827702f2dfbc8041d79101a795f11cbb2cf1e/coverage-7.9.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fae939811e14e53ed8a9818dad51d434a41ee09df9305663735f2e2d2d7d959b", size = 245591 }, - { url = "https://files.pythonhosted.org/packages/c6/e7/a59888e882c9a5f0192d8627a30ae57910d5d449c80229b55e7643c078c4/coverage-7.9.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:31991156251ec202c798501e0a42bbdf2169dcb0f137b1f5c0f4267f3fc68ef9", size = 245402 }, - { url = "https://files.pythonhosted.org/packages/92/a5/72fcd653ae3d214927edc100ce67440ed8a0a1e3576b8d5e6d066ed239db/coverage-7.9.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:d0d67963f9cbfc7c7f96d4ac74ed60ecbebd2ea6eeb51887af0f8dce205e545f", size = 243583 }, - { url = "https://files.pythonhosted.org/packages/5c/f5/84e70e4df28f4a131d580d7d510aa1ffd95037293da66fd20d446090a13b/coverage-7.9.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:49b752a2858b10580969ec6af6f090a9a440a64a301ac1528d7ca5f7ed497f4d", size = 244815 }, - { url = "https://files.pythonhosted.org/packages/39/e7/d73d7cbdbd09fdcf4642655ae843ad403d9cbda55d725721965f3580a314/coverage-7.9.2-cp312-cp312-win32.whl", hash = "sha256:88d7598b8ee130f32f8a43198ee02edd16d7f77692fa056cb779616bbea1b355", size = 214719 }, - { url = "https://files.pythonhosted.org/packages/9f/d6/7486dcc3474e2e6ad26a2af2db7e7c162ccd889c4c68fa14ea8ec189c9e9/coverage-7.9.2-cp312-cp312-win_amd64.whl", hash = "sha256:9dfb070f830739ee49d7c83e4941cc767e503e4394fdecb3b54bfdac1d7662c0", size = 215509 }, - { url = "https://files.pythonhosted.org/packages/b7/34/0439f1ae2593b0346164d907cdf96a529b40b7721a45fdcf8b03c95fcd90/coverage-7.9.2-cp312-cp312-win_arm64.whl", hash = "sha256:4e2c058aef613e79df00e86b6d42a641c877211384ce5bd07585ed7ba71ab31b", size = 213910 }, - { url = "https://files.pythonhosted.org/packages/94/9d/7a8edf7acbcaa5e5c489a646226bed9591ee1c5e6a84733c0140e9ce1ae1/coverage-7.9.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:985abe7f242e0d7bba228ab01070fde1d6c8fa12f142e43debe9ed1dde686038", size = 212367 }, - { url = "https://files.pythonhosted.org/packages/e8/9e/5cd6f130150712301f7e40fb5865c1bc27b97689ec57297e568d972eec3c/coverage-7.9.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82c3939264a76d44fde7f213924021ed31f55ef28111a19649fec90c0f109e6d", size = 212632 }, - { url = "https://files.pythonhosted.org/packages/a8/de/6287a2c2036f9fd991c61cefa8c64e57390e30c894ad3aa52fac4c1e14a8/coverage-7.9.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae5d563e970dbe04382f736ec214ef48103d1b875967c89d83c6e3f21706d5b3", size = 245793 }, - { url = "https://files.pythonhosted.org/packages/06/cc/9b5a9961d8160e3cb0b558c71f8051fe08aa2dd4b502ee937225da564ed1/coverage-7.9.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bdd612e59baed2a93c8843c9a7cb902260f181370f1d772f4842987535071d14", size = 243006 }, - { url = "https://files.pythonhosted.org/packages/49/d9/4616b787d9f597d6443f5588619c1c9f659e1f5fc9eebf63699eb6d34b78/coverage-7.9.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:256ea87cb2a1ed992bcdfc349d8042dcea1b80436f4ddf6e246d6bee4b5d73b6", size = 244990 }, - { url = "https://files.pythonhosted.org/packages/48/83/801cdc10f137b2d02b005a761661649ffa60eb173dcdaeb77f571e4dc192/coverage-7.9.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f44ae036b63c8ea432f610534a2668b0c3aee810e7037ab9d8ff6883de480f5b", size = 245157 }, - { url = "https://files.pythonhosted.org/packages/c8/a4/41911ed7e9d3ceb0ffb019e7635468df7499f5cc3edca5f7dfc078e9c5ec/coverage-7.9.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:82d76ad87c932935417a19b10cfe7abb15fd3f923cfe47dbdaa74ef4e503752d", size = 243128 }, - { url = "https://files.pythonhosted.org/packages/10/41/344543b71d31ac9cb00a664d5d0c9ef134a0fe87cb7d8430003b20fa0b7d/coverage-7.9.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:619317bb86de4193debc712b9e59d5cffd91dc1d178627ab2a77b9870deb2868", size = 244511 }, - { url = "https://files.pythonhosted.org/packages/d5/81/3b68c77e4812105e2a060f6946ba9e6f898ddcdc0d2bfc8b4b152a9ae522/coverage-7.9.2-cp313-cp313-win32.whl", hash = "sha256:0a07757de9feb1dfafd16ab651e0f628fd7ce551604d1bf23e47e1ddca93f08a", size = 214765 }, - { url = "https://files.pythonhosted.org/packages/06/a2/7fac400f6a346bb1a4004eb2a76fbff0e242cd48926a2ce37a22a6a1d917/coverage-7.9.2-cp313-cp313-win_amd64.whl", hash = "sha256:115db3d1f4d3f35f5bb021e270edd85011934ff97c8797216b62f461dd69374b", size = 215536 }, - { url = "https://files.pythonhosted.org/packages/08/47/2c6c215452b4f90d87017e61ea0fd9e0486bb734cb515e3de56e2c32075f/coverage-7.9.2-cp313-cp313-win_arm64.whl", hash = "sha256:48f82f889c80af8b2a7bb6e158d95a3fbec6a3453a1004d04e4f3b5945a02694", size = 213943 }, - { url = "https://files.pythonhosted.org/packages/a3/46/e211e942b22d6af5e0f323faa8a9bc7c447a1cf1923b64c47523f36ed488/coverage-7.9.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:55a28954545f9d2f96870b40f6c3386a59ba8ed50caf2d949676dac3ecab99f5", size = 213088 }, - { url = "https://files.pythonhosted.org/packages/d2/2f/762551f97e124442eccd907bf8b0de54348635b8866a73567eb4e6417acf/coverage-7.9.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cdef6504637731a63c133bb2e6f0f0214e2748495ec15fe42d1e219d1b133f0b", size = 213298 }, - { url = "https://files.pythonhosted.org/packages/7a/b7/76d2d132b7baf7360ed69be0bcab968f151fa31abe6d067f0384439d9edb/coverage-7.9.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bcd5ebe66c7a97273d5d2ddd4ad0ed2e706b39630ed4b53e713d360626c3dbb3", size = 256541 }, - { url = "https://files.pythonhosted.org/packages/a0/17/392b219837d7ad47d8e5974ce5f8dc3deb9f99a53b3bd4d123602f960c81/coverage-7.9.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9303aed20872d7a3c9cb39c5d2b9bdbe44e3a9a1aecb52920f7e7495410dfab8", size = 252761 }, - { url = "https://files.pythonhosted.org/packages/d5/77/4256d3577fe1b0daa8d3836a1ebe68eaa07dd2cbaf20cf5ab1115d6949d4/coverage-7.9.2-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc18ea9e417a04d1920a9a76fe9ebd2f43ca505b81994598482f938d5c315f46", size = 254917 }, - { url = "https://files.pythonhosted.org/packages/53/99/fc1a008eef1805e1ddb123cf17af864743354479ea5129a8f838c433cc2c/coverage-7.9.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6406cff19880aaaadc932152242523e892faff224da29e241ce2fca329866584", size = 256147 }, - { url = "https://files.pythonhosted.org/packages/92/c0/f63bf667e18b7f88c2bdb3160870e277c4874ced87e21426128d70aa741f/coverage-7.9.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2d0d4f6ecdf37fcc19c88fec3e2277d5dee740fb51ffdd69b9579b8c31e4232e", size = 254261 }, - { url = "https://files.pythonhosted.org/packages/8c/32/37dd1c42ce3016ff8ec9e4b607650d2e34845c0585d3518b2a93b4830c1a/coverage-7.9.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c33624f50cf8de418ab2b4d6ca9eda96dc45b2c4231336bac91454520e8d1fac", size = 255099 }, - { url = "https://files.pythonhosted.org/packages/da/2e/af6b86f7c95441ce82f035b3affe1cd147f727bbd92f563be35e2d585683/coverage-7.9.2-cp313-cp313t-win32.whl", hash = "sha256:1df6b76e737c6a92210eebcb2390af59a141f9e9430210595251fbaf02d46926", size = 215440 }, - { url = "https://files.pythonhosted.org/packages/4d/bb/8a785d91b308867f6b2e36e41c569b367c00b70c17f54b13ac29bcd2d8c8/coverage-7.9.2-cp313-cp313t-win_amd64.whl", hash = "sha256:f5fd54310b92741ebe00d9c0d1d7b2b27463952c022da6d47c175d246a98d1bd", size = 216537 }, - { url = "https://files.pythonhosted.org/packages/1d/a0/a6bffb5e0f41a47279fd45a8f3155bf193f77990ae1c30f9c224b61cacb0/coverage-7.9.2-cp313-cp313t-win_arm64.whl", hash = "sha256:c48c2375287108c887ee87d13b4070a381c6537d30e8487b24ec721bf2a781cb", size = 214398 }, - { url = "https://files.pythonhosted.org/packages/d7/85/f8bbefac27d286386961c25515431482a425967e23d3698b75a250872924/coverage-7.9.2-pp39.pp310.pp311-none-any.whl", hash = "sha256:8a1166db2fb62473285bcb092f586e081e92656c7dfa8e9f62b4d39d7e6b5050", size = 204013 }, - { url = "https://files.pythonhosted.org/packages/3c/38/bbe2e63902847cf79036ecc75550d0698af31c91c7575352eb25190d0fb3/coverage-7.9.2-py3-none-any.whl", hash = "sha256:e425cd5b00f6fc0ed7cdbd766c70be8baab4b7839e4d4fe5fac48581dd968ea4", size = 204005 }, +sdist = { url = "https://files.pythonhosted.org/packages/04/b7/c0465ca253df10a9e8dae0692a4ae6e9726d245390aaef92360e1d6d3832/coverage-7.9.2.tar.gz", hash = "sha256:997024fa51e3290264ffd7492ec97d0690293ccd2b45a6cd7d82d945a4a80c8b", size = 813556, upload-time = "2025-07-03T10:54:15.101Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a1/0d/5c2114fd776c207bd55068ae8dc1bef63ecd1b767b3389984a8e58f2b926/coverage-7.9.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:66283a192a14a3854b2e7f3418d7db05cdf411012ab7ff5db98ff3b181e1f912", size = 212039, upload-time = "2025-07-03T10:52:38.955Z" }, + { url = "https://files.pythonhosted.org/packages/cf/ad/dc51f40492dc2d5fcd31bb44577bc0cc8920757d6bc5d3e4293146524ef9/coverage-7.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4e01d138540ef34fcf35c1aa24d06c3de2a4cffa349e29a10056544f35cca15f", size = 212428, upload-time = "2025-07-03T10:52:41.36Z" }, + { url = "https://files.pythonhosted.org/packages/a2/a3/55cb3ff1b36f00df04439c3993d8529193cdf165a2467bf1402539070f16/coverage-7.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f22627c1fe2745ee98d3ab87679ca73a97e75ca75eb5faee48660d060875465f", size = 241534, upload-time = "2025-07-03T10:52:42.956Z" }, + { url = "https://files.pythonhosted.org/packages/eb/c9/a8410b91b6be4f6e9c2e9f0dce93749b6b40b751d7065b4410bf89cb654b/coverage-7.9.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b1c2d8363247b46bd51f393f86c94096e64a1cf6906803fa8d5a9d03784bdbf", size = 239408, upload-time = "2025-07-03T10:52:44.199Z" }, + { url = "https://files.pythonhosted.org/packages/ff/c4/6f3e56d467c612b9070ae71d5d3b114c0b899b5788e1ca3c93068ccb7018/coverage-7.9.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c10c882b114faf82dbd33e876d0cbd5e1d1ebc0d2a74ceef642c6152f3f4d547", size = 240552, upload-time = "2025-07-03T10:52:45.477Z" }, + { url = "https://files.pythonhosted.org/packages/fd/20/04eda789d15af1ce79bce5cc5fd64057c3a0ac08fd0576377a3096c24663/coverage-7.9.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:de3c0378bdf7066c3988d66cd5232d161e933b87103b014ab1b0b4676098fa45", size = 240464, upload-time = "2025-07-03T10:52:46.809Z" }, + { url = "https://files.pythonhosted.org/packages/a9/5a/217b32c94cc1a0b90f253514815332d08ec0812194a1ce9cca97dda1cd20/coverage-7.9.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:1e2f097eae0e5991e7623958a24ced3282676c93c013dde41399ff63e230fcf2", size = 239134, upload-time = "2025-07-03T10:52:48.149Z" }, + { url = "https://files.pythonhosted.org/packages/34/73/1d019c48f413465eb5d3b6898b6279e87141c80049f7dbf73fd020138549/coverage-7.9.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:28dc1f67e83a14e7079b6cea4d314bc8b24d1aed42d3582ff89c0295f09b181e", size = 239405, upload-time = "2025-07-03T10:52:49.687Z" }, + { url = "https://files.pythonhosted.org/packages/49/6c/a2beca7aa2595dad0c0d3f350382c381c92400efe5261e2631f734a0e3fe/coverage-7.9.2-cp310-cp310-win32.whl", hash = "sha256:bf7d773da6af9e10dbddacbf4e5cab13d06d0ed93561d44dae0188a42c65be7e", size = 214519, upload-time = "2025-07-03T10:52:51.036Z" }, + { url = "https://files.pythonhosted.org/packages/fc/c8/91e5e4a21f9a51e2c7cdd86e587ae01a4fcff06fc3fa8cde4d6f7cf68df4/coverage-7.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:0c0378ba787681ab1897f7c89b415bd56b0b2d9a47e5a3d8dc0ea55aac118d6c", size = 215400, upload-time = "2025-07-03T10:52:52.313Z" }, + { url = "https://files.pythonhosted.org/packages/39/40/916786453bcfafa4c788abee4ccd6f592b5b5eca0cd61a32a4e5a7ef6e02/coverage-7.9.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a7a56a2964a9687b6aba5b5ced6971af308ef6f79a91043c05dd4ee3ebc3e9ba", size = 212152, upload-time = "2025-07-03T10:52:53.562Z" }, + { url = "https://files.pythonhosted.org/packages/9f/66/cc13bae303284b546a030762957322bbbff1ee6b6cb8dc70a40f8a78512f/coverage-7.9.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:123d589f32c11d9be7fe2e66d823a236fe759b0096f5db3fb1b75b2fa414a4fa", size = 212540, upload-time = "2025-07-03T10:52:55.196Z" }, + { url = "https://files.pythonhosted.org/packages/0f/3c/d56a764b2e5a3d43257c36af4a62c379df44636817bb5f89265de4bf8bd7/coverage-7.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:333b2e0ca576a7dbd66e85ab402e35c03b0b22f525eed82681c4b866e2e2653a", size = 245097, upload-time = "2025-07-03T10:52:56.509Z" }, + { url = "https://files.pythonhosted.org/packages/b1/46/bd064ea8b3c94eb4ca5d90e34d15b806cba091ffb2b8e89a0d7066c45791/coverage-7.9.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:326802760da234baf9f2f85a39e4a4b5861b94f6c8d95251f699e4f73b1835dc", size = 242812, upload-time = "2025-07-03T10:52:57.842Z" }, + { url = "https://files.pythonhosted.org/packages/43/02/d91992c2b29bc7afb729463bc918ebe5f361be7f1daae93375a5759d1e28/coverage-7.9.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19e7be4cfec248df38ce40968c95d3952fbffd57b400d4b9bb580f28179556d2", size = 244617, upload-time = "2025-07-03T10:52:59.239Z" }, + { url = "https://files.pythonhosted.org/packages/b7/4f/8fadff6bf56595a16d2d6e33415841b0163ac660873ed9a4e9046194f779/coverage-7.9.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0b4a4cb73b9f2b891c1788711408ef9707666501ba23684387277ededab1097c", size = 244263, upload-time = "2025-07-03T10:53:00.601Z" }, + { url = "https://files.pythonhosted.org/packages/9b/d2/e0be7446a2bba11739edb9f9ba4eff30b30d8257370e237418eb44a14d11/coverage-7.9.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:2c8937fa16c8c9fbbd9f118588756e7bcdc7e16a470766a9aef912dd3f117dbd", size = 242314, upload-time = "2025-07-03T10:53:01.932Z" }, + { url = "https://files.pythonhosted.org/packages/9d/7d/dcbac9345000121b8b57a3094c2dfcf1ccc52d8a14a40c1d4bc89f936f80/coverage-7.9.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:42da2280c4d30c57a9b578bafd1d4494fa6c056d4c419d9689e66d775539be74", size = 242904, upload-time = "2025-07-03T10:53:03.478Z" }, + { url = "https://files.pythonhosted.org/packages/41/58/11e8db0a0c0510cf31bbbdc8caf5d74a358b696302a45948d7c768dfd1cf/coverage-7.9.2-cp311-cp311-win32.whl", hash = "sha256:14fa8d3da147f5fdf9d298cacc18791818f3f1a9f542c8958b80c228320e90c6", size = 214553, upload-time = "2025-07-03T10:53:05.174Z" }, + { url = "https://files.pythonhosted.org/packages/3a/7d/751794ec8907a15e257136e48dc1021b1f671220ecccfd6c4eaf30802714/coverage-7.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:549cab4892fc82004f9739963163fd3aac7a7b0df430669b75b86d293d2df2a7", size = 215441, upload-time = "2025-07-03T10:53:06.472Z" }, + { url = "https://files.pythonhosted.org/packages/62/5b/34abcedf7b946c1c9e15b44f326cb5b0da852885312b30e916f674913428/coverage-7.9.2-cp311-cp311-win_arm64.whl", hash = "sha256:c2667a2b913e307f06aa4e5677f01a9746cd08e4b35e14ebcde6420a9ebb4c62", size = 213873, upload-time = "2025-07-03T10:53:07.699Z" }, + { url = "https://files.pythonhosted.org/packages/53/d7/7deefc6fd4f0f1d4c58051f4004e366afc9e7ab60217ac393f247a1de70a/coverage-7.9.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ae9eb07f1cfacd9cfe8eaee6f4ff4b8a289a668c39c165cd0c8548484920ffc0", size = 212344, upload-time = "2025-07-03T10:53:09.3Z" }, + { url = "https://files.pythonhosted.org/packages/95/0c/ee03c95d32be4d519e6a02e601267769ce2e9a91fc8faa1b540e3626c680/coverage-7.9.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9ce85551f9a1119f02adc46d3014b5ee3f765deac166acf20dbb851ceb79b6f3", size = 212580, upload-time = "2025-07-03T10:53:11.52Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9f/826fa4b544b27620086211b87a52ca67592622e1f3af9e0a62c87aea153a/coverage-7.9.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8f6389ac977c5fb322e0e38885fbbf901743f79d47f50db706e7644dcdcb6e1", size = 246383, upload-time = "2025-07-03T10:53:13.134Z" }, + { url = "https://files.pythonhosted.org/packages/7f/b3/4477aafe2a546427b58b9c540665feff874f4db651f4d3cb21b308b3a6d2/coverage-7.9.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff0d9eae8cdfcd58fe7893b88993723583a6ce4dfbfd9f29e001922544f95615", size = 243400, upload-time = "2025-07-03T10:53:14.614Z" }, + { url = "https://files.pythonhosted.org/packages/f8/c2/efffa43778490c226d9d434827702f2dfbc8041d79101a795f11cbb2cf1e/coverage-7.9.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fae939811e14e53ed8a9818dad51d434a41ee09df9305663735f2e2d2d7d959b", size = 245591, upload-time = "2025-07-03T10:53:15.872Z" }, + { url = "https://files.pythonhosted.org/packages/c6/e7/a59888e882c9a5f0192d8627a30ae57910d5d449c80229b55e7643c078c4/coverage-7.9.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:31991156251ec202c798501e0a42bbdf2169dcb0f137b1f5c0f4267f3fc68ef9", size = 245402, upload-time = "2025-07-03T10:53:17.124Z" }, + { url = "https://files.pythonhosted.org/packages/92/a5/72fcd653ae3d214927edc100ce67440ed8a0a1e3576b8d5e6d066ed239db/coverage-7.9.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:d0d67963f9cbfc7c7f96d4ac74ed60ecbebd2ea6eeb51887af0f8dce205e545f", size = 243583, upload-time = "2025-07-03T10:53:18.781Z" }, + { url = "https://files.pythonhosted.org/packages/5c/f5/84e70e4df28f4a131d580d7d510aa1ffd95037293da66fd20d446090a13b/coverage-7.9.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:49b752a2858b10580969ec6af6f090a9a440a64a301ac1528d7ca5f7ed497f4d", size = 244815, upload-time = "2025-07-03T10:53:20.168Z" }, + { url = "https://files.pythonhosted.org/packages/39/e7/d73d7cbdbd09fdcf4642655ae843ad403d9cbda55d725721965f3580a314/coverage-7.9.2-cp312-cp312-win32.whl", hash = "sha256:88d7598b8ee130f32f8a43198ee02edd16d7f77692fa056cb779616bbea1b355", size = 214719, upload-time = "2025-07-03T10:53:21.521Z" }, + { url = "https://files.pythonhosted.org/packages/9f/d6/7486dcc3474e2e6ad26a2af2db7e7c162ccd889c4c68fa14ea8ec189c9e9/coverage-7.9.2-cp312-cp312-win_amd64.whl", hash = "sha256:9dfb070f830739ee49d7c83e4941cc767e503e4394fdecb3b54bfdac1d7662c0", size = 215509, upload-time = "2025-07-03T10:53:22.853Z" }, + { url = "https://files.pythonhosted.org/packages/b7/34/0439f1ae2593b0346164d907cdf96a529b40b7721a45fdcf8b03c95fcd90/coverage-7.9.2-cp312-cp312-win_arm64.whl", hash = "sha256:4e2c058aef613e79df00e86b6d42a641c877211384ce5bd07585ed7ba71ab31b", size = 213910, upload-time = "2025-07-03T10:53:24.472Z" }, + { url = "https://files.pythonhosted.org/packages/94/9d/7a8edf7acbcaa5e5c489a646226bed9591ee1c5e6a84733c0140e9ce1ae1/coverage-7.9.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:985abe7f242e0d7bba228ab01070fde1d6c8fa12f142e43debe9ed1dde686038", size = 212367, upload-time = "2025-07-03T10:53:25.811Z" }, + { url = "https://files.pythonhosted.org/packages/e8/9e/5cd6f130150712301f7e40fb5865c1bc27b97689ec57297e568d972eec3c/coverage-7.9.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82c3939264a76d44fde7f213924021ed31f55ef28111a19649fec90c0f109e6d", size = 212632, upload-time = "2025-07-03T10:53:27.075Z" }, + { url = "https://files.pythonhosted.org/packages/a8/de/6287a2c2036f9fd991c61cefa8c64e57390e30c894ad3aa52fac4c1e14a8/coverage-7.9.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae5d563e970dbe04382f736ec214ef48103d1b875967c89d83c6e3f21706d5b3", size = 245793, upload-time = "2025-07-03T10:53:28.408Z" }, + { url = "https://files.pythonhosted.org/packages/06/cc/9b5a9961d8160e3cb0b558c71f8051fe08aa2dd4b502ee937225da564ed1/coverage-7.9.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bdd612e59baed2a93c8843c9a7cb902260f181370f1d772f4842987535071d14", size = 243006, upload-time = "2025-07-03T10:53:29.754Z" }, + { url = "https://files.pythonhosted.org/packages/49/d9/4616b787d9f597d6443f5588619c1c9f659e1f5fc9eebf63699eb6d34b78/coverage-7.9.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:256ea87cb2a1ed992bcdfc349d8042dcea1b80436f4ddf6e246d6bee4b5d73b6", size = 244990, upload-time = "2025-07-03T10:53:31.098Z" }, + { url = "https://files.pythonhosted.org/packages/48/83/801cdc10f137b2d02b005a761661649ffa60eb173dcdaeb77f571e4dc192/coverage-7.9.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f44ae036b63c8ea432f610534a2668b0c3aee810e7037ab9d8ff6883de480f5b", size = 245157, upload-time = "2025-07-03T10:53:32.717Z" }, + { url = "https://files.pythonhosted.org/packages/c8/a4/41911ed7e9d3ceb0ffb019e7635468df7499f5cc3edca5f7dfc078e9c5ec/coverage-7.9.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:82d76ad87c932935417a19b10cfe7abb15fd3f923cfe47dbdaa74ef4e503752d", size = 243128, upload-time = "2025-07-03T10:53:34.009Z" }, + { url = "https://files.pythonhosted.org/packages/10/41/344543b71d31ac9cb00a664d5d0c9ef134a0fe87cb7d8430003b20fa0b7d/coverage-7.9.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:619317bb86de4193debc712b9e59d5cffd91dc1d178627ab2a77b9870deb2868", size = 244511, upload-time = "2025-07-03T10:53:35.434Z" }, + { url = "https://files.pythonhosted.org/packages/d5/81/3b68c77e4812105e2a060f6946ba9e6f898ddcdc0d2bfc8b4b152a9ae522/coverage-7.9.2-cp313-cp313-win32.whl", hash = "sha256:0a07757de9feb1dfafd16ab651e0f628fd7ce551604d1bf23e47e1ddca93f08a", size = 214765, upload-time = "2025-07-03T10:53:36.787Z" }, + { url = "https://files.pythonhosted.org/packages/06/a2/7fac400f6a346bb1a4004eb2a76fbff0e242cd48926a2ce37a22a6a1d917/coverage-7.9.2-cp313-cp313-win_amd64.whl", hash = "sha256:115db3d1f4d3f35f5bb021e270edd85011934ff97c8797216b62f461dd69374b", size = 215536, upload-time = "2025-07-03T10:53:38.188Z" }, + { url = "https://files.pythonhosted.org/packages/08/47/2c6c215452b4f90d87017e61ea0fd9e0486bb734cb515e3de56e2c32075f/coverage-7.9.2-cp313-cp313-win_arm64.whl", hash = "sha256:48f82f889c80af8b2a7bb6e158d95a3fbec6a3453a1004d04e4f3b5945a02694", size = 213943, upload-time = "2025-07-03T10:53:39.492Z" }, + { url = "https://files.pythonhosted.org/packages/a3/46/e211e942b22d6af5e0f323faa8a9bc7c447a1cf1923b64c47523f36ed488/coverage-7.9.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:55a28954545f9d2f96870b40f6c3386a59ba8ed50caf2d949676dac3ecab99f5", size = 213088, upload-time = "2025-07-03T10:53:40.874Z" }, + { url = "https://files.pythonhosted.org/packages/d2/2f/762551f97e124442eccd907bf8b0de54348635b8866a73567eb4e6417acf/coverage-7.9.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cdef6504637731a63c133bb2e6f0f0214e2748495ec15fe42d1e219d1b133f0b", size = 213298, upload-time = "2025-07-03T10:53:42.218Z" }, + { url = "https://files.pythonhosted.org/packages/7a/b7/76d2d132b7baf7360ed69be0bcab968f151fa31abe6d067f0384439d9edb/coverage-7.9.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bcd5ebe66c7a97273d5d2ddd4ad0ed2e706b39630ed4b53e713d360626c3dbb3", size = 256541, upload-time = "2025-07-03T10:53:43.823Z" }, + { url = "https://files.pythonhosted.org/packages/a0/17/392b219837d7ad47d8e5974ce5f8dc3deb9f99a53b3bd4d123602f960c81/coverage-7.9.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9303aed20872d7a3c9cb39c5d2b9bdbe44e3a9a1aecb52920f7e7495410dfab8", size = 252761, upload-time = "2025-07-03T10:53:45.19Z" }, + { url = "https://files.pythonhosted.org/packages/d5/77/4256d3577fe1b0daa8d3836a1ebe68eaa07dd2cbaf20cf5ab1115d6949d4/coverage-7.9.2-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc18ea9e417a04d1920a9a76fe9ebd2f43ca505b81994598482f938d5c315f46", size = 254917, upload-time = "2025-07-03T10:53:46.931Z" }, + { url = "https://files.pythonhosted.org/packages/53/99/fc1a008eef1805e1ddb123cf17af864743354479ea5129a8f838c433cc2c/coverage-7.9.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6406cff19880aaaadc932152242523e892faff224da29e241ce2fca329866584", size = 256147, upload-time = "2025-07-03T10:53:48.289Z" }, + { url = "https://files.pythonhosted.org/packages/92/c0/f63bf667e18b7f88c2bdb3160870e277c4874ced87e21426128d70aa741f/coverage-7.9.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2d0d4f6ecdf37fcc19c88fec3e2277d5dee740fb51ffdd69b9579b8c31e4232e", size = 254261, upload-time = "2025-07-03T10:53:49.99Z" }, + { url = "https://files.pythonhosted.org/packages/8c/32/37dd1c42ce3016ff8ec9e4b607650d2e34845c0585d3518b2a93b4830c1a/coverage-7.9.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c33624f50cf8de418ab2b4d6ca9eda96dc45b2c4231336bac91454520e8d1fac", size = 255099, upload-time = "2025-07-03T10:53:51.354Z" }, + { url = "https://files.pythonhosted.org/packages/da/2e/af6b86f7c95441ce82f035b3affe1cd147f727bbd92f563be35e2d585683/coverage-7.9.2-cp313-cp313t-win32.whl", hash = "sha256:1df6b76e737c6a92210eebcb2390af59a141f9e9430210595251fbaf02d46926", size = 215440, upload-time = "2025-07-03T10:53:52.808Z" }, + { url = "https://files.pythonhosted.org/packages/4d/bb/8a785d91b308867f6b2e36e41c569b367c00b70c17f54b13ac29bcd2d8c8/coverage-7.9.2-cp313-cp313t-win_amd64.whl", hash = "sha256:f5fd54310b92741ebe00d9c0d1d7b2b27463952c022da6d47c175d246a98d1bd", size = 216537, upload-time = "2025-07-03T10:53:54.273Z" }, + { url = "https://files.pythonhosted.org/packages/1d/a0/a6bffb5e0f41a47279fd45a8f3155bf193f77990ae1c30f9c224b61cacb0/coverage-7.9.2-cp313-cp313t-win_arm64.whl", hash = "sha256:c48c2375287108c887ee87d13b4070a381c6537d30e8487b24ec721bf2a781cb", size = 214398, upload-time = "2025-07-03T10:53:56.715Z" }, + { url = "https://files.pythonhosted.org/packages/d7/85/f8bbefac27d286386961c25515431482a425967e23d3698b75a250872924/coverage-7.9.2-pp39.pp310.pp311-none-any.whl", hash = "sha256:8a1166db2fb62473285bcb092f586e081e92656c7dfa8e9f62b4d39d7e6b5050", size = 204013, upload-time = "2025-07-03T10:54:12.084Z" }, + { url = "https://files.pythonhosted.org/packages/3c/38/bbe2e63902847cf79036ecc75550d0698af31c91c7575352eb25190d0fb3/coverage-7.9.2-py3-none-any.whl", hash = "sha256:e425cd5b00f6fc0ed7cdbd766c70be8baab4b7839e4d4fe5fac48581dd968ea4", size = 204005, upload-time = "2025-07-03T10:54:13.491Z" }, ] [package.optional-dependencies] @@ -496,43 +497,43 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/95/1e/49527ac611af559665f71cbb8f92b332b5ec9c6fbc4e88b0f8e92f5e85df/cryptography-45.0.5.tar.gz", hash = "sha256:72e76caa004ab63accdf26023fccd1d087f6d90ec6048ff33ad0445abf7f605a", size = 744903 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b1/05/2194432935e29b91fb649f6149c1a4f9e6d3d9fc880919f4ad1bcc22641e/cryptography-45.0.5-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3a264aae5f7fbb089dbc01e0242d3b67dffe3e6292e1f5182122bdf58e65215d", size = 4205926 }, - { url = "https://files.pythonhosted.org/packages/07/8b/9ef5da82350175e32de245646b1884fc01124f53eb31164c77f95a08d682/cryptography-45.0.5-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e74d30ec9c7cb2f404af331d5b4099a9b322a8a6b25c4632755c8757345baac5", size = 4429235 }, - { url = "https://files.pythonhosted.org/packages/7c/e1/c809f398adde1994ee53438912192d92a1d0fc0f2d7582659d9ef4c28b0c/cryptography-45.0.5-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:3af26738f2db354aafe492fb3869e955b12b2ef2e16908c8b9cb928128d42c57", size = 4209785 }, - { url = "https://files.pythonhosted.org/packages/d0/8b/07eb6bd5acff58406c5e806eff34a124936f41a4fb52909ffa4d00815f8c/cryptography-45.0.5-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e6c00130ed423201c5bc5544c23359141660b07999ad82e34e7bb8f882bb78e0", size = 3893050 }, - { url = "https://files.pythonhosted.org/packages/ec/ef/3333295ed58d900a13c92806b67e62f27876845a9a908c939f040887cca9/cryptography-45.0.5-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:dd420e577921c8c2d31289536c386aaa30140b473835e97f83bc71ea9d2baf2d", size = 4457379 }, - { url = "https://files.pythonhosted.org/packages/d9/9d/44080674dee514dbb82b21d6fa5d1055368f208304e2ab1828d85c9de8f4/cryptography-45.0.5-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:d05a38884db2ba215218745f0781775806bde4f32e07b135348355fe8e4991d9", size = 4209355 }, - { url = "https://files.pythonhosted.org/packages/c9/d8/0749f7d39f53f8258e5c18a93131919ac465ee1f9dccaf1b3f420235e0b5/cryptography-45.0.5-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:ad0caded895a00261a5b4aa9af828baede54638754b51955a0ac75576b831b27", size = 4456087 }, - { url = "https://files.pythonhosted.org/packages/09/d7/92acac187387bf08902b0bf0699816f08553927bdd6ba3654da0010289b4/cryptography-45.0.5-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9024beb59aca9d31d36fcdc1604dd9bbeed0a55bface9f1908df19178e2f116e", size = 4332873 }, - { url = "https://files.pythonhosted.org/packages/03/c2/840e0710da5106a7c3d4153c7215b2736151bba60bf4491bdb421df5056d/cryptography-45.0.5-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:91098f02ca81579c85f66df8a588c78f331ca19089763d733e34ad359f474174", size = 4564651 }, - { url = "https://files.pythonhosted.org/packages/c2/e7/2187be2f871c0221a81f55ee3105d3cf3e273c0a0853651d7011eada0d7e/cryptography-45.0.5-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3fcfbefc4a7f332dece7272a88e410f611e79458fab97b5efe14e54fe476f4fd", size = 4197780 }, - { url = "https://files.pythonhosted.org/packages/b9/cf/84210c447c06104e6be9122661159ad4ce7a8190011669afceeaea150524/cryptography-45.0.5-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:460f8c39ba66af7db0545a8c6f2eabcbc5a5528fc1cf6c3fa9a1e44cec33385e", size = 4420091 }, - { url = "https://files.pythonhosted.org/packages/3e/6a/cb8b5c8bb82fafffa23aeff8d3a39822593cee6e2f16c5ca5c2ecca344f7/cryptography-45.0.5-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:9b4cf6318915dccfe218e69bbec417fdd7c7185aa7aab139a2c0beb7468c89f0", size = 4198711 }, - { url = "https://files.pythonhosted.org/packages/04/f7/36d2d69df69c94cbb2473871926daf0f01ad8e00fe3986ac3c1e8c4ca4b3/cryptography-45.0.5-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2089cc8f70a6e454601525e5bf2779e665d7865af002a5dec8d14e561002e135", size = 3883299 }, - { url = "https://files.pythonhosted.org/packages/82/c7/f0ea40f016de72f81288e9fe8d1f6748036cb5ba6118774317a3ffc6022d/cryptography-45.0.5-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:0027d566d65a38497bc37e0dd7c2f8ceda73597d2ac9ba93810204f56f52ebc7", size = 4450558 }, - { url = "https://files.pythonhosted.org/packages/06/ae/94b504dc1a3cdf642d710407c62e86296f7da9e66f27ab12a1ee6fdf005b/cryptography-45.0.5-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:be97d3a19c16a9be00edf79dca949c8fa7eff621763666a145f9f9535a5d7f42", size = 4198020 }, - { url = "https://files.pythonhosted.org/packages/05/2b/aaf0adb845d5dabb43480f18f7ca72e94f92c280aa983ddbd0bcd6ecd037/cryptography-45.0.5-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:7760c1c2e1a7084153a0f68fab76e754083b126a47d0117c9ed15e69e2103492", size = 4449759 }, - { url = "https://files.pythonhosted.org/packages/91/e4/f17e02066de63e0100a3a01b56f8f1016973a1d67551beaf585157a86b3f/cryptography-45.0.5-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:6ff8728d8d890b3dda5765276d1bc6fb099252915a2cd3aff960c4c195745dd0", size = 4319991 }, - { url = "https://files.pythonhosted.org/packages/f2/2e/e2dbd629481b499b14516eed933f3276eb3239f7cee2dcfa4ee6b44d4711/cryptography-45.0.5-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:7259038202a47fdecee7e62e0fd0b0738b6daa335354396c6ddebdbe1206af2a", size = 4554189 }, - { url = "https://files.pythonhosted.org/packages/8b/5d/a19441c1e89afb0f173ac13178606ca6fab0d3bd3ebc29e9ed1318b507fc/cryptography-45.0.5-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c648025b6840fe62e57107e0a25f604db740e728bd67da4f6f060f03017d5097", size = 4140906 }, - { url = "https://files.pythonhosted.org/packages/4b/db/daceb259982a3c2da4e619f45b5bfdec0e922a23de213b2636e78ef0919b/cryptography-45.0.5-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b8fa8b0a35a9982a3c60ec79905ba5bb090fc0b9addcfd3dc2dd04267e45f25e", size = 4374411 }, - { url = "https://files.pythonhosted.org/packages/6a/35/5d06ad06402fc522c8bf7eab73422d05e789b4e38fe3206a85e3d6966c11/cryptography-45.0.5-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:14d96584701a887763384f3c47f0ca7c1cce322aa1c31172680eb596b890ec30", size = 4140942 }, - { url = "https://files.pythonhosted.org/packages/65/79/020a5413347e44c382ef1f7f7e7a66817cd6273e3e6b5a72d18177b08b2f/cryptography-45.0.5-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:57c816dfbd1659a367831baca4b775b2a5b43c003daf52e9d57e1d30bc2e1b0e", size = 4374079 }, - { url = "https://files.pythonhosted.org/packages/f0/63/83516cfb87f4a8756eaa4203f93b283fda23d210fc14e1e594bd5f20edb6/cryptography-45.0.5-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:bd4c45986472694e5121084c6ebbd112aa919a25e783b87eb95953c9573906d6", size = 4152447 }, - { url = "https://files.pythonhosted.org/packages/22/11/d2823d2a5a0bd5802b3565437add16f5c8ce1f0778bf3822f89ad2740a38/cryptography-45.0.5-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:982518cd64c54fcada9d7e5cf28eabd3ee76bd03ab18e08a48cad7e8b6f31b18", size = 4386778 }, - { url = "https://files.pythonhosted.org/packages/5f/38/6bf177ca6bce4fe14704ab3e93627c5b0ca05242261a2e43ef3168472540/cryptography-45.0.5-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:12e55281d993a793b0e883066f590c1ae1e802e3acb67f8b442e721e475e6463", size = 4151627 }, - { url = "https://files.pythonhosted.org/packages/38/6a/69fc67e5266bff68a91bcb81dff8fb0aba4d79a78521a08812048913e16f/cryptography-45.0.5-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:5aa1e32983d4443e310f726ee4b071ab7569f58eedfdd65e9675484a4eb67bd1", size = 4385593 }, +sdist = { url = "https://files.pythonhosted.org/packages/95/1e/49527ac611af559665f71cbb8f92b332b5ec9c6fbc4e88b0f8e92f5e85df/cryptography-45.0.5.tar.gz", hash = "sha256:72e76caa004ab63accdf26023fccd1d087f6d90ec6048ff33ad0445abf7f605a", size = 744903, upload-time = "2025-07-02T13:06:25.941Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/05/2194432935e29b91fb649f6149c1a4f9e6d3d9fc880919f4ad1bcc22641e/cryptography-45.0.5-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3a264aae5f7fbb089dbc01e0242d3b67dffe3e6292e1f5182122bdf58e65215d", size = 4205926, upload-time = "2025-07-02T13:05:04.741Z" }, + { url = "https://files.pythonhosted.org/packages/07/8b/9ef5da82350175e32de245646b1884fc01124f53eb31164c77f95a08d682/cryptography-45.0.5-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e74d30ec9c7cb2f404af331d5b4099a9b322a8a6b25c4632755c8757345baac5", size = 4429235, upload-time = "2025-07-02T13:05:07.084Z" }, + { url = "https://files.pythonhosted.org/packages/7c/e1/c809f398adde1994ee53438912192d92a1d0fc0f2d7582659d9ef4c28b0c/cryptography-45.0.5-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:3af26738f2db354aafe492fb3869e955b12b2ef2e16908c8b9cb928128d42c57", size = 4209785, upload-time = "2025-07-02T13:05:09.321Z" }, + { url = "https://files.pythonhosted.org/packages/d0/8b/07eb6bd5acff58406c5e806eff34a124936f41a4fb52909ffa4d00815f8c/cryptography-45.0.5-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e6c00130ed423201c5bc5544c23359141660b07999ad82e34e7bb8f882bb78e0", size = 3893050, upload-time = "2025-07-02T13:05:11.069Z" }, + { url = "https://files.pythonhosted.org/packages/ec/ef/3333295ed58d900a13c92806b67e62f27876845a9a908c939f040887cca9/cryptography-45.0.5-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:dd420e577921c8c2d31289536c386aaa30140b473835e97f83bc71ea9d2baf2d", size = 4457379, upload-time = "2025-07-02T13:05:13.32Z" }, + { url = "https://files.pythonhosted.org/packages/d9/9d/44080674dee514dbb82b21d6fa5d1055368f208304e2ab1828d85c9de8f4/cryptography-45.0.5-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:d05a38884db2ba215218745f0781775806bde4f32e07b135348355fe8e4991d9", size = 4209355, upload-time = "2025-07-02T13:05:15.017Z" }, + { url = "https://files.pythonhosted.org/packages/c9/d8/0749f7d39f53f8258e5c18a93131919ac465ee1f9dccaf1b3f420235e0b5/cryptography-45.0.5-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:ad0caded895a00261a5b4aa9af828baede54638754b51955a0ac75576b831b27", size = 4456087, upload-time = "2025-07-02T13:05:16.945Z" }, + { url = "https://files.pythonhosted.org/packages/09/d7/92acac187387bf08902b0bf0699816f08553927bdd6ba3654da0010289b4/cryptography-45.0.5-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9024beb59aca9d31d36fcdc1604dd9bbeed0a55bface9f1908df19178e2f116e", size = 4332873, upload-time = "2025-07-02T13:05:18.743Z" }, + { url = "https://files.pythonhosted.org/packages/03/c2/840e0710da5106a7c3d4153c7215b2736151bba60bf4491bdb421df5056d/cryptography-45.0.5-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:91098f02ca81579c85f66df8a588c78f331ca19089763d733e34ad359f474174", size = 4564651, upload-time = "2025-07-02T13:05:21.382Z" }, + { url = "https://files.pythonhosted.org/packages/c2/e7/2187be2f871c0221a81f55ee3105d3cf3e273c0a0853651d7011eada0d7e/cryptography-45.0.5-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3fcfbefc4a7f332dece7272a88e410f611e79458fab97b5efe14e54fe476f4fd", size = 4197780, upload-time = "2025-07-02T13:05:29.299Z" }, + { url = "https://files.pythonhosted.org/packages/b9/cf/84210c447c06104e6be9122661159ad4ce7a8190011669afceeaea150524/cryptography-45.0.5-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:460f8c39ba66af7db0545a8c6f2eabcbc5a5528fc1cf6c3fa9a1e44cec33385e", size = 4420091, upload-time = "2025-07-02T13:05:31.221Z" }, + { url = "https://files.pythonhosted.org/packages/3e/6a/cb8b5c8bb82fafffa23aeff8d3a39822593cee6e2f16c5ca5c2ecca344f7/cryptography-45.0.5-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:9b4cf6318915dccfe218e69bbec417fdd7c7185aa7aab139a2c0beb7468c89f0", size = 4198711, upload-time = "2025-07-02T13:05:33.062Z" }, + { url = "https://files.pythonhosted.org/packages/04/f7/36d2d69df69c94cbb2473871926daf0f01ad8e00fe3986ac3c1e8c4ca4b3/cryptography-45.0.5-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2089cc8f70a6e454601525e5bf2779e665d7865af002a5dec8d14e561002e135", size = 3883299, upload-time = "2025-07-02T13:05:34.94Z" }, + { url = "https://files.pythonhosted.org/packages/82/c7/f0ea40f016de72f81288e9fe8d1f6748036cb5ba6118774317a3ffc6022d/cryptography-45.0.5-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:0027d566d65a38497bc37e0dd7c2f8ceda73597d2ac9ba93810204f56f52ebc7", size = 4450558, upload-time = "2025-07-02T13:05:37.288Z" }, + { url = "https://files.pythonhosted.org/packages/06/ae/94b504dc1a3cdf642d710407c62e86296f7da9e66f27ab12a1ee6fdf005b/cryptography-45.0.5-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:be97d3a19c16a9be00edf79dca949c8fa7eff621763666a145f9f9535a5d7f42", size = 4198020, upload-time = "2025-07-02T13:05:39.102Z" }, + { url = "https://files.pythonhosted.org/packages/05/2b/aaf0adb845d5dabb43480f18f7ca72e94f92c280aa983ddbd0bcd6ecd037/cryptography-45.0.5-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:7760c1c2e1a7084153a0f68fab76e754083b126a47d0117c9ed15e69e2103492", size = 4449759, upload-time = "2025-07-02T13:05:41.398Z" }, + { url = "https://files.pythonhosted.org/packages/91/e4/f17e02066de63e0100a3a01b56f8f1016973a1d67551beaf585157a86b3f/cryptography-45.0.5-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:6ff8728d8d890b3dda5765276d1bc6fb099252915a2cd3aff960c4c195745dd0", size = 4319991, upload-time = "2025-07-02T13:05:43.64Z" }, + { url = "https://files.pythonhosted.org/packages/f2/2e/e2dbd629481b499b14516eed933f3276eb3239f7cee2dcfa4ee6b44d4711/cryptography-45.0.5-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:7259038202a47fdecee7e62e0fd0b0738b6daa335354396c6ddebdbe1206af2a", size = 4554189, upload-time = "2025-07-02T13:05:46.045Z" }, + { url = "https://files.pythonhosted.org/packages/8b/5d/a19441c1e89afb0f173ac13178606ca6fab0d3bd3ebc29e9ed1318b507fc/cryptography-45.0.5-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c648025b6840fe62e57107e0a25f604db740e728bd67da4f6f060f03017d5097", size = 4140906, upload-time = "2025-07-02T13:05:55.914Z" }, + { url = "https://files.pythonhosted.org/packages/4b/db/daceb259982a3c2da4e619f45b5bfdec0e922a23de213b2636e78ef0919b/cryptography-45.0.5-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b8fa8b0a35a9982a3c60ec79905ba5bb090fc0b9addcfd3dc2dd04267e45f25e", size = 4374411, upload-time = "2025-07-02T13:05:57.814Z" }, + { url = "https://files.pythonhosted.org/packages/6a/35/5d06ad06402fc522c8bf7eab73422d05e789b4e38fe3206a85e3d6966c11/cryptography-45.0.5-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:14d96584701a887763384f3c47f0ca7c1cce322aa1c31172680eb596b890ec30", size = 4140942, upload-time = "2025-07-02T13:06:00.137Z" }, + { url = "https://files.pythonhosted.org/packages/65/79/020a5413347e44c382ef1f7f7e7a66817cd6273e3e6b5a72d18177b08b2f/cryptography-45.0.5-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:57c816dfbd1659a367831baca4b775b2a5b43c003daf52e9d57e1d30bc2e1b0e", size = 4374079, upload-time = "2025-07-02T13:06:02.043Z" }, + { url = "https://files.pythonhosted.org/packages/f0/63/83516cfb87f4a8756eaa4203f93b283fda23d210fc14e1e594bd5f20edb6/cryptography-45.0.5-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:bd4c45986472694e5121084c6ebbd112aa919a25e783b87eb95953c9573906d6", size = 4152447, upload-time = "2025-07-02T13:06:08.345Z" }, + { url = "https://files.pythonhosted.org/packages/22/11/d2823d2a5a0bd5802b3565437add16f5c8ce1f0778bf3822f89ad2740a38/cryptography-45.0.5-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:982518cd64c54fcada9d7e5cf28eabd3ee76bd03ab18e08a48cad7e8b6f31b18", size = 4386778, upload-time = "2025-07-02T13:06:10.263Z" }, + { url = "https://files.pythonhosted.org/packages/5f/38/6bf177ca6bce4fe14704ab3e93627c5b0ca05242261a2e43ef3168472540/cryptography-45.0.5-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:12e55281d993a793b0e883066f590c1ae1e802e3acb67f8b442e721e475e6463", size = 4151627, upload-time = "2025-07-02T13:06:13.097Z" }, + { url = "https://files.pythonhosted.org/packages/38/6a/69fc67e5266bff68a91bcb81dff8fb0aba4d79a78521a08812048913e16f/cryptography-45.0.5-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:5aa1e32983d4443e310f726ee4b071ab7569f58eedfdd65e9675484a4eb67bd1", size = 4385593, upload-time = "2025-07-02T13:06:15.689Z" }, ] [[package]] name = "daff" version = "1.4.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e1/d0/c0a1374db3afad0f9dfe6c795e5df102af03d49ad5e6e8502fb09eb88110/daff-1.4.2.tar.gz", hash = "sha256:47f0391eda7e2b5011f7ccac006b9178accb465bcb94a2c9f284257fff5d2686", size = 148251 } +sdist = { url = "https://files.pythonhosted.org/packages/e1/d0/c0a1374db3afad0f9dfe6c795e5df102af03d49ad5e6e8502fb09eb88110/daff-1.4.2.tar.gz", hash = "sha256:47f0391eda7e2b5011f7ccac006b9178accb465bcb94a2c9f284257fff5d2686", size = 148251, upload-time = "2025-05-04T19:24:11.521Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/29/fe/d54a874e8d7b88bc03c459f63a993305db50039b734fab751a0466dabfc1/daff-1.4.2-py3-none-any.whl", hash = "sha256:88981a21d065e4378b5c4bd40b975dbfdea9b7ff540071f3bb5e20cc8b3590b5", size = 144922 }, + { url = "https://files.pythonhosted.org/packages/29/fe/d54a874e8d7b88bc03c459f63a993305db50039b734fab751a0466dabfc1/daff-1.4.2-py3-none-any.whl", hash = "sha256:88981a21d065e4378b5c4bd40b975dbfdea9b7ff540071f3bb5e20cc8b3590b5", size = 144922, upload-time = "2025-05-04T19:24:09.999Z" }, ] [[package]] @@ -548,9 +549,9 @@ dependencies = [ { name = "pytz" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1c/4c/2a26631c34922e17bd0315d380f7f83b9a77d4cbe60b2a2f145df07454f6/dbt_adapters-1.16.1.tar.gz", hash = "sha256:cd17f8d5f614455647ebe2eaaddb50c11cf4a7854a2599e276194d5dba66402d", size = 129456 } +sdist = { url = "https://files.pythonhosted.org/packages/1c/4c/2a26631c34922e17bd0315d380f7f83b9a77d4cbe60b2a2f145df07454f6/dbt_adapters-1.16.1.tar.gz", hash = "sha256:cd17f8d5f614455647ebe2eaaddb50c11cf4a7854a2599e276194d5dba66402d", size = 129456, upload-time = "2025-07-10T20:39:41.658Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/c0/5e7419478dd3053cb97f16ea9dd7f05a44e2a125946bd352df16f1ae5824/dbt_adapters-1.16.1-py3-none-any.whl", hash = "sha256:1ccdb1923ed32f9997c9cab6280f765e92935300a6cbae60458f30bf73deb781", size = 166693 }, + { url = "https://files.pythonhosted.org/packages/6a/c0/5e7419478dd3053cb97f16ea9dd7f05a44e2a125946bd352df16f1ae5824/dbt_adapters-1.16.1-py3-none-any.whl", hash = "sha256:1ccdb1923ed32f9997c9cab6280f765e92935300a6cbae60458f30bf73deb781", size = 166693, upload-time = "2025-07-10T20:39:40.421Z" }, ] [[package]] @@ -572,9 +573,9 @@ dependencies = [ { name = "requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ba/87/2a00f61156b4c170acadba1ccc346bf3426f802bd06635e58d5576c55c2c/dbt_common-1.26.0.tar.gz", hash = "sha256:56ad25fae0539452e875255d577aaf3b76a6e44ddf63fa29f8d51b26ae563b74", size = 83844 } +sdist = { url = "https://files.pythonhosted.org/packages/ba/87/2a00f61156b4c170acadba1ccc346bf3426f802bd06635e58d5576c55c2c/dbt_common-1.26.0.tar.gz", hash = "sha256:56ad25fae0539452e875255d577aaf3b76a6e44ddf63fa29f8d51b26ae563b74", size = 83844, upload-time = "2025-07-07T14:49:52.027Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4d/1a/791dda44b342ea0f4ff04347931644b34d80bc4a1ffb7ac9f88677c978ae/dbt_common-1.26.0-py3-none-any.whl", hash = "sha256:5f17fe9faa5e709ac494c314186d1566ca5c6684826e2f35c8d437437835f0ff", size = 86011 }, + { url = "https://files.pythonhosted.org/packages/4d/1a/791dda44b342ea0f4ff04347931644b34d80bc4a1ffb7ac9f88677c978ae/dbt_common-1.26.0-py3-none-any.whl", hash = "sha256:5f17fe9faa5e709ac494c314186d1566ca5c6684826e2f35c8d437437835f0ff", size = 86011, upload-time = "2025-07-07T14:49:49.585Z" }, ] [[package]] @@ -606,9 +607,9 @@ dependencies = [ { name = "sqlparse" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d4/5a/d1912f088180edc32a4a52dd30d8be81e0187d6b2bfe073cf5ba11d4a678/dbt_core-1.10.4.tar.gz", hash = "sha256:45497e813b6e19e0db97df5cc720cb606ce50489d4825e1d091fc04fa408a283", size = 894213 } +sdist = { url = "https://files.pythonhosted.org/packages/d4/5a/d1912f088180edc32a4a52dd30d8be81e0187d6b2bfe073cf5ba11d4a678/dbt_core-1.10.4.tar.gz", hash = "sha256:45497e813b6e19e0db97df5cc720cb606ce50489d4825e1d091fc04fa408a283", size = 894213, upload-time = "2025-07-10T20:42:28.99Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f8/28/f314f540ecaae8c2f9cf861a6349709e98f338d86d07b32a5a713d10d054/dbt_core-1.10.4-py3-none-any.whl", hash = "sha256:461bbd98c77e4ce7ae3f54c8a84aba620516c558deb8ecdc1b2552c3232cca24", size = 979456 }, + { url = "https://files.pythonhosted.org/packages/f8/28/f314f540ecaae8c2f9cf861a6349709e98f338d86d07b32a5a713d10d054/dbt_core-1.10.4-py3-none-any.whl", hash = "sha256:461bbd98c77e4ce7ae3f54c8a84aba620516c558deb8ecdc1b2552c3232cca24", size = 979456, upload-time = "2025-07-10T20:42:27.175Z" }, ] [[package]] @@ -621,32 +622,32 @@ dependencies = [ { name = "dbt-core" }, { name = "duckdb" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/23/ec/30497ba442071b0ffccb9332f3f5454bd9bed32e01fb0ef83f44155dfb6d/dbt_duckdb-1.9.4.tar.gz", hash = "sha256:7e64f28bbcf99abfb3e493303d5261b07fdc7381be97338450f326dce2980521", size = 111408 } +sdist = { url = "https://files.pythonhosted.org/packages/23/ec/30497ba442071b0ffccb9332f3f5454bd9bed32e01fb0ef83f44155dfb6d/dbt_duckdb-1.9.4.tar.gz", hash = "sha256:7e64f28bbcf99abfb3e493303d5261b07fdc7381be97338450f326dce2980521", size = 111408, upload-time = "2025-06-25T18:39:54.617Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9c/79/57012a949783f2fb11ba01e480239de5d8679891651518eb18447cf1645b/dbt_duckdb-1.9.4-py3-none-any.whl", hash = "sha256:b3e8ea0f1fc38fe5b1b881c181174b3d701475695312ff724dc65015bceca5da", size = 72088 }, + { url = "https://files.pythonhosted.org/packages/9c/79/57012a949783f2fb11ba01e480239de5d8679891651518eb18447cf1645b/dbt_duckdb-1.9.4-py3-none-any.whl", hash = "sha256:b3e8ea0f1fc38fe5b1b881c181174b3d701475695312ff724dc65015bceca5da", size = 72088, upload-time = "2025-06-25T18:39:53.087Z" }, ] [[package]] name = "dbt-extractor" version = "0.6.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f1/06/1f7b5d277af4bd7c3ab5065f79407c46a73950f0879fac69e51067c87649/dbt_extractor-0.6.0.tar.gz", hash = "sha256:d6cf08ec793b8bc2bd6e260ef818230ae68a4f71436fa489f08d7db1a52e2ffe", size = 270461 } +sdist = { url = "https://files.pythonhosted.org/packages/f1/06/1f7b5d277af4bd7c3ab5065f79407c46a73950f0879fac69e51067c87649/dbt_extractor-0.6.0.tar.gz", hash = "sha256:d6cf08ec793b8bc2bd6e260ef818230ae68a4f71436fa489f08d7db1a52e2ffe", size = 270461, upload-time = "2025-04-07T16:46:30.532Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9f/dd/ec8f9e48e7dd5a52a69cca7907681d1779cf1cc8b02f2aa2acb6a2bf8bb4/dbt_extractor-0.6.0-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:4b6b1e70dde78cb904ca7a8958c2c803e77779b6ce108f4ea7ac479f5700db89", size = 790206 }, - { url = "https://files.pythonhosted.org/packages/03/5f/233f326336aa21fbd9e7268f239a8464af145abd398a360d894c3286699d/dbt_extractor-0.6.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:dcf14ed245de8df269815ff4c4f555fa72d2621f4fff37c023b8c99d0e421b4f", size = 404381 }, - { url = "https://files.pythonhosted.org/packages/c9/2a/e14c13b9a437780c5712525ce537915b531bba45481fc7102deb4492ff83/dbt_extractor-0.6.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:af451633390ac19669d3bde6c79822e657d32f5d903b3388bb00d56333fd52d5", size = 435109 }, - { url = "https://files.pythonhosted.org/packages/58/2e/1ef1cd2b36973bea0a6823a7b7cd1b3db29b61ddebb015ceaea88b9e9347/dbt_extractor-0.6.0-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:05bcfab7ebd70296ceb31742e8333ba66a2c939de44e61a7088bebafa939aaf6", size = 434550 }, - { url = "https://files.pythonhosted.org/packages/40/5a/468a2855181aaee5402efbf9ef757d074cd306eec22bbcd267cdd0edbe94/dbt_extractor-0.6.0-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:71b3f8897138cc6698d313b9a3d0450fd021937ff5463269ee18ed415541781b", size = 470137 }, - { url = "https://files.pythonhosted.org/packages/b2/18/611dceb2fa7ea668471f290f34fec55fa3283e3ee9d0475d964e6ffaff97/dbt_extractor-0.6.0-cp39-abi3-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:868af715a6328d7317ce6e4db238f850f660fef13fb36b7ab4cf9163ed5f54ff", size = 524331 }, - { url = "https://files.pythonhosted.org/packages/9e/ad/9dd410d4d95e336ae6b10c53c939bf1ff8e9991e1adb5ea4aefc4a87c445/dbt_extractor-0.6.0-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c1fd2b083a75e80b13e9874dc9699bfdfddf3baa9b6a8dea48de06d51a082733", size = 517959 }, - { url = "https://files.pythonhosted.org/packages/a4/4f/6994cdfb51c5652fad0c8f9cf5b3ec1816cb10e99ed145eb27e6a9bcc16b/dbt_extractor-0.6.0-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:311f0d3a4994751c541a4fa303d205727ba90e90c85286c03d3d9284e2bf0bd4", size = 494850 }, - { url = "https://files.pythonhosted.org/packages/df/5e/fad01e18d68ffd09c0f39cdedeed8fcaaea74a8b46d1a944472b5f95b72b/dbt_extractor-0.6.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aecfa43f7e6f139e76d47e4e1d7b189655ae19a8cf697686230bacb89a94ae74", size = 442739 }, - { url = "https://files.pythonhosted.org/packages/9d/82/49068ee2b9f38aa34d0f3196bb7b71d11af86630d5ed5cb6626108c97cd6/dbt_extractor-0.6.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:a5cb810edc60c0486f78cc29739ebda70c81b10a1686861e78addc9f91fcd7de", size = 618014 }, - { url = "https://files.pythonhosted.org/packages/18/c6/cdaf1ac8959d571b5cb3587b8afef9e5fe60b99fe59aca94560808501d8b/dbt_extractor-0.6.0-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:080fd1edf123926ed97929c65a75874d0fea687ccd5d3ebbc9e81b339f099604", size = 697290 }, - { url = "https://files.pythonhosted.org/packages/94/6d/46bdb9a809c66784fcc19b853311568cfd3041c075f0a578cb7116686841/dbt_extractor-0.6.0-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:1b9ed7b15df983a735f87773f6765db8458680c02fcebbf89df4e238503c0e08", size = 644443 }, - { url = "https://files.pythonhosted.org/packages/3b/02/b111856273e414ac80ef58d2103c9b7c6a5b29b1ec248999d3d5873ada00/dbt_extractor-0.6.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:caeaba8d8c813f8e32d586c12615c0c7d6b99bee4f1be845312e80ef731de164", size = 613017 }, - { url = "https://files.pythonhosted.org/packages/c4/de/d1492ab6beaf0a18aee17c7a9562592ac2981e962b4058262f5eb6dabfc5/dbt_extractor-0.6.0-cp39-abi3-win32.whl", hash = "sha256:369dcc3499f160256756585783f1308868076d5a65d0a051348d22da8b90e67d", size = 252721 }, - { url = "https://files.pythonhosted.org/packages/60/36/f5b1c4159fa911607f3a49fcbc535e4783870fd887bc0a1b3ad42587cb73/dbt_extractor-0.6.0-cp39-abi3-win_amd64.whl", hash = "sha256:a79a570fdcb672505ac2bdc12360a2a7aec622ef604d8c607225854ff862518c", size = 277146 }, + { url = "https://files.pythonhosted.org/packages/9f/dd/ec8f9e48e7dd5a52a69cca7907681d1779cf1cc8b02f2aa2acb6a2bf8bb4/dbt_extractor-0.6.0-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:4b6b1e70dde78cb904ca7a8958c2c803e77779b6ce108f4ea7ac479f5700db89", size = 790206, upload-time = "2025-04-07T16:46:05.352Z" }, + { url = "https://files.pythonhosted.org/packages/03/5f/233f326336aa21fbd9e7268f239a8464af145abd398a360d894c3286699d/dbt_extractor-0.6.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:dcf14ed245de8df269815ff4c4f555fa72d2621f4fff37c023b8c99d0e421b4f", size = 404381, upload-time = "2025-04-07T16:46:07.471Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2a/e14c13b9a437780c5712525ce537915b531bba45481fc7102deb4492ff83/dbt_extractor-0.6.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:af451633390ac19669d3bde6c79822e657d32f5d903b3388bb00d56333fd52d5", size = 435109, upload-time = "2025-04-07T16:46:09.443Z" }, + { url = "https://files.pythonhosted.org/packages/58/2e/1ef1cd2b36973bea0a6823a7b7cd1b3db29b61ddebb015ceaea88b9e9347/dbt_extractor-0.6.0-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:05bcfab7ebd70296ceb31742e8333ba66a2c939de44e61a7088bebafa939aaf6", size = 434550, upload-time = "2025-04-07T16:46:10.916Z" }, + { url = "https://files.pythonhosted.org/packages/40/5a/468a2855181aaee5402efbf9ef757d074cd306eec22bbcd267cdd0edbe94/dbt_extractor-0.6.0-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:71b3f8897138cc6698d313b9a3d0450fd021937ff5463269ee18ed415541781b", size = 470137, upload-time = "2025-04-07T16:46:12.36Z" }, + { url = "https://files.pythonhosted.org/packages/b2/18/611dceb2fa7ea668471f290f34fec55fa3283e3ee9d0475d964e6ffaff97/dbt_extractor-0.6.0-cp39-abi3-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:868af715a6328d7317ce6e4db238f850f660fef13fb36b7ab4cf9163ed5f54ff", size = 524331, upload-time = "2025-04-07T16:46:14.177Z" }, + { url = "https://files.pythonhosted.org/packages/9e/ad/9dd410d4d95e336ae6b10c53c939bf1ff8e9991e1adb5ea4aefc4a87c445/dbt_extractor-0.6.0-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c1fd2b083a75e80b13e9874dc9699bfdfddf3baa9b6a8dea48de06d51a082733", size = 517959, upload-time = "2025-04-07T16:46:15.68Z" }, + { url = "https://files.pythonhosted.org/packages/a4/4f/6994cdfb51c5652fad0c8f9cf5b3ec1816cb10e99ed145eb27e6a9bcc16b/dbt_extractor-0.6.0-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:311f0d3a4994751c541a4fa303d205727ba90e90c85286c03d3d9284e2bf0bd4", size = 494850, upload-time = "2025-04-07T16:46:17.265Z" }, + { url = "https://files.pythonhosted.org/packages/df/5e/fad01e18d68ffd09c0f39cdedeed8fcaaea74a8b46d1a944472b5f95b72b/dbt_extractor-0.6.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aecfa43f7e6f139e76d47e4e1d7b189655ae19a8cf697686230bacb89a94ae74", size = 442739, upload-time = "2025-04-07T16:46:19.002Z" }, + { url = "https://files.pythonhosted.org/packages/9d/82/49068ee2b9f38aa34d0f3196bb7b71d11af86630d5ed5cb6626108c97cd6/dbt_extractor-0.6.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:a5cb810edc60c0486f78cc29739ebda70c81b10a1686861e78addc9f91fcd7de", size = 618014, upload-time = "2025-04-07T16:46:21.571Z" }, + { url = "https://files.pythonhosted.org/packages/18/c6/cdaf1ac8959d571b5cb3587b8afef9e5fe60b99fe59aca94560808501d8b/dbt_extractor-0.6.0-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:080fd1edf123926ed97929c65a75874d0fea687ccd5d3ebbc9e81b339f099604", size = 697290, upload-time = "2025-04-07T16:46:23.089Z" }, + { url = "https://files.pythonhosted.org/packages/94/6d/46bdb9a809c66784fcc19b853311568cfd3041c075f0a578cb7116686841/dbt_extractor-0.6.0-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:1b9ed7b15df983a735f87773f6765db8458680c02fcebbf89df4e238503c0e08", size = 644443, upload-time = "2025-04-07T16:46:24.463Z" }, + { url = "https://files.pythonhosted.org/packages/3b/02/b111856273e414ac80ef58d2103c9b7c6a5b29b1ec248999d3d5873ada00/dbt_extractor-0.6.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:caeaba8d8c813f8e32d586c12615c0c7d6b99bee4f1be845312e80ef731de164", size = 613017, upload-time = "2025-04-07T16:46:25.913Z" }, + { url = "https://files.pythonhosted.org/packages/c4/de/d1492ab6beaf0a18aee17c7a9562592ac2981e962b4058262f5eb6dabfc5/dbt_extractor-0.6.0-cp39-abi3-win32.whl", hash = "sha256:369dcc3499f160256756585783f1308868076d5a65d0a051348d22da8b90e67d", size = 252721, upload-time = "2025-04-07T16:46:27.295Z" }, + { url = "https://files.pythonhosted.org/packages/60/36/f5b1c4159fa911607f3a49fcbc535e4783870fd887bc0a1b3ad42587cb73/dbt_extractor-0.6.0-cp39-abi3-win_amd64.whl", hash = "sha256:a79a570fdcb672505ac2bdc12360a2a7aec622ef604d8c607225854ff862518c", size = 277146, upload-time = "2025-04-07T16:46:28.991Z" }, ] [[package]] @@ -656,9 +657,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/56/9f/2d0e9fb150eef240369e4fe423662ab54150b09b113deaed8890c32d44cb/dbt_protos-1.0.337.tar.gz", hash = "sha256:bed47c23406b6b3113bf069165870e6d99cd93a6f99064af780e35f7df5d4cd2", size = 74916 } +sdist = { url = "https://files.pythonhosted.org/packages/56/9f/2d0e9fb150eef240369e4fe423662ab54150b09b113deaed8890c32d44cb/dbt_protos-1.0.337.tar.gz", hash = "sha256:bed47c23406b6b3113bf069165870e6d99cd93a6f99064af780e35f7df5d4cd2", size = 74916, upload-time = "2025-07-07T14:59:44.59Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ba/22/3452b1ae7623c342e0fb216e1a96c434bd82fbc1b53fc588d9bb3d571ee8/dbt_protos-1.0.337-py3-none-any.whl", hash = "sha256:8f58c9f5fb335f85be1e1b2da3f0b2198f62188ff7c89dea494c57264fd9ac60", size = 92385 }, + { url = "https://files.pythonhosted.org/packages/ba/22/3452b1ae7623c342e0fb216e1a96c434bd82fbc1b53fc588d9bb3d571ee8/dbt_protos-1.0.337-py3-none-any.whl", hash = "sha256:8f58c9f5fb335f85be1e1b2da3f0b2198f62188ff7c89dea494c57264fd9ac60", size = 92385, upload-time = "2025-07-07T14:59:43.169Z" }, ] [[package]] @@ -676,9 +677,9 @@ dependencies = [ { name = "pyyaml" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b0/91/c702d8fb143541fda10f5eb7a7a89f34bda38ee043ecb3e3653363d0c5a0/dbt_semantic_interfaces-0.9.0.tar.gz", hash = "sha256:5c921257dce8bb51c9ffb5479f2bdd959e16ebfb98ee833de6daa70788c47271", size = 93865 } +sdist = { url = "https://files.pythonhosted.org/packages/b0/91/c702d8fb143541fda10f5eb7a7a89f34bda38ee043ecb3e3653363d0c5a0/dbt_semantic_interfaces-0.9.0.tar.gz", hash = "sha256:5c921257dce8bb51c9ffb5479f2bdd959e16ebfb98ee833de6daa70788c47271", size = 93865, upload-time = "2025-07-09T20:06:30.454Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b3/82/41708b2b69d5fead88dea5ca0d863d6291da83ca6f1bd19246842d397e2b/dbt_semantic_interfaces-0.9.0-py3-none-any.whl", hash = "sha256:1b54c06ba89190a47a7f0563360930a0cce869e55b484ca09d261ade0e319155", size = 147008 }, + { url = "https://files.pythonhosted.org/packages/b3/82/41708b2b69d5fead88dea5ca0d863d6291da83ca6f1bd19246842d397e2b/dbt_semantic_interfaces-0.9.0-py3-none-any.whl", hash = "sha256:1b54c06ba89190a47a7f0563360930a0cce869e55b484ca09d261ade0e319155", size = 147008, upload-time = "2025-07-09T20:06:32.466Z" }, ] [[package]] @@ -688,59 +689,59 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "ordered-set" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/70/10/6f4b0bd0627d542f63a24f38e29d77095dc63d5f45bc1a7b4a6ca8750fa9/deepdiff-7.0.1.tar.gz", hash = "sha256:260c16f052d4badbf60351b4f77e8390bee03a0b516246f6839bc813fb429ddf", size = 421718 } +sdist = { url = "https://files.pythonhosted.org/packages/70/10/6f4b0bd0627d542f63a24f38e29d77095dc63d5f45bc1a7b4a6ca8750fa9/deepdiff-7.0.1.tar.gz", hash = "sha256:260c16f052d4badbf60351b4f77e8390bee03a0b516246f6839bc813fb429ddf", size = 421718, upload-time = "2024-04-08T22:59:24.578Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/18/e6/d27d37dc55dbf40cdbd665aa52844b065ac760c9a02a02265f97ea7a4256/deepdiff-7.0.1-py3-none-any.whl", hash = "sha256:447760081918216aa4fd4ca78a4b6a848b81307b2ea94c810255334b759e1dc3", size = 80825 }, + { url = "https://files.pythonhosted.org/packages/18/e6/d27d37dc55dbf40cdbd665aa52844b065ac760c9a02a02265f97ea7a4256/deepdiff-7.0.1-py3-none-any.whl", hash = "sha256:447760081918216aa4fd4ca78a4b6a848b81307b2ea94c810255334b759e1dc3", size = 80825, upload-time = "2024-04-08T22:59:21.885Z" }, ] [[package]] name = "distro" version = "1.9.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722 } +sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722, upload-time = "2023-12-24T09:54:32.31Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277 }, + { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" }, ] [[package]] name = "docutils" version = "0.21.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ae/ed/aefcc8cd0ba62a0560c3c18c33925362d46c6075480bfa4df87b28e169a9/docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f", size = 2204444 } +sdist = { url = "https://files.pythonhosted.org/packages/ae/ed/aefcc8cd0ba62a0560c3c18c33925362d46c6075480bfa4df87b28e169a9/docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f", size = 2204444, upload-time = "2024-04-23T18:57:18.24Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8f/d7/9322c609343d929e75e7e5e6255e614fcc67572cfd083959cdef3b7aad79/docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2", size = 587408 }, + { url = "https://files.pythonhosted.org/packages/8f/d7/9322c609343d929e75e7e5e6255e614fcc67572cfd083959cdef3b7aad79/docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2", size = 587408, upload-time = "2024-04-23T18:57:14.835Z" }, ] [[package]] name = "duckdb" version = "1.4.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ea/e7/21cf50a3d52ffceee1f0bcc3997fa96a5062e6bab705baee4f6c4e33cce5/duckdb-1.4.1.tar.gz", hash = "sha256:f903882f045d057ebccad12ac69975952832edfe133697694854bb784b8d6c76", size = 18461687 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/68/cc/00a07de0e33d16763edd4132d7c8a2f9efd57a2f296a25a948f239a1fadf/duckdb-1.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:296b4fff3908fb4c47b0aa1d77bd1933375e75401009d2dc81af8e7a0b8a05b4", size = 29062814 }, - { url = "https://files.pythonhosted.org/packages/17/ea/fb0fda8886d1928f1b2a53a1163ef94f6f4b41f6d8b29eee457acfc2fa67/duckdb-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0b4182800092115feee5d71a8691efb283d3c9f5eb0b36362b308ef007a12222", size = 16161652 }, - { url = "https://files.pythonhosted.org/packages/b4/5f/052e6436a71f461e61cd3a982954c029145a84b58cefa1dfb3eb2d96e4fc/duckdb-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:67cc3b6c7f7ba07a69e9331b8ccea7a60cbcd4204bb473e5da9b71588bd2eca9", size = 13753030 }, - { url = "https://files.pythonhosted.org/packages/c2/fd/3ae3c89d0f6ad54c0be4430e572306fbfc9f173c97b23c5025a540449325/duckdb-1.4.1-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0cef0cee7030b561640cb9af718f8841b19cdd2aa020d53561057b5743bea90b", size = 18487683 }, - { url = "https://files.pythonhosted.org/packages/d4/3c/eef454cd7c3880c2d55b50e18a9c7a213bf91ded79efcfb573d8d6dd8a47/duckdb-1.4.1-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2bf93347f37a46bacce6ac859d651dbf5731e2c94a64ab358300425b09e3de23", size = 20487080 }, - { url = "https://files.pythonhosted.org/packages/bb/5b/b619f4c986a1cb0b06315239da9ce5fd94a20c07a344d03e2635d56a6967/duckdb-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:2e60d2361f978908a3d96eebaf1f4b346f283afcc467351aae50ea45ca293a2b", size = 12324436 }, - { url = "https://files.pythonhosted.org/packages/d9/52/606f13fa9669a24166d2fe523e28982d8ef9039874b4de774255c7806d1f/duckdb-1.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:605d563c1d5203ca992497cd33fb386ac3d533deca970f9dcf539f62a34e22a9", size = 29065894 }, - { url = "https://files.pythonhosted.org/packages/84/57/138241952ece868b9577e607858466315bed1739e1fbb47205df4dfdfd88/duckdb-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d3305c7c4b70336171de7adfdb50431f23671c000f11839b580c4201d9ce6ef5", size = 16163720 }, - { url = "https://files.pythonhosted.org/packages/a3/81/afa3a0a78498a6f4acfea75c48a70c5082032d9ac87822713d7c2d164af1/duckdb-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a063d6febbe34b32f1ad2e68822db4d0e4b1102036f49aaeeb22b844427a75df", size = 13756223 }, - { url = "https://files.pythonhosted.org/packages/47/dd/5f6064fbd9248e37a3e806a244f81e0390ab8f989d231b584fb954f257fc/duckdb-1.4.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1ffcaaf74f7d1df3684b54685cbf8d3ce732781c541def8e1ced304859733ae", size = 18487022 }, - { url = "https://files.pythonhosted.org/packages/a1/10/b54969a1c42fd9344ad39228d671faceb8aa9f144b67cd9531a63551757f/duckdb-1.4.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:685d3d1599dc08160e0fa0cf09e93ac4ff8b8ed399cb69f8b5391cd46b5b207c", size = 20491004 }, - { url = "https://files.pythonhosted.org/packages/ed/d5/7332ae8f804869a4e895937821b776199a283f8d9fc775fd3ae5a0558099/duckdb-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:78f1d28a15ae73bd449c43f80233732adffa49be1840a32de8f1a6bb5b286764", size = 12327619 }, - { url = "https://files.pythonhosted.org/packages/0e/6c/906a3fe41cd247b5638866fc1245226b528de196588802d4df4df1e6e819/duckdb-1.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:cd1765a7d180b7482874586859fc23bc9969d7d6c96ced83b245e6c6f49cde7f", size = 29076820 }, - { url = "https://files.pythonhosted.org/packages/66/c7/01dd33083f01f618c2a29f6dd068baf16945b8cbdb132929d3766610bbbb/duckdb-1.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8ed7a86725185470953410823762956606693c0813bb64e09c7d44dbd9253a64", size = 16167558 }, - { url = "https://files.pythonhosted.org/packages/81/e2/f983b4b7ae1dfbdd2792dd31dee9a0d35f88554452cbfc6c9d65e22fdfa9/duckdb-1.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8a189bdfc64cfb9cc1adfbe4f2dcfde0a4992ec08505ad8ce33c886e4813f0bf", size = 13762226 }, - { url = "https://files.pythonhosted.org/packages/ed/34/fb69a7be19b90f573b3cc890961be7b11870b77514769655657514f10a98/duckdb-1.4.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a9090089b6486f7319c92acdeed8acda022d4374032d78a465956f50fc52fabf", size = 18500901 }, - { url = "https://files.pythonhosted.org/packages/e4/a5/1395d7b49d5589e85da9a9d7ffd8b50364c9d159c2807bef72d547f0ad1e/duckdb-1.4.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:142552ea3e768048e0e8c832077a545ca07792631c59edaee925e3e67401c2a0", size = 20514177 }, - { url = "https://files.pythonhosted.org/packages/c0/21/08f10706d30252753349ec545833fc0cea67c11abd0b5223acf2827f1056/duckdb-1.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:567f3b3a785a9e8650612461893c49ca799661d2345a6024dda48324ece89ded", size = 12336422 }, - { url = "https://files.pythonhosted.org/packages/d7/08/705988c33e38665c969f7876b3ca4328be578554aa7e3dc0f34158da3e64/duckdb-1.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:46496a2518752ae0c6c5d75d4cdecf56ea23dd098746391176dd8e42cf157791", size = 29077070 }, - { url = "https://files.pythonhosted.org/packages/99/c5/7c9165f1e6b9069441bcda4da1e19382d4a2357783d37ff9ae238c5c41ac/duckdb-1.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1c65ae7e9b541cea07d8075343bcfebdecc29a3c0481aa6078ee63d51951cfcd", size = 16167506 }, - { url = "https://files.pythonhosted.org/packages/38/46/267f4a570a0ee3ae6871ddc03435f9942884284e22a7ba9b7cb252ee69b6/duckdb-1.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:598d1a314e34b65d9399ddd066ccce1eeab6a60a2ef5885a84ce5ed62dbaf729", size = 13762330 }, - { url = "https://files.pythonhosted.org/packages/15/7b/c4f272a40c36d82df20937d93a1780eb39ab0107fe42b62cba889151eab9/duckdb-1.4.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e2f16b8def782d484a9f035fc422bb6f06941ed0054b4511ddcdc514a7fb6a75", size = 18504687 }, - { url = "https://files.pythonhosted.org/packages/17/fc/9b958751f0116d7b0406406b07fa6f5a10c22d699be27826d0b896f9bf51/duckdb-1.4.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a5a7d0aed068a5c33622a8848857947cab5cfb3f2a315b1251849bac2c74c492", size = 20513823 }, - { url = "https://files.pythonhosted.org/packages/30/79/4f544d73fcc0513b71296cb3ebb28a227d22e80dec27204977039b9fa875/duckdb-1.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:280fd663dacdd12bb3c3bf41f3e5b2e5b95e00b88120afabb8b8befa5f335c6f", size = 12336460 }, +sdist = { url = "https://files.pythonhosted.org/packages/ea/e7/21cf50a3d52ffceee1f0bcc3997fa96a5062e6bab705baee4f6c4e33cce5/duckdb-1.4.1.tar.gz", hash = "sha256:f903882f045d057ebccad12ac69975952832edfe133697694854bb784b8d6c76", size = 18461687, upload-time = "2025-10-07T10:37:28.605Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/68/cc/00a07de0e33d16763edd4132d7c8a2f9efd57a2f296a25a948f239a1fadf/duckdb-1.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:296b4fff3908fb4c47b0aa1d77bd1933375e75401009d2dc81af8e7a0b8a05b4", size = 29062814, upload-time = "2025-10-07T10:36:14.261Z" }, + { url = "https://files.pythonhosted.org/packages/17/ea/fb0fda8886d1928f1b2a53a1163ef94f6f4b41f6d8b29eee457acfc2fa67/duckdb-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0b4182800092115feee5d71a8691efb283d3c9f5eb0b36362b308ef007a12222", size = 16161652, upload-time = "2025-10-07T10:36:17.358Z" }, + { url = "https://files.pythonhosted.org/packages/b4/5f/052e6436a71f461e61cd3a982954c029145a84b58cefa1dfb3eb2d96e4fc/duckdb-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:67cc3b6c7f7ba07a69e9331b8ccea7a60cbcd4204bb473e5da9b71588bd2eca9", size = 13753030, upload-time = "2025-10-07T10:36:19.782Z" }, + { url = "https://files.pythonhosted.org/packages/c2/fd/3ae3c89d0f6ad54c0be4430e572306fbfc9f173c97b23c5025a540449325/duckdb-1.4.1-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0cef0cee7030b561640cb9af718f8841b19cdd2aa020d53561057b5743bea90b", size = 18487683, upload-time = "2025-10-07T10:36:22.375Z" }, + { url = "https://files.pythonhosted.org/packages/d4/3c/eef454cd7c3880c2d55b50e18a9c7a213bf91ded79efcfb573d8d6dd8a47/duckdb-1.4.1-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2bf93347f37a46bacce6ac859d651dbf5731e2c94a64ab358300425b09e3de23", size = 20487080, upload-time = "2025-10-07T10:36:24.692Z" }, + { url = "https://files.pythonhosted.org/packages/bb/5b/b619f4c986a1cb0b06315239da9ce5fd94a20c07a344d03e2635d56a6967/duckdb-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:2e60d2361f978908a3d96eebaf1f4b346f283afcc467351aae50ea45ca293a2b", size = 12324436, upload-time = "2025-10-07T10:36:27.458Z" }, + { url = "https://files.pythonhosted.org/packages/d9/52/606f13fa9669a24166d2fe523e28982d8ef9039874b4de774255c7806d1f/duckdb-1.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:605d563c1d5203ca992497cd33fb386ac3d533deca970f9dcf539f62a34e22a9", size = 29065894, upload-time = "2025-10-07T10:36:29.837Z" }, + { url = "https://files.pythonhosted.org/packages/84/57/138241952ece868b9577e607858466315bed1739e1fbb47205df4dfdfd88/duckdb-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d3305c7c4b70336171de7adfdb50431f23671c000f11839b580c4201d9ce6ef5", size = 16163720, upload-time = "2025-10-07T10:36:32.241Z" }, + { url = "https://files.pythonhosted.org/packages/a3/81/afa3a0a78498a6f4acfea75c48a70c5082032d9ac87822713d7c2d164af1/duckdb-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a063d6febbe34b32f1ad2e68822db4d0e4b1102036f49aaeeb22b844427a75df", size = 13756223, upload-time = "2025-10-07T10:36:34.673Z" }, + { url = "https://files.pythonhosted.org/packages/47/dd/5f6064fbd9248e37a3e806a244f81e0390ab8f989d231b584fb954f257fc/duckdb-1.4.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1ffcaaf74f7d1df3684b54685cbf8d3ce732781c541def8e1ced304859733ae", size = 18487022, upload-time = "2025-10-07T10:36:36.759Z" }, + { url = "https://files.pythonhosted.org/packages/a1/10/b54969a1c42fd9344ad39228d671faceb8aa9f144b67cd9531a63551757f/duckdb-1.4.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:685d3d1599dc08160e0fa0cf09e93ac4ff8b8ed399cb69f8b5391cd46b5b207c", size = 20491004, upload-time = "2025-10-07T10:36:39.318Z" }, + { url = "https://files.pythonhosted.org/packages/ed/d5/7332ae8f804869a4e895937821b776199a283f8d9fc775fd3ae5a0558099/duckdb-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:78f1d28a15ae73bd449c43f80233732adffa49be1840a32de8f1a6bb5b286764", size = 12327619, upload-time = "2025-10-07T10:36:41.509Z" }, + { url = "https://files.pythonhosted.org/packages/0e/6c/906a3fe41cd247b5638866fc1245226b528de196588802d4df4df1e6e819/duckdb-1.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:cd1765a7d180b7482874586859fc23bc9969d7d6c96ced83b245e6c6f49cde7f", size = 29076820, upload-time = "2025-10-07T10:36:43.782Z" }, + { url = "https://files.pythonhosted.org/packages/66/c7/01dd33083f01f618c2a29f6dd068baf16945b8cbdb132929d3766610bbbb/duckdb-1.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8ed7a86725185470953410823762956606693c0813bb64e09c7d44dbd9253a64", size = 16167558, upload-time = "2025-10-07T10:36:46.003Z" }, + { url = "https://files.pythonhosted.org/packages/81/e2/f983b4b7ae1dfbdd2792dd31dee9a0d35f88554452cbfc6c9d65e22fdfa9/duckdb-1.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8a189bdfc64cfb9cc1adfbe4f2dcfde0a4992ec08505ad8ce33c886e4813f0bf", size = 13762226, upload-time = "2025-10-07T10:36:48.55Z" }, + { url = "https://files.pythonhosted.org/packages/ed/34/fb69a7be19b90f573b3cc890961be7b11870b77514769655657514f10a98/duckdb-1.4.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a9090089b6486f7319c92acdeed8acda022d4374032d78a465956f50fc52fabf", size = 18500901, upload-time = "2025-10-07T10:36:52.445Z" }, + { url = "https://files.pythonhosted.org/packages/e4/a5/1395d7b49d5589e85da9a9d7ffd8b50364c9d159c2807bef72d547f0ad1e/duckdb-1.4.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:142552ea3e768048e0e8c832077a545ca07792631c59edaee925e3e67401c2a0", size = 20514177, upload-time = "2025-10-07T10:36:55.358Z" }, + { url = "https://files.pythonhosted.org/packages/c0/21/08f10706d30252753349ec545833fc0cea67c11abd0b5223acf2827f1056/duckdb-1.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:567f3b3a785a9e8650612461893c49ca799661d2345a6024dda48324ece89ded", size = 12336422, upload-time = "2025-10-07T10:36:57.521Z" }, + { url = "https://files.pythonhosted.org/packages/d7/08/705988c33e38665c969f7876b3ca4328be578554aa7e3dc0f34158da3e64/duckdb-1.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:46496a2518752ae0c6c5d75d4cdecf56ea23dd098746391176dd8e42cf157791", size = 29077070, upload-time = "2025-10-07T10:36:59.83Z" }, + { url = "https://files.pythonhosted.org/packages/99/c5/7c9165f1e6b9069441bcda4da1e19382d4a2357783d37ff9ae238c5c41ac/duckdb-1.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1c65ae7e9b541cea07d8075343bcfebdecc29a3c0481aa6078ee63d51951cfcd", size = 16167506, upload-time = "2025-10-07T10:37:02.24Z" }, + { url = "https://files.pythonhosted.org/packages/38/46/267f4a570a0ee3ae6871ddc03435f9942884284e22a7ba9b7cb252ee69b6/duckdb-1.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:598d1a314e34b65d9399ddd066ccce1eeab6a60a2ef5885a84ce5ed62dbaf729", size = 13762330, upload-time = "2025-10-07T10:37:04.581Z" }, + { url = "https://files.pythonhosted.org/packages/15/7b/c4f272a40c36d82df20937d93a1780eb39ab0107fe42b62cba889151eab9/duckdb-1.4.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e2f16b8def782d484a9f035fc422bb6f06941ed0054b4511ddcdc514a7fb6a75", size = 18504687, upload-time = "2025-10-07T10:37:06.991Z" }, + { url = "https://files.pythonhosted.org/packages/17/fc/9b958751f0116d7b0406406b07fa6f5a10c22d699be27826d0b896f9bf51/duckdb-1.4.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a5a7d0aed068a5c33622a8848857947cab5cfb3f2a315b1251849bac2c74c492", size = 20513823, upload-time = "2025-10-07T10:37:09.349Z" }, + { url = "https://files.pythonhosted.org/packages/30/79/4f544d73fcc0513b71296cb3ebb28a227d22e80dec27204977039b9fa875/duckdb-1.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:280fd663dacdd12bb3c3bf41f3e5b2e5b95e00b88120afabb8b8befa5f335c6f", size = 12336460, upload-time = "2025-10-07T10:37:12.154Z" }, ] [[package]] @@ -750,9 +751,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749 } +sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749, upload-time = "2025-05-10T17:42:51.123Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674 }, + { url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674, upload-time = "2025-05-10T17:42:49.33Z" }, ] [[package]] @@ -765,103 +766,103 @@ dependencies = [ { name = "starlette" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8c/e3/77a2df0946703973b9905fd0cde6172c15e0781984320123b4f5079e7113/fastapi-0.121.0.tar.gz", hash = "sha256:06663356a0b1ee93e875bbf05a31fb22314f5bed455afaaad2b2dad7f26e98fa", size = 342412 } +sdist = { url = "https://files.pythonhosted.org/packages/8c/e3/77a2df0946703973b9905fd0cde6172c15e0781984320123b4f5079e7113/fastapi-0.121.0.tar.gz", hash = "sha256:06663356a0b1ee93e875bbf05a31fb22314f5bed455afaaad2b2dad7f26e98fa", size = 342412, upload-time = "2025-11-03T10:25:54.818Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/dd/2c/42277afc1ba1a18f8358561eee40785d27becab8f80a1f945c0a3051c6eb/fastapi-0.121.0-py3-none-any.whl", hash = "sha256:8bdf1b15a55f4e4b0d6201033da9109ea15632cb76cf156e7b8b4019f2172106", size = 109183 }, + { url = "https://files.pythonhosted.org/packages/dd/2c/42277afc1ba1a18f8358561eee40785d27becab8f80a1f945c0a3051c6eb/fastapi-0.121.0-py3-none-any.whl", hash = "sha256:8bdf1b15a55f4e4b0d6201033da9109ea15632cb76cf156e7b8b4019f2172106", size = 109183, upload-time = "2025-11-03T10:25:53.27Z" }, ] [[package]] name = "frozenlist" version = "1.7.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/79/b1/b64018016eeb087db503b038296fd782586432b9c077fc5c7839e9cb6ef6/frozenlist-1.7.0.tar.gz", hash = "sha256:2e310d81923c2437ea8670467121cc3e9b0f76d3043cc1d2331d56c7fb7a3a8f", size = 45078 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/af/36/0da0a49409f6b47cc2d060dc8c9040b897b5902a8a4e37d9bc1deb11f680/frozenlist-1.7.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cc4df77d638aa2ed703b878dd093725b72a824c3c546c076e8fdf276f78ee84a", size = 81304 }, - { url = "https://files.pythonhosted.org/packages/77/f0/77c11d13d39513b298e267b22eb6cb559c103d56f155aa9a49097221f0b6/frozenlist-1.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:716a9973a2cc963160394f701964fe25012600f3d311f60c790400b00e568b61", size = 47735 }, - { url = "https://files.pythonhosted.org/packages/37/12/9d07fa18971a44150593de56b2f2947c46604819976784bcf6ea0d5db43b/frozenlist-1.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a0fd1bad056a3600047fb9462cff4c5322cebc59ebf5d0a3725e0ee78955001d", size = 46775 }, - { url = "https://files.pythonhosted.org/packages/70/34/f73539227e06288fcd1f8a76853e755b2b48bca6747e99e283111c18bcd4/frozenlist-1.7.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3789ebc19cb811163e70fe2bd354cea097254ce6e707ae42e56f45e31e96cb8e", size = 224644 }, - { url = "https://files.pythonhosted.org/packages/fb/68/c1d9c2f4a6e438e14613bad0f2973567586610cc22dcb1e1241da71de9d3/frozenlist-1.7.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:af369aa35ee34f132fcfad5be45fbfcde0e3a5f6a1ec0712857f286b7d20cca9", size = 222125 }, - { url = "https://files.pythonhosted.org/packages/b9/d0/98e8f9a515228d708344d7c6986752be3e3192d1795f748c24bcf154ad99/frozenlist-1.7.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac64b6478722eeb7a3313d494f8342ef3478dff539d17002f849101b212ef97c", size = 233455 }, - { url = "https://files.pythonhosted.org/packages/79/df/8a11bcec5600557f40338407d3e5bea80376ed1c01a6c0910fcfdc4b8993/frozenlist-1.7.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f89f65d85774f1797239693cef07ad4c97fdd0639544bad9ac4b869782eb1981", size = 227339 }, - { url = "https://files.pythonhosted.org/packages/50/82/41cb97d9c9a5ff94438c63cc343eb7980dac4187eb625a51bdfdb7707314/frozenlist-1.7.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1073557c941395fdfcfac13eb2456cb8aad89f9de27bae29fabca8e563b12615", size = 212969 }, - { url = "https://files.pythonhosted.org/packages/13/47/f9179ee5ee4f55629e4f28c660b3fdf2775c8bfde8f9c53f2de2d93f52a9/frozenlist-1.7.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ed8d2fa095aae4bdc7fdd80351009a48d286635edffee66bf865e37a9125c50", size = 222862 }, - { url = "https://files.pythonhosted.org/packages/1a/52/df81e41ec6b953902c8b7e3a83bee48b195cb0e5ec2eabae5d8330c78038/frozenlist-1.7.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:24c34bea555fe42d9f928ba0a740c553088500377448febecaa82cc3e88aa1fa", size = 222492 }, - { url = "https://files.pythonhosted.org/packages/84/17/30d6ea87fa95a9408245a948604b82c1a4b8b3e153cea596421a2aef2754/frozenlist-1.7.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:69cac419ac6a6baad202c85aaf467b65ac860ac2e7f2ac1686dc40dbb52f6577", size = 238250 }, - { url = "https://files.pythonhosted.org/packages/8f/00/ecbeb51669e3c3df76cf2ddd66ae3e48345ec213a55e3887d216eb4fbab3/frozenlist-1.7.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:960d67d0611f4c87da7e2ae2eacf7ea81a5be967861e0c63cf205215afbfac59", size = 218720 }, - { url = "https://files.pythonhosted.org/packages/1a/c0/c224ce0e0eb31cc57f67742071bb470ba8246623c1823a7530be0e76164c/frozenlist-1.7.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:41be2964bd4b15bf575e5daee5a5ce7ed3115320fb3c2b71fca05582ffa4dc9e", size = 232585 }, - { url = "https://files.pythonhosted.org/packages/55/3c/34cb694abf532f31f365106deebdeac9e45c19304d83cf7d51ebbb4ca4d1/frozenlist-1.7.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:46d84d49e00c9429238a7ce02dc0be8f6d7cd0cd405abd1bebdc991bf27c15bd", size = 234248 }, - { url = "https://files.pythonhosted.org/packages/98/c0/2052d8b6cecda2e70bd81299e3512fa332abb6dcd2969b9c80dfcdddbf75/frozenlist-1.7.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:15900082e886edb37480335d9d518cec978afc69ccbc30bd18610b7c1b22a718", size = 221621 }, - { url = "https://files.pythonhosted.org/packages/c5/bf/7dcebae315436903b1d98ffb791a09d674c88480c158aa171958a3ac07f0/frozenlist-1.7.0-cp310-cp310-win32.whl", hash = "sha256:400ddd24ab4e55014bba442d917203c73b2846391dd42ca5e38ff52bb18c3c5e", size = 39578 }, - { url = "https://files.pythonhosted.org/packages/8f/5f/f69818f017fa9a3d24d1ae39763e29b7f60a59e46d5f91b9c6b21622f4cd/frozenlist-1.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:6eb93efb8101ef39d32d50bce242c84bcbddb4f7e9febfa7b524532a239b4464", size = 43830 }, - { url = "https://files.pythonhosted.org/packages/34/7e/803dde33760128acd393a27eb002f2020ddb8d99d30a44bfbaab31c5f08a/frozenlist-1.7.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:aa51e147a66b2d74de1e6e2cf5921890de6b0f4820b257465101d7f37b49fb5a", size = 82251 }, - { url = "https://files.pythonhosted.org/packages/75/a9/9c2c5760b6ba45eae11334db454c189d43d34a4c0b489feb2175e5e64277/frozenlist-1.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9b35db7ce1cd71d36ba24f80f0c9e7cff73a28d7a74e91fe83e23d27c7828750", size = 48183 }, - { url = "https://files.pythonhosted.org/packages/47/be/4038e2d869f8a2da165f35a6befb9158c259819be22eeaf9c9a8f6a87771/frozenlist-1.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:34a69a85e34ff37791e94542065c8416c1afbf820b68f720452f636d5fb990cd", size = 47107 }, - { url = "https://files.pythonhosted.org/packages/79/26/85314b8a83187c76a37183ceed886381a5f992975786f883472fcb6dc5f2/frozenlist-1.7.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a646531fa8d82c87fe4bb2e596f23173caec9185bfbca5d583b4ccfb95183e2", size = 237333 }, - { url = "https://files.pythonhosted.org/packages/1f/fd/e5b64f7d2c92a41639ffb2ad44a6a82f347787abc0c7df5f49057cf11770/frozenlist-1.7.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:79b2ffbba483f4ed36a0f236ccb85fbb16e670c9238313709638167670ba235f", size = 231724 }, - { url = "https://files.pythonhosted.org/packages/20/fb/03395c0a43a5976af4bf7534759d214405fbbb4c114683f434dfdd3128ef/frozenlist-1.7.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a26f205c9ca5829cbf82bb2a84b5c36f7184c4316617d7ef1b271a56720d6b30", size = 245842 }, - { url = "https://files.pythonhosted.org/packages/d0/15/c01c8e1dffdac5d9803507d824f27aed2ba76b6ed0026fab4d9866e82f1f/frozenlist-1.7.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bcacfad3185a623fa11ea0e0634aac7b691aa925d50a440f39b458e41c561d98", size = 239767 }, - { url = "https://files.pythonhosted.org/packages/14/99/3f4c6fe882c1f5514b6848aa0a69b20cb5e5d8e8f51a339d48c0e9305ed0/frozenlist-1.7.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:72c1b0fe8fe451b34f12dce46445ddf14bd2a5bcad7e324987194dc8e3a74c86", size = 224130 }, - { url = "https://files.pythonhosted.org/packages/4d/83/220a374bd7b2aeba9d0725130665afe11de347d95c3620b9b82cc2fcab97/frozenlist-1.7.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61d1a5baeaac6c0798ff6edfaeaa00e0e412d49946c53fae8d4b8e8b3566c4ae", size = 235301 }, - { url = "https://files.pythonhosted.org/packages/03/3c/3e3390d75334a063181625343e8daab61b77e1b8214802cc4e8a1bb678fc/frozenlist-1.7.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7edf5c043c062462f09b6820de9854bf28cc6cc5b6714b383149745e287181a8", size = 234606 }, - { url = "https://files.pythonhosted.org/packages/23/1e/58232c19608b7a549d72d9903005e2d82488f12554a32de2d5fb59b9b1ba/frozenlist-1.7.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:d50ac7627b3a1bd2dcef6f9da89a772694ec04d9a61b66cf87f7d9446b4a0c31", size = 248372 }, - { url = "https://files.pythonhosted.org/packages/c0/a4/e4a567e01702a88a74ce8a324691e62a629bf47d4f8607f24bf1c7216e7f/frozenlist-1.7.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ce48b2fece5aeb45265bb7a58259f45027db0abff478e3077e12b05b17fb9da7", size = 229860 }, - { url = "https://files.pythonhosted.org/packages/73/a6/63b3374f7d22268b41a9db73d68a8233afa30ed164c46107b33c4d18ecdd/frozenlist-1.7.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:fe2365ae915a1fafd982c146754e1de6ab3478def8a59c86e1f7242d794f97d5", size = 245893 }, - { url = "https://files.pythonhosted.org/packages/6d/eb/d18b3f6e64799a79673c4ba0b45e4cfbe49c240edfd03a68be20002eaeaa/frozenlist-1.7.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:45a6f2fdbd10e074e8814eb98b05292f27bad7d1883afbe009d96abdcf3bc898", size = 246323 }, - { url = "https://files.pythonhosted.org/packages/5a/f5/720f3812e3d06cd89a1d5db9ff6450088b8f5c449dae8ffb2971a44da506/frozenlist-1.7.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:21884e23cffabb157a9dd7e353779077bf5b8f9a58e9b262c6caad2ef5f80a56", size = 233149 }, - { url = "https://files.pythonhosted.org/packages/69/68/03efbf545e217d5db8446acfd4c447c15b7c8cf4dbd4a58403111df9322d/frozenlist-1.7.0-cp311-cp311-win32.whl", hash = "sha256:284d233a8953d7b24f9159b8a3496fc1ddc00f4db99c324bd5fb5f22d8698ea7", size = 39565 }, - { url = "https://files.pythonhosted.org/packages/58/17/fe61124c5c333ae87f09bb67186d65038834a47d974fc10a5fadb4cc5ae1/frozenlist-1.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:387cbfdcde2f2353f19c2f66bbb52406d06ed77519ac7ee21be0232147c2592d", size = 44019 }, - { url = "https://files.pythonhosted.org/packages/ef/a2/c8131383f1e66adad5f6ecfcce383d584ca94055a34d683bbb24ac5f2f1c/frozenlist-1.7.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3dbf9952c4bb0e90e98aec1bd992b3318685005702656bc6f67c1a32b76787f2", size = 81424 }, - { url = "https://files.pythonhosted.org/packages/4c/9d/02754159955088cb52567337d1113f945b9e444c4960771ea90eb73de8db/frozenlist-1.7.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1f5906d3359300b8a9bb194239491122e6cf1444c2efb88865426f170c262cdb", size = 47952 }, - { url = "https://files.pythonhosted.org/packages/01/7a/0046ef1bd6699b40acd2067ed6d6670b4db2f425c56980fa21c982c2a9db/frozenlist-1.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3dabd5a8f84573c8d10d8859a50ea2dec01eea372031929871368c09fa103478", size = 46688 }, - { url = "https://files.pythonhosted.org/packages/d6/a2/a910bafe29c86997363fb4c02069df4ff0b5bc39d33c5198b4e9dd42d8f8/frozenlist-1.7.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa57daa5917f1738064f302bf2626281a1cb01920c32f711fbc7bc36111058a8", size = 243084 }, - { url = "https://files.pythonhosted.org/packages/64/3e/5036af9d5031374c64c387469bfcc3af537fc0f5b1187d83a1cf6fab1639/frozenlist-1.7.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c193dda2b6d49f4c4398962810fa7d7c78f032bf45572b3e04dd5249dff27e08", size = 233524 }, - { url = "https://files.pythonhosted.org/packages/06/39/6a17b7c107a2887e781a48ecf20ad20f1c39d94b2a548c83615b5b879f28/frozenlist-1.7.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfe2b675cf0aaa6d61bf8fbffd3c274b3c9b7b1623beb3809df8a81399a4a9c4", size = 248493 }, - { url = "https://files.pythonhosted.org/packages/be/00/711d1337c7327d88c44d91dd0f556a1c47fb99afc060ae0ef66b4d24793d/frozenlist-1.7.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8fc5d5cda37f62b262405cf9652cf0856839c4be8ee41be0afe8858f17f4c94b", size = 244116 }, - { url = "https://files.pythonhosted.org/packages/24/fe/74e6ec0639c115df13d5850e75722750adabdc7de24e37e05a40527ca539/frozenlist-1.7.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0d5ce521d1dd7d620198829b87ea002956e4319002ef0bc8d3e6d045cb4646e", size = 224557 }, - { url = "https://files.pythonhosted.org/packages/8d/db/48421f62a6f77c553575201e89048e97198046b793f4a089c79a6e3268bd/frozenlist-1.7.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:488d0a7d6a0008ca0db273c542098a0fa9e7dfaa7e57f70acef43f32b3f69dca", size = 241820 }, - { url = "https://files.pythonhosted.org/packages/1d/fa/cb4a76bea23047c8462976ea7b7a2bf53997a0ca171302deae9d6dd12096/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:15a7eaba63983d22c54d255b854e8108e7e5f3e89f647fc854bd77a237e767df", size = 236542 }, - { url = "https://files.pythonhosted.org/packages/5d/32/476a4b5cfaa0ec94d3f808f193301debff2ea42288a099afe60757ef6282/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1eaa7e9c6d15df825bf255649e05bd8a74b04a4d2baa1ae46d9c2d00b2ca2cb5", size = 249350 }, - { url = "https://files.pythonhosted.org/packages/8d/ba/9a28042f84a6bf8ea5dbc81cfff8eaef18d78b2a1ad9d51c7bc5b029ad16/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e4389e06714cfa9d47ab87f784a7c5be91d3934cd6e9a7b85beef808297cc025", size = 225093 }, - { url = "https://files.pythonhosted.org/packages/bc/29/3a32959e68f9cf000b04e79ba574527c17e8842e38c91d68214a37455786/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:73bd45e1488c40b63fe5a7df892baf9e2a4d4bb6409a2b3b78ac1c6236178e01", size = 245482 }, - { url = "https://files.pythonhosted.org/packages/80/e8/edf2f9e00da553f07f5fa165325cfc302dead715cab6ac8336a5f3d0adc2/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:99886d98e1643269760e5fe0df31e5ae7050788dd288947f7f007209b8c33f08", size = 249590 }, - { url = "https://files.pythonhosted.org/packages/1c/80/9a0eb48b944050f94cc51ee1c413eb14a39543cc4f760ed12657a5a3c45a/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:290a172aae5a4c278c6da8a96222e6337744cd9c77313efe33d5670b9f65fc43", size = 237785 }, - { url = "https://files.pythonhosted.org/packages/f3/74/87601e0fb0369b7a2baf404ea921769c53b7ae00dee7dcfe5162c8c6dbf0/frozenlist-1.7.0-cp312-cp312-win32.whl", hash = "sha256:426c7bc70e07cfebc178bc4c2bf2d861d720c4fff172181eeb4a4c41d4ca2ad3", size = 39487 }, - { url = "https://files.pythonhosted.org/packages/0b/15/c026e9a9fc17585a9d461f65d8593d281fedf55fbf7eb53f16c6df2392f9/frozenlist-1.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:563b72efe5da92e02eb68c59cb37205457c977aa7a449ed1b37e6939e5c47c6a", size = 43874 }, - { url = "https://files.pythonhosted.org/packages/24/90/6b2cebdabdbd50367273c20ff6b57a3dfa89bd0762de02c3a1eb42cb6462/frozenlist-1.7.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee80eeda5e2a4e660651370ebffd1286542b67e268aa1ac8d6dbe973120ef7ee", size = 79791 }, - { url = "https://files.pythonhosted.org/packages/83/2e/5b70b6a3325363293fe5fc3ae74cdcbc3e996c2a11dde2fd9f1fb0776d19/frozenlist-1.7.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d1a81c85417b914139e3a9b995d4a1c84559afc839a93cf2cb7f15e6e5f6ed2d", size = 47165 }, - { url = "https://files.pythonhosted.org/packages/f4/25/a0895c99270ca6966110f4ad98e87e5662eab416a17e7fd53c364bf8b954/frozenlist-1.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cbb65198a9132ebc334f237d7b0df163e4de83fb4f2bdfe46c1e654bdb0c5d43", size = 45881 }, - { url = "https://files.pythonhosted.org/packages/19/7c/71bb0bbe0832793c601fff68cd0cf6143753d0c667f9aec93d3c323f4b55/frozenlist-1.7.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dab46c723eeb2c255a64f9dc05b8dd601fde66d6b19cdb82b2e09cc6ff8d8b5d", size = 232409 }, - { url = "https://files.pythonhosted.org/packages/c0/45/ed2798718910fe6eb3ba574082aaceff4528e6323f9a8570be0f7028d8e9/frozenlist-1.7.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6aeac207a759d0dedd2e40745575ae32ab30926ff4fa49b1635def65806fddee", size = 225132 }, - { url = "https://files.pythonhosted.org/packages/ba/e2/8417ae0f8eacb1d071d4950f32f229aa6bf68ab69aab797b72a07ea68d4f/frozenlist-1.7.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bd8c4e58ad14b4fa7802b8be49d47993182fdd4023393899632c88fd8cd994eb", size = 237638 }, - { url = "https://files.pythonhosted.org/packages/f8/b7/2ace5450ce85f2af05a871b8c8719b341294775a0a6c5585d5e6170f2ce7/frozenlist-1.7.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04fb24d104f425da3540ed83cbfc31388a586a7696142004c577fa61c6298c3f", size = 233539 }, - { url = "https://files.pythonhosted.org/packages/46/b9/6989292c5539553dba63f3c83dc4598186ab2888f67c0dc1d917e6887db6/frozenlist-1.7.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6a5c505156368e4ea6b53b5ac23c92d7edc864537ff911d2fb24c140bb175e60", size = 215646 }, - { url = "https://files.pythonhosted.org/packages/72/31/bc8c5c99c7818293458fe745dab4fd5730ff49697ccc82b554eb69f16a24/frozenlist-1.7.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8bd7eb96a675f18aa5c553eb7ddc24a43c8c18f22e1f9925528128c052cdbe00", size = 232233 }, - { url = "https://files.pythonhosted.org/packages/59/52/460db4d7ba0811b9ccb85af996019f5d70831f2f5f255f7cc61f86199795/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:05579bf020096fe05a764f1f84cd104a12f78eaab68842d036772dc6d4870b4b", size = 227996 }, - { url = "https://files.pythonhosted.org/packages/ba/c9/f4b39e904c03927b7ecf891804fd3b4df3db29b9e487c6418e37988d6e9d/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:376b6222d114e97eeec13d46c486facd41d4f43bab626b7c3f6a8b4e81a5192c", size = 242280 }, - { url = "https://files.pythonhosted.org/packages/b8/33/3f8d6ced42f162d743e3517781566b8481322be321b486d9d262adf70bfb/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:0aa7e176ebe115379b5b1c95b4096fb1c17cce0847402e227e712c27bdb5a949", size = 217717 }, - { url = "https://files.pythonhosted.org/packages/3e/e8/ad683e75da6ccef50d0ab0c2b2324b32f84fc88ceee778ed79b8e2d2fe2e/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3fbba20e662b9c2130dc771e332a99eff5da078b2b2648153a40669a6d0e36ca", size = 236644 }, - { url = "https://files.pythonhosted.org/packages/b2/14/8d19ccdd3799310722195a72ac94ddc677541fb4bef4091d8e7775752360/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:f3f4410a0a601d349dd406b5713fec59b4cee7e71678d5b17edda7f4655a940b", size = 238879 }, - { url = "https://files.pythonhosted.org/packages/ce/13/c12bf657494c2fd1079a48b2db49fa4196325909249a52d8f09bc9123fd7/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e2cdfaaec6a2f9327bf43c933c0319a7c429058e8537c508964a133dffee412e", size = 232502 }, - { url = "https://files.pythonhosted.org/packages/d7/8b/e7f9dfde869825489382bc0d512c15e96d3964180c9499efcec72e85db7e/frozenlist-1.7.0-cp313-cp313-win32.whl", hash = "sha256:5fc4df05a6591c7768459caba1b342d9ec23fa16195e744939ba5914596ae3e1", size = 39169 }, - { url = "https://files.pythonhosted.org/packages/35/89/a487a98d94205d85745080a37860ff5744b9820a2c9acbcdd9440bfddf98/frozenlist-1.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:52109052b9791a3e6b5d1b65f4b909703984b770694d3eb64fad124c835d7cba", size = 43219 }, - { url = "https://files.pythonhosted.org/packages/56/d5/5c4cf2319a49eddd9dd7145e66c4866bdc6f3dbc67ca3d59685149c11e0d/frozenlist-1.7.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a6f86e4193bb0e235ef6ce3dde5cbabed887e0b11f516ce8a0f4d3b33078ec2d", size = 84345 }, - { url = "https://files.pythonhosted.org/packages/a4/7d/ec2c1e1dc16b85bc9d526009961953df9cec8481b6886debb36ec9107799/frozenlist-1.7.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:82d664628865abeb32d90ae497fb93df398a69bb3434463d172b80fc25b0dd7d", size = 48880 }, - { url = "https://files.pythonhosted.org/packages/69/86/f9596807b03de126e11e7d42ac91e3d0b19a6599c714a1989a4e85eeefc4/frozenlist-1.7.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:912a7e8375a1c9a68325a902f3953191b7b292aa3c3fb0d71a216221deca460b", size = 48498 }, - { url = "https://files.pythonhosted.org/packages/5e/cb/df6de220f5036001005f2d726b789b2c0b65f2363b104bbc16f5be8084f8/frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9537c2777167488d539bc5de2ad262efc44388230e5118868e172dd4a552b146", size = 292296 }, - { url = "https://files.pythonhosted.org/packages/83/1f/de84c642f17c8f851a2905cee2dae401e5e0daca9b5ef121e120e19aa825/frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:f34560fb1b4c3e30ba35fa9a13894ba39e5acfc5f60f57d8accde65f46cc5e74", size = 273103 }, - { url = "https://files.pythonhosted.org/packages/88/3c/c840bfa474ba3fa13c772b93070893c6e9d5c0350885760376cbe3b6c1b3/frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:acd03d224b0175f5a850edc104ac19040d35419eddad04e7cf2d5986d98427f1", size = 292869 }, - { url = "https://files.pythonhosted.org/packages/a6/1c/3efa6e7d5a39a1d5ef0abeb51c48fb657765794a46cf124e5aca2c7a592c/frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2038310bc582f3d6a09b3816ab01737d60bf7b1ec70f5356b09e84fb7408ab1", size = 291467 }, - { url = "https://files.pythonhosted.org/packages/4f/00/d5c5e09d4922c395e2f2f6b79b9a20dab4b67daaf78ab92e7729341f61f6/frozenlist-1.7.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b8c05e4c8e5f36e5e088caa1bf78a687528f83c043706640a92cb76cd6999384", size = 266028 }, - { url = "https://files.pythonhosted.org/packages/4e/27/72765be905619dfde25a7f33813ac0341eb6b076abede17a2e3fbfade0cb/frozenlist-1.7.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:765bb588c86e47d0b68f23c1bee323d4b703218037765dcf3f25c838c6fecceb", size = 284294 }, - { url = "https://files.pythonhosted.org/packages/88/67/c94103a23001b17808eb7dd1200c156bb69fb68e63fcf0693dde4cd6228c/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:32dc2e08c67d86d0969714dd484fd60ff08ff81d1a1e40a77dd34a387e6ebc0c", size = 281898 }, - { url = "https://files.pythonhosted.org/packages/42/34/a3e2c00c00f9e2a9db5653bca3fec306349e71aff14ae45ecc6d0951dd24/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:c0303e597eb5a5321b4de9c68e9845ac8f290d2ab3f3e2c864437d3c5a30cd65", size = 290465 }, - { url = "https://files.pythonhosted.org/packages/bb/73/f89b7fbce8b0b0c095d82b008afd0590f71ccb3dee6eee41791cf8cd25fd/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:a47f2abb4e29b3a8d0b530f7c3598badc6b134562b1a5caee867f7c62fee51e3", size = 266385 }, - { url = "https://files.pythonhosted.org/packages/cd/45/e365fdb554159462ca12df54bc59bfa7a9a273ecc21e99e72e597564d1ae/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:3d688126c242a6fabbd92e02633414d40f50bb6002fa4cf995a1d18051525657", size = 288771 }, - { url = "https://files.pythonhosted.org/packages/00/11/47b6117002a0e904f004d70ec5194fe9144f117c33c851e3d51c765962d0/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:4e7e9652b3d367c7bd449a727dc79d5043f48b88d0cbfd4f9f1060cf2b414104", size = 288206 }, - { url = "https://files.pythonhosted.org/packages/40/37/5f9f3c3fd7f7746082ec67bcdc204db72dad081f4f83a503d33220a92973/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:1a85e345b4c43db8b842cab1feb41be5cc0b10a1830e6295b69d7310f99becaf", size = 282620 }, - { url = "https://files.pythonhosted.org/packages/0b/31/8fbc5af2d183bff20f21aa743b4088eac4445d2bb1cdece449ae80e4e2d1/frozenlist-1.7.0-cp313-cp313t-win32.whl", hash = "sha256:3a14027124ddb70dfcee5148979998066897e79f89f64b13328595c4bdf77c81", size = 43059 }, - { url = "https://files.pythonhosted.org/packages/bb/ed/41956f52105b8dbc26e457c5705340c67c8cc2b79f394b79bffc09d0e938/frozenlist-1.7.0-cp313-cp313t-win_amd64.whl", hash = "sha256:3bf8010d71d4507775f658e9823210b7427be36625b387221642725b515dcf3e", size = 47516 }, - { url = "https://files.pythonhosted.org/packages/ee/45/b82e3c16be2182bff01179db177fe144d58b5dc787a7d4492c6ed8b9317f/frozenlist-1.7.0-py3-none-any.whl", hash = "sha256:9a5af342e34f7e97caf8c995864c7a396418ae2859cc6fdf1b1073020d516a7e", size = 13106 }, +sdist = { url = "https://files.pythonhosted.org/packages/79/b1/b64018016eeb087db503b038296fd782586432b9c077fc5c7839e9cb6ef6/frozenlist-1.7.0.tar.gz", hash = "sha256:2e310d81923c2437ea8670467121cc3e9b0f76d3043cc1d2331d56c7fb7a3a8f", size = 45078, upload-time = "2025-06-09T23:02:35.538Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/af/36/0da0a49409f6b47cc2d060dc8c9040b897b5902a8a4e37d9bc1deb11f680/frozenlist-1.7.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cc4df77d638aa2ed703b878dd093725b72a824c3c546c076e8fdf276f78ee84a", size = 81304, upload-time = "2025-06-09T22:59:46.226Z" }, + { url = "https://files.pythonhosted.org/packages/77/f0/77c11d13d39513b298e267b22eb6cb559c103d56f155aa9a49097221f0b6/frozenlist-1.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:716a9973a2cc963160394f701964fe25012600f3d311f60c790400b00e568b61", size = 47735, upload-time = "2025-06-09T22:59:48.133Z" }, + { url = "https://files.pythonhosted.org/packages/37/12/9d07fa18971a44150593de56b2f2947c46604819976784bcf6ea0d5db43b/frozenlist-1.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a0fd1bad056a3600047fb9462cff4c5322cebc59ebf5d0a3725e0ee78955001d", size = 46775, upload-time = "2025-06-09T22:59:49.564Z" }, + { url = "https://files.pythonhosted.org/packages/70/34/f73539227e06288fcd1f8a76853e755b2b48bca6747e99e283111c18bcd4/frozenlist-1.7.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3789ebc19cb811163e70fe2bd354cea097254ce6e707ae42e56f45e31e96cb8e", size = 224644, upload-time = "2025-06-09T22:59:51.35Z" }, + { url = "https://files.pythonhosted.org/packages/fb/68/c1d9c2f4a6e438e14613bad0f2973567586610cc22dcb1e1241da71de9d3/frozenlist-1.7.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:af369aa35ee34f132fcfad5be45fbfcde0e3a5f6a1ec0712857f286b7d20cca9", size = 222125, upload-time = "2025-06-09T22:59:52.884Z" }, + { url = "https://files.pythonhosted.org/packages/b9/d0/98e8f9a515228d708344d7c6986752be3e3192d1795f748c24bcf154ad99/frozenlist-1.7.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac64b6478722eeb7a3313d494f8342ef3478dff539d17002f849101b212ef97c", size = 233455, upload-time = "2025-06-09T22:59:54.74Z" }, + { url = "https://files.pythonhosted.org/packages/79/df/8a11bcec5600557f40338407d3e5bea80376ed1c01a6c0910fcfdc4b8993/frozenlist-1.7.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f89f65d85774f1797239693cef07ad4c97fdd0639544bad9ac4b869782eb1981", size = 227339, upload-time = "2025-06-09T22:59:56.187Z" }, + { url = "https://files.pythonhosted.org/packages/50/82/41cb97d9c9a5ff94438c63cc343eb7980dac4187eb625a51bdfdb7707314/frozenlist-1.7.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1073557c941395fdfcfac13eb2456cb8aad89f9de27bae29fabca8e563b12615", size = 212969, upload-time = "2025-06-09T22:59:57.604Z" }, + { url = "https://files.pythonhosted.org/packages/13/47/f9179ee5ee4f55629e4f28c660b3fdf2775c8bfde8f9c53f2de2d93f52a9/frozenlist-1.7.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ed8d2fa095aae4bdc7fdd80351009a48d286635edffee66bf865e37a9125c50", size = 222862, upload-time = "2025-06-09T22:59:59.498Z" }, + { url = "https://files.pythonhosted.org/packages/1a/52/df81e41ec6b953902c8b7e3a83bee48b195cb0e5ec2eabae5d8330c78038/frozenlist-1.7.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:24c34bea555fe42d9f928ba0a740c553088500377448febecaa82cc3e88aa1fa", size = 222492, upload-time = "2025-06-09T23:00:01.026Z" }, + { url = "https://files.pythonhosted.org/packages/84/17/30d6ea87fa95a9408245a948604b82c1a4b8b3e153cea596421a2aef2754/frozenlist-1.7.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:69cac419ac6a6baad202c85aaf467b65ac860ac2e7f2ac1686dc40dbb52f6577", size = 238250, upload-time = "2025-06-09T23:00:03.401Z" }, + { url = "https://files.pythonhosted.org/packages/8f/00/ecbeb51669e3c3df76cf2ddd66ae3e48345ec213a55e3887d216eb4fbab3/frozenlist-1.7.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:960d67d0611f4c87da7e2ae2eacf7ea81a5be967861e0c63cf205215afbfac59", size = 218720, upload-time = "2025-06-09T23:00:05.282Z" }, + { url = "https://files.pythonhosted.org/packages/1a/c0/c224ce0e0eb31cc57f67742071bb470ba8246623c1823a7530be0e76164c/frozenlist-1.7.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:41be2964bd4b15bf575e5daee5a5ce7ed3115320fb3c2b71fca05582ffa4dc9e", size = 232585, upload-time = "2025-06-09T23:00:07.962Z" }, + { url = "https://files.pythonhosted.org/packages/55/3c/34cb694abf532f31f365106deebdeac9e45c19304d83cf7d51ebbb4ca4d1/frozenlist-1.7.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:46d84d49e00c9429238a7ce02dc0be8f6d7cd0cd405abd1bebdc991bf27c15bd", size = 234248, upload-time = "2025-06-09T23:00:09.428Z" }, + { url = "https://files.pythonhosted.org/packages/98/c0/2052d8b6cecda2e70bd81299e3512fa332abb6dcd2969b9c80dfcdddbf75/frozenlist-1.7.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:15900082e886edb37480335d9d518cec978afc69ccbc30bd18610b7c1b22a718", size = 221621, upload-time = "2025-06-09T23:00:11.32Z" }, + { url = "https://files.pythonhosted.org/packages/c5/bf/7dcebae315436903b1d98ffb791a09d674c88480c158aa171958a3ac07f0/frozenlist-1.7.0-cp310-cp310-win32.whl", hash = "sha256:400ddd24ab4e55014bba442d917203c73b2846391dd42ca5e38ff52bb18c3c5e", size = 39578, upload-time = "2025-06-09T23:00:13.526Z" }, + { url = "https://files.pythonhosted.org/packages/8f/5f/f69818f017fa9a3d24d1ae39763e29b7f60a59e46d5f91b9c6b21622f4cd/frozenlist-1.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:6eb93efb8101ef39d32d50bce242c84bcbddb4f7e9febfa7b524532a239b4464", size = 43830, upload-time = "2025-06-09T23:00:14.98Z" }, + { url = "https://files.pythonhosted.org/packages/34/7e/803dde33760128acd393a27eb002f2020ddb8d99d30a44bfbaab31c5f08a/frozenlist-1.7.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:aa51e147a66b2d74de1e6e2cf5921890de6b0f4820b257465101d7f37b49fb5a", size = 82251, upload-time = "2025-06-09T23:00:16.279Z" }, + { url = "https://files.pythonhosted.org/packages/75/a9/9c2c5760b6ba45eae11334db454c189d43d34a4c0b489feb2175e5e64277/frozenlist-1.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9b35db7ce1cd71d36ba24f80f0c9e7cff73a28d7a74e91fe83e23d27c7828750", size = 48183, upload-time = "2025-06-09T23:00:17.698Z" }, + { url = "https://files.pythonhosted.org/packages/47/be/4038e2d869f8a2da165f35a6befb9158c259819be22eeaf9c9a8f6a87771/frozenlist-1.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:34a69a85e34ff37791e94542065c8416c1afbf820b68f720452f636d5fb990cd", size = 47107, upload-time = "2025-06-09T23:00:18.952Z" }, + { url = "https://files.pythonhosted.org/packages/79/26/85314b8a83187c76a37183ceed886381a5f992975786f883472fcb6dc5f2/frozenlist-1.7.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a646531fa8d82c87fe4bb2e596f23173caec9185bfbca5d583b4ccfb95183e2", size = 237333, upload-time = "2025-06-09T23:00:20.275Z" }, + { url = "https://files.pythonhosted.org/packages/1f/fd/e5b64f7d2c92a41639ffb2ad44a6a82f347787abc0c7df5f49057cf11770/frozenlist-1.7.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:79b2ffbba483f4ed36a0f236ccb85fbb16e670c9238313709638167670ba235f", size = 231724, upload-time = "2025-06-09T23:00:21.705Z" }, + { url = "https://files.pythonhosted.org/packages/20/fb/03395c0a43a5976af4bf7534759d214405fbbb4c114683f434dfdd3128ef/frozenlist-1.7.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a26f205c9ca5829cbf82bb2a84b5c36f7184c4316617d7ef1b271a56720d6b30", size = 245842, upload-time = "2025-06-09T23:00:23.148Z" }, + { url = "https://files.pythonhosted.org/packages/d0/15/c01c8e1dffdac5d9803507d824f27aed2ba76b6ed0026fab4d9866e82f1f/frozenlist-1.7.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bcacfad3185a623fa11ea0e0634aac7b691aa925d50a440f39b458e41c561d98", size = 239767, upload-time = "2025-06-09T23:00:25.103Z" }, + { url = "https://files.pythonhosted.org/packages/14/99/3f4c6fe882c1f5514b6848aa0a69b20cb5e5d8e8f51a339d48c0e9305ed0/frozenlist-1.7.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:72c1b0fe8fe451b34f12dce46445ddf14bd2a5bcad7e324987194dc8e3a74c86", size = 224130, upload-time = "2025-06-09T23:00:27.061Z" }, + { url = "https://files.pythonhosted.org/packages/4d/83/220a374bd7b2aeba9d0725130665afe11de347d95c3620b9b82cc2fcab97/frozenlist-1.7.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61d1a5baeaac6c0798ff6edfaeaa00e0e412d49946c53fae8d4b8e8b3566c4ae", size = 235301, upload-time = "2025-06-09T23:00:29.02Z" }, + { url = "https://files.pythonhosted.org/packages/03/3c/3e3390d75334a063181625343e8daab61b77e1b8214802cc4e8a1bb678fc/frozenlist-1.7.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7edf5c043c062462f09b6820de9854bf28cc6cc5b6714b383149745e287181a8", size = 234606, upload-time = "2025-06-09T23:00:30.514Z" }, + { url = "https://files.pythonhosted.org/packages/23/1e/58232c19608b7a549d72d9903005e2d82488f12554a32de2d5fb59b9b1ba/frozenlist-1.7.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:d50ac7627b3a1bd2dcef6f9da89a772694ec04d9a61b66cf87f7d9446b4a0c31", size = 248372, upload-time = "2025-06-09T23:00:31.966Z" }, + { url = "https://files.pythonhosted.org/packages/c0/a4/e4a567e01702a88a74ce8a324691e62a629bf47d4f8607f24bf1c7216e7f/frozenlist-1.7.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ce48b2fece5aeb45265bb7a58259f45027db0abff478e3077e12b05b17fb9da7", size = 229860, upload-time = "2025-06-09T23:00:33.375Z" }, + { url = "https://files.pythonhosted.org/packages/73/a6/63b3374f7d22268b41a9db73d68a8233afa30ed164c46107b33c4d18ecdd/frozenlist-1.7.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:fe2365ae915a1fafd982c146754e1de6ab3478def8a59c86e1f7242d794f97d5", size = 245893, upload-time = "2025-06-09T23:00:35.002Z" }, + { url = "https://files.pythonhosted.org/packages/6d/eb/d18b3f6e64799a79673c4ba0b45e4cfbe49c240edfd03a68be20002eaeaa/frozenlist-1.7.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:45a6f2fdbd10e074e8814eb98b05292f27bad7d1883afbe009d96abdcf3bc898", size = 246323, upload-time = "2025-06-09T23:00:36.468Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f5/720f3812e3d06cd89a1d5db9ff6450088b8f5c449dae8ffb2971a44da506/frozenlist-1.7.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:21884e23cffabb157a9dd7e353779077bf5b8f9a58e9b262c6caad2ef5f80a56", size = 233149, upload-time = "2025-06-09T23:00:37.963Z" }, + { url = "https://files.pythonhosted.org/packages/69/68/03efbf545e217d5db8446acfd4c447c15b7c8cf4dbd4a58403111df9322d/frozenlist-1.7.0-cp311-cp311-win32.whl", hash = "sha256:284d233a8953d7b24f9159b8a3496fc1ddc00f4db99c324bd5fb5f22d8698ea7", size = 39565, upload-time = "2025-06-09T23:00:39.753Z" }, + { url = "https://files.pythonhosted.org/packages/58/17/fe61124c5c333ae87f09bb67186d65038834a47d974fc10a5fadb4cc5ae1/frozenlist-1.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:387cbfdcde2f2353f19c2f66bbb52406d06ed77519ac7ee21be0232147c2592d", size = 44019, upload-time = "2025-06-09T23:00:40.988Z" }, + { url = "https://files.pythonhosted.org/packages/ef/a2/c8131383f1e66adad5f6ecfcce383d584ca94055a34d683bbb24ac5f2f1c/frozenlist-1.7.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3dbf9952c4bb0e90e98aec1bd992b3318685005702656bc6f67c1a32b76787f2", size = 81424, upload-time = "2025-06-09T23:00:42.24Z" }, + { url = "https://files.pythonhosted.org/packages/4c/9d/02754159955088cb52567337d1113f945b9e444c4960771ea90eb73de8db/frozenlist-1.7.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1f5906d3359300b8a9bb194239491122e6cf1444c2efb88865426f170c262cdb", size = 47952, upload-time = "2025-06-09T23:00:43.481Z" }, + { url = "https://files.pythonhosted.org/packages/01/7a/0046ef1bd6699b40acd2067ed6d6670b4db2f425c56980fa21c982c2a9db/frozenlist-1.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3dabd5a8f84573c8d10d8859a50ea2dec01eea372031929871368c09fa103478", size = 46688, upload-time = "2025-06-09T23:00:44.793Z" }, + { url = "https://files.pythonhosted.org/packages/d6/a2/a910bafe29c86997363fb4c02069df4ff0b5bc39d33c5198b4e9dd42d8f8/frozenlist-1.7.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa57daa5917f1738064f302bf2626281a1cb01920c32f711fbc7bc36111058a8", size = 243084, upload-time = "2025-06-09T23:00:46.125Z" }, + { url = "https://files.pythonhosted.org/packages/64/3e/5036af9d5031374c64c387469bfcc3af537fc0f5b1187d83a1cf6fab1639/frozenlist-1.7.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c193dda2b6d49f4c4398962810fa7d7c78f032bf45572b3e04dd5249dff27e08", size = 233524, upload-time = "2025-06-09T23:00:47.73Z" }, + { url = "https://files.pythonhosted.org/packages/06/39/6a17b7c107a2887e781a48ecf20ad20f1c39d94b2a548c83615b5b879f28/frozenlist-1.7.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfe2b675cf0aaa6d61bf8fbffd3c274b3c9b7b1623beb3809df8a81399a4a9c4", size = 248493, upload-time = "2025-06-09T23:00:49.742Z" }, + { url = "https://files.pythonhosted.org/packages/be/00/711d1337c7327d88c44d91dd0f556a1c47fb99afc060ae0ef66b4d24793d/frozenlist-1.7.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8fc5d5cda37f62b262405cf9652cf0856839c4be8ee41be0afe8858f17f4c94b", size = 244116, upload-time = "2025-06-09T23:00:51.352Z" }, + { url = "https://files.pythonhosted.org/packages/24/fe/74e6ec0639c115df13d5850e75722750adabdc7de24e37e05a40527ca539/frozenlist-1.7.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0d5ce521d1dd7d620198829b87ea002956e4319002ef0bc8d3e6d045cb4646e", size = 224557, upload-time = "2025-06-09T23:00:52.855Z" }, + { url = "https://files.pythonhosted.org/packages/8d/db/48421f62a6f77c553575201e89048e97198046b793f4a089c79a6e3268bd/frozenlist-1.7.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:488d0a7d6a0008ca0db273c542098a0fa9e7dfaa7e57f70acef43f32b3f69dca", size = 241820, upload-time = "2025-06-09T23:00:54.43Z" }, + { url = "https://files.pythonhosted.org/packages/1d/fa/cb4a76bea23047c8462976ea7b7a2bf53997a0ca171302deae9d6dd12096/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:15a7eaba63983d22c54d255b854e8108e7e5f3e89f647fc854bd77a237e767df", size = 236542, upload-time = "2025-06-09T23:00:56.409Z" }, + { url = "https://files.pythonhosted.org/packages/5d/32/476a4b5cfaa0ec94d3f808f193301debff2ea42288a099afe60757ef6282/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1eaa7e9c6d15df825bf255649e05bd8a74b04a4d2baa1ae46d9c2d00b2ca2cb5", size = 249350, upload-time = "2025-06-09T23:00:58.468Z" }, + { url = "https://files.pythonhosted.org/packages/8d/ba/9a28042f84a6bf8ea5dbc81cfff8eaef18d78b2a1ad9d51c7bc5b029ad16/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e4389e06714cfa9d47ab87f784a7c5be91d3934cd6e9a7b85beef808297cc025", size = 225093, upload-time = "2025-06-09T23:01:00.015Z" }, + { url = "https://files.pythonhosted.org/packages/bc/29/3a32959e68f9cf000b04e79ba574527c17e8842e38c91d68214a37455786/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:73bd45e1488c40b63fe5a7df892baf9e2a4d4bb6409a2b3b78ac1c6236178e01", size = 245482, upload-time = "2025-06-09T23:01:01.474Z" }, + { url = "https://files.pythonhosted.org/packages/80/e8/edf2f9e00da553f07f5fa165325cfc302dead715cab6ac8336a5f3d0adc2/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:99886d98e1643269760e5fe0df31e5ae7050788dd288947f7f007209b8c33f08", size = 249590, upload-time = "2025-06-09T23:01:02.961Z" }, + { url = "https://files.pythonhosted.org/packages/1c/80/9a0eb48b944050f94cc51ee1c413eb14a39543cc4f760ed12657a5a3c45a/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:290a172aae5a4c278c6da8a96222e6337744cd9c77313efe33d5670b9f65fc43", size = 237785, upload-time = "2025-06-09T23:01:05.095Z" }, + { url = "https://files.pythonhosted.org/packages/f3/74/87601e0fb0369b7a2baf404ea921769c53b7ae00dee7dcfe5162c8c6dbf0/frozenlist-1.7.0-cp312-cp312-win32.whl", hash = "sha256:426c7bc70e07cfebc178bc4c2bf2d861d720c4fff172181eeb4a4c41d4ca2ad3", size = 39487, upload-time = "2025-06-09T23:01:06.54Z" }, + { url = "https://files.pythonhosted.org/packages/0b/15/c026e9a9fc17585a9d461f65d8593d281fedf55fbf7eb53f16c6df2392f9/frozenlist-1.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:563b72efe5da92e02eb68c59cb37205457c977aa7a449ed1b37e6939e5c47c6a", size = 43874, upload-time = "2025-06-09T23:01:07.752Z" }, + { url = "https://files.pythonhosted.org/packages/24/90/6b2cebdabdbd50367273c20ff6b57a3dfa89bd0762de02c3a1eb42cb6462/frozenlist-1.7.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee80eeda5e2a4e660651370ebffd1286542b67e268aa1ac8d6dbe973120ef7ee", size = 79791, upload-time = "2025-06-09T23:01:09.368Z" }, + { url = "https://files.pythonhosted.org/packages/83/2e/5b70b6a3325363293fe5fc3ae74cdcbc3e996c2a11dde2fd9f1fb0776d19/frozenlist-1.7.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d1a81c85417b914139e3a9b995d4a1c84559afc839a93cf2cb7f15e6e5f6ed2d", size = 47165, upload-time = "2025-06-09T23:01:10.653Z" }, + { url = "https://files.pythonhosted.org/packages/f4/25/a0895c99270ca6966110f4ad98e87e5662eab416a17e7fd53c364bf8b954/frozenlist-1.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cbb65198a9132ebc334f237d7b0df163e4de83fb4f2bdfe46c1e654bdb0c5d43", size = 45881, upload-time = "2025-06-09T23:01:12.296Z" }, + { url = "https://files.pythonhosted.org/packages/19/7c/71bb0bbe0832793c601fff68cd0cf6143753d0c667f9aec93d3c323f4b55/frozenlist-1.7.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dab46c723eeb2c255a64f9dc05b8dd601fde66d6b19cdb82b2e09cc6ff8d8b5d", size = 232409, upload-time = "2025-06-09T23:01:13.641Z" }, + { url = "https://files.pythonhosted.org/packages/c0/45/ed2798718910fe6eb3ba574082aaceff4528e6323f9a8570be0f7028d8e9/frozenlist-1.7.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6aeac207a759d0dedd2e40745575ae32ab30926ff4fa49b1635def65806fddee", size = 225132, upload-time = "2025-06-09T23:01:15.264Z" }, + { url = "https://files.pythonhosted.org/packages/ba/e2/8417ae0f8eacb1d071d4950f32f229aa6bf68ab69aab797b72a07ea68d4f/frozenlist-1.7.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bd8c4e58ad14b4fa7802b8be49d47993182fdd4023393899632c88fd8cd994eb", size = 237638, upload-time = "2025-06-09T23:01:16.752Z" }, + { url = "https://files.pythonhosted.org/packages/f8/b7/2ace5450ce85f2af05a871b8c8719b341294775a0a6c5585d5e6170f2ce7/frozenlist-1.7.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04fb24d104f425da3540ed83cbfc31388a586a7696142004c577fa61c6298c3f", size = 233539, upload-time = "2025-06-09T23:01:18.202Z" }, + { url = "https://files.pythonhosted.org/packages/46/b9/6989292c5539553dba63f3c83dc4598186ab2888f67c0dc1d917e6887db6/frozenlist-1.7.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6a5c505156368e4ea6b53b5ac23c92d7edc864537ff911d2fb24c140bb175e60", size = 215646, upload-time = "2025-06-09T23:01:19.649Z" }, + { url = "https://files.pythonhosted.org/packages/72/31/bc8c5c99c7818293458fe745dab4fd5730ff49697ccc82b554eb69f16a24/frozenlist-1.7.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8bd7eb96a675f18aa5c553eb7ddc24a43c8c18f22e1f9925528128c052cdbe00", size = 232233, upload-time = "2025-06-09T23:01:21.175Z" }, + { url = "https://files.pythonhosted.org/packages/59/52/460db4d7ba0811b9ccb85af996019f5d70831f2f5f255f7cc61f86199795/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:05579bf020096fe05a764f1f84cd104a12f78eaab68842d036772dc6d4870b4b", size = 227996, upload-time = "2025-06-09T23:01:23.098Z" }, + { url = "https://files.pythonhosted.org/packages/ba/c9/f4b39e904c03927b7ecf891804fd3b4df3db29b9e487c6418e37988d6e9d/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:376b6222d114e97eeec13d46c486facd41d4f43bab626b7c3f6a8b4e81a5192c", size = 242280, upload-time = "2025-06-09T23:01:24.808Z" }, + { url = "https://files.pythonhosted.org/packages/b8/33/3f8d6ced42f162d743e3517781566b8481322be321b486d9d262adf70bfb/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:0aa7e176ebe115379b5b1c95b4096fb1c17cce0847402e227e712c27bdb5a949", size = 217717, upload-time = "2025-06-09T23:01:26.28Z" }, + { url = "https://files.pythonhosted.org/packages/3e/e8/ad683e75da6ccef50d0ab0c2b2324b32f84fc88ceee778ed79b8e2d2fe2e/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3fbba20e662b9c2130dc771e332a99eff5da078b2b2648153a40669a6d0e36ca", size = 236644, upload-time = "2025-06-09T23:01:27.887Z" }, + { url = "https://files.pythonhosted.org/packages/b2/14/8d19ccdd3799310722195a72ac94ddc677541fb4bef4091d8e7775752360/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:f3f4410a0a601d349dd406b5713fec59b4cee7e71678d5b17edda7f4655a940b", size = 238879, upload-time = "2025-06-09T23:01:29.524Z" }, + { url = "https://files.pythonhosted.org/packages/ce/13/c12bf657494c2fd1079a48b2db49fa4196325909249a52d8f09bc9123fd7/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e2cdfaaec6a2f9327bf43c933c0319a7c429058e8537c508964a133dffee412e", size = 232502, upload-time = "2025-06-09T23:01:31.287Z" }, + { url = "https://files.pythonhosted.org/packages/d7/8b/e7f9dfde869825489382bc0d512c15e96d3964180c9499efcec72e85db7e/frozenlist-1.7.0-cp313-cp313-win32.whl", hash = "sha256:5fc4df05a6591c7768459caba1b342d9ec23fa16195e744939ba5914596ae3e1", size = 39169, upload-time = "2025-06-09T23:01:35.503Z" }, + { url = "https://files.pythonhosted.org/packages/35/89/a487a98d94205d85745080a37860ff5744b9820a2c9acbcdd9440bfddf98/frozenlist-1.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:52109052b9791a3e6b5d1b65f4b909703984b770694d3eb64fad124c835d7cba", size = 43219, upload-time = "2025-06-09T23:01:36.784Z" }, + { url = "https://files.pythonhosted.org/packages/56/d5/5c4cf2319a49eddd9dd7145e66c4866bdc6f3dbc67ca3d59685149c11e0d/frozenlist-1.7.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a6f86e4193bb0e235ef6ce3dde5cbabed887e0b11f516ce8a0f4d3b33078ec2d", size = 84345, upload-time = "2025-06-09T23:01:38.295Z" }, + { url = "https://files.pythonhosted.org/packages/a4/7d/ec2c1e1dc16b85bc9d526009961953df9cec8481b6886debb36ec9107799/frozenlist-1.7.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:82d664628865abeb32d90ae497fb93df398a69bb3434463d172b80fc25b0dd7d", size = 48880, upload-time = "2025-06-09T23:01:39.887Z" }, + { url = "https://files.pythonhosted.org/packages/69/86/f9596807b03de126e11e7d42ac91e3d0b19a6599c714a1989a4e85eeefc4/frozenlist-1.7.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:912a7e8375a1c9a68325a902f3953191b7b292aa3c3fb0d71a216221deca460b", size = 48498, upload-time = "2025-06-09T23:01:41.318Z" }, + { url = "https://files.pythonhosted.org/packages/5e/cb/df6de220f5036001005f2d726b789b2c0b65f2363b104bbc16f5be8084f8/frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9537c2777167488d539bc5de2ad262efc44388230e5118868e172dd4a552b146", size = 292296, upload-time = "2025-06-09T23:01:42.685Z" }, + { url = "https://files.pythonhosted.org/packages/83/1f/de84c642f17c8f851a2905cee2dae401e5e0daca9b5ef121e120e19aa825/frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:f34560fb1b4c3e30ba35fa9a13894ba39e5acfc5f60f57d8accde65f46cc5e74", size = 273103, upload-time = "2025-06-09T23:01:44.166Z" }, + { url = "https://files.pythonhosted.org/packages/88/3c/c840bfa474ba3fa13c772b93070893c6e9d5c0350885760376cbe3b6c1b3/frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:acd03d224b0175f5a850edc104ac19040d35419eddad04e7cf2d5986d98427f1", size = 292869, upload-time = "2025-06-09T23:01:45.681Z" }, + { url = "https://files.pythonhosted.org/packages/a6/1c/3efa6e7d5a39a1d5ef0abeb51c48fb657765794a46cf124e5aca2c7a592c/frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2038310bc582f3d6a09b3816ab01737d60bf7b1ec70f5356b09e84fb7408ab1", size = 291467, upload-time = "2025-06-09T23:01:47.234Z" }, + { url = "https://files.pythonhosted.org/packages/4f/00/d5c5e09d4922c395e2f2f6b79b9a20dab4b67daaf78ab92e7729341f61f6/frozenlist-1.7.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b8c05e4c8e5f36e5e088caa1bf78a687528f83c043706640a92cb76cd6999384", size = 266028, upload-time = "2025-06-09T23:01:48.819Z" }, + { url = "https://files.pythonhosted.org/packages/4e/27/72765be905619dfde25a7f33813ac0341eb6b076abede17a2e3fbfade0cb/frozenlist-1.7.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:765bb588c86e47d0b68f23c1bee323d4b703218037765dcf3f25c838c6fecceb", size = 284294, upload-time = "2025-06-09T23:01:50.394Z" }, + { url = "https://files.pythonhosted.org/packages/88/67/c94103a23001b17808eb7dd1200c156bb69fb68e63fcf0693dde4cd6228c/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:32dc2e08c67d86d0969714dd484fd60ff08ff81d1a1e40a77dd34a387e6ebc0c", size = 281898, upload-time = "2025-06-09T23:01:52.234Z" }, + { url = "https://files.pythonhosted.org/packages/42/34/a3e2c00c00f9e2a9db5653bca3fec306349e71aff14ae45ecc6d0951dd24/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:c0303e597eb5a5321b4de9c68e9845ac8f290d2ab3f3e2c864437d3c5a30cd65", size = 290465, upload-time = "2025-06-09T23:01:53.788Z" }, + { url = "https://files.pythonhosted.org/packages/bb/73/f89b7fbce8b0b0c095d82b008afd0590f71ccb3dee6eee41791cf8cd25fd/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:a47f2abb4e29b3a8d0b530f7c3598badc6b134562b1a5caee867f7c62fee51e3", size = 266385, upload-time = "2025-06-09T23:01:55.769Z" }, + { url = "https://files.pythonhosted.org/packages/cd/45/e365fdb554159462ca12df54bc59bfa7a9a273ecc21e99e72e597564d1ae/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:3d688126c242a6fabbd92e02633414d40f50bb6002fa4cf995a1d18051525657", size = 288771, upload-time = "2025-06-09T23:01:57.4Z" }, + { url = "https://files.pythonhosted.org/packages/00/11/47b6117002a0e904f004d70ec5194fe9144f117c33c851e3d51c765962d0/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:4e7e9652b3d367c7bd449a727dc79d5043f48b88d0cbfd4f9f1060cf2b414104", size = 288206, upload-time = "2025-06-09T23:01:58.936Z" }, + { url = "https://files.pythonhosted.org/packages/40/37/5f9f3c3fd7f7746082ec67bcdc204db72dad081f4f83a503d33220a92973/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:1a85e345b4c43db8b842cab1feb41be5cc0b10a1830e6295b69d7310f99becaf", size = 282620, upload-time = "2025-06-09T23:02:00.493Z" }, + { url = "https://files.pythonhosted.org/packages/0b/31/8fbc5af2d183bff20f21aa743b4088eac4445d2bb1cdece449ae80e4e2d1/frozenlist-1.7.0-cp313-cp313t-win32.whl", hash = "sha256:3a14027124ddb70dfcee5148979998066897e79f89f64b13328595c4bdf77c81", size = 43059, upload-time = "2025-06-09T23:02:02.072Z" }, + { url = "https://files.pythonhosted.org/packages/bb/ed/41956f52105b8dbc26e457c5705340c67c8cc2b79f394b79bffc09d0e938/frozenlist-1.7.0-cp313-cp313t-win_amd64.whl", hash = "sha256:3bf8010d71d4507775f658e9823210b7427be36625b387221642725b515dcf3e", size = 47516, upload-time = "2025-06-09T23:02:03.779Z" }, + { url = "https://files.pythonhosted.org/packages/ee/45/b82e3c16be2182bff01179db177fe144d58b5dc787a7d4492c6ed8b9317f/frozenlist-1.7.0-py3-none-any.whl", hash = "sha256:9a5af342e34f7e97caf8c995864c7a396418ae2859cc6fdf1b1073020d516a7e", size = 13106, upload-time = "2025-06-09T23:02:34.204Z" }, ] [[package]] @@ -871,18 +872,18 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/39/24/33db22342cf4a2ea27c9955e6713140fedd51e8b141b5ce5260897020f1a/googleapis_common_protos-1.70.0.tar.gz", hash = "sha256:0e1b44e0ea153e6594f9f394fef15193a68aaaea2d843f83e2742717ca753257", size = 145903 } +sdist = { url = "https://files.pythonhosted.org/packages/39/24/33db22342cf4a2ea27c9955e6713140fedd51e8b141b5ce5260897020f1a/googleapis_common_protos-1.70.0.tar.gz", hash = "sha256:0e1b44e0ea153e6594f9f394fef15193a68aaaea2d843f83e2742717ca753257", size = 145903, upload-time = "2025-04-14T10:17:02.924Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/86/f1/62a193f0227cf15a920390abe675f386dec35f7ae3ffe6da582d3ade42c7/googleapis_common_protos-1.70.0-py3-none-any.whl", hash = "sha256:b8bfcca8c25a2bb253e0e0b0adaf8c00773e5e6af6fd92397576680b807e0fd8", size = 294530 }, + { url = "https://files.pythonhosted.org/packages/86/f1/62a193f0227cf15a920390abe675f386dec35f7ae3ffe6da582d3ade42c7/googleapis_common_protos-1.70.0-py3-none-any.whl", hash = "sha256:b8bfcca8c25a2bb253e0e0b0adaf8c00773e5e6af6fd92397576680b807e0fd8", size = 294530, upload-time = "2025-04-14T10:17:01.271Z" }, ] [[package]] name = "h11" version = "0.16.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250 } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515 }, + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, ] [[package]] @@ -893,52 +894,52 @@ dependencies = [ { name = "certifi" }, { name = "h11" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484 } +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784 }, + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, ] [[package]] name = "httptools" version = "0.7.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b5/46/120a669232c7bdedb9d52d4aeae7e6c7dfe151e99dc70802e2fc7a5e1993/httptools-0.7.1.tar.gz", hash = "sha256:abd72556974f8e7c74a259655924a717a2365b236c882c3f6f8a45fe94703ac9", size = 258961 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/e5/c07e0bcf4ec8db8164e9f6738c048b2e66aabf30e7506f440c4cc6953f60/httptools-0.7.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:11d01b0ff1fe02c4c32d60af61a4d613b74fad069e47e06e9067758c01e9ac78", size = 204531 }, - { url = "https://files.pythonhosted.org/packages/7e/4f/35e3a63f863a659f92ffd92bef131f3e81cf849af26e6435b49bd9f6f751/httptools-0.7.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:84d86c1e5afdc479a6fdabf570be0d3eb791df0ae727e8dbc0259ed1249998d4", size = 109408 }, - { url = "https://files.pythonhosted.org/packages/f5/71/b0a9193641d9e2471ac541d3b1b869538a5fb6419d52fd2669fa9c79e4b8/httptools-0.7.1-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c8c751014e13d88d2be5f5f14fc8b89612fcfa92a9cc480f2bc1598357a23a05", size = 440889 }, - { url = "https://files.pythonhosted.org/packages/eb/d9/2e34811397b76718750fea44658cb0205b84566e895192115252e008b152/httptools-0.7.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:654968cb6b6c77e37b832a9be3d3ecabb243bbe7a0b8f65fbc5b6b04c8fcabed", size = 440460 }, - { url = "https://files.pythonhosted.org/packages/01/3f/a04626ebeacc489866bb4d82362c0657b2262bef381d68310134be7f40bb/httptools-0.7.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b580968316348b474b020edf3988eecd5d6eec4634ee6561e72ae3a2a0e00a8a", size = 425267 }, - { url = "https://files.pythonhosted.org/packages/a5/99/adcd4f66614db627b587627c8ad6f4c55f18881549bab10ecf180562e7b9/httptools-0.7.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d496e2f5245319da9d764296e86c5bb6fcf0cf7a8806d3d000717a889c8c0b7b", size = 424429 }, - { url = "https://files.pythonhosted.org/packages/d5/72/ec8fc904a8fd30ba022dfa85f3bbc64c3c7cd75b669e24242c0658e22f3c/httptools-0.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:cbf8317bfccf0fed3b5680c559d3459cccf1abe9039bfa159e62e391c7270568", size = 86173 }, - { url = "https://files.pythonhosted.org/packages/9c/08/17e07e8d89ab8f343c134616d72eebfe03798835058e2ab579dcc8353c06/httptools-0.7.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:474d3b7ab469fefcca3697a10d11a32ee2b9573250206ba1e50d5980910da657", size = 206521 }, - { url = "https://files.pythonhosted.org/packages/aa/06/c9c1b41ff52f16aee526fd10fbda99fa4787938aa776858ddc4a1ea825ec/httptools-0.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3c3b7366bb6c7b96bd72d0dbe7f7d5eead261361f013be5f6d9590465ea1c70", size = 110375 }, - { url = "https://files.pythonhosted.org/packages/cc/cc/10935db22fda0ee34c76f047590ca0a8bd9de531406a3ccb10a90e12ea21/httptools-0.7.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:379b479408b8747f47f3b253326183d7c009a3936518cdb70db58cffd369d9df", size = 456621 }, - { url = "https://files.pythonhosted.org/packages/0e/84/875382b10d271b0c11aa5d414b44f92f8dd53e9b658aec338a79164fa548/httptools-0.7.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cad6b591a682dcc6cf1397c3900527f9affef1e55a06c4547264796bbd17cf5e", size = 454954 }, - { url = "https://files.pythonhosted.org/packages/30/e1/44f89b280f7e46c0b1b2ccee5737d46b3bb13136383958f20b580a821ca0/httptools-0.7.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:eb844698d11433d2139bbeeb56499102143beb582bd6c194e3ba69c22f25c274", size = 440175 }, - { url = "https://files.pythonhosted.org/packages/6f/7e/b9287763159e700e335028bc1824359dc736fa9b829dacedace91a39b37e/httptools-0.7.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f65744d7a8bdb4bda5e1fa23e4ba16832860606fcc09d674d56e425e991539ec", size = 440310 }, - { url = "https://files.pythonhosted.org/packages/b3/07/5b614f592868e07f5c94b1f301b5e14a21df4e8076215a3bccb830a687d8/httptools-0.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:135fbe974b3718eada677229312e97f3b31f8a9c8ffa3ae6f565bf808d5b6bcb", size = 86875 }, - { url = "https://files.pythonhosted.org/packages/53/7f/403e5d787dc4942316e515e949b0c8a013d84078a915910e9f391ba9b3ed/httptools-0.7.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:38e0c83a2ea9746ebbd643bdfb521b9aa4a91703e2cd705c20443405d2fd16a5", size = 206280 }, - { url = "https://files.pythonhosted.org/packages/2a/0d/7f3fd28e2ce311ccc998c388dd1c53b18120fda3b70ebb022b135dc9839b/httptools-0.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f25bbaf1235e27704f1a7b86cd3304eabc04f569c828101d94a0e605ef7205a5", size = 110004 }, - { url = "https://files.pythonhosted.org/packages/84/a6/b3965e1e146ef5762870bbe76117876ceba51a201e18cc31f5703e454596/httptools-0.7.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2c15f37ef679ab9ecc06bfc4e6e8628c32a8e4b305459de7cf6785acd57e4d03", size = 517655 }, - { url = "https://files.pythonhosted.org/packages/11/7d/71fee6f1844e6fa378f2eddde6c3e41ce3a1fb4b2d81118dd544e3441ec0/httptools-0.7.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7fe6e96090df46b36ccfaf746f03034e5ab723162bc51b0a4cf58305324036f2", size = 511440 }, - { url = "https://files.pythonhosted.org/packages/22/a5/079d216712a4f3ffa24af4a0381b108aa9c45b7a5cc6eb141f81726b1823/httptools-0.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f72fdbae2dbc6e68b8239defb48e6a5937b12218e6ffc2c7846cc37befa84362", size = 495186 }, - { url = "https://files.pythonhosted.org/packages/e9/9e/025ad7b65278745dee3bd0ebf9314934c4592560878308a6121f7f812084/httptools-0.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e99c7b90a29fd82fea9ef57943d501a16f3404d7b9ee81799d41639bdaae412c", size = 499192 }, - { url = "https://files.pythonhosted.org/packages/6d/de/40a8f202b987d43afc4d54689600ff03ce65680ede2f31df348d7f368b8f/httptools-0.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:3e14f530fefa7499334a79b0cf7e7cd2992870eb893526fb097d51b4f2d0f321", size = 86694 }, - { url = "https://files.pythonhosted.org/packages/09/8f/c77b1fcbfd262d422f12da02feb0d218fa228d52485b77b953832105bb90/httptools-0.7.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6babce6cfa2a99545c60bfef8bee0cc0545413cb0018f617c8059a30ad985de3", size = 202889 }, - { url = "https://files.pythonhosted.org/packages/0a/1a/22887f53602feaa066354867bc49a68fc295c2293433177ee90870a7d517/httptools-0.7.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:601b7628de7504077dd3dcb3791c6b8694bbd967148a6d1f01806509254fb1ca", size = 108180 }, - { url = "https://files.pythonhosted.org/packages/32/6a/6aaa91937f0010d288d3d124ca2946d48d60c3a5ee7ca62afe870e3ea011/httptools-0.7.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:04c6c0e6c5fb0739c5b8a9eb046d298650a0ff38cf42537fc372b28dc7e4472c", size = 478596 }, - { url = "https://files.pythonhosted.org/packages/6d/70/023d7ce117993107be88d2cbca566a7c1323ccbaf0af7eabf2064fe356f6/httptools-0.7.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:69d4f9705c405ae3ee83d6a12283dc9feba8cc6aaec671b412917e644ab4fa66", size = 473268 }, - { url = "https://files.pythonhosted.org/packages/32/4d/9dd616c38da088e3f436e9a616e1d0cc66544b8cdac405cc4e81c8679fc7/httptools-0.7.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:44c8f4347d4b31269c8a9205d8a5ee2df5322b09bbbd30f8f862185bb6b05346", size = 455517 }, - { url = "https://files.pythonhosted.org/packages/1d/3a/a6c595c310b7df958e739aae88724e24f9246a514d909547778d776799be/httptools-0.7.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:465275d76db4d554918aba40bf1cbebe324670f3dfc979eaffaa5d108e2ed650", size = 458337 }, - { url = "https://files.pythonhosted.org/packages/fd/82/88e8d6d2c51edc1cc391b6e044c6c435b6aebe97b1abc33db1b0b24cd582/httptools-0.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:322d00c2068d125bd570f7bf78b2d367dad02b919d8581d7476d8b75b294e3e6", size = 85743 }, - { url = "https://files.pythonhosted.org/packages/34/50/9d095fcbb6de2d523e027a2f304d4551855c2f46e0b82befd718b8b20056/httptools-0.7.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:c08fe65728b8d70b6923ce31e3956f859d5e1e8548e6f22ec520a962c6757270", size = 203619 }, - { url = "https://files.pythonhosted.org/packages/07/f0/89720dc5139ae54b03f861b5e2c55a37dba9a5da7d51e1e824a1f343627f/httptools-0.7.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7aea2e3c3953521c3c51106ee11487a910d45586e351202474d45472db7d72d3", size = 108714 }, - { url = "https://files.pythonhosted.org/packages/b3/cb/eea88506f191fb552c11787c23f9a405f4c7b0c5799bf73f2249cd4f5228/httptools-0.7.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0e68b8582f4ea9166be62926077a3334064d422cf08ab87d8b74664f8e9058e1", size = 472909 }, - { url = "https://files.pythonhosted.org/packages/e0/4a/a548bdfae6369c0d078bab5769f7b66f17f1bfaa6fa28f81d6be6959066b/httptools-0.7.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:df091cf961a3be783d6aebae963cc9b71e00d57fa6f149025075217bc6a55a7b", size = 470831 }, - { url = "https://files.pythonhosted.org/packages/4d/31/14df99e1c43bd132eec921c2e7e11cda7852f65619bc0fc5bdc2d0cb126c/httptools-0.7.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f084813239e1eb403ddacd06a30de3d3e09a9b76e7894dcda2b22f8a726e9c60", size = 452631 }, - { url = "https://files.pythonhosted.org/packages/22/d2/b7e131f7be8d854d48cb6d048113c30f9a46dca0c9a8b08fcb3fcd588cdc/httptools-0.7.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7347714368fb2b335e9063bc2b96f2f87a9ceffcd9758ac295f8bbcd3ffbc0ca", size = 452910 }, - { url = "https://files.pythonhosted.org/packages/53/cf/878f3b91e4e6e011eff6d1fa9ca39f7eb17d19c9d7971b04873734112f30/httptools-0.7.1-cp314-cp314-win_amd64.whl", hash = "sha256:cfabda2a5bb85aa2a904ce06d974a3f30fb36cc63d7feaddec05d2050acede96", size = 88205 }, +sdist = { url = "https://files.pythonhosted.org/packages/b5/46/120a669232c7bdedb9d52d4aeae7e6c7dfe151e99dc70802e2fc7a5e1993/httptools-0.7.1.tar.gz", hash = "sha256:abd72556974f8e7c74a259655924a717a2365b236c882c3f6f8a45fe94703ac9", size = 258961, upload-time = "2025-10-10T03:55:08.559Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/e5/c07e0bcf4ec8db8164e9f6738c048b2e66aabf30e7506f440c4cc6953f60/httptools-0.7.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:11d01b0ff1fe02c4c32d60af61a4d613b74fad069e47e06e9067758c01e9ac78", size = 204531, upload-time = "2025-10-10T03:54:20.887Z" }, + { url = "https://files.pythonhosted.org/packages/7e/4f/35e3a63f863a659f92ffd92bef131f3e81cf849af26e6435b49bd9f6f751/httptools-0.7.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:84d86c1e5afdc479a6fdabf570be0d3eb791df0ae727e8dbc0259ed1249998d4", size = 109408, upload-time = "2025-10-10T03:54:22.455Z" }, + { url = "https://files.pythonhosted.org/packages/f5/71/b0a9193641d9e2471ac541d3b1b869538a5fb6419d52fd2669fa9c79e4b8/httptools-0.7.1-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c8c751014e13d88d2be5f5f14fc8b89612fcfa92a9cc480f2bc1598357a23a05", size = 440889, upload-time = "2025-10-10T03:54:23.753Z" }, + { url = "https://files.pythonhosted.org/packages/eb/d9/2e34811397b76718750fea44658cb0205b84566e895192115252e008b152/httptools-0.7.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:654968cb6b6c77e37b832a9be3d3ecabb243bbe7a0b8f65fbc5b6b04c8fcabed", size = 440460, upload-time = "2025-10-10T03:54:25.313Z" }, + { url = "https://files.pythonhosted.org/packages/01/3f/a04626ebeacc489866bb4d82362c0657b2262bef381d68310134be7f40bb/httptools-0.7.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b580968316348b474b020edf3988eecd5d6eec4634ee6561e72ae3a2a0e00a8a", size = 425267, upload-time = "2025-10-10T03:54:26.81Z" }, + { url = "https://files.pythonhosted.org/packages/a5/99/adcd4f66614db627b587627c8ad6f4c55f18881549bab10ecf180562e7b9/httptools-0.7.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d496e2f5245319da9d764296e86c5bb6fcf0cf7a8806d3d000717a889c8c0b7b", size = 424429, upload-time = "2025-10-10T03:54:28.174Z" }, + { url = "https://files.pythonhosted.org/packages/d5/72/ec8fc904a8fd30ba022dfa85f3bbc64c3c7cd75b669e24242c0658e22f3c/httptools-0.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:cbf8317bfccf0fed3b5680c559d3459cccf1abe9039bfa159e62e391c7270568", size = 86173, upload-time = "2025-10-10T03:54:29.5Z" }, + { url = "https://files.pythonhosted.org/packages/9c/08/17e07e8d89ab8f343c134616d72eebfe03798835058e2ab579dcc8353c06/httptools-0.7.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:474d3b7ab469fefcca3697a10d11a32ee2b9573250206ba1e50d5980910da657", size = 206521, upload-time = "2025-10-10T03:54:31.002Z" }, + { url = "https://files.pythonhosted.org/packages/aa/06/c9c1b41ff52f16aee526fd10fbda99fa4787938aa776858ddc4a1ea825ec/httptools-0.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3c3b7366bb6c7b96bd72d0dbe7f7d5eead261361f013be5f6d9590465ea1c70", size = 110375, upload-time = "2025-10-10T03:54:31.941Z" }, + { url = "https://files.pythonhosted.org/packages/cc/cc/10935db22fda0ee34c76f047590ca0a8bd9de531406a3ccb10a90e12ea21/httptools-0.7.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:379b479408b8747f47f3b253326183d7c009a3936518cdb70db58cffd369d9df", size = 456621, upload-time = "2025-10-10T03:54:33.176Z" }, + { url = "https://files.pythonhosted.org/packages/0e/84/875382b10d271b0c11aa5d414b44f92f8dd53e9b658aec338a79164fa548/httptools-0.7.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cad6b591a682dcc6cf1397c3900527f9affef1e55a06c4547264796bbd17cf5e", size = 454954, upload-time = "2025-10-10T03:54:34.226Z" }, + { url = "https://files.pythonhosted.org/packages/30/e1/44f89b280f7e46c0b1b2ccee5737d46b3bb13136383958f20b580a821ca0/httptools-0.7.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:eb844698d11433d2139bbeeb56499102143beb582bd6c194e3ba69c22f25c274", size = 440175, upload-time = "2025-10-10T03:54:35.942Z" }, + { url = "https://files.pythonhosted.org/packages/6f/7e/b9287763159e700e335028bc1824359dc736fa9b829dacedace91a39b37e/httptools-0.7.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f65744d7a8bdb4bda5e1fa23e4ba16832860606fcc09d674d56e425e991539ec", size = 440310, upload-time = "2025-10-10T03:54:37.1Z" }, + { url = "https://files.pythonhosted.org/packages/b3/07/5b614f592868e07f5c94b1f301b5e14a21df4e8076215a3bccb830a687d8/httptools-0.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:135fbe974b3718eada677229312e97f3b31f8a9c8ffa3ae6f565bf808d5b6bcb", size = 86875, upload-time = "2025-10-10T03:54:38.421Z" }, + { url = "https://files.pythonhosted.org/packages/53/7f/403e5d787dc4942316e515e949b0c8a013d84078a915910e9f391ba9b3ed/httptools-0.7.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:38e0c83a2ea9746ebbd643bdfb521b9aa4a91703e2cd705c20443405d2fd16a5", size = 206280, upload-time = "2025-10-10T03:54:39.274Z" }, + { url = "https://files.pythonhosted.org/packages/2a/0d/7f3fd28e2ce311ccc998c388dd1c53b18120fda3b70ebb022b135dc9839b/httptools-0.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f25bbaf1235e27704f1a7b86cd3304eabc04f569c828101d94a0e605ef7205a5", size = 110004, upload-time = "2025-10-10T03:54:40.403Z" }, + { url = "https://files.pythonhosted.org/packages/84/a6/b3965e1e146ef5762870bbe76117876ceba51a201e18cc31f5703e454596/httptools-0.7.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2c15f37ef679ab9ecc06bfc4e6e8628c32a8e4b305459de7cf6785acd57e4d03", size = 517655, upload-time = "2025-10-10T03:54:41.347Z" }, + { url = "https://files.pythonhosted.org/packages/11/7d/71fee6f1844e6fa378f2eddde6c3e41ce3a1fb4b2d81118dd544e3441ec0/httptools-0.7.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7fe6e96090df46b36ccfaf746f03034e5ab723162bc51b0a4cf58305324036f2", size = 511440, upload-time = "2025-10-10T03:54:42.452Z" }, + { url = "https://files.pythonhosted.org/packages/22/a5/079d216712a4f3ffa24af4a0381b108aa9c45b7a5cc6eb141f81726b1823/httptools-0.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f72fdbae2dbc6e68b8239defb48e6a5937b12218e6ffc2c7846cc37befa84362", size = 495186, upload-time = "2025-10-10T03:54:43.937Z" }, + { url = "https://files.pythonhosted.org/packages/e9/9e/025ad7b65278745dee3bd0ebf9314934c4592560878308a6121f7f812084/httptools-0.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e99c7b90a29fd82fea9ef57943d501a16f3404d7b9ee81799d41639bdaae412c", size = 499192, upload-time = "2025-10-10T03:54:45.003Z" }, + { url = "https://files.pythonhosted.org/packages/6d/de/40a8f202b987d43afc4d54689600ff03ce65680ede2f31df348d7f368b8f/httptools-0.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:3e14f530fefa7499334a79b0cf7e7cd2992870eb893526fb097d51b4f2d0f321", size = 86694, upload-time = "2025-10-10T03:54:45.923Z" }, + { url = "https://files.pythonhosted.org/packages/09/8f/c77b1fcbfd262d422f12da02feb0d218fa228d52485b77b953832105bb90/httptools-0.7.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6babce6cfa2a99545c60bfef8bee0cc0545413cb0018f617c8059a30ad985de3", size = 202889, upload-time = "2025-10-10T03:54:47.089Z" }, + { url = "https://files.pythonhosted.org/packages/0a/1a/22887f53602feaa066354867bc49a68fc295c2293433177ee90870a7d517/httptools-0.7.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:601b7628de7504077dd3dcb3791c6b8694bbd967148a6d1f01806509254fb1ca", size = 108180, upload-time = "2025-10-10T03:54:48.052Z" }, + { url = "https://files.pythonhosted.org/packages/32/6a/6aaa91937f0010d288d3d124ca2946d48d60c3a5ee7ca62afe870e3ea011/httptools-0.7.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:04c6c0e6c5fb0739c5b8a9eb046d298650a0ff38cf42537fc372b28dc7e4472c", size = 478596, upload-time = "2025-10-10T03:54:48.919Z" }, + { url = "https://files.pythonhosted.org/packages/6d/70/023d7ce117993107be88d2cbca566a7c1323ccbaf0af7eabf2064fe356f6/httptools-0.7.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:69d4f9705c405ae3ee83d6a12283dc9feba8cc6aaec671b412917e644ab4fa66", size = 473268, upload-time = "2025-10-10T03:54:49.993Z" }, + { url = "https://files.pythonhosted.org/packages/32/4d/9dd616c38da088e3f436e9a616e1d0cc66544b8cdac405cc4e81c8679fc7/httptools-0.7.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:44c8f4347d4b31269c8a9205d8a5ee2df5322b09bbbd30f8f862185bb6b05346", size = 455517, upload-time = "2025-10-10T03:54:51.066Z" }, + { url = "https://files.pythonhosted.org/packages/1d/3a/a6c595c310b7df958e739aae88724e24f9246a514d909547778d776799be/httptools-0.7.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:465275d76db4d554918aba40bf1cbebe324670f3dfc979eaffaa5d108e2ed650", size = 458337, upload-time = "2025-10-10T03:54:52.196Z" }, + { url = "https://files.pythonhosted.org/packages/fd/82/88e8d6d2c51edc1cc391b6e044c6c435b6aebe97b1abc33db1b0b24cd582/httptools-0.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:322d00c2068d125bd570f7bf78b2d367dad02b919d8581d7476d8b75b294e3e6", size = 85743, upload-time = "2025-10-10T03:54:53.448Z" }, + { url = "https://files.pythonhosted.org/packages/34/50/9d095fcbb6de2d523e027a2f304d4551855c2f46e0b82befd718b8b20056/httptools-0.7.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:c08fe65728b8d70b6923ce31e3956f859d5e1e8548e6f22ec520a962c6757270", size = 203619, upload-time = "2025-10-10T03:54:54.321Z" }, + { url = "https://files.pythonhosted.org/packages/07/f0/89720dc5139ae54b03f861b5e2c55a37dba9a5da7d51e1e824a1f343627f/httptools-0.7.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7aea2e3c3953521c3c51106ee11487a910d45586e351202474d45472db7d72d3", size = 108714, upload-time = "2025-10-10T03:54:55.163Z" }, + { url = "https://files.pythonhosted.org/packages/b3/cb/eea88506f191fb552c11787c23f9a405f4c7b0c5799bf73f2249cd4f5228/httptools-0.7.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0e68b8582f4ea9166be62926077a3334064d422cf08ab87d8b74664f8e9058e1", size = 472909, upload-time = "2025-10-10T03:54:56.056Z" }, + { url = "https://files.pythonhosted.org/packages/e0/4a/a548bdfae6369c0d078bab5769f7b66f17f1bfaa6fa28f81d6be6959066b/httptools-0.7.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:df091cf961a3be783d6aebae963cc9b71e00d57fa6f149025075217bc6a55a7b", size = 470831, upload-time = "2025-10-10T03:54:57.219Z" }, + { url = "https://files.pythonhosted.org/packages/4d/31/14df99e1c43bd132eec921c2e7e11cda7852f65619bc0fc5bdc2d0cb126c/httptools-0.7.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f084813239e1eb403ddacd06a30de3d3e09a9b76e7894dcda2b22f8a726e9c60", size = 452631, upload-time = "2025-10-10T03:54:58.219Z" }, + { url = "https://files.pythonhosted.org/packages/22/d2/b7e131f7be8d854d48cb6d048113c30f9a46dca0c9a8b08fcb3fcd588cdc/httptools-0.7.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7347714368fb2b335e9063bc2b96f2f87a9ceffcd9758ac295f8bbcd3ffbc0ca", size = 452910, upload-time = "2025-10-10T03:54:59.366Z" }, + { url = "https://files.pythonhosted.org/packages/53/cf/878f3b91e4e6e011eff6d1fa9ca39f7eb17d19c9d7971b04873734112f30/httptools-0.7.1-cp314-cp314-win_amd64.whl", hash = "sha256:cfabda2a5bb85aa2a904ce06d974a3f30fb36cc63d7feaddec05d2050acede96", size = 88205, upload-time = "2025-10-10T03:55:00.389Z" }, ] [[package]] @@ -951,18 +952,18 @@ dependencies = [ { name = "httpcore" }, { name = "idna" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406 } +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517 }, + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, ] [[package]] name = "httpx-sse" version = "0.4.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6e/fa/66bd985dd0b7c109a3bcb89272ee0bfb7e2b4d06309ad7b38ff866734b2a/httpx_sse-0.4.1.tar.gz", hash = "sha256:8f44d34414bc7b21bf3602713005c5df4917884f76072479b21f68befa4ea26e", size = 12998 } +sdist = { url = "https://files.pythonhosted.org/packages/6e/fa/66bd985dd0b7c109a3bcb89272ee0bfb7e2b4d06309ad7b38ff866734b2a/httpx_sse-0.4.1.tar.gz", hash = "sha256:8f44d34414bc7b21bf3602713005c5df4917884f76072479b21f68befa4ea26e", size = 12998, upload-time = "2025-06-24T13:21:05.71Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/25/0a/6269e3473b09aed2dab8aa1a600c70f31f00ae1349bee30658f7e358a159/httpx_sse-0.4.1-py3-none-any.whl", hash = "sha256:cba42174344c3a5b06f255ce65b350880f962d99ead85e776f23c6618a377a37", size = 8054 }, + { url = "https://files.pythonhosted.org/packages/25/0a/6269e3473b09aed2dab8aa1a600c70f31f00ae1349bee30658f7e358a159/httpx_sse-0.4.1-py3-none-any.whl", hash = "sha256:cba42174344c3a5b06f255ce65b350880f962d99ead85e776f23c6618a377a37", size = 8054, upload-time = "2025-06-24T13:21:04.772Z" }, ] [[package]] @@ -972,9 +973,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/48/a4/c0b698a7250b7a5c2956427406560701862215c646e079a7907846608f44/hvac-2.3.0.tar.gz", hash = "sha256:1b85e3320e8642dd82f234db63253cda169a817589e823713dc5fca83119b1e2", size = 332660 } +sdist = { url = "https://files.pythonhosted.org/packages/48/a4/c0b698a7250b7a5c2956427406560701862215c646e079a7907846608f44/hvac-2.3.0.tar.gz", hash = "sha256:1b85e3320e8642dd82f234db63253cda169a817589e823713dc5fca83119b1e2", size = 332660, upload-time = "2024-06-18T14:46:09.748Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0b/34/56facf52e2ea14ce640f434ccf00311af6f3a1df0019d4682ba28ea09948/hvac-2.3.0-py3-none-any.whl", hash = "sha256:a3afc5710760b6ee9b3571769df87a0333da45da05a5f9f963e1d3925a84be7d", size = 155860 }, + { url = "https://files.pythonhosted.org/packages/0b/34/56facf52e2ea14ce640f434ccf00311af6f3a1df0019d4682ba28ea09948/hvac-2.3.0-py3-none-any.whl", hash = "sha256:a3afc5710760b6ee9b3571769df87a0333da45da05a5f9f963e1d3925a84be7d", size = 155860, upload-time = "2024-06-18T14:46:05.399Z" }, ] [[package]] @@ -984,18 +985,18 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/22/11/102da08f88412d875fa2f1a9a469ff7ad4c874b0ca6fed0048fe385bdb3d/id-1.5.0.tar.gz", hash = "sha256:292cb8a49eacbbdbce97244f47a97b4c62540169c976552e497fd57df0734c1d", size = 15237 } +sdist = { url = "https://files.pythonhosted.org/packages/22/11/102da08f88412d875fa2f1a9a469ff7ad4c874b0ca6fed0048fe385bdb3d/id-1.5.0.tar.gz", hash = "sha256:292cb8a49eacbbdbce97244f47a97b4c62540169c976552e497fd57df0734c1d", size = 15237, upload-time = "2024-12-04T19:53:05.575Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9f/cb/18326d2d89ad3b0dd143da971e77afd1e6ca6674f1b1c3df4b6bec6279fc/id-1.5.0-py3-none-any.whl", hash = "sha256:f1434e1cef91f2cbb8a4ec64663d5a23b9ed43ef44c4c957d02583d61714c658", size = 13611 }, + { url = "https://files.pythonhosted.org/packages/9f/cb/18326d2d89ad3b0dd143da971e77afd1e6ca6674f1b1c3df4b6bec6279fc/id-1.5.0-py3-none-any.whl", hash = "sha256:f1434e1cef91f2cbb8a4ec64663d5a23b9ed43ef44c4c957d02583d61714c658", size = 13611, upload-time = "2024-12-04T19:53:03.02Z" }, ] [[package]] name = "idna" version = "3.10" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, ] [[package]] @@ -1005,18 +1006,18 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "zipp" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/76/66/650a33bd90f786193e4de4b3ad86ea60b53c89b669a5c7be931fac31cdb0/importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000", size = 56641 } +sdist = { url = "https://files.pythonhosted.org/packages/76/66/650a33bd90f786193e4de4b3ad86ea60b53c89b669a5c7be931fac31cdb0/importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000", size = 56641, upload-time = "2025-04-27T15:29:01.736Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/20/b0/36bd937216ec521246249be3bf9855081de4c5e06a0c9b4219dbeda50373/importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd", size = 27656 }, + { url = "https://files.pythonhosted.org/packages/20/b0/36bd937216ec521246249be3bf9855081de4c5e06a0c9b4219dbeda50373/importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd", size = 27656, upload-time = "2025-04-27T15:29:00.214Z" }, ] [[package]] name = "iniconfig" version = "2.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793 } +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050 }, + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, ] [[package]] @@ -1026,9 +1027,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "six" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/db/7a/c0a56c7d56c7fa723988f122fa1f1ccf8c5c4ccc48efad0d214b49e5b1af/isodate-0.6.1.tar.gz", hash = "sha256:48c5881de7e8b0a0d648cb024c8062dc84e7b840ed81e864c7614fd3c127bde9", size = 28443 } +sdist = { url = "https://files.pythonhosted.org/packages/db/7a/c0a56c7d56c7fa723988f122fa1f1ccf8c5c4ccc48efad0d214b49e5b1af/isodate-0.6.1.tar.gz", hash = "sha256:48c5881de7e8b0a0d648cb024c8062dc84e7b840ed81e864c7614fd3c127bde9", size = 28443, upload-time = "2021-12-13T20:28:31.525Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b6/85/7882d311924cbcfc70b1890780763e36ff0b140c7e51c110fc59a532f087/isodate-0.6.1-py2.py3-none-any.whl", hash = "sha256:0751eece944162659049d35f4f549ed815792b38793f07cf73381c1c87cbed96", size = 41722 }, + { url = "https://files.pythonhosted.org/packages/b6/85/7882d311924cbcfc70b1890780763e36ff0b140c7e51c110fc59a532f087/isodate-0.6.1-py2.py3-none-any.whl", hash = "sha256:0751eece944162659049d35f4f549ed815792b38793f07cf73381c1c87cbed96", size = 41722, upload-time = "2021-12-13T20:28:29.073Z" }, ] [[package]] @@ -1038,9 +1039,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "more-itertools" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/06/c0/ed4a27bc5571b99e3cff68f8a9fa5b56ff7df1c2251cc715a652ddd26402/jaraco.classes-3.4.0.tar.gz", hash = "sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd", size = 11780 } +sdist = { url = "https://files.pythonhosted.org/packages/06/c0/ed4a27bc5571b99e3cff68f8a9fa5b56ff7df1c2251cc715a652ddd26402/jaraco.classes-3.4.0.tar.gz", hash = "sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd", size = 11780, upload-time = "2024-03-31T07:27:36.643Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7f/66/b15ce62552d84bbfcec9a4873ab79d993a1dd4edb922cbfccae192bd5b5f/jaraco.classes-3.4.0-py3-none-any.whl", hash = "sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790", size = 6777 }, + { url = "https://files.pythonhosted.org/packages/7f/66/b15ce62552d84bbfcec9a4873ab79d993a1dd4edb922cbfccae192bd5b5f/jaraco.classes-3.4.0-py3-none-any.whl", hash = "sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790", size = 6777, upload-time = "2024-03-31T07:27:34.792Z" }, ] [[package]] @@ -1050,9 +1051,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "backports-tarfile", marker = "python_full_version < '3.12'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/df/ad/f3777b81bf0b6e7bc7514a1656d3e637b2e8e15fab2ce3235730b3e7a4e6/jaraco_context-6.0.1.tar.gz", hash = "sha256:9bae4ea555cf0b14938dc0aee7c9f32ed303aa20a3b73e7dc80111628792d1b3", size = 13912 } +sdist = { url = "https://files.pythonhosted.org/packages/df/ad/f3777b81bf0b6e7bc7514a1656d3e637b2e8e15fab2ce3235730b3e7a4e6/jaraco_context-6.0.1.tar.gz", hash = "sha256:9bae4ea555cf0b14938dc0aee7c9f32ed303aa20a3b73e7dc80111628792d1b3", size = 13912, upload-time = "2024-08-20T03:39:27.358Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ff/db/0c52c4cf5e4bd9f5d7135ec7669a3a767af21b3a308e1ed3674881e52b62/jaraco.context-6.0.1-py3-none-any.whl", hash = "sha256:f797fc481b490edb305122c9181830a3a5b76d84ef6d1aef2fb9b47ab956f9e4", size = 6825 }, + { url = "https://files.pythonhosted.org/packages/ff/db/0c52c4cf5e4bd9f5d7135ec7669a3a767af21b3a308e1ed3674881e52b62/jaraco.context-6.0.1-py3-none-any.whl", hash = "sha256:f797fc481b490edb305122c9181830a3a5b76d84ef6d1aef2fb9b47ab956f9e4", size = 6825, upload-time = "2024-08-20T03:39:25.966Z" }, ] [[package]] @@ -1062,18 +1063,18 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "more-itertools" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/49/1c/831faaaa0f090b711c355c6d8b2abf277c72133aab472b6932b03322294c/jaraco_functools-4.2.1.tar.gz", hash = "sha256:be634abfccabce56fa3053f8c7ebe37b682683a4ee7793670ced17bab0087353", size = 19661 } +sdist = { url = "https://files.pythonhosted.org/packages/49/1c/831faaaa0f090b711c355c6d8b2abf277c72133aab472b6932b03322294c/jaraco_functools-4.2.1.tar.gz", hash = "sha256:be634abfccabce56fa3053f8c7ebe37b682683a4ee7793670ced17bab0087353", size = 19661, upload-time = "2025-06-21T19:22:03.201Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/fd/179a20f832824514df39a90bb0e5372b314fea99f217f5ab942b10a8a4e8/jaraco_functools-4.2.1-py3-none-any.whl", hash = "sha256:590486285803805f4b1f99c60ca9e94ed348d4added84b74c7a12885561e524e", size = 10349 }, + { url = "https://files.pythonhosted.org/packages/f3/fd/179a20f832824514df39a90bb0e5372b314fea99f217f5ab942b10a8a4e8/jaraco_functools-4.2.1-py3-none-any.whl", hash = "sha256:590486285803805f4b1f99c60ca9e94ed348d4added84b74c7a12885561e524e", size = 10349, upload-time = "2025-06-21T19:22:02.039Z" }, ] [[package]] name = "jeepney" version = "0.9.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7b/6f/357efd7602486741aa73ffc0617fb310a29b588ed0fd69c2399acbb85b0c/jeepney-0.9.0.tar.gz", hash = "sha256:cf0e9e845622b81e4a28df94c40345400256ec608d0e55bb8a3feaa9163f5732", size = 106758 } +sdist = { url = "https://files.pythonhosted.org/packages/7b/6f/357efd7602486741aa73ffc0617fb310a29b588ed0fd69c2399acbb85b0c/jeepney-0.9.0.tar.gz", hash = "sha256:cf0e9e845622b81e4a28df94c40345400256ec608d0e55bb8a3feaa9163f5732", size = 106758, upload-time = "2025-02-27T18:51:01.684Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b2/a3/e137168c9c44d18eff0376253da9f1e9234d0239e0ee230d2fee6cea8e55/jeepney-0.9.0-py3-none-any.whl", hash = "sha256:97e5714520c16fc0a45695e5365a2e11b81ea79bba796e26f9f1d178cb182683", size = 49010 }, + { url = "https://files.pythonhosted.org/packages/b2/a3/e137168c9c44d18eff0376253da9f1e9234d0239e0ee230d2fee6cea8e55/jeepney-0.9.0-py3-none-any.whl", hash = "sha256:97e5714520c16fc0a45695e5365a2e11b81ea79bba796e26f9f1d178cb182683", size = 49010, upload-time = "2025-02-27T18:51:00.104Z" }, ] [[package]] @@ -1083,9 +1084,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markupsafe" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115 } +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899 }, + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, ] [[package]] @@ -1098,9 +1099,9 @@ dependencies = [ { name = "referencing" }, { name = "rpds-py" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/bf/d3/1cf5326b923a53515d8f3a2cd442e6d7e94fcc444716e879ea70a0ce3177/jsonschema-4.24.0.tar.gz", hash = "sha256:0b4e8069eb12aedfa881333004bccaec24ecef5a8a6a4b6df142b2cc9599d196", size = 353480 } +sdist = { url = "https://files.pythonhosted.org/packages/bf/d3/1cf5326b923a53515d8f3a2cd442e6d7e94fcc444716e879ea70a0ce3177/jsonschema-4.24.0.tar.gz", hash = "sha256:0b4e8069eb12aedfa881333004bccaec24ecef5a8a6a4b6df142b2cc9599d196", size = 353480, upload-time = "2025-05-26T18:48:10.459Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a2/3d/023389198f69c722d039351050738d6755376c8fd343e91dc493ea485905/jsonschema-4.24.0-py3-none-any.whl", hash = "sha256:a462455f19f5faf404a7902952b6f0e3ce868f3ee09a359b05eca6673bd8412d", size = 88709 }, + { url = "https://files.pythonhosted.org/packages/a2/3d/023389198f69c722d039351050738d6755376c8fd343e91dc493ea485905/jsonschema-4.24.0-py3-none-any.whl", hash = "sha256:a462455f19f5faf404a7902952b6f0e3ce868f3ee09a359b05eca6673bd8412d", size = 88709, upload-time = "2025-05-26T18:48:08.417Z" }, ] [[package]] @@ -1110,9 +1111,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "referencing" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/bf/ce/46fbd9c8119cfc3581ee5643ea49464d168028cfb5caff5fc0596d0cf914/jsonschema_specifications-2025.4.1.tar.gz", hash = "sha256:630159c9f4dbea161a6a2205c3011cc4f18ff381b189fff48bb39b9bf26ae608", size = 15513 } +sdist = { url = "https://files.pythonhosted.org/packages/bf/ce/46fbd9c8119cfc3581ee5643ea49464d168028cfb5caff5fc0596d0cf914/jsonschema_specifications-2025.4.1.tar.gz", hash = "sha256:630159c9f4dbea161a6a2205c3011cc4f18ff381b189fff48bb39b9bf26ae608", size = 15513, upload-time = "2025-04-23T12:34:07.418Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/01/0e/b27cdbaccf30b890c40ed1da9fd4a3593a5cf94dae54fb34f8a4b74fcd3f/jsonschema_specifications-2025.4.1-py3-none-any.whl", hash = "sha256:4653bffbd6584f7de83a67e0d620ef16900b390ddc7939d56684d6c81e33f1af", size = 18437 }, + { url = "https://files.pythonhosted.org/packages/01/0e/b27cdbaccf30b890c40ed1da9fd4a3593a5cf94dae54fb34f8a4b74fcd3f/jsonschema_specifications-2025.4.1-py3-none-any.whl", hash = "sha256:4653bffbd6584f7de83a67e0d620ef16900b390ddc7939d56684d6c81e33f1af", size = 18437, upload-time = "2025-04-23T12:34:05.422Z" }, ] [[package]] @@ -1128,36 +1129,36 @@ dependencies = [ { name = "pywin32-ctypes", marker = "sys_platform == 'win32'" }, { name = "secretstorage", marker = "sys_platform == 'linux'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/70/09/d904a6e96f76ff214be59e7aa6ef7190008f52a0ab6689760a98de0bf37d/keyring-25.6.0.tar.gz", hash = "sha256:0b39998aa941431eb3d9b0d4b2460bc773b9df6fed7621c2dfb291a7e0187a66", size = 62750 } +sdist = { url = "https://files.pythonhosted.org/packages/70/09/d904a6e96f76ff214be59e7aa6ef7190008f52a0ab6689760a98de0bf37d/keyring-25.6.0.tar.gz", hash = "sha256:0b39998aa941431eb3d9b0d4b2460bc773b9df6fed7621c2dfb291a7e0187a66", size = 62750, upload-time = "2024-12-25T15:26:45.782Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d3/32/da7f44bcb1105d3e88a0b74ebdca50c59121d2ddf71c9e34ba47df7f3a56/keyring-25.6.0-py3-none-any.whl", hash = "sha256:552a3f7af126ece7ed5c89753650eec89c7eaae8617d0aa4d9ad2b75111266bd", size = 39085 }, + { url = "https://files.pythonhosted.org/packages/d3/32/da7f44bcb1105d3e88a0b74ebdca50c59121d2ddf71c9e34ba47df7f3a56/keyring-25.6.0-py3-none-any.whl", hash = "sha256:552a3f7af126ece7ed5c89753650eec89c7eaae8617d0aa4d9ad2b75111266bd", size = 39085, upload-time = "2024-12-25T15:26:44.377Z" }, ] [[package]] name = "lark" version = "0.12.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/02/1d/29681d27b84e384ea50b5546e9f0089126afbc91754db4ca89593fcfd0e8/lark-0.12.0.tar.gz", hash = "sha256:7da76fcfddadabbbbfd949bbae221efd33938451d90b1fefbbc423c3cccf48ef", size = 235168 } +sdist = { url = "https://files.pythonhosted.org/packages/02/1d/29681d27b84e384ea50b5546e9f0089126afbc91754db4ca89593fcfd0e8/lark-0.12.0.tar.gz", hash = "sha256:7da76fcfddadabbbbfd949bbae221efd33938451d90b1fefbbc423c3cccf48ef", size = 235168, upload-time = "2021-11-12T11:15:32.124Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/39/cef2ccdfd984ae3cf93878d050c1b7c9354dd9493ce83fd9bb33a41f7a33/lark-0.12.0-py2.py3-none-any.whl", hash = "sha256:ed1d891cbcf5151ead1c1d14663bf542443e579e63a76ae175b01b899bd854ca", size = 103540 }, + { url = "https://files.pythonhosted.org/packages/cb/39/cef2ccdfd984ae3cf93878d050c1b7c9354dd9493ce83fd9bb33a41f7a33/lark-0.12.0-py2.py3-none-any.whl", hash = "sha256:ed1d891cbcf5151ead1c1d14663bf542443e579e63a76ae175b01b899bd854ca", size = 103540, upload-time = "2021-11-12T11:15:34.408Z" }, ] [[package]] name = "leather" version = "0.4.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ed/6e/48a05e2f7f62a616d675cfee182643f2dd8023bf7429aa326f4bebd629c8/leather-0.4.0.tar.gz", hash = "sha256:f964bec2086f3153a6c16e707f20cb718f811f57af116075f4c0f4805c608b95", size = 43877 } +sdist = { url = "https://files.pythonhosted.org/packages/ed/6e/48a05e2f7f62a616d675cfee182643f2dd8023bf7429aa326f4bebd629c8/leather-0.4.0.tar.gz", hash = "sha256:f964bec2086f3153a6c16e707f20cb718f811f57af116075f4c0f4805c608b95", size = 43877, upload-time = "2024-02-23T22:03:36.657Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a1/30/9ec597c962c5249ebd5c580386e4b5f2884cd943af42634291ee3b406415/leather-0.4.0-py2.py3-none-any.whl", hash = "sha256:18290bc93749ae39039af5e31e871fcfad74d26c4c3ea28ea4f681f4571b3a2b", size = 30256 }, + { url = "https://files.pythonhosted.org/packages/a1/30/9ec597c962c5249ebd5c580386e4b5f2884cd943af42634291ee3b406415/leather-0.4.0-py2.py3-none-any.whl", hash = "sha256:18290bc93749ae39039af5e31e871fcfad74d26c4c3ea28ea4f681f4571b3a2b", size = 30256, upload-time = "2024-02-23T22:03:34.75Z" }, ] [[package]] name = "makefun" version = "1.16.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7b/cf/6780ab8bc3b84a1cce3e4400aed3d64b6db7d5e227a2f75b6ded5674701a/makefun-1.16.0.tar.gz", hash = "sha256:e14601831570bff1f6d7e68828bcd30d2f5856f24bad5de0ccb22921ceebc947", size = 73565 } +sdist = { url = "https://files.pythonhosted.org/packages/7b/cf/6780ab8bc3b84a1cce3e4400aed3d64b6db7d5e227a2f75b6ded5674701a/makefun-1.16.0.tar.gz", hash = "sha256:e14601831570bff1f6d7e68828bcd30d2f5856f24bad5de0ccb22921ceebc947", size = 73565, upload-time = "2025-05-09T15:00:42.313Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/c0/4bc973defd1270b89ccaae04cef0d5fa3ea85b59b108ad2c08aeea9afb76/makefun-1.16.0-py2.py3-none-any.whl", hash = "sha256:43baa4c3e7ae2b17de9ceac20b669e9a67ceeadff31581007cca20a07bbe42c4", size = 22923 }, + { url = "https://files.pythonhosted.org/packages/b7/c0/4bc973defd1270b89ccaae04cef0d5fa3ea85b59b108ad2c08aeea9afb76/makefun-1.16.0-py2.py3-none-any.whl", hash = "sha256:43baa4c3e7ae2b17de9ceac20b669e9a67ceeadff31581007cca20a07bbe42c4", size = 22923, upload-time = "2025-05-09T15:00:41.042Z" }, ] [[package]] @@ -1167,67 +1168,67 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mdurl" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596 } +sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596, upload-time = "2023-06-03T06:41:14.443Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528 }, + { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528, upload-time = "2023-06-03T06:41:11.019Z" }, ] [[package]] name = "markupsafe" version = "3.0.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/04/90/d08277ce111dd22f77149fd1a5d4653eeb3b3eaacbdfcbae5afb2600eebd/MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8", size = 14357 }, - { url = "https://files.pythonhosted.org/packages/04/e1/6e2194baeae0bca1fae6629dc0cbbb968d4d941469cbab11a3872edff374/MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158", size = 12393 }, - { url = "https://files.pythonhosted.org/packages/1d/69/35fa85a8ece0a437493dc61ce0bb6d459dcba482c34197e3efc829aa357f/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579", size = 21732 }, - { url = "https://files.pythonhosted.org/packages/22/35/137da042dfb4720b638d2937c38a9c2df83fe32d20e8c8f3185dbfef05f7/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d", size = 20866 }, - { url = "https://files.pythonhosted.org/packages/29/28/6d029a903727a1b62edb51863232152fd335d602def598dade38996887f0/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb", size = 20964 }, - { url = "https://files.pythonhosted.org/packages/cc/cd/07438f95f83e8bc028279909d9c9bd39e24149b0d60053a97b2bc4f8aa51/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b", size = 21977 }, - { url = "https://files.pythonhosted.org/packages/29/01/84b57395b4cc062f9c4c55ce0df7d3108ca32397299d9df00fedd9117d3d/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c", size = 21366 }, - { url = "https://files.pythonhosted.org/packages/bd/6e/61ebf08d8940553afff20d1fb1ba7294b6f8d279df9fd0c0db911b4bbcfd/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171", size = 21091 }, - { url = "https://files.pythonhosted.org/packages/11/23/ffbf53694e8c94ebd1e7e491de185124277964344733c45481f32ede2499/MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50", size = 15065 }, - { url = "https://files.pythonhosted.org/packages/44/06/e7175d06dd6e9172d4a69a72592cb3f7a996a9c396eee29082826449bbc3/MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a", size = 15514 }, - { url = "https://files.pythonhosted.org/packages/6b/28/bbf83e3f76936960b850435576dd5e67034e200469571be53f69174a2dfd/MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", size = 14353 }, - { url = "https://files.pythonhosted.org/packages/6c/30/316d194b093cde57d448a4c3209f22e3046c5bb2fb0820b118292b334be7/MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", size = 12392 }, - { url = "https://files.pythonhosted.org/packages/f2/96/9cdafba8445d3a53cae530aaf83c38ec64c4d5427d975c974084af5bc5d2/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", size = 23984 }, - { url = "https://files.pythonhosted.org/packages/f1/a4/aefb044a2cd8d7334c8a47d3fb2c9f328ac48cb349468cc31c20b539305f/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", size = 23120 }, - { url = "https://files.pythonhosted.org/packages/8d/21/5e4851379f88f3fad1de30361db501300d4f07bcad047d3cb0449fc51f8c/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", size = 23032 }, - { url = "https://files.pythonhosted.org/packages/00/7b/e92c64e079b2d0d7ddf69899c98842f3f9a60a1ae72657c89ce2655c999d/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", size = 24057 }, - { url = "https://files.pythonhosted.org/packages/f9/ac/46f960ca323037caa0a10662ef97d0a4728e890334fc156b9f9e52bcc4ca/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", size = 23359 }, - { url = "https://files.pythonhosted.org/packages/69/84/83439e16197337b8b14b6a5b9c2105fff81d42c2a7c5b58ac7b62ee2c3b1/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", size = 23306 }, - { url = "https://files.pythonhosted.org/packages/9a/34/a15aa69f01e2181ed8d2b685c0d2f6655d5cca2c4db0ddea775e631918cd/MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", size = 15094 }, - { url = "https://files.pythonhosted.org/packages/da/b8/3a3bd761922d416f3dc5d00bfbed11f66b1ab89a0c2b6e887240a30b0f6b/MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", size = 15521 }, - { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274 }, - { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348 }, - { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149 }, - { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118 }, - { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993 }, - { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178 }, - { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319 }, - { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352 }, - { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097 }, - { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601 }, - { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274 }, - { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352 }, - { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122 }, - { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085 }, - { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978 }, - { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208 }, - { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357 }, - { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344 }, - { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101 }, - { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603 }, - { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510 }, - { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486 }, - { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480 }, - { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914 }, - { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796 }, - { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473 }, - { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114 }, - { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098 }, - { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208 }, - { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739 }, +sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload-time = "2024-10-18T15:21:54.129Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/90/d08277ce111dd22f77149fd1a5d4653eeb3b3eaacbdfcbae5afb2600eebd/MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8", size = 14357, upload-time = "2024-10-18T15:20:51.44Z" }, + { url = "https://files.pythonhosted.org/packages/04/e1/6e2194baeae0bca1fae6629dc0cbbb968d4d941469cbab11a3872edff374/MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158", size = 12393, upload-time = "2024-10-18T15:20:52.426Z" }, + { url = "https://files.pythonhosted.org/packages/1d/69/35fa85a8ece0a437493dc61ce0bb6d459dcba482c34197e3efc829aa357f/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579", size = 21732, upload-time = "2024-10-18T15:20:53.578Z" }, + { url = "https://files.pythonhosted.org/packages/22/35/137da042dfb4720b638d2937c38a9c2df83fe32d20e8c8f3185dbfef05f7/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d", size = 20866, upload-time = "2024-10-18T15:20:55.06Z" }, + { url = "https://files.pythonhosted.org/packages/29/28/6d029a903727a1b62edb51863232152fd335d602def598dade38996887f0/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb", size = 20964, upload-time = "2024-10-18T15:20:55.906Z" }, + { url = "https://files.pythonhosted.org/packages/cc/cd/07438f95f83e8bc028279909d9c9bd39e24149b0d60053a97b2bc4f8aa51/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b", size = 21977, upload-time = "2024-10-18T15:20:57.189Z" }, + { url = "https://files.pythonhosted.org/packages/29/01/84b57395b4cc062f9c4c55ce0df7d3108ca32397299d9df00fedd9117d3d/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c", size = 21366, upload-time = "2024-10-18T15:20:58.235Z" }, + { url = "https://files.pythonhosted.org/packages/bd/6e/61ebf08d8940553afff20d1fb1ba7294b6f8d279df9fd0c0db911b4bbcfd/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171", size = 21091, upload-time = "2024-10-18T15:20:59.235Z" }, + { url = "https://files.pythonhosted.org/packages/11/23/ffbf53694e8c94ebd1e7e491de185124277964344733c45481f32ede2499/MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50", size = 15065, upload-time = "2024-10-18T15:21:00.307Z" }, + { url = "https://files.pythonhosted.org/packages/44/06/e7175d06dd6e9172d4a69a72592cb3f7a996a9c396eee29082826449bbc3/MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a", size = 15514, upload-time = "2024-10-18T15:21:01.122Z" }, + { url = "https://files.pythonhosted.org/packages/6b/28/bbf83e3f76936960b850435576dd5e67034e200469571be53f69174a2dfd/MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", size = 14353, upload-time = "2024-10-18T15:21:02.187Z" }, + { url = "https://files.pythonhosted.org/packages/6c/30/316d194b093cde57d448a4c3209f22e3046c5bb2fb0820b118292b334be7/MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", size = 12392, upload-time = "2024-10-18T15:21:02.941Z" }, + { url = "https://files.pythonhosted.org/packages/f2/96/9cdafba8445d3a53cae530aaf83c38ec64c4d5427d975c974084af5bc5d2/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", size = 23984, upload-time = "2024-10-18T15:21:03.953Z" }, + { url = "https://files.pythonhosted.org/packages/f1/a4/aefb044a2cd8d7334c8a47d3fb2c9f328ac48cb349468cc31c20b539305f/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", size = 23120, upload-time = "2024-10-18T15:21:06.495Z" }, + { url = "https://files.pythonhosted.org/packages/8d/21/5e4851379f88f3fad1de30361db501300d4f07bcad047d3cb0449fc51f8c/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", size = 23032, upload-time = "2024-10-18T15:21:07.295Z" }, + { url = "https://files.pythonhosted.org/packages/00/7b/e92c64e079b2d0d7ddf69899c98842f3f9a60a1ae72657c89ce2655c999d/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", size = 24057, upload-time = "2024-10-18T15:21:08.073Z" }, + { url = "https://files.pythonhosted.org/packages/f9/ac/46f960ca323037caa0a10662ef97d0a4728e890334fc156b9f9e52bcc4ca/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", size = 23359, upload-time = "2024-10-18T15:21:09.318Z" }, + { url = "https://files.pythonhosted.org/packages/69/84/83439e16197337b8b14b6a5b9c2105fff81d42c2a7c5b58ac7b62ee2c3b1/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", size = 23306, upload-time = "2024-10-18T15:21:10.185Z" }, + { url = "https://files.pythonhosted.org/packages/9a/34/a15aa69f01e2181ed8d2b685c0d2f6655d5cca2c4db0ddea775e631918cd/MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", size = 15094, upload-time = "2024-10-18T15:21:11.005Z" }, + { url = "https://files.pythonhosted.org/packages/da/b8/3a3bd761922d416f3dc5d00bfbed11f66b1ab89a0c2b6e887240a30b0f6b/MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", size = 15521, upload-time = "2024-10-18T15:21:12.911Z" }, + { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274, upload-time = "2024-10-18T15:21:13.777Z" }, + { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348, upload-time = "2024-10-18T15:21:14.822Z" }, + { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149, upload-time = "2024-10-18T15:21:15.642Z" }, + { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118, upload-time = "2024-10-18T15:21:17.133Z" }, + { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993, upload-time = "2024-10-18T15:21:18.064Z" }, + { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178, upload-time = "2024-10-18T15:21:18.859Z" }, + { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319, upload-time = "2024-10-18T15:21:19.671Z" }, + { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352, upload-time = "2024-10-18T15:21:20.971Z" }, + { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097, upload-time = "2024-10-18T15:21:22.646Z" }, + { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601, upload-time = "2024-10-18T15:21:23.499Z" }, + { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274, upload-time = "2024-10-18T15:21:24.577Z" }, + { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352, upload-time = "2024-10-18T15:21:25.382Z" }, + { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122, upload-time = "2024-10-18T15:21:26.199Z" }, + { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085, upload-time = "2024-10-18T15:21:27.029Z" }, + { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978, upload-time = "2024-10-18T15:21:27.846Z" }, + { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208, upload-time = "2024-10-18T15:21:28.744Z" }, + { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357, upload-time = "2024-10-18T15:21:29.545Z" }, + { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344, upload-time = "2024-10-18T15:21:30.366Z" }, + { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101, upload-time = "2024-10-18T15:21:31.207Z" }, + { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603, upload-time = "2024-10-18T15:21:32.032Z" }, + { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510, upload-time = "2024-10-18T15:21:33.625Z" }, + { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486, upload-time = "2024-10-18T15:21:34.611Z" }, + { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480, upload-time = "2024-10-18T15:21:35.398Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914, upload-time = "2024-10-18T15:21:36.231Z" }, + { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796, upload-time = "2024-10-18T15:21:37.073Z" }, + { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473, upload-time = "2024-10-18T15:21:37.932Z" }, + { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114, upload-time = "2024-10-18T15:21:39.799Z" }, + { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098, upload-time = "2024-10-18T15:21:40.813Z" }, + { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208, upload-time = "2024-10-18T15:21:41.814Z" }, + { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739, upload-time = "2024-10-18T15:21:42.784Z" }, ] [[package]] @@ -1237,9 +1238,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/eb/47/0a450b281bef2d7e97ec02c8e1168d821e283f58e02e6c403b2bb4d73c1c/mashumaro-3.14.tar.gz", hash = "sha256:5ef6f2b963892cbe9a4ceb3441dfbea37f8c3412523f25d42e9b3a7186555f1d", size = 166160 } +sdist = { url = "https://files.pythonhosted.org/packages/eb/47/0a450b281bef2d7e97ec02c8e1168d821e283f58e02e6c403b2bb4d73c1c/mashumaro-3.14.tar.gz", hash = "sha256:5ef6f2b963892cbe9a4ceb3441dfbea37f8c3412523f25d42e9b3a7186555f1d", size = 166160, upload-time = "2024-10-23T21:48:40.164Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1b/35/8d63733a2c12149d0c7663c29bf626bdbeea5f0ff963afe58a42b4810981/mashumaro-3.14-py3-none-any.whl", hash = "sha256:c12a649599a8f7b1a0b35d18f12e678423c3066189f7bc7bd8dd431c5c8132c3", size = 92183 }, + { url = "https://files.pythonhosted.org/packages/1b/35/8d63733a2c12149d0c7663c29bf626bdbeea5f0ff963afe58a42b4810981/mashumaro-3.14-py3-none-any.whl", hash = "sha256:c12a649599a8f7b1a0b35d18f12e678423c3066189f7bc7bd8dd431c5c8132c3", size = 92183, upload-time = "2024-10-23T21:48:38.334Z" }, ] [package.optional-dependencies] @@ -1264,75 +1265,75 @@ dependencies = [ { name = "starlette" }, { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/66/85/f36d538b1286b7758f35c1b69d93f2719d2df90c01bd074eadd35f6afc35/mcp-1.12.2.tar.gz", hash = "sha256:a4b7c742c50ce6ed6d6a6c096cca0e3893f5aecc89a59ed06d47c4e6ba41edcc", size = 426202 } +sdist = { url = "https://files.pythonhosted.org/packages/66/85/f36d538b1286b7758f35c1b69d93f2719d2df90c01bd074eadd35f6afc35/mcp-1.12.2.tar.gz", hash = "sha256:a4b7c742c50ce6ed6d6a6c096cca0e3893f5aecc89a59ed06d47c4e6ba41edcc", size = 426202, upload-time = "2025-07-24T18:29:05.175Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2f/cf/3fd38cfe43962452e4bfadc6966b2ea0afaf8e0286cb3991c247c8c33ebd/mcp-1.12.2-py3-none-any.whl", hash = "sha256:b86d584bb60193a42bd78aef01882c5c42d614e416cbf0480149839377ab5a5f", size = 158473 }, + { url = "https://files.pythonhosted.org/packages/2f/cf/3fd38cfe43962452e4bfadc6966b2ea0afaf8e0286cb3991c247c8c33ebd/mcp-1.12.2-py3-none-any.whl", hash = "sha256:b86d584bb60193a42bd78aef01882c5c42d614e416cbf0480149839377ab5a5f", size = 158473, upload-time = "2025-07-24T18:29:03.419Z" }, ] [[package]] name = "mdurl" version = "0.1.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729 } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 }, + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, ] [[package]] name = "more-itertools" version = "10.7.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ce/a0/834b0cebabbfc7e311f30b46c8188790a37f89fc8d756660346fe5abfd09/more_itertools-10.7.0.tar.gz", hash = "sha256:9fddd5403be01a94b204faadcff459ec3568cf110265d3c54323e1e866ad29d3", size = 127671 } +sdist = { url = "https://files.pythonhosted.org/packages/ce/a0/834b0cebabbfc7e311f30b46c8188790a37f89fc8d756660346fe5abfd09/more_itertools-10.7.0.tar.gz", hash = "sha256:9fddd5403be01a94b204faadcff459ec3568cf110265d3c54323e1e866ad29d3", size = 127671, upload-time = "2025-04-22T14:17:41.838Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2b/9f/7ba6f94fc1e9ac3d2b853fdff3035fb2fa5afbed898c4a72b8a020610594/more_itertools-10.7.0-py3-none-any.whl", hash = "sha256:d43980384673cb07d2f7d2d918c616b30c659c089ee23953f601d6609c67510e", size = 65278 }, + { url = "https://files.pythonhosted.org/packages/2b/9f/7ba6f94fc1e9ac3d2b853fdff3035fb2fa5afbed898c4a72b8a020610594/more_itertools-10.7.0-py3-none-any.whl", hash = "sha256:d43980384673cb07d2f7d2d918c616b30c659c089ee23953f601d6609c67510e", size = 65278, upload-time = "2025-04-22T14:17:40.49Z" }, ] [[package]] name = "msgpack" version = "1.1.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/45/b1/ea4f68038a18c77c9467400d166d74c4ffa536f34761f7983a104357e614/msgpack-1.1.1.tar.gz", hash = "sha256:77b79ce34a2bdab2594f490c8e80dd62a02d650b91a75159a63ec413b8d104cd", size = 173555 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/33/52/f30da112c1dc92cf64f57d08a273ac771e7b29dea10b4b30369b2d7e8546/msgpack-1.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:353b6fc0c36fde68b661a12949d7d49f8f51ff5fa019c1e47c87c4ff34b080ed", size = 81799 }, - { url = "https://files.pythonhosted.org/packages/e4/35/7bfc0def2f04ab4145f7f108e3563f9b4abae4ab0ed78a61f350518cc4d2/msgpack-1.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:79c408fcf76a958491b4e3b103d1c417044544b68e96d06432a189b43d1215c8", size = 78278 }, - { url = "https://files.pythonhosted.org/packages/e8/c5/df5d6c1c39856bc55f800bf82778fd4c11370667f9b9e9d51b2f5da88f20/msgpack-1.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78426096939c2c7482bf31ef15ca219a9e24460289c00dd0b94411040bb73ad2", size = 402805 }, - { url = "https://files.pythonhosted.org/packages/20/8e/0bb8c977efecfe6ea7116e2ed73a78a8d32a947f94d272586cf02a9757db/msgpack-1.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b17ba27727a36cb73aabacaa44b13090feb88a01d012c0f4be70c00f75048b4", size = 408642 }, - { url = "https://files.pythonhosted.org/packages/59/a1/731d52c1aeec52006be6d1f8027c49fdc2cfc3ab7cbe7c28335b2910d7b6/msgpack-1.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7a17ac1ea6ec3c7687d70201cfda3b1e8061466f28f686c24f627cae4ea8efd0", size = 395143 }, - { url = "https://files.pythonhosted.org/packages/2b/92/b42911c52cda2ba67a6418ffa7d08969edf2e760b09015593c8a8a27a97d/msgpack-1.1.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:88d1e966c9235c1d4e2afac21ca83933ba59537e2e2727a999bf3f515ca2af26", size = 395986 }, - { url = "https://files.pythonhosted.org/packages/61/dc/8ae165337e70118d4dab651b8b562dd5066dd1e6dd57b038f32ebc3e2f07/msgpack-1.1.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:f6d58656842e1b2ddbe07f43f56b10a60f2ba5826164910968f5933e5178af75", size = 402682 }, - { url = "https://files.pythonhosted.org/packages/58/27/555851cb98dcbd6ce041df1eacb25ac30646575e9cd125681aa2f4b1b6f1/msgpack-1.1.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:96decdfc4adcbc087f5ea7ebdcfd3dee9a13358cae6e81d54be962efc38f6338", size = 406368 }, - { url = "https://files.pythonhosted.org/packages/d4/64/39a26add4ce16f24e99eabb9005e44c663db00e3fce17d4ae1ae9d61df99/msgpack-1.1.1-cp310-cp310-win32.whl", hash = "sha256:6640fd979ca9a212e4bcdf6eb74051ade2c690b862b679bfcb60ae46e6dc4bfd", size = 65004 }, - { url = "https://files.pythonhosted.org/packages/7d/18/73dfa3e9d5d7450d39debde5b0d848139f7de23bd637a4506e36c9800fd6/msgpack-1.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:8b65b53204fe1bd037c40c4148d00ef918eb2108d24c9aaa20bc31f9810ce0a8", size = 71548 }, - { url = "https://files.pythonhosted.org/packages/7f/83/97f24bf9848af23fe2ba04380388216defc49a8af6da0c28cc636d722502/msgpack-1.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:71ef05c1726884e44f8b1d1773604ab5d4d17729d8491403a705e649116c9558", size = 82728 }, - { url = "https://files.pythonhosted.org/packages/aa/7f/2eaa388267a78401f6e182662b08a588ef4f3de6f0eab1ec09736a7aaa2b/msgpack-1.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:36043272c6aede309d29d56851f8841ba907a1a3d04435e43e8a19928e243c1d", size = 79279 }, - { url = "https://files.pythonhosted.org/packages/f8/46/31eb60f4452c96161e4dfd26dbca562b4ec68c72e4ad07d9566d7ea35e8a/msgpack-1.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a32747b1b39c3ac27d0670122b57e6e57f28eefb725e0b625618d1b59bf9d1e0", size = 423859 }, - { url = "https://files.pythonhosted.org/packages/45/16/a20fa8c32825cc7ae8457fab45670c7a8996d7746ce80ce41cc51e3b2bd7/msgpack-1.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a8b10fdb84a43e50d38057b06901ec9da52baac6983d3f709d8507f3889d43f", size = 429975 }, - { url = "https://files.pythonhosted.org/packages/86/ea/6c958e07692367feeb1a1594d35e22b62f7f476f3c568b002a5ea09d443d/msgpack-1.1.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba0c325c3f485dc54ec298d8b024e134acf07c10d494ffa24373bea729acf704", size = 413528 }, - { url = "https://files.pythonhosted.org/packages/75/05/ac84063c5dae79722bda9f68b878dc31fc3059adb8633c79f1e82c2cd946/msgpack-1.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:88daaf7d146e48ec71212ce21109b66e06a98e5e44dca47d853cbfe171d6c8d2", size = 413338 }, - { url = "https://files.pythonhosted.org/packages/69/e8/fe86b082c781d3e1c09ca0f4dacd457ede60a13119b6ce939efe2ea77b76/msgpack-1.1.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:d8b55ea20dc59b181d3f47103f113e6f28a5e1c89fd5b67b9140edb442ab67f2", size = 422658 }, - { url = "https://files.pythonhosted.org/packages/3b/2b/bafc9924df52d8f3bb7c00d24e57be477f4d0f967c0a31ef5e2225e035c7/msgpack-1.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4a28e8072ae9779f20427af07f53bbb8b4aa81151054e882aee333b158da8752", size = 427124 }, - { url = "https://files.pythonhosted.org/packages/a2/3b/1f717e17e53e0ed0b68fa59e9188f3f610c79d7151f0e52ff3cd8eb6b2dc/msgpack-1.1.1-cp311-cp311-win32.whl", hash = "sha256:7da8831f9a0fdb526621ba09a281fadc58ea12701bc709e7b8cbc362feabc295", size = 65016 }, - { url = "https://files.pythonhosted.org/packages/48/45/9d1780768d3b249accecc5a38c725eb1e203d44a191f7b7ff1941f7df60c/msgpack-1.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:5fd1b58e1431008a57247d6e7cc4faa41c3607e8e7d4aaf81f7c29ea013cb458", size = 72267 }, - { url = "https://files.pythonhosted.org/packages/e3/26/389b9c593eda2b8551b2e7126ad3a06af6f9b44274eb3a4f054d48ff7e47/msgpack-1.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ae497b11f4c21558d95de9f64fff7053544f4d1a17731c866143ed6bb4591238", size = 82359 }, - { url = "https://files.pythonhosted.org/packages/ab/65/7d1de38c8a22cf8b1551469159d4b6cf49be2126adc2482de50976084d78/msgpack-1.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:33be9ab121df9b6b461ff91baac6f2731f83d9b27ed948c5b9d1978ae28bf157", size = 79172 }, - { url = "https://files.pythonhosted.org/packages/0f/bd/cacf208b64d9577a62c74b677e1ada005caa9b69a05a599889d6fc2ab20a/msgpack-1.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f64ae8fe7ffba251fecb8408540c34ee9df1c26674c50c4544d72dbf792e5ce", size = 425013 }, - { url = "https://files.pythonhosted.org/packages/4d/ec/fd869e2567cc9c01278a736cfd1697941ba0d4b81a43e0aa2e8d71dab208/msgpack-1.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a494554874691720ba5891c9b0b39474ba43ffb1aaf32a5dac874effb1619e1a", size = 426905 }, - { url = "https://files.pythonhosted.org/packages/55/2a/35860f33229075bce803a5593d046d8b489d7ba2fc85701e714fc1aaf898/msgpack-1.1.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cb643284ab0ed26f6957d969fe0dd8bb17beb567beb8998140b5e38a90974f6c", size = 407336 }, - { url = "https://files.pythonhosted.org/packages/8c/16/69ed8f3ada150bf92745fb4921bd621fd2cdf5a42e25eb50bcc57a5328f0/msgpack-1.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d275a9e3c81b1093c060c3837e580c37f47c51eca031f7b5fb76f7b8470f5f9b", size = 409485 }, - { url = "https://files.pythonhosted.org/packages/c6/b6/0c398039e4c6d0b2e37c61d7e0e9d13439f91f780686deb8ee64ecf1ae71/msgpack-1.1.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4fd6b577e4541676e0cc9ddc1709d25014d3ad9a66caa19962c4f5de30fc09ef", size = 412182 }, - { url = "https://files.pythonhosted.org/packages/b8/d0/0cf4a6ecb9bc960d624c93effaeaae75cbf00b3bc4a54f35c8507273cda1/msgpack-1.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb29aaa613c0a1c40d1af111abf025f1732cab333f96f285d6a93b934738a68a", size = 419883 }, - { url = "https://files.pythonhosted.org/packages/62/83/9697c211720fa71a2dfb632cad6196a8af3abea56eece220fde4674dc44b/msgpack-1.1.1-cp312-cp312-win32.whl", hash = "sha256:870b9a626280c86cff9c576ec0d9cbcc54a1e5ebda9cd26dab12baf41fee218c", size = 65406 }, - { url = "https://files.pythonhosted.org/packages/c0/23/0abb886e80eab08f5e8c485d6f13924028602829f63b8f5fa25a06636628/msgpack-1.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:5692095123007180dca3e788bb4c399cc26626da51629a31d40207cb262e67f4", size = 72558 }, - { url = "https://files.pythonhosted.org/packages/a1/38/561f01cf3577430b59b340b51329803d3a5bf6a45864a55f4ef308ac11e3/msgpack-1.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3765afa6bd4832fc11c3749be4ba4b69a0e8d7b728f78e68120a157a4c5d41f0", size = 81677 }, - { url = "https://files.pythonhosted.org/packages/09/48/54a89579ea36b6ae0ee001cba8c61f776451fad3c9306cd80f5b5c55be87/msgpack-1.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8ddb2bcfd1a8b9e431c8d6f4f7db0773084e107730ecf3472f1dfe9ad583f3d9", size = 78603 }, - { url = "https://files.pythonhosted.org/packages/a0/60/daba2699b308e95ae792cdc2ef092a38eb5ee422f9d2fbd4101526d8a210/msgpack-1.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:196a736f0526a03653d829d7d4c5500a97eea3648aebfd4b6743875f28aa2af8", size = 420504 }, - { url = "https://files.pythonhosted.org/packages/20/22/2ebae7ae43cd8f2debc35c631172ddf14e2a87ffcc04cf43ff9df9fff0d3/msgpack-1.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d592d06e3cc2f537ceeeb23d38799c6ad83255289bb84c2e5792e5a8dea268a", size = 423749 }, - { url = "https://files.pythonhosted.org/packages/40/1b/54c08dd5452427e1179a40b4b607e37e2664bca1c790c60c442c8e972e47/msgpack-1.1.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4df2311b0ce24f06ba253fda361f938dfecd7b961576f9be3f3fbd60e87130ac", size = 404458 }, - { url = "https://files.pythonhosted.org/packages/2e/60/6bb17e9ffb080616a51f09928fdd5cac1353c9becc6c4a8abd4e57269a16/msgpack-1.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e4141c5a32b5e37905b5940aacbc59739f036930367d7acce7a64e4dec1f5e0b", size = 405976 }, - { url = "https://files.pythonhosted.org/packages/ee/97/88983e266572e8707c1f4b99c8fd04f9eb97b43f2db40e3172d87d8642db/msgpack-1.1.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b1ce7f41670c5a69e1389420436f41385b1aa2504c3b0c30620764b15dded2e7", size = 408607 }, - { url = "https://files.pythonhosted.org/packages/bc/66/36c78af2efaffcc15a5a61ae0df53a1d025f2680122e2a9eb8442fed3ae4/msgpack-1.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4147151acabb9caed4e474c3344181e91ff7a388b888f1e19ea04f7e73dc7ad5", size = 424172 }, - { url = "https://files.pythonhosted.org/packages/8c/87/a75eb622b555708fe0427fab96056d39d4c9892b0c784b3a721088c7ee37/msgpack-1.1.1-cp313-cp313-win32.whl", hash = "sha256:500e85823a27d6d9bba1d057c871b4210c1dd6fb01fbb764e37e4e8847376323", size = 65347 }, - { url = "https://files.pythonhosted.org/packages/ca/91/7dc28d5e2a11a5ad804cf2b7f7a5fcb1eb5a4966d66a5d2b41aee6376543/msgpack-1.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:6d489fba546295983abd142812bda76b57e33d0b9f5d5b71c09a583285506f69", size = 72341 }, +sdist = { url = "https://files.pythonhosted.org/packages/45/b1/ea4f68038a18c77c9467400d166d74c4ffa536f34761f7983a104357e614/msgpack-1.1.1.tar.gz", hash = "sha256:77b79ce34a2bdab2594f490c8e80dd62a02d650b91a75159a63ec413b8d104cd", size = 173555, upload-time = "2025-06-13T06:52:51.324Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/52/f30da112c1dc92cf64f57d08a273ac771e7b29dea10b4b30369b2d7e8546/msgpack-1.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:353b6fc0c36fde68b661a12949d7d49f8f51ff5fa019c1e47c87c4ff34b080ed", size = 81799, upload-time = "2025-06-13T06:51:37.228Z" }, + { url = "https://files.pythonhosted.org/packages/e4/35/7bfc0def2f04ab4145f7f108e3563f9b4abae4ab0ed78a61f350518cc4d2/msgpack-1.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:79c408fcf76a958491b4e3b103d1c417044544b68e96d06432a189b43d1215c8", size = 78278, upload-time = "2025-06-13T06:51:38.534Z" }, + { url = "https://files.pythonhosted.org/packages/e8/c5/df5d6c1c39856bc55f800bf82778fd4c11370667f9b9e9d51b2f5da88f20/msgpack-1.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78426096939c2c7482bf31ef15ca219a9e24460289c00dd0b94411040bb73ad2", size = 402805, upload-time = "2025-06-13T06:51:39.538Z" }, + { url = "https://files.pythonhosted.org/packages/20/8e/0bb8c977efecfe6ea7116e2ed73a78a8d32a947f94d272586cf02a9757db/msgpack-1.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b17ba27727a36cb73aabacaa44b13090feb88a01d012c0f4be70c00f75048b4", size = 408642, upload-time = "2025-06-13T06:51:41.092Z" }, + { url = "https://files.pythonhosted.org/packages/59/a1/731d52c1aeec52006be6d1f8027c49fdc2cfc3ab7cbe7c28335b2910d7b6/msgpack-1.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7a17ac1ea6ec3c7687d70201cfda3b1e8061466f28f686c24f627cae4ea8efd0", size = 395143, upload-time = "2025-06-13T06:51:42.575Z" }, + { url = "https://files.pythonhosted.org/packages/2b/92/b42911c52cda2ba67a6418ffa7d08969edf2e760b09015593c8a8a27a97d/msgpack-1.1.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:88d1e966c9235c1d4e2afac21ca83933ba59537e2e2727a999bf3f515ca2af26", size = 395986, upload-time = "2025-06-13T06:51:43.807Z" }, + { url = "https://files.pythonhosted.org/packages/61/dc/8ae165337e70118d4dab651b8b562dd5066dd1e6dd57b038f32ebc3e2f07/msgpack-1.1.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:f6d58656842e1b2ddbe07f43f56b10a60f2ba5826164910968f5933e5178af75", size = 402682, upload-time = "2025-06-13T06:51:45.534Z" }, + { url = "https://files.pythonhosted.org/packages/58/27/555851cb98dcbd6ce041df1eacb25ac30646575e9cd125681aa2f4b1b6f1/msgpack-1.1.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:96decdfc4adcbc087f5ea7ebdcfd3dee9a13358cae6e81d54be962efc38f6338", size = 406368, upload-time = "2025-06-13T06:51:46.97Z" }, + { url = "https://files.pythonhosted.org/packages/d4/64/39a26add4ce16f24e99eabb9005e44c663db00e3fce17d4ae1ae9d61df99/msgpack-1.1.1-cp310-cp310-win32.whl", hash = "sha256:6640fd979ca9a212e4bcdf6eb74051ade2c690b862b679bfcb60ae46e6dc4bfd", size = 65004, upload-time = "2025-06-13T06:51:48.582Z" }, + { url = "https://files.pythonhosted.org/packages/7d/18/73dfa3e9d5d7450d39debde5b0d848139f7de23bd637a4506e36c9800fd6/msgpack-1.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:8b65b53204fe1bd037c40c4148d00ef918eb2108d24c9aaa20bc31f9810ce0a8", size = 71548, upload-time = "2025-06-13T06:51:49.558Z" }, + { url = "https://files.pythonhosted.org/packages/7f/83/97f24bf9848af23fe2ba04380388216defc49a8af6da0c28cc636d722502/msgpack-1.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:71ef05c1726884e44f8b1d1773604ab5d4d17729d8491403a705e649116c9558", size = 82728, upload-time = "2025-06-13T06:51:50.68Z" }, + { url = "https://files.pythonhosted.org/packages/aa/7f/2eaa388267a78401f6e182662b08a588ef4f3de6f0eab1ec09736a7aaa2b/msgpack-1.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:36043272c6aede309d29d56851f8841ba907a1a3d04435e43e8a19928e243c1d", size = 79279, upload-time = "2025-06-13T06:51:51.72Z" }, + { url = "https://files.pythonhosted.org/packages/f8/46/31eb60f4452c96161e4dfd26dbca562b4ec68c72e4ad07d9566d7ea35e8a/msgpack-1.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a32747b1b39c3ac27d0670122b57e6e57f28eefb725e0b625618d1b59bf9d1e0", size = 423859, upload-time = "2025-06-13T06:51:52.749Z" }, + { url = "https://files.pythonhosted.org/packages/45/16/a20fa8c32825cc7ae8457fab45670c7a8996d7746ce80ce41cc51e3b2bd7/msgpack-1.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a8b10fdb84a43e50d38057b06901ec9da52baac6983d3f709d8507f3889d43f", size = 429975, upload-time = "2025-06-13T06:51:53.97Z" }, + { url = "https://files.pythonhosted.org/packages/86/ea/6c958e07692367feeb1a1594d35e22b62f7f476f3c568b002a5ea09d443d/msgpack-1.1.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba0c325c3f485dc54ec298d8b024e134acf07c10d494ffa24373bea729acf704", size = 413528, upload-time = "2025-06-13T06:51:55.507Z" }, + { url = "https://files.pythonhosted.org/packages/75/05/ac84063c5dae79722bda9f68b878dc31fc3059adb8633c79f1e82c2cd946/msgpack-1.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:88daaf7d146e48ec71212ce21109b66e06a98e5e44dca47d853cbfe171d6c8d2", size = 413338, upload-time = "2025-06-13T06:51:57.023Z" }, + { url = "https://files.pythonhosted.org/packages/69/e8/fe86b082c781d3e1c09ca0f4dacd457ede60a13119b6ce939efe2ea77b76/msgpack-1.1.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:d8b55ea20dc59b181d3f47103f113e6f28a5e1c89fd5b67b9140edb442ab67f2", size = 422658, upload-time = "2025-06-13T06:51:58.419Z" }, + { url = "https://files.pythonhosted.org/packages/3b/2b/bafc9924df52d8f3bb7c00d24e57be477f4d0f967c0a31ef5e2225e035c7/msgpack-1.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4a28e8072ae9779f20427af07f53bbb8b4aa81151054e882aee333b158da8752", size = 427124, upload-time = "2025-06-13T06:51:59.969Z" }, + { url = "https://files.pythonhosted.org/packages/a2/3b/1f717e17e53e0ed0b68fa59e9188f3f610c79d7151f0e52ff3cd8eb6b2dc/msgpack-1.1.1-cp311-cp311-win32.whl", hash = "sha256:7da8831f9a0fdb526621ba09a281fadc58ea12701bc709e7b8cbc362feabc295", size = 65016, upload-time = "2025-06-13T06:52:01.294Z" }, + { url = "https://files.pythonhosted.org/packages/48/45/9d1780768d3b249accecc5a38c725eb1e203d44a191f7b7ff1941f7df60c/msgpack-1.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:5fd1b58e1431008a57247d6e7cc4faa41c3607e8e7d4aaf81f7c29ea013cb458", size = 72267, upload-time = "2025-06-13T06:52:02.568Z" }, + { url = "https://files.pythonhosted.org/packages/e3/26/389b9c593eda2b8551b2e7126ad3a06af6f9b44274eb3a4f054d48ff7e47/msgpack-1.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ae497b11f4c21558d95de9f64fff7053544f4d1a17731c866143ed6bb4591238", size = 82359, upload-time = "2025-06-13T06:52:03.909Z" }, + { url = "https://files.pythonhosted.org/packages/ab/65/7d1de38c8a22cf8b1551469159d4b6cf49be2126adc2482de50976084d78/msgpack-1.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:33be9ab121df9b6b461ff91baac6f2731f83d9b27ed948c5b9d1978ae28bf157", size = 79172, upload-time = "2025-06-13T06:52:05.246Z" }, + { url = "https://files.pythonhosted.org/packages/0f/bd/cacf208b64d9577a62c74b677e1ada005caa9b69a05a599889d6fc2ab20a/msgpack-1.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f64ae8fe7ffba251fecb8408540c34ee9df1c26674c50c4544d72dbf792e5ce", size = 425013, upload-time = "2025-06-13T06:52:06.341Z" }, + { url = "https://files.pythonhosted.org/packages/4d/ec/fd869e2567cc9c01278a736cfd1697941ba0d4b81a43e0aa2e8d71dab208/msgpack-1.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a494554874691720ba5891c9b0b39474ba43ffb1aaf32a5dac874effb1619e1a", size = 426905, upload-time = "2025-06-13T06:52:07.501Z" }, + { url = "https://files.pythonhosted.org/packages/55/2a/35860f33229075bce803a5593d046d8b489d7ba2fc85701e714fc1aaf898/msgpack-1.1.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cb643284ab0ed26f6957d969fe0dd8bb17beb567beb8998140b5e38a90974f6c", size = 407336, upload-time = "2025-06-13T06:52:09.047Z" }, + { url = "https://files.pythonhosted.org/packages/8c/16/69ed8f3ada150bf92745fb4921bd621fd2cdf5a42e25eb50bcc57a5328f0/msgpack-1.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d275a9e3c81b1093c060c3837e580c37f47c51eca031f7b5fb76f7b8470f5f9b", size = 409485, upload-time = "2025-06-13T06:52:10.382Z" }, + { url = "https://files.pythonhosted.org/packages/c6/b6/0c398039e4c6d0b2e37c61d7e0e9d13439f91f780686deb8ee64ecf1ae71/msgpack-1.1.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4fd6b577e4541676e0cc9ddc1709d25014d3ad9a66caa19962c4f5de30fc09ef", size = 412182, upload-time = "2025-06-13T06:52:11.644Z" }, + { url = "https://files.pythonhosted.org/packages/b8/d0/0cf4a6ecb9bc960d624c93effaeaae75cbf00b3bc4a54f35c8507273cda1/msgpack-1.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb29aaa613c0a1c40d1af111abf025f1732cab333f96f285d6a93b934738a68a", size = 419883, upload-time = "2025-06-13T06:52:12.806Z" }, + { url = "https://files.pythonhosted.org/packages/62/83/9697c211720fa71a2dfb632cad6196a8af3abea56eece220fde4674dc44b/msgpack-1.1.1-cp312-cp312-win32.whl", hash = "sha256:870b9a626280c86cff9c576ec0d9cbcc54a1e5ebda9cd26dab12baf41fee218c", size = 65406, upload-time = "2025-06-13T06:52:14.271Z" }, + { url = "https://files.pythonhosted.org/packages/c0/23/0abb886e80eab08f5e8c485d6f13924028602829f63b8f5fa25a06636628/msgpack-1.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:5692095123007180dca3e788bb4c399cc26626da51629a31d40207cb262e67f4", size = 72558, upload-time = "2025-06-13T06:52:15.252Z" }, + { url = "https://files.pythonhosted.org/packages/a1/38/561f01cf3577430b59b340b51329803d3a5bf6a45864a55f4ef308ac11e3/msgpack-1.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3765afa6bd4832fc11c3749be4ba4b69a0e8d7b728f78e68120a157a4c5d41f0", size = 81677, upload-time = "2025-06-13T06:52:16.64Z" }, + { url = "https://files.pythonhosted.org/packages/09/48/54a89579ea36b6ae0ee001cba8c61f776451fad3c9306cd80f5b5c55be87/msgpack-1.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8ddb2bcfd1a8b9e431c8d6f4f7db0773084e107730ecf3472f1dfe9ad583f3d9", size = 78603, upload-time = "2025-06-13T06:52:17.843Z" }, + { url = "https://files.pythonhosted.org/packages/a0/60/daba2699b308e95ae792cdc2ef092a38eb5ee422f9d2fbd4101526d8a210/msgpack-1.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:196a736f0526a03653d829d7d4c5500a97eea3648aebfd4b6743875f28aa2af8", size = 420504, upload-time = "2025-06-13T06:52:18.982Z" }, + { url = "https://files.pythonhosted.org/packages/20/22/2ebae7ae43cd8f2debc35c631172ddf14e2a87ffcc04cf43ff9df9fff0d3/msgpack-1.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d592d06e3cc2f537ceeeb23d38799c6ad83255289bb84c2e5792e5a8dea268a", size = 423749, upload-time = "2025-06-13T06:52:20.211Z" }, + { url = "https://files.pythonhosted.org/packages/40/1b/54c08dd5452427e1179a40b4b607e37e2664bca1c790c60c442c8e972e47/msgpack-1.1.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4df2311b0ce24f06ba253fda361f938dfecd7b961576f9be3f3fbd60e87130ac", size = 404458, upload-time = "2025-06-13T06:52:21.429Z" }, + { url = "https://files.pythonhosted.org/packages/2e/60/6bb17e9ffb080616a51f09928fdd5cac1353c9becc6c4a8abd4e57269a16/msgpack-1.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e4141c5a32b5e37905b5940aacbc59739f036930367d7acce7a64e4dec1f5e0b", size = 405976, upload-time = "2025-06-13T06:52:22.995Z" }, + { url = "https://files.pythonhosted.org/packages/ee/97/88983e266572e8707c1f4b99c8fd04f9eb97b43f2db40e3172d87d8642db/msgpack-1.1.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b1ce7f41670c5a69e1389420436f41385b1aa2504c3b0c30620764b15dded2e7", size = 408607, upload-time = "2025-06-13T06:52:24.152Z" }, + { url = "https://files.pythonhosted.org/packages/bc/66/36c78af2efaffcc15a5a61ae0df53a1d025f2680122e2a9eb8442fed3ae4/msgpack-1.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4147151acabb9caed4e474c3344181e91ff7a388b888f1e19ea04f7e73dc7ad5", size = 424172, upload-time = "2025-06-13T06:52:25.704Z" }, + { url = "https://files.pythonhosted.org/packages/8c/87/a75eb622b555708fe0427fab96056d39d4c9892b0c784b3a721088c7ee37/msgpack-1.1.1-cp313-cp313-win32.whl", hash = "sha256:500e85823a27d6d9bba1d057c871b4210c1dd6fb01fbb764e37e4e8847376323", size = 65347, upload-time = "2025-06-13T06:52:26.846Z" }, + { url = "https://files.pythonhosted.org/packages/ca/91/7dc28d5e2a11a5ad804cf2b7f7a5fcb1eb5a4966d66a5d2b41aee6376543/msgpack-1.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:6d489fba546295983abd142812bda76b57e33d0b9f5d5b71c09a583285506f69", size = 72341, upload-time = "2025-06-13T06:52:27.835Z" }, ] [[package]] @@ -1342,104 +1343,104 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3d/2c/5dad12e82fbdf7470f29bff2171484bf07cb3b16ada60a6589af8f376440/multidict-6.6.3.tar.gz", hash = "sha256:798a9eb12dab0a6c2e29c1de6f3468af5cb2da6053a20dfa3344907eed0937cc", size = 101006 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0b/67/414933982bce2efce7cbcb3169eaaf901e0f25baec69432b4874dfb1f297/multidict-6.6.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a2be5b7b35271f7fff1397204ba6708365e3d773579fe2a30625e16c4b4ce817", size = 77017 }, - { url = "https://files.pythonhosted.org/packages/8a/fe/d8a3ee1fad37dc2ef4f75488b0d9d4f25bf204aad8306cbab63d97bff64a/multidict-6.6.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:12f4581d2930840295c461764b9a65732ec01250b46c6b2c510d7ee68872b140", size = 44897 }, - { url = "https://files.pythonhosted.org/packages/1f/e0/265d89af8c98240265d82b8cbcf35897f83b76cd59ee3ab3879050fd8c45/multidict-6.6.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dd7793bab517e706c9ed9d7310b06c8672fd0aeee5781bfad612f56b8e0f7d14", size = 44574 }, - { url = "https://files.pythonhosted.org/packages/e6/05/6b759379f7e8e04ccc97cfb2a5dcc5cdbd44a97f072b2272dc51281e6a40/multidict-6.6.3-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:72d8815f2cd3cf3df0f83cac3f3ef801d908b2d90409ae28102e0553af85545a", size = 225729 }, - { url = "https://files.pythonhosted.org/packages/4e/f5/8d5a15488edd9a91fa4aad97228d785df208ed6298580883aa3d9def1959/multidict-6.6.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:531e331a2ee53543ab32b16334e2deb26f4e6b9b28e41f8e0c87e99a6c8e2d69", size = 242515 }, - { url = "https://files.pythonhosted.org/packages/6e/b5/a8f317d47d0ac5bb746d6d8325885c8967c2a8ce0bb57be5399e3642cccb/multidict-6.6.3-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:42ca5aa9329a63be8dc49040f63817d1ac980e02eeddba763a9ae5b4027b9c9c", size = 222224 }, - { url = "https://files.pythonhosted.org/packages/76/88/18b2a0d5e80515fa22716556061189c2853ecf2aa2133081ebbe85ebea38/multidict-6.6.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:208b9b9757060b9faa6f11ab4bc52846e4f3c2fb8b14d5680c8aac80af3dc751", size = 253124 }, - { url = "https://files.pythonhosted.org/packages/62/bf/ebfcfd6b55a1b05ef16d0775ae34c0fe15e8dab570d69ca9941073b969e7/multidict-6.6.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:acf6b97bd0884891af6a8b43d0f586ab2fcf8e717cbd47ab4bdddc09e20652d8", size = 251529 }, - { url = "https://files.pythonhosted.org/packages/44/11/780615a98fd3775fc309d0234d563941af69ade2df0bb82c91dda6ddaea1/multidict-6.6.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:68e9e12ed00e2089725669bdc88602b0b6f8d23c0c95e52b95f0bc69f7fe9b55", size = 241627 }, - { url = "https://files.pythonhosted.org/packages/28/3d/35f33045e21034b388686213752cabc3a1b9d03e20969e6fa8f1b1d82db1/multidict-6.6.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:05db2f66c9addb10cfa226e1acb363450fab2ff8a6df73c622fefe2f5af6d4e7", size = 239351 }, - { url = "https://files.pythonhosted.org/packages/6e/cc/ff84c03b95b430015d2166d9aae775a3985d757b94f6635010d0038d9241/multidict-6.6.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:0db58da8eafb514db832a1b44f8fa7906fdd102f7d982025f816a93ba45e3dcb", size = 233429 }, - { url = "https://files.pythonhosted.org/packages/2e/f0/8cd49a0b37bdea673a4b793c2093f2f4ba8e7c9d6d7c9bd672fd6d38cd11/multidict-6.6.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:14117a41c8fdb3ee19c743b1c027da0736fdb79584d61a766da53d399b71176c", size = 243094 }, - { url = "https://files.pythonhosted.org/packages/96/19/5d9a0cfdafe65d82b616a45ae950975820289069f885328e8185e64283c2/multidict-6.6.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:877443eaaabcd0b74ff32ebeed6f6176c71850feb7d6a1d2db65945256ea535c", size = 248957 }, - { url = "https://files.pythonhosted.org/packages/e6/dc/c90066151da87d1e489f147b9b4327927241e65f1876702fafec6729c014/multidict-6.6.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:70b72e749a4f6e7ed8fb334fa8d8496384840319512746a5f42fa0aec79f4d61", size = 243590 }, - { url = "https://files.pythonhosted.org/packages/ec/39/458afb0cccbb0ee9164365273be3e039efddcfcb94ef35924b7dbdb05db0/multidict-6.6.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:43571f785b86afd02b3855c5ac8e86ec921b760298d6f82ff2a61daf5a35330b", size = 237487 }, - { url = "https://files.pythonhosted.org/packages/35/38/0016adac3990426610a081787011177e661875546b434f50a26319dc8372/multidict-6.6.3-cp310-cp310-win32.whl", hash = "sha256:20c5a0c3c13a15fd5ea86c42311859f970070e4e24de5a550e99d7c271d76318", size = 41390 }, - { url = "https://files.pythonhosted.org/packages/f3/d2/17897a8f3f2c5363d969b4c635aa40375fe1f09168dc09a7826780bfb2a4/multidict-6.6.3-cp310-cp310-win_amd64.whl", hash = "sha256:ab0a34a007704c625e25a9116c6770b4d3617a071c8a7c30cd338dfbadfe6485", size = 45954 }, - { url = "https://files.pythonhosted.org/packages/2d/5f/d4a717c1e457fe44072e33fa400d2b93eb0f2819c4d669381f925b7cba1f/multidict-6.6.3-cp310-cp310-win_arm64.whl", hash = "sha256:769841d70ca8bdd140a715746199fc6473414bd02efd678d75681d2d6a8986c5", size = 42981 }, - { url = "https://files.pythonhosted.org/packages/08/f0/1a39863ced51f639c81a5463fbfa9eb4df59c20d1a8769ab9ef4ca57ae04/multidict-6.6.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:18f4eba0cbac3546b8ae31e0bbc55b02c801ae3cbaf80c247fcdd89b456ff58c", size = 76445 }, - { url = "https://files.pythonhosted.org/packages/c9/0e/a7cfa451c7b0365cd844e90b41e21fab32edaa1e42fc0c9f68461ce44ed7/multidict-6.6.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef43b5dd842382329e4797c46f10748d8c2b6e0614f46b4afe4aee9ac33159df", size = 44610 }, - { url = "https://files.pythonhosted.org/packages/c6/bb/a14a4efc5ee748cc1904b0748be278c31b9295ce5f4d2ef66526f410b94d/multidict-6.6.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bf9bd1fd5eec01494e0f2e8e446a74a85d5e49afb63d75a9934e4a5423dba21d", size = 44267 }, - { url = "https://files.pythonhosted.org/packages/c2/f8/410677d563c2d55e063ef74fe578f9d53fe6b0a51649597a5861f83ffa15/multidict-6.6.3-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:5bd8d6f793a787153956cd35e24f60485bf0651c238e207b9a54f7458b16d539", size = 230004 }, - { url = "https://files.pythonhosted.org/packages/fd/df/2b787f80059314a98e1ec6a4cc7576244986df3e56b3c755e6fc7c99e038/multidict-6.6.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1bf99b4daf908c73856bd87ee0a2499c3c9a3d19bb04b9c6025e66af3fd07462", size = 247196 }, - { url = "https://files.pythonhosted.org/packages/05/f2/f9117089151b9a8ab39f9019620d10d9718eec2ac89e7ca9d30f3ec78e96/multidict-6.6.3-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0b9e59946b49dafaf990fd9c17ceafa62976e8471a14952163d10a7a630413a9", size = 225337 }, - { url = "https://files.pythonhosted.org/packages/93/2d/7115300ec5b699faa152c56799b089a53ed69e399c3c2d528251f0aeda1a/multidict-6.6.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e2db616467070d0533832d204c54eea6836a5e628f2cb1e6dfd8cd6ba7277cb7", size = 257079 }, - { url = "https://files.pythonhosted.org/packages/15/ea/ff4bab367623e39c20d3b07637225c7688d79e4f3cc1f3b9f89867677f9a/multidict-6.6.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7394888236621f61dcdd25189b2768ae5cc280f041029a5bcf1122ac63df79f9", size = 255461 }, - { url = "https://files.pythonhosted.org/packages/74/07/2c9246cda322dfe08be85f1b8739646f2c4c5113a1422d7a407763422ec4/multidict-6.6.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f114d8478733ca7388e7c7e0ab34b72547476b97009d643644ac33d4d3fe1821", size = 246611 }, - { url = "https://files.pythonhosted.org/packages/a8/62/279c13d584207d5697a752a66ffc9bb19355a95f7659140cb1b3cf82180e/multidict-6.6.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cdf22e4db76d323bcdc733514bf732e9fb349707c98d341d40ebcc6e9318ef3d", size = 243102 }, - { url = "https://files.pythonhosted.org/packages/69/cc/e06636f48c6d51e724a8bc8d9e1db5f136fe1df066d7cafe37ef4000f86a/multidict-6.6.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:e995a34c3d44ab511bfc11aa26869b9d66c2d8c799fa0e74b28a473a692532d6", size = 238693 }, - { url = "https://files.pythonhosted.org/packages/89/a4/66c9d8fb9acf3b226cdd468ed009537ac65b520aebdc1703dd6908b19d33/multidict-6.6.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:766a4a5996f54361d8d5a9050140aa5362fe48ce51c755a50c0bc3706460c430", size = 246582 }, - { url = "https://files.pythonhosted.org/packages/cf/01/c69e0317be556e46257826d5449feb4e6aa0d18573e567a48a2c14156f1f/multidict-6.6.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:3893a0d7d28a7fe6ca7a1f760593bc13038d1d35daf52199d431b61d2660602b", size = 253355 }, - { url = "https://files.pythonhosted.org/packages/c0/da/9cc1da0299762d20e626fe0042e71b5694f9f72d7d3f9678397cbaa71b2b/multidict-6.6.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:934796c81ea996e61914ba58064920d6cad5d99140ac3167901eb932150e2e56", size = 247774 }, - { url = "https://files.pythonhosted.org/packages/e6/91/b22756afec99cc31105ddd4a52f95ab32b1a4a58f4d417979c570c4a922e/multidict-6.6.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9ed948328aec2072bc00f05d961ceadfd3e9bfc2966c1319aeaf7b7c21219183", size = 242275 }, - { url = "https://files.pythonhosted.org/packages/be/f1/adcc185b878036a20399d5be5228f3cbe7f823d78985d101d425af35c800/multidict-6.6.3-cp311-cp311-win32.whl", hash = "sha256:9f5b28c074c76afc3e4c610c488e3493976fe0e596dd3db6c8ddfbb0134dcac5", size = 41290 }, - { url = "https://files.pythonhosted.org/packages/e0/d4/27652c1c6526ea6b4f5ddd397e93f4232ff5de42bea71d339bc6a6cc497f/multidict-6.6.3-cp311-cp311-win_amd64.whl", hash = "sha256:bc7f6fbc61b1c16050a389c630da0b32fc6d4a3d191394ab78972bf5edc568c2", size = 45942 }, - { url = "https://files.pythonhosted.org/packages/16/18/23f4932019804e56d3c2413e237f866444b774b0263bcb81df2fdecaf593/multidict-6.6.3-cp311-cp311-win_arm64.whl", hash = "sha256:d4e47d8faffaae822fb5cba20937c048d4f734f43572e7079298a6c39fb172cb", size = 42880 }, - { url = "https://files.pythonhosted.org/packages/0e/a0/6b57988ea102da0623ea814160ed78d45a2645e4bbb499c2896d12833a70/multidict-6.6.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:056bebbeda16b2e38642d75e9e5310c484b7c24e3841dc0fb943206a72ec89d6", size = 76514 }, - { url = "https://files.pythonhosted.org/packages/07/7a/d1e92665b0850c6c0508f101f9cf0410c1afa24973e1115fe9c6a185ebf7/multidict-6.6.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e5f481cccb3c5c5e5de5d00b5141dc589c1047e60d07e85bbd7dea3d4580d63f", size = 45394 }, - { url = "https://files.pythonhosted.org/packages/52/6f/dd104490e01be6ef8bf9573705d8572f8c2d2c561f06e3826b081d9e6591/multidict-6.6.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:10bea2ee839a759ee368b5a6e47787f399b41e70cf0c20d90dfaf4158dfb4e55", size = 43590 }, - { url = "https://files.pythonhosted.org/packages/44/fe/06e0e01b1b0611e6581b7fd5a85b43dacc08b6cea3034f902f383b0873e5/multidict-6.6.3-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:2334cfb0fa9549d6ce2c21af2bfbcd3ac4ec3646b1b1581c88e3e2b1779ec92b", size = 237292 }, - { url = "https://files.pythonhosted.org/packages/ce/71/4f0e558fb77696b89c233c1ee2d92f3e1d5459070a0e89153c9e9e804186/multidict-6.6.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8fee016722550a2276ca2cb5bb624480e0ed2bd49125b2b73b7010b9090e888", size = 258385 }, - { url = "https://files.pythonhosted.org/packages/e3/25/cca0e68228addad24903801ed1ab42e21307a1b4b6dd2cf63da5d3ae082a/multidict-6.6.3-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5511cb35f5c50a2db21047c875eb42f308c5583edf96bd8ebf7d770a9d68f6d", size = 242328 }, - { url = "https://files.pythonhosted.org/packages/6e/a3/46f2d420d86bbcb8fe660b26a10a219871a0fbf4d43cb846a4031533f3e0/multidict-6.6.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:712b348f7f449948e0a6c4564a21c7db965af900973a67db432d724619b3c680", size = 268057 }, - { url = "https://files.pythonhosted.org/packages/9e/73/1c743542fe00794a2ec7466abd3f312ccb8fad8dff9f36d42e18fb1ec33e/multidict-6.6.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e4e15d2138ee2694e038e33b7c3da70e6b0ad8868b9f8094a72e1414aeda9c1a", size = 269341 }, - { url = "https://files.pythonhosted.org/packages/a4/11/6ec9dcbe2264b92778eeb85407d1df18812248bf3506a5a1754bc035db0c/multidict-6.6.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8df25594989aebff8a130f7899fa03cbfcc5d2b5f4a461cf2518236fe6f15961", size = 256081 }, - { url = "https://files.pythonhosted.org/packages/9b/2b/631b1e2afeb5f1696846d747d36cda075bfdc0bc7245d6ba5c319278d6c4/multidict-6.6.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:159ca68bfd284a8860f8d8112cf0521113bffd9c17568579e4d13d1f1dc76b65", size = 253581 }, - { url = "https://files.pythonhosted.org/packages/bf/0e/7e3b93f79efeb6111d3bf9a1a69e555ba1d07ad1c11bceb56b7310d0d7ee/multidict-6.6.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:e098c17856a8c9ade81b4810888c5ad1914099657226283cab3062c0540b0643", size = 250750 }, - { url = "https://files.pythonhosted.org/packages/ad/9e/086846c1d6601948e7de556ee464a2d4c85e33883e749f46b9547d7b0704/multidict-6.6.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:67c92ed673049dec52d7ed39f8cf9ebbadf5032c774058b4406d18c8f8fe7063", size = 251548 }, - { url = "https://files.pythonhosted.org/packages/8c/7b/86ec260118e522f1a31550e87b23542294880c97cfbf6fb18cc67b044c66/multidict-6.6.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:bd0578596e3a835ef451784053cfd327d607fc39ea1a14812139339a18a0dbc3", size = 262718 }, - { url = "https://files.pythonhosted.org/packages/8c/bd/22ce8f47abb0be04692c9fc4638508b8340987b18691aa7775d927b73f72/multidict-6.6.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:346055630a2df2115cd23ae271910b4cae40f4e336773550dca4889b12916e75", size = 259603 }, - { url = "https://files.pythonhosted.org/packages/07/9c/91b7ac1691be95cd1f4a26e36a74b97cda6aa9820632d31aab4410f46ebd/multidict-6.6.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:555ff55a359302b79de97e0468e9ee80637b0de1fce77721639f7cd9440b3a10", size = 251351 }, - { url = "https://files.pythonhosted.org/packages/6f/5c/4d7adc739884f7a9fbe00d1eac8c034023ef8bad71f2ebe12823ca2e3649/multidict-6.6.3-cp312-cp312-win32.whl", hash = "sha256:73ab034fb8d58ff85c2bcbadc470efc3fafeea8affcf8722855fb94557f14cc5", size = 41860 }, - { url = "https://files.pythonhosted.org/packages/6a/a3/0fbc7afdf7cb1aa12a086b02959307848eb6bcc8f66fcb66c0cb57e2a2c1/multidict-6.6.3-cp312-cp312-win_amd64.whl", hash = "sha256:04cbcce84f63b9af41bad04a54d4cc4e60e90c35b9e6ccb130be2d75b71f8c17", size = 45982 }, - { url = "https://files.pythonhosted.org/packages/b8/95/8c825bd70ff9b02462dc18d1295dd08d3e9e4eb66856d292ffa62cfe1920/multidict-6.6.3-cp312-cp312-win_arm64.whl", hash = "sha256:0f1130b896ecb52d2a1e615260f3ea2af55fa7dc3d7c3003ba0c3121a759b18b", size = 43210 }, - { url = "https://files.pythonhosted.org/packages/52/1d/0bebcbbb4f000751fbd09957257903d6e002943fc668d841a4cf2fb7f872/multidict-6.6.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:540d3c06d48507357a7d57721e5094b4f7093399a0106c211f33540fdc374d55", size = 75843 }, - { url = "https://files.pythonhosted.org/packages/07/8f/cbe241b0434cfe257f65c2b1bcf9e8d5fb52bc708c5061fb29b0fed22bdf/multidict-6.6.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9c19cea2a690f04247d43f366d03e4eb110a0dc4cd1bbeee4d445435428ed35b", size = 45053 }, - { url = "https://files.pythonhosted.org/packages/32/d2/0b3b23f9dbad5b270b22a3ac3ea73ed0a50ef2d9a390447061178ed6bdb8/multidict-6.6.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7af039820cfd00effec86bda5d8debef711a3e86a1d3772e85bea0f243a4bd65", size = 43273 }, - { url = "https://files.pythonhosted.org/packages/fd/fe/6eb68927e823999e3683bc49678eb20374ba9615097d085298fd5b386564/multidict-6.6.3-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:500b84f51654fdc3944e936f2922114349bf8fdcac77c3092b03449f0e5bc2b3", size = 237124 }, - { url = "https://files.pythonhosted.org/packages/e7/ab/320d8507e7726c460cb77117848b3834ea0d59e769f36fdae495f7669929/multidict-6.6.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f3fc723ab8a5c5ed6c50418e9bfcd8e6dceba6c271cee6728a10a4ed8561520c", size = 256892 }, - { url = "https://files.pythonhosted.org/packages/76/60/38ee422db515ac69834e60142a1a69111ac96026e76e8e9aa347fd2e4591/multidict-6.6.3-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:94c47ea3ade005b5976789baaed66d4de4480d0a0bf31cef6edaa41c1e7b56a6", size = 240547 }, - { url = "https://files.pythonhosted.org/packages/27/fb/905224fde2dff042b030c27ad95a7ae744325cf54b890b443d30a789b80e/multidict-6.6.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dbc7cf464cc6d67e83e136c9f55726da3a30176f020a36ead246eceed87f1cd8", size = 266223 }, - { url = "https://files.pythonhosted.org/packages/76/35/dc38ab361051beae08d1a53965e3e1a418752fc5be4d3fb983c5582d8784/multidict-6.6.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:900eb9f9da25ada070f8ee4a23f884e0ee66fe4e1a38c3af644256a508ad81ca", size = 267262 }, - { url = "https://files.pythonhosted.org/packages/1f/a3/0a485b7f36e422421b17e2bbb5a81c1af10eac1d4476f2ff92927c730479/multidict-6.6.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7c6df517cf177da5d47ab15407143a89cd1a23f8b335f3a28d57e8b0a3dbb884", size = 254345 }, - { url = "https://files.pythonhosted.org/packages/b4/59/bcdd52c1dab7c0e0d75ff19cac751fbd5f850d1fc39172ce809a74aa9ea4/multidict-6.6.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4ef421045f13879e21c994b36e728d8e7d126c91a64b9185810ab51d474f27e7", size = 252248 }, - { url = "https://files.pythonhosted.org/packages/bb/a4/2d96aaa6eae8067ce108d4acee6f45ced5728beda55c0f02ae1072c730d1/multidict-6.6.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:6c1e61bb4f80895c081790b6b09fa49e13566df8fbff817da3f85b3a8192e36b", size = 250115 }, - { url = "https://files.pythonhosted.org/packages/25/d2/ed9f847fa5c7d0677d4f02ea2c163d5e48573de3f57bacf5670e43a5ffaa/multidict-6.6.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e5e8523bb12d7623cd8300dbd91b9e439a46a028cd078ca695eb66ba31adee3c", size = 249649 }, - { url = "https://files.pythonhosted.org/packages/1f/af/9155850372563fc550803d3f25373308aa70f59b52cff25854086ecb4a79/multidict-6.6.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:ef58340cc896219e4e653dade08fea5c55c6df41bcc68122e3be3e9d873d9a7b", size = 261203 }, - { url = "https://files.pythonhosted.org/packages/36/2f/c6a728f699896252cf309769089568a33c6439626648843f78743660709d/multidict-6.6.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fc9dc435ec8699e7b602b94fe0cd4703e69273a01cbc34409af29e7820f777f1", size = 258051 }, - { url = "https://files.pythonhosted.org/packages/d0/60/689880776d6b18fa2b70f6cc74ff87dd6c6b9b47bd9cf74c16fecfaa6ad9/multidict-6.6.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9e864486ef4ab07db5e9cb997bad2b681514158d6954dd1958dfb163b83d53e6", size = 249601 }, - { url = "https://files.pythonhosted.org/packages/75/5e/325b11f2222a549019cf2ef879c1f81f94a0d40ace3ef55cf529915ba6cc/multidict-6.6.3-cp313-cp313-win32.whl", hash = "sha256:5633a82fba8e841bc5c5c06b16e21529573cd654f67fd833650a215520a6210e", size = 41683 }, - { url = "https://files.pythonhosted.org/packages/b1/ad/cf46e73f5d6e3c775cabd2a05976547f3f18b39bee06260369a42501f053/multidict-6.6.3-cp313-cp313-win_amd64.whl", hash = "sha256:e93089c1570a4ad54c3714a12c2cef549dc9d58e97bcded193d928649cab78e9", size = 45811 }, - { url = "https://files.pythonhosted.org/packages/c5/c9/2e3fe950db28fb7c62e1a5f46e1e38759b072e2089209bc033c2798bb5ec/multidict-6.6.3-cp313-cp313-win_arm64.whl", hash = "sha256:c60b401f192e79caec61f166da9c924e9f8bc65548d4246842df91651e83d600", size = 43056 }, - { url = "https://files.pythonhosted.org/packages/3a/58/aaf8114cf34966e084a8cc9517771288adb53465188843d5a19862cb6dc3/multidict-6.6.3-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:02fd8f32d403a6ff13864b0851f1f523d4c988051eea0471d4f1fd8010f11134", size = 82811 }, - { url = "https://files.pythonhosted.org/packages/71/af/5402e7b58a1f5b987a07ad98f2501fdba2a4f4b4c30cf114e3ce8db64c87/multidict-6.6.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:f3aa090106b1543f3f87b2041eef3c156c8da2aed90c63a2fbed62d875c49c37", size = 48304 }, - { url = "https://files.pythonhosted.org/packages/39/65/ab3c8cafe21adb45b24a50266fd747147dec7847425bc2a0f6934b3ae9ce/multidict-6.6.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e924fb978615a5e33ff644cc42e6aa241effcf4f3322c09d4f8cebde95aff5f8", size = 46775 }, - { url = "https://files.pythonhosted.org/packages/49/ba/9fcc1b332f67cc0c0c8079e263bfab6660f87fe4e28a35921771ff3eea0d/multidict-6.6.3-cp313-cp313t-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:b9fe5a0e57c6dbd0e2ce81ca66272282c32cd11d31658ee9553849d91289e1c1", size = 229773 }, - { url = "https://files.pythonhosted.org/packages/a4/14/0145a251f555f7c754ce2dcbcd012939bbd1f34f066fa5d28a50e722a054/multidict-6.6.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b24576f208793ebae00280c59927c3b7c2a3b1655e443a25f753c4611bc1c373", size = 250083 }, - { url = "https://files.pythonhosted.org/packages/9e/d4/d5c0bd2bbb173b586c249a151a26d2fb3ec7d53c96e42091c9fef4e1f10c/multidict-6.6.3-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:135631cb6c58eac37d7ac0df380294fecdc026b28837fa07c02e459c7fb9c54e", size = 228980 }, - { url = "https://files.pythonhosted.org/packages/21/32/c9a2d8444a50ec48c4733ccc67254100c10e1c8ae8e40c7a2d2183b59b97/multidict-6.6.3-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:274d416b0df887aef98f19f21578653982cfb8a05b4e187d4a17103322eeaf8f", size = 257776 }, - { url = "https://files.pythonhosted.org/packages/68/d0/14fa1699f4ef629eae08ad6201c6b476098f5efb051b296f4c26be7a9fdf/multidict-6.6.3-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e252017a817fad7ce05cafbe5711ed40faeb580e63b16755a3a24e66fa1d87c0", size = 256882 }, - { url = "https://files.pythonhosted.org/packages/da/88/84a27570fbe303c65607d517a5f147cd2fc046c2d1da02b84b17b9bdc2aa/multidict-6.6.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e4cc8d848cd4fe1cdee28c13ea79ab0ed37fc2e89dd77bac86a2e7959a8c3bc", size = 247816 }, - { url = "https://files.pythonhosted.org/packages/1c/60/dca352a0c999ce96a5d8b8ee0b2b9f729dcad2e0b0c195f8286269a2074c/multidict-6.6.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9e236a7094b9c4c1b7585f6b9cca34b9d833cf079f7e4c49e6a4a6ec9bfdc68f", size = 245341 }, - { url = "https://files.pythonhosted.org/packages/50/ef/433fa3ed06028f03946f3993223dada70fb700f763f70c00079533c34578/multidict-6.6.3-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:e0cb0ab69915c55627c933f0b555a943d98ba71b4d1c57bc0d0a66e2567c7471", size = 235854 }, - { url = "https://files.pythonhosted.org/packages/1b/1f/487612ab56fbe35715320905215a57fede20de7db40a261759690dc80471/multidict-6.6.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:81ef2f64593aba09c5212a3d0f8c906a0d38d710a011f2f42759704d4557d3f2", size = 243432 }, - { url = "https://files.pythonhosted.org/packages/da/6f/ce8b79de16cd885c6f9052c96a3671373d00c59b3ee635ea93e6e81b8ccf/multidict-6.6.3-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:b9cbc60010de3562545fa198bfc6d3825df430ea96d2cc509c39bd71e2e7d648", size = 252731 }, - { url = "https://files.pythonhosted.org/packages/bb/fe/a2514a6aba78e5abefa1624ca85ae18f542d95ac5cde2e3815a9fbf369aa/multidict-6.6.3-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:70d974eaaa37211390cd02ef93b7e938de564bbffa866f0b08d07e5e65da783d", size = 247086 }, - { url = "https://files.pythonhosted.org/packages/8c/22/b788718d63bb3cce752d107a57c85fcd1a212c6c778628567c9713f9345a/multidict-6.6.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:3713303e4a6663c6d01d648a68f2848701001f3390a030edaaf3fc949c90bf7c", size = 243338 }, - { url = "https://files.pythonhosted.org/packages/22/d6/fdb3d0670819f2228f3f7d9af613d5e652c15d170c83e5f1c94fbc55a25b/multidict-6.6.3-cp313-cp313t-win32.whl", hash = "sha256:639ecc9fe7cd73f2495f62c213e964843826f44505a3e5d82805aa85cac6f89e", size = 47812 }, - { url = "https://files.pythonhosted.org/packages/b6/d6/a9d2c808f2c489ad199723197419207ecbfbc1776f6e155e1ecea9c883aa/multidict-6.6.3-cp313-cp313t-win_amd64.whl", hash = "sha256:9f97e181f344a0ef3881b573d31de8542cc0dbc559ec68c8f8b5ce2c2e91646d", size = 53011 }, - { url = "https://files.pythonhosted.org/packages/f2/40/b68001cba8188dd267590a111f9661b6256debc327137667e832bf5d66e8/multidict-6.6.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ce8b7693da41a3c4fde5871c738a81490cea5496c671d74374c8ab889e1834fb", size = 45254 }, - { url = "https://files.pythonhosted.org/packages/d8/30/9aec301e9772b098c1f5c0ca0279237c9766d94b97802e9888010c64b0ed/multidict-6.6.3-py3-none-any.whl", hash = "sha256:8db10f29c7541fc5da4defd8cd697e1ca429db743fa716325f236079b96f775a", size = 12313 }, +sdist = { url = "https://files.pythonhosted.org/packages/3d/2c/5dad12e82fbdf7470f29bff2171484bf07cb3b16ada60a6589af8f376440/multidict-6.6.3.tar.gz", hash = "sha256:798a9eb12dab0a6c2e29c1de6f3468af5cb2da6053a20dfa3344907eed0937cc", size = 101006, upload-time = "2025-06-30T15:53:46.929Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/67/414933982bce2efce7cbcb3169eaaf901e0f25baec69432b4874dfb1f297/multidict-6.6.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a2be5b7b35271f7fff1397204ba6708365e3d773579fe2a30625e16c4b4ce817", size = 77017, upload-time = "2025-06-30T15:50:58.931Z" }, + { url = "https://files.pythonhosted.org/packages/8a/fe/d8a3ee1fad37dc2ef4f75488b0d9d4f25bf204aad8306cbab63d97bff64a/multidict-6.6.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:12f4581d2930840295c461764b9a65732ec01250b46c6b2c510d7ee68872b140", size = 44897, upload-time = "2025-06-30T15:51:00.999Z" }, + { url = "https://files.pythonhosted.org/packages/1f/e0/265d89af8c98240265d82b8cbcf35897f83b76cd59ee3ab3879050fd8c45/multidict-6.6.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dd7793bab517e706c9ed9d7310b06c8672fd0aeee5781bfad612f56b8e0f7d14", size = 44574, upload-time = "2025-06-30T15:51:02.449Z" }, + { url = "https://files.pythonhosted.org/packages/e6/05/6b759379f7e8e04ccc97cfb2a5dcc5cdbd44a97f072b2272dc51281e6a40/multidict-6.6.3-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:72d8815f2cd3cf3df0f83cac3f3ef801d908b2d90409ae28102e0553af85545a", size = 225729, upload-time = "2025-06-30T15:51:03.794Z" }, + { url = "https://files.pythonhosted.org/packages/4e/f5/8d5a15488edd9a91fa4aad97228d785df208ed6298580883aa3d9def1959/multidict-6.6.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:531e331a2ee53543ab32b16334e2deb26f4e6b9b28e41f8e0c87e99a6c8e2d69", size = 242515, upload-time = "2025-06-30T15:51:05.002Z" }, + { url = "https://files.pythonhosted.org/packages/6e/b5/a8f317d47d0ac5bb746d6d8325885c8967c2a8ce0bb57be5399e3642cccb/multidict-6.6.3-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:42ca5aa9329a63be8dc49040f63817d1ac980e02eeddba763a9ae5b4027b9c9c", size = 222224, upload-time = "2025-06-30T15:51:06.148Z" }, + { url = "https://files.pythonhosted.org/packages/76/88/18b2a0d5e80515fa22716556061189c2853ecf2aa2133081ebbe85ebea38/multidict-6.6.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:208b9b9757060b9faa6f11ab4bc52846e4f3c2fb8b14d5680c8aac80af3dc751", size = 253124, upload-time = "2025-06-30T15:51:07.375Z" }, + { url = "https://files.pythonhosted.org/packages/62/bf/ebfcfd6b55a1b05ef16d0775ae34c0fe15e8dab570d69ca9941073b969e7/multidict-6.6.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:acf6b97bd0884891af6a8b43d0f586ab2fcf8e717cbd47ab4bdddc09e20652d8", size = 251529, upload-time = "2025-06-30T15:51:08.691Z" }, + { url = "https://files.pythonhosted.org/packages/44/11/780615a98fd3775fc309d0234d563941af69ade2df0bb82c91dda6ddaea1/multidict-6.6.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:68e9e12ed00e2089725669bdc88602b0b6f8d23c0c95e52b95f0bc69f7fe9b55", size = 241627, upload-time = "2025-06-30T15:51:10.605Z" }, + { url = "https://files.pythonhosted.org/packages/28/3d/35f33045e21034b388686213752cabc3a1b9d03e20969e6fa8f1b1d82db1/multidict-6.6.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:05db2f66c9addb10cfa226e1acb363450fab2ff8a6df73c622fefe2f5af6d4e7", size = 239351, upload-time = "2025-06-30T15:51:12.18Z" }, + { url = "https://files.pythonhosted.org/packages/6e/cc/ff84c03b95b430015d2166d9aae775a3985d757b94f6635010d0038d9241/multidict-6.6.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:0db58da8eafb514db832a1b44f8fa7906fdd102f7d982025f816a93ba45e3dcb", size = 233429, upload-time = "2025-06-30T15:51:13.533Z" }, + { url = "https://files.pythonhosted.org/packages/2e/f0/8cd49a0b37bdea673a4b793c2093f2f4ba8e7c9d6d7c9bd672fd6d38cd11/multidict-6.6.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:14117a41c8fdb3ee19c743b1c027da0736fdb79584d61a766da53d399b71176c", size = 243094, upload-time = "2025-06-30T15:51:14.815Z" }, + { url = "https://files.pythonhosted.org/packages/96/19/5d9a0cfdafe65d82b616a45ae950975820289069f885328e8185e64283c2/multidict-6.6.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:877443eaaabcd0b74ff32ebeed6f6176c71850feb7d6a1d2db65945256ea535c", size = 248957, upload-time = "2025-06-30T15:51:16.076Z" }, + { url = "https://files.pythonhosted.org/packages/e6/dc/c90066151da87d1e489f147b9b4327927241e65f1876702fafec6729c014/multidict-6.6.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:70b72e749a4f6e7ed8fb334fa8d8496384840319512746a5f42fa0aec79f4d61", size = 243590, upload-time = "2025-06-30T15:51:17.413Z" }, + { url = "https://files.pythonhosted.org/packages/ec/39/458afb0cccbb0ee9164365273be3e039efddcfcb94ef35924b7dbdb05db0/multidict-6.6.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:43571f785b86afd02b3855c5ac8e86ec921b760298d6f82ff2a61daf5a35330b", size = 237487, upload-time = "2025-06-30T15:51:19.039Z" }, + { url = "https://files.pythonhosted.org/packages/35/38/0016adac3990426610a081787011177e661875546b434f50a26319dc8372/multidict-6.6.3-cp310-cp310-win32.whl", hash = "sha256:20c5a0c3c13a15fd5ea86c42311859f970070e4e24de5a550e99d7c271d76318", size = 41390, upload-time = "2025-06-30T15:51:20.362Z" }, + { url = "https://files.pythonhosted.org/packages/f3/d2/17897a8f3f2c5363d969b4c635aa40375fe1f09168dc09a7826780bfb2a4/multidict-6.6.3-cp310-cp310-win_amd64.whl", hash = "sha256:ab0a34a007704c625e25a9116c6770b4d3617a071c8a7c30cd338dfbadfe6485", size = 45954, upload-time = "2025-06-30T15:51:21.383Z" }, + { url = "https://files.pythonhosted.org/packages/2d/5f/d4a717c1e457fe44072e33fa400d2b93eb0f2819c4d669381f925b7cba1f/multidict-6.6.3-cp310-cp310-win_arm64.whl", hash = "sha256:769841d70ca8bdd140a715746199fc6473414bd02efd678d75681d2d6a8986c5", size = 42981, upload-time = "2025-06-30T15:51:22.809Z" }, + { url = "https://files.pythonhosted.org/packages/08/f0/1a39863ced51f639c81a5463fbfa9eb4df59c20d1a8769ab9ef4ca57ae04/multidict-6.6.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:18f4eba0cbac3546b8ae31e0bbc55b02c801ae3cbaf80c247fcdd89b456ff58c", size = 76445, upload-time = "2025-06-30T15:51:24.01Z" }, + { url = "https://files.pythonhosted.org/packages/c9/0e/a7cfa451c7b0365cd844e90b41e21fab32edaa1e42fc0c9f68461ce44ed7/multidict-6.6.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef43b5dd842382329e4797c46f10748d8c2b6e0614f46b4afe4aee9ac33159df", size = 44610, upload-time = "2025-06-30T15:51:25.158Z" }, + { url = "https://files.pythonhosted.org/packages/c6/bb/a14a4efc5ee748cc1904b0748be278c31b9295ce5f4d2ef66526f410b94d/multidict-6.6.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bf9bd1fd5eec01494e0f2e8e446a74a85d5e49afb63d75a9934e4a5423dba21d", size = 44267, upload-time = "2025-06-30T15:51:26.326Z" }, + { url = "https://files.pythonhosted.org/packages/c2/f8/410677d563c2d55e063ef74fe578f9d53fe6b0a51649597a5861f83ffa15/multidict-6.6.3-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:5bd8d6f793a787153956cd35e24f60485bf0651c238e207b9a54f7458b16d539", size = 230004, upload-time = "2025-06-30T15:51:27.491Z" }, + { url = "https://files.pythonhosted.org/packages/fd/df/2b787f80059314a98e1ec6a4cc7576244986df3e56b3c755e6fc7c99e038/multidict-6.6.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1bf99b4daf908c73856bd87ee0a2499c3c9a3d19bb04b9c6025e66af3fd07462", size = 247196, upload-time = "2025-06-30T15:51:28.762Z" }, + { url = "https://files.pythonhosted.org/packages/05/f2/f9117089151b9a8ab39f9019620d10d9718eec2ac89e7ca9d30f3ec78e96/multidict-6.6.3-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0b9e59946b49dafaf990fd9c17ceafa62976e8471a14952163d10a7a630413a9", size = 225337, upload-time = "2025-06-30T15:51:30.025Z" }, + { url = "https://files.pythonhosted.org/packages/93/2d/7115300ec5b699faa152c56799b089a53ed69e399c3c2d528251f0aeda1a/multidict-6.6.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e2db616467070d0533832d204c54eea6836a5e628f2cb1e6dfd8cd6ba7277cb7", size = 257079, upload-time = "2025-06-30T15:51:31.716Z" }, + { url = "https://files.pythonhosted.org/packages/15/ea/ff4bab367623e39c20d3b07637225c7688d79e4f3cc1f3b9f89867677f9a/multidict-6.6.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7394888236621f61dcdd25189b2768ae5cc280f041029a5bcf1122ac63df79f9", size = 255461, upload-time = "2025-06-30T15:51:33.029Z" }, + { url = "https://files.pythonhosted.org/packages/74/07/2c9246cda322dfe08be85f1b8739646f2c4c5113a1422d7a407763422ec4/multidict-6.6.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f114d8478733ca7388e7c7e0ab34b72547476b97009d643644ac33d4d3fe1821", size = 246611, upload-time = "2025-06-30T15:51:34.47Z" }, + { url = "https://files.pythonhosted.org/packages/a8/62/279c13d584207d5697a752a66ffc9bb19355a95f7659140cb1b3cf82180e/multidict-6.6.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cdf22e4db76d323bcdc733514bf732e9fb349707c98d341d40ebcc6e9318ef3d", size = 243102, upload-time = "2025-06-30T15:51:36.525Z" }, + { url = "https://files.pythonhosted.org/packages/69/cc/e06636f48c6d51e724a8bc8d9e1db5f136fe1df066d7cafe37ef4000f86a/multidict-6.6.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:e995a34c3d44ab511bfc11aa26869b9d66c2d8c799fa0e74b28a473a692532d6", size = 238693, upload-time = "2025-06-30T15:51:38.278Z" }, + { url = "https://files.pythonhosted.org/packages/89/a4/66c9d8fb9acf3b226cdd468ed009537ac65b520aebdc1703dd6908b19d33/multidict-6.6.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:766a4a5996f54361d8d5a9050140aa5362fe48ce51c755a50c0bc3706460c430", size = 246582, upload-time = "2025-06-30T15:51:39.709Z" }, + { url = "https://files.pythonhosted.org/packages/cf/01/c69e0317be556e46257826d5449feb4e6aa0d18573e567a48a2c14156f1f/multidict-6.6.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:3893a0d7d28a7fe6ca7a1f760593bc13038d1d35daf52199d431b61d2660602b", size = 253355, upload-time = "2025-06-30T15:51:41.013Z" }, + { url = "https://files.pythonhosted.org/packages/c0/da/9cc1da0299762d20e626fe0042e71b5694f9f72d7d3f9678397cbaa71b2b/multidict-6.6.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:934796c81ea996e61914ba58064920d6cad5d99140ac3167901eb932150e2e56", size = 247774, upload-time = "2025-06-30T15:51:42.291Z" }, + { url = "https://files.pythonhosted.org/packages/e6/91/b22756afec99cc31105ddd4a52f95ab32b1a4a58f4d417979c570c4a922e/multidict-6.6.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9ed948328aec2072bc00f05d961ceadfd3e9bfc2966c1319aeaf7b7c21219183", size = 242275, upload-time = "2025-06-30T15:51:43.642Z" }, + { url = "https://files.pythonhosted.org/packages/be/f1/adcc185b878036a20399d5be5228f3cbe7f823d78985d101d425af35c800/multidict-6.6.3-cp311-cp311-win32.whl", hash = "sha256:9f5b28c074c76afc3e4c610c488e3493976fe0e596dd3db6c8ddfbb0134dcac5", size = 41290, upload-time = "2025-06-30T15:51:45.264Z" }, + { url = "https://files.pythonhosted.org/packages/e0/d4/27652c1c6526ea6b4f5ddd397e93f4232ff5de42bea71d339bc6a6cc497f/multidict-6.6.3-cp311-cp311-win_amd64.whl", hash = "sha256:bc7f6fbc61b1c16050a389c630da0b32fc6d4a3d191394ab78972bf5edc568c2", size = 45942, upload-time = "2025-06-30T15:51:46.377Z" }, + { url = "https://files.pythonhosted.org/packages/16/18/23f4932019804e56d3c2413e237f866444b774b0263bcb81df2fdecaf593/multidict-6.6.3-cp311-cp311-win_arm64.whl", hash = "sha256:d4e47d8faffaae822fb5cba20937c048d4f734f43572e7079298a6c39fb172cb", size = 42880, upload-time = "2025-06-30T15:51:47.561Z" }, + { url = "https://files.pythonhosted.org/packages/0e/a0/6b57988ea102da0623ea814160ed78d45a2645e4bbb499c2896d12833a70/multidict-6.6.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:056bebbeda16b2e38642d75e9e5310c484b7c24e3841dc0fb943206a72ec89d6", size = 76514, upload-time = "2025-06-30T15:51:48.728Z" }, + { url = "https://files.pythonhosted.org/packages/07/7a/d1e92665b0850c6c0508f101f9cf0410c1afa24973e1115fe9c6a185ebf7/multidict-6.6.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e5f481cccb3c5c5e5de5d00b5141dc589c1047e60d07e85bbd7dea3d4580d63f", size = 45394, upload-time = "2025-06-30T15:51:49.986Z" }, + { url = "https://files.pythonhosted.org/packages/52/6f/dd104490e01be6ef8bf9573705d8572f8c2d2c561f06e3826b081d9e6591/multidict-6.6.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:10bea2ee839a759ee368b5a6e47787f399b41e70cf0c20d90dfaf4158dfb4e55", size = 43590, upload-time = "2025-06-30T15:51:51.331Z" }, + { url = "https://files.pythonhosted.org/packages/44/fe/06e0e01b1b0611e6581b7fd5a85b43dacc08b6cea3034f902f383b0873e5/multidict-6.6.3-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:2334cfb0fa9549d6ce2c21af2bfbcd3ac4ec3646b1b1581c88e3e2b1779ec92b", size = 237292, upload-time = "2025-06-30T15:51:52.584Z" }, + { url = "https://files.pythonhosted.org/packages/ce/71/4f0e558fb77696b89c233c1ee2d92f3e1d5459070a0e89153c9e9e804186/multidict-6.6.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8fee016722550a2276ca2cb5bb624480e0ed2bd49125b2b73b7010b9090e888", size = 258385, upload-time = "2025-06-30T15:51:53.913Z" }, + { url = "https://files.pythonhosted.org/packages/e3/25/cca0e68228addad24903801ed1ab42e21307a1b4b6dd2cf63da5d3ae082a/multidict-6.6.3-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5511cb35f5c50a2db21047c875eb42f308c5583edf96bd8ebf7d770a9d68f6d", size = 242328, upload-time = "2025-06-30T15:51:55.672Z" }, + { url = "https://files.pythonhosted.org/packages/6e/a3/46f2d420d86bbcb8fe660b26a10a219871a0fbf4d43cb846a4031533f3e0/multidict-6.6.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:712b348f7f449948e0a6c4564a21c7db965af900973a67db432d724619b3c680", size = 268057, upload-time = "2025-06-30T15:51:57.037Z" }, + { url = "https://files.pythonhosted.org/packages/9e/73/1c743542fe00794a2ec7466abd3f312ccb8fad8dff9f36d42e18fb1ec33e/multidict-6.6.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e4e15d2138ee2694e038e33b7c3da70e6b0ad8868b9f8094a72e1414aeda9c1a", size = 269341, upload-time = "2025-06-30T15:51:59.111Z" }, + { url = "https://files.pythonhosted.org/packages/a4/11/6ec9dcbe2264b92778eeb85407d1df18812248bf3506a5a1754bc035db0c/multidict-6.6.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8df25594989aebff8a130f7899fa03cbfcc5d2b5f4a461cf2518236fe6f15961", size = 256081, upload-time = "2025-06-30T15:52:00.533Z" }, + { url = "https://files.pythonhosted.org/packages/9b/2b/631b1e2afeb5f1696846d747d36cda075bfdc0bc7245d6ba5c319278d6c4/multidict-6.6.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:159ca68bfd284a8860f8d8112cf0521113bffd9c17568579e4d13d1f1dc76b65", size = 253581, upload-time = "2025-06-30T15:52:02.43Z" }, + { url = "https://files.pythonhosted.org/packages/bf/0e/7e3b93f79efeb6111d3bf9a1a69e555ba1d07ad1c11bceb56b7310d0d7ee/multidict-6.6.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:e098c17856a8c9ade81b4810888c5ad1914099657226283cab3062c0540b0643", size = 250750, upload-time = "2025-06-30T15:52:04.26Z" }, + { url = "https://files.pythonhosted.org/packages/ad/9e/086846c1d6601948e7de556ee464a2d4c85e33883e749f46b9547d7b0704/multidict-6.6.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:67c92ed673049dec52d7ed39f8cf9ebbadf5032c774058b4406d18c8f8fe7063", size = 251548, upload-time = "2025-06-30T15:52:06.002Z" }, + { url = "https://files.pythonhosted.org/packages/8c/7b/86ec260118e522f1a31550e87b23542294880c97cfbf6fb18cc67b044c66/multidict-6.6.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:bd0578596e3a835ef451784053cfd327d607fc39ea1a14812139339a18a0dbc3", size = 262718, upload-time = "2025-06-30T15:52:07.707Z" }, + { url = "https://files.pythonhosted.org/packages/8c/bd/22ce8f47abb0be04692c9fc4638508b8340987b18691aa7775d927b73f72/multidict-6.6.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:346055630a2df2115cd23ae271910b4cae40f4e336773550dca4889b12916e75", size = 259603, upload-time = "2025-06-30T15:52:09.58Z" }, + { url = "https://files.pythonhosted.org/packages/07/9c/91b7ac1691be95cd1f4a26e36a74b97cda6aa9820632d31aab4410f46ebd/multidict-6.6.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:555ff55a359302b79de97e0468e9ee80637b0de1fce77721639f7cd9440b3a10", size = 251351, upload-time = "2025-06-30T15:52:10.947Z" }, + { url = "https://files.pythonhosted.org/packages/6f/5c/4d7adc739884f7a9fbe00d1eac8c034023ef8bad71f2ebe12823ca2e3649/multidict-6.6.3-cp312-cp312-win32.whl", hash = "sha256:73ab034fb8d58ff85c2bcbadc470efc3fafeea8affcf8722855fb94557f14cc5", size = 41860, upload-time = "2025-06-30T15:52:12.334Z" }, + { url = "https://files.pythonhosted.org/packages/6a/a3/0fbc7afdf7cb1aa12a086b02959307848eb6bcc8f66fcb66c0cb57e2a2c1/multidict-6.6.3-cp312-cp312-win_amd64.whl", hash = "sha256:04cbcce84f63b9af41bad04a54d4cc4e60e90c35b9e6ccb130be2d75b71f8c17", size = 45982, upload-time = "2025-06-30T15:52:13.6Z" }, + { url = "https://files.pythonhosted.org/packages/b8/95/8c825bd70ff9b02462dc18d1295dd08d3e9e4eb66856d292ffa62cfe1920/multidict-6.6.3-cp312-cp312-win_arm64.whl", hash = "sha256:0f1130b896ecb52d2a1e615260f3ea2af55fa7dc3d7c3003ba0c3121a759b18b", size = 43210, upload-time = "2025-06-30T15:52:14.893Z" }, + { url = "https://files.pythonhosted.org/packages/52/1d/0bebcbbb4f000751fbd09957257903d6e002943fc668d841a4cf2fb7f872/multidict-6.6.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:540d3c06d48507357a7d57721e5094b4f7093399a0106c211f33540fdc374d55", size = 75843, upload-time = "2025-06-30T15:52:16.155Z" }, + { url = "https://files.pythonhosted.org/packages/07/8f/cbe241b0434cfe257f65c2b1bcf9e8d5fb52bc708c5061fb29b0fed22bdf/multidict-6.6.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9c19cea2a690f04247d43f366d03e4eb110a0dc4cd1bbeee4d445435428ed35b", size = 45053, upload-time = "2025-06-30T15:52:17.429Z" }, + { url = "https://files.pythonhosted.org/packages/32/d2/0b3b23f9dbad5b270b22a3ac3ea73ed0a50ef2d9a390447061178ed6bdb8/multidict-6.6.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7af039820cfd00effec86bda5d8debef711a3e86a1d3772e85bea0f243a4bd65", size = 43273, upload-time = "2025-06-30T15:52:19.346Z" }, + { url = "https://files.pythonhosted.org/packages/fd/fe/6eb68927e823999e3683bc49678eb20374ba9615097d085298fd5b386564/multidict-6.6.3-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:500b84f51654fdc3944e936f2922114349bf8fdcac77c3092b03449f0e5bc2b3", size = 237124, upload-time = "2025-06-30T15:52:20.773Z" }, + { url = "https://files.pythonhosted.org/packages/e7/ab/320d8507e7726c460cb77117848b3834ea0d59e769f36fdae495f7669929/multidict-6.6.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f3fc723ab8a5c5ed6c50418e9bfcd8e6dceba6c271cee6728a10a4ed8561520c", size = 256892, upload-time = "2025-06-30T15:52:22.242Z" }, + { url = "https://files.pythonhosted.org/packages/76/60/38ee422db515ac69834e60142a1a69111ac96026e76e8e9aa347fd2e4591/multidict-6.6.3-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:94c47ea3ade005b5976789baaed66d4de4480d0a0bf31cef6edaa41c1e7b56a6", size = 240547, upload-time = "2025-06-30T15:52:23.736Z" }, + { url = "https://files.pythonhosted.org/packages/27/fb/905224fde2dff042b030c27ad95a7ae744325cf54b890b443d30a789b80e/multidict-6.6.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dbc7cf464cc6d67e83e136c9f55726da3a30176f020a36ead246eceed87f1cd8", size = 266223, upload-time = "2025-06-30T15:52:25.185Z" }, + { url = "https://files.pythonhosted.org/packages/76/35/dc38ab361051beae08d1a53965e3e1a418752fc5be4d3fb983c5582d8784/multidict-6.6.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:900eb9f9da25ada070f8ee4a23f884e0ee66fe4e1a38c3af644256a508ad81ca", size = 267262, upload-time = "2025-06-30T15:52:26.969Z" }, + { url = "https://files.pythonhosted.org/packages/1f/a3/0a485b7f36e422421b17e2bbb5a81c1af10eac1d4476f2ff92927c730479/multidict-6.6.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7c6df517cf177da5d47ab15407143a89cd1a23f8b335f3a28d57e8b0a3dbb884", size = 254345, upload-time = "2025-06-30T15:52:28.467Z" }, + { url = "https://files.pythonhosted.org/packages/b4/59/bcdd52c1dab7c0e0d75ff19cac751fbd5f850d1fc39172ce809a74aa9ea4/multidict-6.6.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4ef421045f13879e21c994b36e728d8e7d126c91a64b9185810ab51d474f27e7", size = 252248, upload-time = "2025-06-30T15:52:29.938Z" }, + { url = "https://files.pythonhosted.org/packages/bb/a4/2d96aaa6eae8067ce108d4acee6f45ced5728beda55c0f02ae1072c730d1/multidict-6.6.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:6c1e61bb4f80895c081790b6b09fa49e13566df8fbff817da3f85b3a8192e36b", size = 250115, upload-time = "2025-06-30T15:52:31.416Z" }, + { url = "https://files.pythonhosted.org/packages/25/d2/ed9f847fa5c7d0677d4f02ea2c163d5e48573de3f57bacf5670e43a5ffaa/multidict-6.6.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e5e8523bb12d7623cd8300dbd91b9e439a46a028cd078ca695eb66ba31adee3c", size = 249649, upload-time = "2025-06-30T15:52:32.996Z" }, + { url = "https://files.pythonhosted.org/packages/1f/af/9155850372563fc550803d3f25373308aa70f59b52cff25854086ecb4a79/multidict-6.6.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:ef58340cc896219e4e653dade08fea5c55c6df41bcc68122e3be3e9d873d9a7b", size = 261203, upload-time = "2025-06-30T15:52:34.521Z" }, + { url = "https://files.pythonhosted.org/packages/36/2f/c6a728f699896252cf309769089568a33c6439626648843f78743660709d/multidict-6.6.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fc9dc435ec8699e7b602b94fe0cd4703e69273a01cbc34409af29e7820f777f1", size = 258051, upload-time = "2025-06-30T15:52:35.999Z" }, + { url = "https://files.pythonhosted.org/packages/d0/60/689880776d6b18fa2b70f6cc74ff87dd6c6b9b47bd9cf74c16fecfaa6ad9/multidict-6.6.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9e864486ef4ab07db5e9cb997bad2b681514158d6954dd1958dfb163b83d53e6", size = 249601, upload-time = "2025-06-30T15:52:37.473Z" }, + { url = "https://files.pythonhosted.org/packages/75/5e/325b11f2222a549019cf2ef879c1f81f94a0d40ace3ef55cf529915ba6cc/multidict-6.6.3-cp313-cp313-win32.whl", hash = "sha256:5633a82fba8e841bc5c5c06b16e21529573cd654f67fd833650a215520a6210e", size = 41683, upload-time = "2025-06-30T15:52:38.927Z" }, + { url = "https://files.pythonhosted.org/packages/b1/ad/cf46e73f5d6e3c775cabd2a05976547f3f18b39bee06260369a42501f053/multidict-6.6.3-cp313-cp313-win_amd64.whl", hash = "sha256:e93089c1570a4ad54c3714a12c2cef549dc9d58e97bcded193d928649cab78e9", size = 45811, upload-time = "2025-06-30T15:52:40.207Z" }, + { url = "https://files.pythonhosted.org/packages/c5/c9/2e3fe950db28fb7c62e1a5f46e1e38759b072e2089209bc033c2798bb5ec/multidict-6.6.3-cp313-cp313-win_arm64.whl", hash = "sha256:c60b401f192e79caec61f166da9c924e9f8bc65548d4246842df91651e83d600", size = 43056, upload-time = "2025-06-30T15:52:41.575Z" }, + { url = "https://files.pythonhosted.org/packages/3a/58/aaf8114cf34966e084a8cc9517771288adb53465188843d5a19862cb6dc3/multidict-6.6.3-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:02fd8f32d403a6ff13864b0851f1f523d4c988051eea0471d4f1fd8010f11134", size = 82811, upload-time = "2025-06-30T15:52:43.281Z" }, + { url = "https://files.pythonhosted.org/packages/71/af/5402e7b58a1f5b987a07ad98f2501fdba2a4f4b4c30cf114e3ce8db64c87/multidict-6.6.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:f3aa090106b1543f3f87b2041eef3c156c8da2aed90c63a2fbed62d875c49c37", size = 48304, upload-time = "2025-06-30T15:52:45.026Z" }, + { url = "https://files.pythonhosted.org/packages/39/65/ab3c8cafe21adb45b24a50266fd747147dec7847425bc2a0f6934b3ae9ce/multidict-6.6.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e924fb978615a5e33ff644cc42e6aa241effcf4f3322c09d4f8cebde95aff5f8", size = 46775, upload-time = "2025-06-30T15:52:46.459Z" }, + { url = "https://files.pythonhosted.org/packages/49/ba/9fcc1b332f67cc0c0c8079e263bfab6660f87fe4e28a35921771ff3eea0d/multidict-6.6.3-cp313-cp313t-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:b9fe5a0e57c6dbd0e2ce81ca66272282c32cd11d31658ee9553849d91289e1c1", size = 229773, upload-time = "2025-06-30T15:52:47.88Z" }, + { url = "https://files.pythonhosted.org/packages/a4/14/0145a251f555f7c754ce2dcbcd012939bbd1f34f066fa5d28a50e722a054/multidict-6.6.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b24576f208793ebae00280c59927c3b7c2a3b1655e443a25f753c4611bc1c373", size = 250083, upload-time = "2025-06-30T15:52:49.366Z" }, + { url = "https://files.pythonhosted.org/packages/9e/d4/d5c0bd2bbb173b586c249a151a26d2fb3ec7d53c96e42091c9fef4e1f10c/multidict-6.6.3-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:135631cb6c58eac37d7ac0df380294fecdc026b28837fa07c02e459c7fb9c54e", size = 228980, upload-time = "2025-06-30T15:52:50.903Z" }, + { url = "https://files.pythonhosted.org/packages/21/32/c9a2d8444a50ec48c4733ccc67254100c10e1c8ae8e40c7a2d2183b59b97/multidict-6.6.3-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:274d416b0df887aef98f19f21578653982cfb8a05b4e187d4a17103322eeaf8f", size = 257776, upload-time = "2025-06-30T15:52:52.764Z" }, + { url = "https://files.pythonhosted.org/packages/68/d0/14fa1699f4ef629eae08ad6201c6b476098f5efb051b296f4c26be7a9fdf/multidict-6.6.3-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e252017a817fad7ce05cafbe5711ed40faeb580e63b16755a3a24e66fa1d87c0", size = 256882, upload-time = "2025-06-30T15:52:54.596Z" }, + { url = "https://files.pythonhosted.org/packages/da/88/84a27570fbe303c65607d517a5f147cd2fc046c2d1da02b84b17b9bdc2aa/multidict-6.6.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e4cc8d848cd4fe1cdee28c13ea79ab0ed37fc2e89dd77bac86a2e7959a8c3bc", size = 247816, upload-time = "2025-06-30T15:52:56.175Z" }, + { url = "https://files.pythonhosted.org/packages/1c/60/dca352a0c999ce96a5d8b8ee0b2b9f729dcad2e0b0c195f8286269a2074c/multidict-6.6.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9e236a7094b9c4c1b7585f6b9cca34b9d833cf079f7e4c49e6a4a6ec9bfdc68f", size = 245341, upload-time = "2025-06-30T15:52:57.752Z" }, + { url = "https://files.pythonhosted.org/packages/50/ef/433fa3ed06028f03946f3993223dada70fb700f763f70c00079533c34578/multidict-6.6.3-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:e0cb0ab69915c55627c933f0b555a943d98ba71b4d1c57bc0d0a66e2567c7471", size = 235854, upload-time = "2025-06-30T15:52:59.74Z" }, + { url = "https://files.pythonhosted.org/packages/1b/1f/487612ab56fbe35715320905215a57fede20de7db40a261759690dc80471/multidict-6.6.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:81ef2f64593aba09c5212a3d0f8c906a0d38d710a011f2f42759704d4557d3f2", size = 243432, upload-time = "2025-06-30T15:53:01.602Z" }, + { url = "https://files.pythonhosted.org/packages/da/6f/ce8b79de16cd885c6f9052c96a3671373d00c59b3ee635ea93e6e81b8ccf/multidict-6.6.3-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:b9cbc60010de3562545fa198bfc6d3825df430ea96d2cc509c39bd71e2e7d648", size = 252731, upload-time = "2025-06-30T15:53:03.517Z" }, + { url = "https://files.pythonhosted.org/packages/bb/fe/a2514a6aba78e5abefa1624ca85ae18f542d95ac5cde2e3815a9fbf369aa/multidict-6.6.3-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:70d974eaaa37211390cd02ef93b7e938de564bbffa866f0b08d07e5e65da783d", size = 247086, upload-time = "2025-06-30T15:53:05.48Z" }, + { url = "https://files.pythonhosted.org/packages/8c/22/b788718d63bb3cce752d107a57c85fcd1a212c6c778628567c9713f9345a/multidict-6.6.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:3713303e4a6663c6d01d648a68f2848701001f3390a030edaaf3fc949c90bf7c", size = 243338, upload-time = "2025-06-30T15:53:07.522Z" }, + { url = "https://files.pythonhosted.org/packages/22/d6/fdb3d0670819f2228f3f7d9af613d5e652c15d170c83e5f1c94fbc55a25b/multidict-6.6.3-cp313-cp313t-win32.whl", hash = "sha256:639ecc9fe7cd73f2495f62c213e964843826f44505a3e5d82805aa85cac6f89e", size = 47812, upload-time = "2025-06-30T15:53:09.263Z" }, + { url = "https://files.pythonhosted.org/packages/b6/d6/a9d2c808f2c489ad199723197419207ecbfbc1776f6e155e1ecea9c883aa/multidict-6.6.3-cp313-cp313t-win_amd64.whl", hash = "sha256:9f97e181f344a0ef3881b573d31de8542cc0dbc559ec68c8f8b5ce2c2e91646d", size = 53011, upload-time = "2025-06-30T15:53:11.038Z" }, + { url = "https://files.pythonhosted.org/packages/f2/40/b68001cba8188dd267590a111f9661b6256debc327137667e832bf5d66e8/multidict-6.6.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ce8b7693da41a3c4fde5871c738a81490cea5496c671d74374c8ab889e1834fb", size = 45254, upload-time = "2025-06-30T15:53:12.421Z" }, + { url = "https://files.pythonhosted.org/packages/d8/30/9aec301e9772b098c1f5c0ca0279237c9766d94b97802e9888010c64b0ed/multidict-6.6.3-py3-none-any.whl", hash = "sha256:8db10f29c7541fc5da4defd8cd697e1ca429db743fa716325f236079b96f775a", size = 12313, upload-time = "2025-06-30T15:53:45.437Z" }, ] [[package]] name = "mxcp" -version = "0.10.0rc14" +version = "0.10.0" source = { editable = "." } dependencies = [ { name = "aiohttp" }, @@ -1572,6 +1573,7 @@ requires-dist = [ { name = "types-pyyaml", marker = "extra == 'dev'" }, { name = "uvicorn", extras = ["standard"], specifier = ">=0.27.0" }, ] +provides-extras = ["dev", "vault", "onepassword", "all"] [package.metadata.requires-dev] dev = [ @@ -1590,42 +1592,42 @@ dependencies = [ { name = "tomli", marker = "python_full_version < '3.11'" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/81/69/92c7fa98112e4d9eb075a239caa4ef4649ad7d441545ccffbd5e34607cbb/mypy-1.16.1.tar.gz", hash = "sha256:6bd00a0a2094841c5e47e7374bb42b83d64c527a502e3334e1173a0c24437bab", size = 3324747 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8e/12/2bf23a80fcef5edb75de9a1e295d778e0f46ea89eb8b115818b663eff42b/mypy-1.16.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b4f0fed1022a63c6fec38f28b7fc77fca47fd490445c69d0a66266c59dd0b88a", size = 10958644 }, - { url = "https://files.pythonhosted.org/packages/08/50/bfe47b3b278eacf348291742fd5e6613bbc4b3434b72ce9361896417cfe5/mypy-1.16.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:86042bbf9f5a05ea000d3203cf87aa9d0ccf9a01f73f71c58979eb9249f46d72", size = 10087033 }, - { url = "https://files.pythonhosted.org/packages/21/de/40307c12fe25675a0776aaa2cdd2879cf30d99eec91b898de00228dc3ab5/mypy-1.16.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ea7469ee5902c95542bea7ee545f7006508c65c8c54b06dc2c92676ce526f3ea", size = 11875645 }, - { url = "https://files.pythonhosted.org/packages/a6/d8/85bdb59e4a98b7a31495bd8f1a4445d8ffc86cde4ab1f8c11d247c11aedc/mypy-1.16.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:352025753ef6a83cb9e7f2427319bb7875d1fdda8439d1e23de12ab164179574", size = 12616986 }, - { url = "https://files.pythonhosted.org/packages/0e/d0/bb25731158fa8f8ee9e068d3e94fcceb4971fedf1424248496292512afe9/mypy-1.16.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ff9fa5b16e4c1364eb89a4d16bcda9987f05d39604e1e6c35378a2987c1aac2d", size = 12878632 }, - { url = "https://files.pythonhosted.org/packages/2d/11/822a9beb7a2b825c0cb06132ca0a5183f8327a5e23ef89717c9474ba0bc6/mypy-1.16.1-cp310-cp310-win_amd64.whl", hash = "sha256:1256688e284632382f8f3b9e2123df7d279f603c561f099758e66dd6ed4e8bd6", size = 9484391 }, - { url = "https://files.pythonhosted.org/packages/9a/61/ec1245aa1c325cb7a6c0f8570a2eee3bfc40fa90d19b1267f8e50b5c8645/mypy-1.16.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:472e4e4c100062488ec643f6162dd0d5208e33e2f34544e1fc931372e806c0cc", size = 10890557 }, - { url = "https://files.pythonhosted.org/packages/6b/bb/6eccc0ba0aa0c7a87df24e73f0ad34170514abd8162eb0c75fd7128171fb/mypy-1.16.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ea16e2a7d2714277e349e24d19a782a663a34ed60864006e8585db08f8ad1782", size = 10012921 }, - { url = "https://files.pythonhosted.org/packages/5f/80/b337a12e2006715f99f529e732c5f6a8c143bb58c92bb142d5ab380963a5/mypy-1.16.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:08e850ea22adc4d8a4014651575567b0318ede51e8e9fe7a68f25391af699507", size = 11802887 }, - { url = "https://files.pythonhosted.org/packages/d9/59/f7af072d09793d581a745a25737c7c0a945760036b16aeb620f658a017af/mypy-1.16.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22d76a63a42619bfb90122889b903519149879ddbf2ba4251834727944c8baca", size = 12531658 }, - { url = "https://files.pythonhosted.org/packages/82/c4/607672f2d6c0254b94a646cfc45ad589dd71b04aa1f3d642b840f7cce06c/mypy-1.16.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2c7ce0662b6b9dc8f4ed86eb7a5d505ee3298c04b40ec13b30e572c0e5ae17c4", size = 12732486 }, - { url = "https://files.pythonhosted.org/packages/b6/5e/136555ec1d80df877a707cebf9081bd3a9f397dedc1ab9750518d87489ec/mypy-1.16.1-cp311-cp311-win_amd64.whl", hash = "sha256:211287e98e05352a2e1d4e8759c5490925a7c784ddc84207f4714822f8cf99b6", size = 9479482 }, - { url = "https://files.pythonhosted.org/packages/b4/d6/39482e5fcc724c15bf6280ff5806548c7185e0c090712a3736ed4d07e8b7/mypy-1.16.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:af4792433f09575d9eeca5c63d7d90ca4aeceda9d8355e136f80f8967639183d", size = 11066493 }, - { url = "https://files.pythonhosted.org/packages/e6/e5/26c347890efc6b757f4d5bb83f4a0cf5958b8cf49c938ac99b8b72b420a6/mypy-1.16.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:66df38405fd8466ce3517eda1f6640611a0b8e70895e2a9462d1d4323c5eb4b9", size = 10081687 }, - { url = "https://files.pythonhosted.org/packages/44/c7/b5cb264c97b86914487d6a24bd8688c0172e37ec0f43e93b9691cae9468b/mypy-1.16.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:44e7acddb3c48bd2713994d098729494117803616e116032af192871aed80b79", size = 11839723 }, - { url = "https://files.pythonhosted.org/packages/15/f8/491997a9b8a554204f834ed4816bda813aefda31cf873bb099deee3c9a99/mypy-1.16.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0ab5eca37b50188163fa7c1b73c685ac66c4e9bdee4a85c9adac0e91d8895e15", size = 12722980 }, - { url = "https://files.pythonhosted.org/packages/df/f0/2bd41e174b5fd93bc9de9a28e4fb673113633b8a7f3a607fa4a73595e468/mypy-1.16.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb6229b2c9086247e21a83c309754b9058b438704ad2f6807f0d8227f6ebdd", size = 12903328 }, - { url = "https://files.pythonhosted.org/packages/61/81/5572108a7bec2c46b8aff7e9b524f371fe6ab5efb534d38d6b37b5490da8/mypy-1.16.1-cp312-cp312-win_amd64.whl", hash = "sha256:1f0435cf920e287ff68af3d10a118a73f212deb2ce087619eb4e648116d1fe9b", size = 9562321 }, - { url = "https://files.pythonhosted.org/packages/28/e3/96964af4a75a949e67df4b95318fe2b7427ac8189bbc3ef28f92a1c5bc56/mypy-1.16.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ddc91eb318c8751c69ddb200a5937f1232ee8efb4e64e9f4bc475a33719de438", size = 11063480 }, - { url = "https://files.pythonhosted.org/packages/f5/4d/cd1a42b8e5be278fab7010fb289d9307a63e07153f0ae1510a3d7b703193/mypy-1.16.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:87ff2c13d58bdc4bbe7dc0dedfe622c0f04e2cb2a492269f3b418df2de05c536", size = 10090538 }, - { url = "https://files.pythonhosted.org/packages/c9/4f/c3c6b4b66374b5f68bab07c8cabd63a049ff69796b844bc759a0ca99bb2a/mypy-1.16.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a7cfb0fe29fe5a9841b7c8ee6dffb52382c45acdf68f032145b75620acfbd6f", size = 11836839 }, - { url = "https://files.pythonhosted.org/packages/b4/7e/81ca3b074021ad9775e5cb97ebe0089c0f13684b066a750b7dc208438403/mypy-1.16.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:051e1677689c9d9578b9c7f4d206d763f9bbd95723cd1416fad50db49d52f359", size = 12715634 }, - { url = "https://files.pythonhosted.org/packages/e9/95/bdd40c8be346fa4c70edb4081d727a54d0a05382d84966869738cfa8a497/mypy-1.16.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d5d2309511cc56c021b4b4e462907c2b12f669b2dbeb68300110ec27723971be", size = 12895584 }, - { url = "https://files.pythonhosted.org/packages/5a/fd/d486a0827a1c597b3b48b1bdef47228a6e9ee8102ab8c28f944cb83b65dc/mypy-1.16.1-cp313-cp313-win_amd64.whl", hash = "sha256:4f58ac32771341e38a853c5d0ec0dfe27e18e27da9cdb8bbc882d2249c71a3ee", size = 9573886 }, - { url = "https://files.pythonhosted.org/packages/cf/d3/53e684e78e07c1a2bf7105715e5edd09ce951fc3f47cf9ed095ec1b7a037/mypy-1.16.1-py3-none-any.whl", hash = "sha256:5fc2ac4027d0ef28d6ba69a0343737a23c4d1b83672bf38d1fe237bdc0643b37", size = 2265923 }, +sdist = { url = "https://files.pythonhosted.org/packages/81/69/92c7fa98112e4d9eb075a239caa4ef4649ad7d441545ccffbd5e34607cbb/mypy-1.16.1.tar.gz", hash = "sha256:6bd00a0a2094841c5e47e7374bb42b83d64c527a502e3334e1173a0c24437bab", size = 3324747, upload-time = "2025-06-16T16:51:35.145Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8e/12/2bf23a80fcef5edb75de9a1e295d778e0f46ea89eb8b115818b663eff42b/mypy-1.16.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b4f0fed1022a63c6fec38f28b7fc77fca47fd490445c69d0a66266c59dd0b88a", size = 10958644, upload-time = "2025-06-16T16:51:11.649Z" }, + { url = "https://files.pythonhosted.org/packages/08/50/bfe47b3b278eacf348291742fd5e6613bbc4b3434b72ce9361896417cfe5/mypy-1.16.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:86042bbf9f5a05ea000d3203cf87aa9d0ccf9a01f73f71c58979eb9249f46d72", size = 10087033, upload-time = "2025-06-16T16:35:30.089Z" }, + { url = "https://files.pythonhosted.org/packages/21/de/40307c12fe25675a0776aaa2cdd2879cf30d99eec91b898de00228dc3ab5/mypy-1.16.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ea7469ee5902c95542bea7ee545f7006508c65c8c54b06dc2c92676ce526f3ea", size = 11875645, upload-time = "2025-06-16T16:35:48.49Z" }, + { url = "https://files.pythonhosted.org/packages/a6/d8/85bdb59e4a98b7a31495bd8f1a4445d8ffc86cde4ab1f8c11d247c11aedc/mypy-1.16.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:352025753ef6a83cb9e7f2427319bb7875d1fdda8439d1e23de12ab164179574", size = 12616986, upload-time = "2025-06-16T16:48:39.526Z" }, + { url = "https://files.pythonhosted.org/packages/0e/d0/bb25731158fa8f8ee9e068d3e94fcceb4971fedf1424248496292512afe9/mypy-1.16.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ff9fa5b16e4c1364eb89a4d16bcda9987f05d39604e1e6c35378a2987c1aac2d", size = 12878632, upload-time = "2025-06-16T16:36:08.195Z" }, + { url = "https://files.pythonhosted.org/packages/2d/11/822a9beb7a2b825c0cb06132ca0a5183f8327a5e23ef89717c9474ba0bc6/mypy-1.16.1-cp310-cp310-win_amd64.whl", hash = "sha256:1256688e284632382f8f3b9e2123df7d279f603c561f099758e66dd6ed4e8bd6", size = 9484391, upload-time = "2025-06-16T16:37:56.151Z" }, + { url = "https://files.pythonhosted.org/packages/9a/61/ec1245aa1c325cb7a6c0f8570a2eee3bfc40fa90d19b1267f8e50b5c8645/mypy-1.16.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:472e4e4c100062488ec643f6162dd0d5208e33e2f34544e1fc931372e806c0cc", size = 10890557, upload-time = "2025-06-16T16:37:21.421Z" }, + { url = "https://files.pythonhosted.org/packages/6b/bb/6eccc0ba0aa0c7a87df24e73f0ad34170514abd8162eb0c75fd7128171fb/mypy-1.16.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ea16e2a7d2714277e349e24d19a782a663a34ed60864006e8585db08f8ad1782", size = 10012921, upload-time = "2025-06-16T16:51:28.659Z" }, + { url = "https://files.pythonhosted.org/packages/5f/80/b337a12e2006715f99f529e732c5f6a8c143bb58c92bb142d5ab380963a5/mypy-1.16.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:08e850ea22adc4d8a4014651575567b0318ede51e8e9fe7a68f25391af699507", size = 11802887, upload-time = "2025-06-16T16:50:53.627Z" }, + { url = "https://files.pythonhosted.org/packages/d9/59/f7af072d09793d581a745a25737c7c0a945760036b16aeb620f658a017af/mypy-1.16.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22d76a63a42619bfb90122889b903519149879ddbf2ba4251834727944c8baca", size = 12531658, upload-time = "2025-06-16T16:33:55.002Z" }, + { url = "https://files.pythonhosted.org/packages/82/c4/607672f2d6c0254b94a646cfc45ad589dd71b04aa1f3d642b840f7cce06c/mypy-1.16.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2c7ce0662b6b9dc8f4ed86eb7a5d505ee3298c04b40ec13b30e572c0e5ae17c4", size = 12732486, upload-time = "2025-06-16T16:37:03.301Z" }, + { url = "https://files.pythonhosted.org/packages/b6/5e/136555ec1d80df877a707cebf9081bd3a9f397dedc1ab9750518d87489ec/mypy-1.16.1-cp311-cp311-win_amd64.whl", hash = "sha256:211287e98e05352a2e1d4e8759c5490925a7c784ddc84207f4714822f8cf99b6", size = 9479482, upload-time = "2025-06-16T16:47:37.48Z" }, + { url = "https://files.pythonhosted.org/packages/b4/d6/39482e5fcc724c15bf6280ff5806548c7185e0c090712a3736ed4d07e8b7/mypy-1.16.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:af4792433f09575d9eeca5c63d7d90ca4aeceda9d8355e136f80f8967639183d", size = 11066493, upload-time = "2025-06-16T16:47:01.683Z" }, + { url = "https://files.pythonhosted.org/packages/e6/e5/26c347890efc6b757f4d5bb83f4a0cf5958b8cf49c938ac99b8b72b420a6/mypy-1.16.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:66df38405fd8466ce3517eda1f6640611a0b8e70895e2a9462d1d4323c5eb4b9", size = 10081687, upload-time = "2025-06-16T16:48:19.367Z" }, + { url = "https://files.pythonhosted.org/packages/44/c7/b5cb264c97b86914487d6a24bd8688c0172e37ec0f43e93b9691cae9468b/mypy-1.16.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:44e7acddb3c48bd2713994d098729494117803616e116032af192871aed80b79", size = 11839723, upload-time = "2025-06-16T16:49:20.912Z" }, + { url = "https://files.pythonhosted.org/packages/15/f8/491997a9b8a554204f834ed4816bda813aefda31cf873bb099deee3c9a99/mypy-1.16.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0ab5eca37b50188163fa7c1b73c685ac66c4e9bdee4a85c9adac0e91d8895e15", size = 12722980, upload-time = "2025-06-16T16:37:40.929Z" }, + { url = "https://files.pythonhosted.org/packages/df/f0/2bd41e174b5fd93bc9de9a28e4fb673113633b8a7f3a607fa4a73595e468/mypy-1.16.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb6229b2c9086247e21a83c309754b9058b438704ad2f6807f0d8227f6ebdd", size = 12903328, upload-time = "2025-06-16T16:34:35.099Z" }, + { url = "https://files.pythonhosted.org/packages/61/81/5572108a7bec2c46b8aff7e9b524f371fe6ab5efb534d38d6b37b5490da8/mypy-1.16.1-cp312-cp312-win_amd64.whl", hash = "sha256:1f0435cf920e287ff68af3d10a118a73f212deb2ce087619eb4e648116d1fe9b", size = 9562321, upload-time = "2025-06-16T16:48:58.823Z" }, + { url = "https://files.pythonhosted.org/packages/28/e3/96964af4a75a949e67df4b95318fe2b7427ac8189bbc3ef28f92a1c5bc56/mypy-1.16.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ddc91eb318c8751c69ddb200a5937f1232ee8efb4e64e9f4bc475a33719de438", size = 11063480, upload-time = "2025-06-16T16:47:56.205Z" }, + { url = "https://files.pythonhosted.org/packages/f5/4d/cd1a42b8e5be278fab7010fb289d9307a63e07153f0ae1510a3d7b703193/mypy-1.16.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:87ff2c13d58bdc4bbe7dc0dedfe622c0f04e2cb2a492269f3b418df2de05c536", size = 10090538, upload-time = "2025-06-16T16:46:43.92Z" }, + { url = "https://files.pythonhosted.org/packages/c9/4f/c3c6b4b66374b5f68bab07c8cabd63a049ff69796b844bc759a0ca99bb2a/mypy-1.16.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a7cfb0fe29fe5a9841b7c8ee6dffb52382c45acdf68f032145b75620acfbd6f", size = 11836839, upload-time = "2025-06-16T16:36:28.039Z" }, + { url = "https://files.pythonhosted.org/packages/b4/7e/81ca3b074021ad9775e5cb97ebe0089c0f13684b066a750b7dc208438403/mypy-1.16.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:051e1677689c9d9578b9c7f4d206d763f9bbd95723cd1416fad50db49d52f359", size = 12715634, upload-time = "2025-06-16T16:50:34.441Z" }, + { url = "https://files.pythonhosted.org/packages/e9/95/bdd40c8be346fa4c70edb4081d727a54d0a05382d84966869738cfa8a497/mypy-1.16.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d5d2309511cc56c021b4b4e462907c2b12f669b2dbeb68300110ec27723971be", size = 12895584, upload-time = "2025-06-16T16:34:54.857Z" }, + { url = "https://files.pythonhosted.org/packages/5a/fd/d486a0827a1c597b3b48b1bdef47228a6e9ee8102ab8c28f944cb83b65dc/mypy-1.16.1-cp313-cp313-win_amd64.whl", hash = "sha256:4f58ac32771341e38a853c5d0ec0dfe27e18e27da9cdb8bbc882d2249c71a3ee", size = 9573886, upload-time = "2025-06-16T16:36:43.589Z" }, + { url = "https://files.pythonhosted.org/packages/cf/d3/53e684e78e07c1a2bf7105715e5edd09ce951fc3f47cf9ed095ec1b7a037/mypy-1.16.1-py3-none-any.whl", hash = "sha256:5fc2ac4027d0ef28d6ba69a0343737a23c4d1b83672bf38d1fe237bdc0643b37", size = 2265923, upload-time = "2025-06-16T16:48:02.366Z" }, ] [[package]] name = "mypy-extensions" version = "1.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343 } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963 }, + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, ] [[package]] @@ -1635,9 +1637,9 @@ source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version < '3.11'", ] -sdist = { url = "https://files.pythonhosted.org/packages/fd/1d/06475e1cd5264c0b870ea2cc6fdb3e37177c1e565c43f56ff17a10e3937f/networkx-3.4.2.tar.gz", hash = "sha256:307c3669428c5362aab27c8a1260aa8f47c4e91d3891f48be0141738d8d053e1", size = 2151368 } +sdist = { url = "https://files.pythonhosted.org/packages/fd/1d/06475e1cd5264c0b870ea2cc6fdb3e37177c1e565c43f56ff17a10e3937f/networkx-3.4.2.tar.gz", hash = "sha256:307c3669428c5362aab27c8a1260aa8f47c4e91d3891f48be0141738d8d053e1", size = 2151368, upload-time = "2024-10-21T12:39:38.695Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b9/54/dd730b32ea14ea797530a4479b2ed46a6fb250f682a9cfb997e968bf0261/networkx-3.4.2-py3-none-any.whl", hash = "sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f", size = 1723263 }, + { url = "https://files.pythonhosted.org/packages/b9/54/dd730b32ea14ea797530a4479b2ed46a6fb250f682a9cfb997e968bf0261/networkx-3.4.2-py3-none-any.whl", hash = "sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f", size = 1723263, upload-time = "2024-10-21T12:39:36.247Z" }, ] [[package]] @@ -1648,40 +1650,40 @@ resolution-markers = [ "python_full_version >= '3.12'", "python_full_version == '3.11.*'", ] -sdist = { url = "https://files.pythonhosted.org/packages/6c/4f/ccdb8ad3a38e583f214547fd2f7ff1fc160c43a75af88e6aec213404b96a/networkx-3.5.tar.gz", hash = "sha256:d4c6f9cf81f52d69230866796b82afbccdec3db7ae4fbd1b65ea750feed50037", size = 2471065 } +sdist = { url = "https://files.pythonhosted.org/packages/6c/4f/ccdb8ad3a38e583f214547fd2f7ff1fc160c43a75af88e6aec213404b96a/networkx-3.5.tar.gz", hash = "sha256:d4c6f9cf81f52d69230866796b82afbccdec3db7ae4fbd1b65ea750feed50037", size = 2471065, upload-time = "2025-05-29T11:35:07.804Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/eb/8d/776adee7bbf76365fdd7f2552710282c79a4ead5d2a46408c9043a2b70ba/networkx-3.5-py3-none-any.whl", hash = "sha256:0030d386a9a06dee3565298b4a734b68589749a544acbb6c412dc9e2489ec6ec", size = 2034406 }, + { url = "https://files.pythonhosted.org/packages/eb/8d/776adee7bbf76365fdd7f2552710282c79a4ead5d2a46408c9043a2b70ba/networkx-3.5-py3-none-any.whl", hash = "sha256:0030d386a9a06dee3565298b4a734b68589749a544acbb6c412dc9e2489ec6ec", size = 2034406, upload-time = "2025-05-29T11:35:04.961Z" }, ] [[package]] name = "nh3" version = "0.2.21" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/37/30/2f81466f250eb7f591d4d193930df661c8c23e9056bdc78e365b646054d8/nh3-0.2.21.tar.gz", hash = "sha256:4990e7ee6a55490dbf00d61a6f476c9a3258e31e711e13713b2ea7d6616f670e", size = 16581 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7f/81/b83775687fcf00e08ade6d4605f0be9c4584cb44c4973d9f27b7456a31c9/nh3-0.2.21-cp313-cp313t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:fcff321bd60c6c5c9cb4ddf2554e22772bb41ebd93ad88171bbbb6f271255286", size = 1297678 }, - { url = "https://files.pythonhosted.org/packages/22/ee/d0ad8fb4b5769f073b2df6807f69a5e57ca9cea504b78809921aef460d20/nh3-0.2.21-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31eedcd7d08b0eae28ba47f43fd33a653b4cdb271d64f1aeda47001618348fde", size = 733774 }, - { url = "https://files.pythonhosted.org/packages/ea/76/b450141e2d384ede43fe53953552f1c6741a499a8c20955ad049555cabc8/nh3-0.2.21-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d426d7be1a2f3d896950fe263332ed1662f6c78525b4520c8e9861f8d7f0d243", size = 760012 }, - { url = "https://files.pythonhosted.org/packages/97/90/1182275db76cd8fbb1f6bf84c770107fafee0cb7da3e66e416bcb9633da2/nh3-0.2.21-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9d67709bc0d7d1f5797b21db26e7a8b3d15d21c9c5f58ccfe48b5328483b685b", size = 923619 }, - { url = "https://files.pythonhosted.org/packages/29/c7/269a7cfbec9693fad8d767c34a755c25ccb8d048fc1dfc7a7d86bc99375c/nh3-0.2.21-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:55823c5ea1f6b267a4fad5de39bc0524d49a47783e1fe094bcf9c537a37df251", size = 1000384 }, - { url = "https://files.pythonhosted.org/packages/68/a9/48479dbf5f49ad93f0badd73fbb48b3d769189f04c6c69b0df261978b009/nh3-0.2.21-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:818f2b6df3763e058efa9e69677b5a92f9bc0acff3295af5ed013da544250d5b", size = 918908 }, - { url = "https://files.pythonhosted.org/packages/d7/da/0279c118f8be2dc306e56819880b19a1cf2379472e3b79fc8eab44e267e3/nh3-0.2.21-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:b3b5c58161e08549904ac4abd450dacd94ff648916f7c376ae4b2c0652b98ff9", size = 909180 }, - { url = "https://files.pythonhosted.org/packages/26/16/93309693f8abcb1088ae143a9c8dbcece9c8f7fb297d492d3918340c41f1/nh3-0.2.21-cp313-cp313t-win32.whl", hash = "sha256:637d4a10c834e1b7d9548592c7aad760611415fcd5bd346f77fd8a064309ae6d", size = 532747 }, - { url = "https://files.pythonhosted.org/packages/a2/3a/96eb26c56cbb733c0b4a6a907fab8408ddf3ead5d1b065830a8f6a9c3557/nh3-0.2.21-cp313-cp313t-win_amd64.whl", hash = "sha256:713d16686596e556b65e7f8c58328c2df63f1a7abe1277d87625dcbbc012ef82", size = 528908 }, - { url = "https://files.pythonhosted.org/packages/ba/1d/b1ef74121fe325a69601270f276021908392081f4953d50b03cbb38b395f/nh3-0.2.21-cp38-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:a772dec5b7b7325780922dd904709f0f5f3a79fbf756de5291c01370f6df0967", size = 1316133 }, - { url = "https://files.pythonhosted.org/packages/b8/f2/2c7f79ce6de55b41e7715f7f59b159fd59f6cdb66223c05b42adaee2b645/nh3-0.2.21-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d002b648592bf3033adfd875a48f09b8ecc000abd7f6a8769ed86b6ccc70c759", size = 758328 }, - { url = "https://files.pythonhosted.org/packages/6d/ad/07bd706fcf2b7979c51b83d8b8def28f413b090cf0cb0035ee6b425e9de5/nh3-0.2.21-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2a5174551f95f2836f2ad6a8074560f261cf9740a48437d6151fd2d4d7d617ab", size = 747020 }, - { url = "https://files.pythonhosted.org/packages/75/99/06a6ba0b8a0d79c3d35496f19accc58199a1fb2dce5e711a31be7e2c1426/nh3-0.2.21-cp38-abi3-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:b8d55ea1fc7ae3633d758a92aafa3505cd3cc5a6e40470c9164d54dff6f96d42", size = 944878 }, - { url = "https://files.pythonhosted.org/packages/79/d4/dc76f5dc50018cdaf161d436449181557373869aacf38a826885192fc587/nh3-0.2.21-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6ae319f17cd8960d0612f0f0ddff5a90700fa71926ca800e9028e7851ce44a6f", size = 903460 }, - { url = "https://files.pythonhosted.org/packages/cd/c3/d4f8037b2ab02ebf5a2e8637bd54736ed3d0e6a2869e10341f8d9085f00e/nh3-0.2.21-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:63ca02ac6f27fc80f9894409eb61de2cb20ef0a23740c7e29f9ec827139fa578", size = 839369 }, - { url = "https://files.pythonhosted.org/packages/11/a9/1cd3c6964ec51daed7b01ca4686a5c793581bf4492cbd7274b3f544c9abe/nh3-0.2.21-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5f77e62aed5c4acad635239ac1290404c7e940c81abe561fd2af011ff59f585", size = 739036 }, - { url = "https://files.pythonhosted.org/packages/fd/04/bfb3ff08d17a8a96325010ae6c53ba41de6248e63cdb1b88ef6369a6cdfc/nh3-0.2.21-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:087ffadfdcd497658c3adc797258ce0f06be8a537786a7217649fc1c0c60c293", size = 768712 }, - { url = "https://files.pythonhosted.org/packages/9e/aa/cfc0bf545d668b97d9adea4f8b4598667d2b21b725d83396c343ad12bba7/nh3-0.2.21-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ac7006c3abd097790e611fe4646ecb19a8d7f2184b882f6093293b8d9b887431", size = 930559 }, - { url = "https://files.pythonhosted.org/packages/78/9d/6f5369a801d3a1b02e6a9a097d56bcc2f6ef98cffebf03c4bb3850d8e0f0/nh3-0.2.21-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:6141caabe00bbddc869665b35fc56a478eb774a8c1dfd6fba9fe1dfdf29e6efa", size = 1008591 }, - { url = "https://files.pythonhosted.org/packages/a6/df/01b05299f68c69e480edff608248313cbb5dbd7595c5e048abe8972a57f9/nh3-0.2.21-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:20979783526641c81d2f5bfa6ca5ccca3d1e4472474b162c6256745fbfe31cd1", size = 925670 }, - { url = "https://files.pythonhosted.org/packages/3d/79/bdba276f58d15386a3387fe8d54e980fb47557c915f5448d8c6ac6f7ea9b/nh3-0.2.21-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a7ea28cd49293749d67e4fcf326c554c83ec912cd09cd94aa7ec3ab1921c8283", size = 917093 }, - { url = "https://files.pythonhosted.org/packages/e7/d8/c6f977a5cd4011c914fb58f5ae573b071d736187ccab31bfb1d539f4af9f/nh3-0.2.21-cp38-abi3-win32.whl", hash = "sha256:6c9c30b8b0d291a7c5ab0967ab200598ba33208f754f2f4920e9343bdd88f79a", size = 537623 }, - { url = "https://files.pythonhosted.org/packages/23/fc/8ce756c032c70ae3dd1d48a3552577a325475af2a2f629604b44f571165c/nh3-0.2.21-cp38-abi3-win_amd64.whl", hash = "sha256:bb0014948f04d7976aabae43fcd4cb7f551f9f8ce785a4c9ef66e6c2590f8629", size = 535283 }, +sdist = { url = "https://files.pythonhosted.org/packages/37/30/2f81466f250eb7f591d4d193930df661c8c23e9056bdc78e365b646054d8/nh3-0.2.21.tar.gz", hash = "sha256:4990e7ee6a55490dbf00d61a6f476c9a3258e31e711e13713b2ea7d6616f670e", size = 16581, upload-time = "2025-02-25T13:38:44.619Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/81/b83775687fcf00e08ade6d4605f0be9c4584cb44c4973d9f27b7456a31c9/nh3-0.2.21-cp313-cp313t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:fcff321bd60c6c5c9cb4ddf2554e22772bb41ebd93ad88171bbbb6f271255286", size = 1297678, upload-time = "2025-02-25T13:37:56.063Z" }, + { url = "https://files.pythonhosted.org/packages/22/ee/d0ad8fb4b5769f073b2df6807f69a5e57ca9cea504b78809921aef460d20/nh3-0.2.21-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31eedcd7d08b0eae28ba47f43fd33a653b4cdb271d64f1aeda47001618348fde", size = 733774, upload-time = "2025-02-25T13:37:58.419Z" }, + { url = "https://files.pythonhosted.org/packages/ea/76/b450141e2d384ede43fe53953552f1c6741a499a8c20955ad049555cabc8/nh3-0.2.21-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d426d7be1a2f3d896950fe263332ed1662f6c78525b4520c8e9861f8d7f0d243", size = 760012, upload-time = "2025-02-25T13:38:01.017Z" }, + { url = "https://files.pythonhosted.org/packages/97/90/1182275db76cd8fbb1f6bf84c770107fafee0cb7da3e66e416bcb9633da2/nh3-0.2.21-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9d67709bc0d7d1f5797b21db26e7a8b3d15d21c9c5f58ccfe48b5328483b685b", size = 923619, upload-time = "2025-02-25T13:38:02.617Z" }, + { url = "https://files.pythonhosted.org/packages/29/c7/269a7cfbec9693fad8d767c34a755c25ccb8d048fc1dfc7a7d86bc99375c/nh3-0.2.21-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:55823c5ea1f6b267a4fad5de39bc0524d49a47783e1fe094bcf9c537a37df251", size = 1000384, upload-time = "2025-02-25T13:38:04.402Z" }, + { url = "https://files.pythonhosted.org/packages/68/a9/48479dbf5f49ad93f0badd73fbb48b3d769189f04c6c69b0df261978b009/nh3-0.2.21-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:818f2b6df3763e058efa9e69677b5a92f9bc0acff3295af5ed013da544250d5b", size = 918908, upload-time = "2025-02-25T13:38:06.693Z" }, + { url = "https://files.pythonhosted.org/packages/d7/da/0279c118f8be2dc306e56819880b19a1cf2379472e3b79fc8eab44e267e3/nh3-0.2.21-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:b3b5c58161e08549904ac4abd450dacd94ff648916f7c376ae4b2c0652b98ff9", size = 909180, upload-time = "2025-02-25T13:38:10.941Z" }, + { url = "https://files.pythonhosted.org/packages/26/16/93309693f8abcb1088ae143a9c8dbcece9c8f7fb297d492d3918340c41f1/nh3-0.2.21-cp313-cp313t-win32.whl", hash = "sha256:637d4a10c834e1b7d9548592c7aad760611415fcd5bd346f77fd8a064309ae6d", size = 532747, upload-time = "2025-02-25T13:38:12.548Z" }, + { url = "https://files.pythonhosted.org/packages/a2/3a/96eb26c56cbb733c0b4a6a907fab8408ddf3ead5d1b065830a8f6a9c3557/nh3-0.2.21-cp313-cp313t-win_amd64.whl", hash = "sha256:713d16686596e556b65e7f8c58328c2df63f1a7abe1277d87625dcbbc012ef82", size = 528908, upload-time = "2025-02-25T13:38:14.059Z" }, + { url = "https://files.pythonhosted.org/packages/ba/1d/b1ef74121fe325a69601270f276021908392081f4953d50b03cbb38b395f/nh3-0.2.21-cp38-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:a772dec5b7b7325780922dd904709f0f5f3a79fbf756de5291c01370f6df0967", size = 1316133, upload-time = "2025-02-25T13:38:16.601Z" }, + { url = "https://files.pythonhosted.org/packages/b8/f2/2c7f79ce6de55b41e7715f7f59b159fd59f6cdb66223c05b42adaee2b645/nh3-0.2.21-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d002b648592bf3033adfd875a48f09b8ecc000abd7f6a8769ed86b6ccc70c759", size = 758328, upload-time = "2025-02-25T13:38:18.972Z" }, + { url = "https://files.pythonhosted.org/packages/6d/ad/07bd706fcf2b7979c51b83d8b8def28f413b090cf0cb0035ee6b425e9de5/nh3-0.2.21-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2a5174551f95f2836f2ad6a8074560f261cf9740a48437d6151fd2d4d7d617ab", size = 747020, upload-time = "2025-02-25T13:38:20.571Z" }, + { url = "https://files.pythonhosted.org/packages/75/99/06a6ba0b8a0d79c3d35496f19accc58199a1fb2dce5e711a31be7e2c1426/nh3-0.2.21-cp38-abi3-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:b8d55ea1fc7ae3633d758a92aafa3505cd3cc5a6e40470c9164d54dff6f96d42", size = 944878, upload-time = "2025-02-25T13:38:22.204Z" }, + { url = "https://files.pythonhosted.org/packages/79/d4/dc76f5dc50018cdaf161d436449181557373869aacf38a826885192fc587/nh3-0.2.21-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6ae319f17cd8960d0612f0f0ddff5a90700fa71926ca800e9028e7851ce44a6f", size = 903460, upload-time = "2025-02-25T13:38:25.951Z" }, + { url = "https://files.pythonhosted.org/packages/cd/c3/d4f8037b2ab02ebf5a2e8637bd54736ed3d0e6a2869e10341f8d9085f00e/nh3-0.2.21-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:63ca02ac6f27fc80f9894409eb61de2cb20ef0a23740c7e29f9ec827139fa578", size = 839369, upload-time = "2025-02-25T13:38:28.174Z" }, + { url = "https://files.pythonhosted.org/packages/11/a9/1cd3c6964ec51daed7b01ca4686a5c793581bf4492cbd7274b3f544c9abe/nh3-0.2.21-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5f77e62aed5c4acad635239ac1290404c7e940c81abe561fd2af011ff59f585", size = 739036, upload-time = "2025-02-25T13:38:30.539Z" }, + { url = "https://files.pythonhosted.org/packages/fd/04/bfb3ff08d17a8a96325010ae6c53ba41de6248e63cdb1b88ef6369a6cdfc/nh3-0.2.21-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:087ffadfdcd497658c3adc797258ce0f06be8a537786a7217649fc1c0c60c293", size = 768712, upload-time = "2025-02-25T13:38:32.992Z" }, + { url = "https://files.pythonhosted.org/packages/9e/aa/cfc0bf545d668b97d9adea4f8b4598667d2b21b725d83396c343ad12bba7/nh3-0.2.21-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ac7006c3abd097790e611fe4646ecb19a8d7f2184b882f6093293b8d9b887431", size = 930559, upload-time = "2025-02-25T13:38:35.204Z" }, + { url = "https://files.pythonhosted.org/packages/78/9d/6f5369a801d3a1b02e6a9a097d56bcc2f6ef98cffebf03c4bb3850d8e0f0/nh3-0.2.21-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:6141caabe00bbddc869665b35fc56a478eb774a8c1dfd6fba9fe1dfdf29e6efa", size = 1008591, upload-time = "2025-02-25T13:38:37.099Z" }, + { url = "https://files.pythonhosted.org/packages/a6/df/01b05299f68c69e480edff608248313cbb5dbd7595c5e048abe8972a57f9/nh3-0.2.21-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:20979783526641c81d2f5bfa6ca5ccca3d1e4472474b162c6256745fbfe31cd1", size = 925670, upload-time = "2025-02-25T13:38:38.696Z" }, + { url = "https://files.pythonhosted.org/packages/3d/79/bdba276f58d15386a3387fe8d54e980fb47557c915f5448d8c6ac6f7ea9b/nh3-0.2.21-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a7ea28cd49293749d67e4fcf326c554c83ec912cd09cd94aa7ec3ab1921c8283", size = 917093, upload-time = "2025-02-25T13:38:40.249Z" }, + { url = "https://files.pythonhosted.org/packages/e7/d8/c6f977a5cd4011c914fb58f5ae573b071d736187ccab31bfb1d539f4af9f/nh3-0.2.21-cp38-abi3-win32.whl", hash = "sha256:6c9c30b8b0d291a7c5ab0967ab200598ba33208f754f2f4920e9343bdd88f79a", size = 537623, upload-time = "2025-02-25T13:38:41.893Z" }, + { url = "https://files.pythonhosted.org/packages/23/fc/8ce756c032c70ae3dd1d48a3552577a325475af2a2f629604b44f571165c/nh3-0.2.21-cp38-abi3-win_amd64.whl", hash = "sha256:bb0014948f04d7976aabae43fcd4cb7f551f9f8ce785a4c9ef66e6c2590f8629", size = 535283, upload-time = "2025-02-25T13:38:43.355Z" }, ] [[package]] @@ -1691,62 +1693,62 @@ source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version < '3.11'", ] -sdist = { url = "https://files.pythonhosted.org/packages/76/21/7d2a95e4bba9dc13d043ee156a356c0a8f0c6309dff6b21b4d71a073b8a8/numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", size = 20276440 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9a/3e/ed6db5be21ce87955c0cbd3009f2803f59fa08df21b5df06862e2d8e2bdd/numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb", size = 21165245 }, - { url = "https://files.pythonhosted.org/packages/22/c2/4b9221495b2a132cc9d2eb862e21d42a009f5a60e45fc44b00118c174bff/numpy-2.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90", size = 14360048 }, - { url = "https://files.pythonhosted.org/packages/fd/77/dc2fcfc66943c6410e2bf598062f5959372735ffda175b39906d54f02349/numpy-2.2.6-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163", size = 5340542 }, - { url = "https://files.pythonhosted.org/packages/7a/4f/1cb5fdc353a5f5cc7feb692db9b8ec2c3d6405453f982435efc52561df58/numpy-2.2.6-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf", size = 6878301 }, - { url = "https://files.pythonhosted.org/packages/eb/17/96a3acd228cec142fcb8723bd3cc39c2a474f7dcf0a5d16731980bcafa95/numpy-2.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83", size = 14297320 }, - { url = "https://files.pythonhosted.org/packages/b4/63/3de6a34ad7ad6646ac7d2f55ebc6ad439dbbf9c4370017c50cf403fb19b5/numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915", size = 16801050 }, - { url = "https://files.pythonhosted.org/packages/07/b6/89d837eddef52b3d0cec5c6ba0456c1bf1b9ef6a6672fc2b7873c3ec4e2e/numpy-2.2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680", size = 15807034 }, - { url = "https://files.pythonhosted.org/packages/01/c8/dc6ae86e3c61cfec1f178e5c9f7858584049b6093f843bca541f94120920/numpy-2.2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289", size = 18614185 }, - { url = "https://files.pythonhosted.org/packages/5b/c5/0064b1b7e7c89137b471ccec1fd2282fceaae0ab3a9550f2568782d80357/numpy-2.2.6-cp310-cp310-win32.whl", hash = "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d", size = 6527149 }, - { url = "https://files.pythonhosted.org/packages/a3/dd/4b822569d6b96c39d1215dbae0582fd99954dcbcf0c1a13c61783feaca3f/numpy-2.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3", size = 12904620 }, - { url = "https://files.pythonhosted.org/packages/da/a8/4f83e2aa666a9fbf56d6118faaaf5f1974d456b1823fda0a176eff722839/numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae", size = 21176963 }, - { url = "https://files.pythonhosted.org/packages/b3/2b/64e1affc7972decb74c9e29e5649fac940514910960ba25cd9af4488b66c/numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a", size = 14406743 }, - { url = "https://files.pythonhosted.org/packages/4a/9f/0121e375000b5e50ffdd8b25bf78d8e1a5aa4cca3f185d41265198c7b834/numpy-2.2.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42", size = 5352616 }, - { url = "https://files.pythonhosted.org/packages/31/0d/b48c405c91693635fbe2dcd7bc84a33a602add5f63286e024d3b6741411c/numpy-2.2.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491", size = 6889579 }, - { url = "https://files.pythonhosted.org/packages/52/b8/7f0554d49b565d0171eab6e99001846882000883998e7b7d9f0d98b1f934/numpy-2.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a", size = 14312005 }, - { url = "https://files.pythonhosted.org/packages/b3/dd/2238b898e51bd6d389b7389ffb20d7f4c10066d80351187ec8e303a5a475/numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf", size = 16821570 }, - { url = "https://files.pythonhosted.org/packages/83/6c/44d0325722cf644f191042bf47eedad61c1e6df2432ed65cbe28509d404e/numpy-2.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1", size = 15818548 }, - { url = "https://files.pythonhosted.org/packages/ae/9d/81e8216030ce66be25279098789b665d49ff19eef08bfa8cb96d4957f422/numpy-2.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab", size = 18620521 }, - { url = "https://files.pythonhosted.org/packages/6a/fd/e19617b9530b031db51b0926eed5345ce8ddc669bb3bc0044b23e275ebe8/numpy-2.2.6-cp311-cp311-win32.whl", hash = "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47", size = 6525866 }, - { url = "https://files.pythonhosted.org/packages/31/0a/f354fb7176b81747d870f7991dc763e157a934c717b67b58456bc63da3df/numpy-2.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303", size = 12907455 }, - { url = "https://files.pythonhosted.org/packages/82/5d/c00588b6cf18e1da539b45d3598d3557084990dcc4331960c15ee776ee41/numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff", size = 20875348 }, - { url = "https://files.pythonhosted.org/packages/66/ee/560deadcdde6c2f90200450d5938f63a34b37e27ebff162810f716f6a230/numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c", size = 14119362 }, - { url = "https://files.pythonhosted.org/packages/3c/65/4baa99f1c53b30adf0acd9a5519078871ddde8d2339dc5a7fde80d9d87da/numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3", size = 5084103 }, - { url = "https://files.pythonhosted.org/packages/cc/89/e5a34c071a0570cc40c9a54eb472d113eea6d002e9ae12bb3a8407fb912e/numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282", size = 6625382 }, - { url = "https://files.pythonhosted.org/packages/f8/35/8c80729f1ff76b3921d5c9487c7ac3de9b2a103b1cd05e905b3090513510/numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87", size = 14018462 }, - { url = "https://files.pythonhosted.org/packages/8c/3d/1e1db36cfd41f895d266b103df00ca5b3cbe965184df824dec5c08c6b803/numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249", size = 16527618 }, - { url = "https://files.pythonhosted.org/packages/61/c6/03ed30992602c85aa3cd95b9070a514f8b3c33e31124694438d88809ae36/numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49", size = 15505511 }, - { url = "https://files.pythonhosted.org/packages/b7/25/5761d832a81df431e260719ec45de696414266613c9ee268394dd5ad8236/numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de", size = 18313783 }, - { url = "https://files.pythonhosted.org/packages/57/0a/72d5a3527c5ebffcd47bde9162c39fae1f90138c961e5296491ce778e682/numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4", size = 6246506 }, - { url = "https://files.pythonhosted.org/packages/36/fa/8c9210162ca1b88529ab76b41ba02d433fd54fecaf6feb70ef9f124683f1/numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2", size = 12614190 }, - { url = "https://files.pythonhosted.org/packages/f9/5c/6657823f4f594f72b5471f1db1ab12e26e890bb2e41897522d134d2a3e81/numpy-2.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84", size = 20867828 }, - { url = "https://files.pythonhosted.org/packages/dc/9e/14520dc3dadf3c803473bd07e9b2bd1b69bc583cb2497b47000fed2fa92f/numpy-2.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b", size = 14143006 }, - { url = "https://files.pythonhosted.org/packages/4f/06/7e96c57d90bebdce9918412087fc22ca9851cceaf5567a45c1f404480e9e/numpy-2.2.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d", size = 5076765 }, - { url = "https://files.pythonhosted.org/packages/73/ed/63d920c23b4289fdac96ddbdd6132e9427790977d5457cd132f18e76eae0/numpy-2.2.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566", size = 6617736 }, - { url = "https://files.pythonhosted.org/packages/85/c5/e19c8f99d83fd377ec8c7e0cf627a8049746da54afc24ef0a0cb73d5dfb5/numpy-2.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f", size = 14010719 }, - { url = "https://files.pythonhosted.org/packages/19/49/4df9123aafa7b539317bf6d342cb6d227e49f7a35b99c287a6109b13dd93/numpy-2.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f", size = 16526072 }, - { url = "https://files.pythonhosted.org/packages/b2/6c/04b5f47f4f32f7c2b0e7260442a8cbcf8168b0e1a41ff1495da42f42a14f/numpy-2.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868", size = 15503213 }, - { url = "https://files.pythonhosted.org/packages/17/0a/5cd92e352c1307640d5b6fec1b2ffb06cd0dabe7d7b8227f97933d378422/numpy-2.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d", size = 18316632 }, - { url = "https://files.pythonhosted.org/packages/f0/3b/5cba2b1d88760ef86596ad0f3d484b1cbff7c115ae2429678465057c5155/numpy-2.2.6-cp313-cp313-win32.whl", hash = "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd", size = 6244532 }, - { url = "https://files.pythonhosted.org/packages/cb/3b/d58c12eafcb298d4e6d0d40216866ab15f59e55d148a5658bb3132311fcf/numpy-2.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c", size = 12610885 }, - { url = "https://files.pythonhosted.org/packages/6b/9e/4bf918b818e516322db999ac25d00c75788ddfd2d2ade4fa66f1f38097e1/numpy-2.2.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6", size = 20963467 }, - { url = "https://files.pythonhosted.org/packages/61/66/d2de6b291507517ff2e438e13ff7b1e2cdbdb7cb40b3ed475377aece69f9/numpy-2.2.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda", size = 14225144 }, - { url = "https://files.pythonhosted.org/packages/e4/25/480387655407ead912e28ba3a820bc69af9adf13bcbe40b299d454ec011f/numpy-2.2.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40", size = 5200217 }, - { url = "https://files.pythonhosted.org/packages/aa/4a/6e313b5108f53dcbf3aca0c0f3e9c92f4c10ce57a0a721851f9785872895/numpy-2.2.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8", size = 6712014 }, - { url = "https://files.pythonhosted.org/packages/b7/30/172c2d5c4be71fdf476e9de553443cf8e25feddbe185e0bd88b096915bcc/numpy-2.2.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f", size = 14077935 }, - { url = "https://files.pythonhosted.org/packages/12/fb/9e743f8d4e4d3c710902cf87af3512082ae3d43b945d5d16563f26ec251d/numpy-2.2.6-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa", size = 16600122 }, - { url = "https://files.pythonhosted.org/packages/12/75/ee20da0e58d3a66f204f38916757e01e33a9737d0b22373b3eb5a27358f9/numpy-2.2.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571", size = 15586143 }, - { url = "https://files.pythonhosted.org/packages/76/95/bef5b37f29fc5e739947e9ce5179ad402875633308504a52d188302319c8/numpy-2.2.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1", size = 18385260 }, - { url = "https://files.pythonhosted.org/packages/09/04/f2f83279d287407cf36a7a8053a5abe7be3622a4363337338f2585e4afda/numpy-2.2.6-cp313-cp313t-win32.whl", hash = "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff", size = 6377225 }, - { url = "https://files.pythonhosted.org/packages/67/0e/35082d13c09c02c011cf21570543d202ad929d961c02a147493cb0c2bdf5/numpy-2.2.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06", size = 12771374 }, - { url = "https://files.pythonhosted.org/packages/9e/3b/d94a75f4dbf1ef5d321523ecac21ef23a3cd2ac8b78ae2aac40873590229/numpy-2.2.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d", size = 21040391 }, - { url = "https://files.pythonhosted.org/packages/17/f4/09b2fa1b58f0fb4f7c7963a1649c64c4d315752240377ed74d9cd878f7b5/numpy-2.2.6-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db", size = 6786754 }, - { url = "https://files.pythonhosted.org/packages/af/30/feba75f143bdc868a1cc3f44ccfa6c4b9ec522b36458e738cd00f67b573f/numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543", size = 16643476 }, - { url = "https://files.pythonhosted.org/packages/37/48/ac2a9584402fb6c0cd5b5d1a91dcf176b15760130dd386bbafdbfe3640bf/numpy-2.2.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00", size = 12812666 }, +sdist = { url = "https://files.pythonhosted.org/packages/76/21/7d2a95e4bba9dc13d043ee156a356c0a8f0c6309dff6b21b4d71a073b8a8/numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", size = 20276440, upload-time = "2025-05-17T22:38:04.611Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/3e/ed6db5be21ce87955c0cbd3009f2803f59fa08df21b5df06862e2d8e2bdd/numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb", size = 21165245, upload-time = "2025-05-17T21:27:58.555Z" }, + { url = "https://files.pythonhosted.org/packages/22/c2/4b9221495b2a132cc9d2eb862e21d42a009f5a60e45fc44b00118c174bff/numpy-2.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90", size = 14360048, upload-time = "2025-05-17T21:28:21.406Z" }, + { url = "https://files.pythonhosted.org/packages/fd/77/dc2fcfc66943c6410e2bf598062f5959372735ffda175b39906d54f02349/numpy-2.2.6-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163", size = 5340542, upload-time = "2025-05-17T21:28:30.931Z" }, + { url = "https://files.pythonhosted.org/packages/7a/4f/1cb5fdc353a5f5cc7feb692db9b8ec2c3d6405453f982435efc52561df58/numpy-2.2.6-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf", size = 6878301, upload-time = "2025-05-17T21:28:41.613Z" }, + { url = "https://files.pythonhosted.org/packages/eb/17/96a3acd228cec142fcb8723bd3cc39c2a474f7dcf0a5d16731980bcafa95/numpy-2.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83", size = 14297320, upload-time = "2025-05-17T21:29:02.78Z" }, + { url = "https://files.pythonhosted.org/packages/b4/63/3de6a34ad7ad6646ac7d2f55ebc6ad439dbbf9c4370017c50cf403fb19b5/numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915", size = 16801050, upload-time = "2025-05-17T21:29:27.675Z" }, + { url = "https://files.pythonhosted.org/packages/07/b6/89d837eddef52b3d0cec5c6ba0456c1bf1b9ef6a6672fc2b7873c3ec4e2e/numpy-2.2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680", size = 15807034, upload-time = "2025-05-17T21:29:51.102Z" }, + { url = "https://files.pythonhosted.org/packages/01/c8/dc6ae86e3c61cfec1f178e5c9f7858584049b6093f843bca541f94120920/numpy-2.2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289", size = 18614185, upload-time = "2025-05-17T21:30:18.703Z" }, + { url = "https://files.pythonhosted.org/packages/5b/c5/0064b1b7e7c89137b471ccec1fd2282fceaae0ab3a9550f2568782d80357/numpy-2.2.6-cp310-cp310-win32.whl", hash = "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d", size = 6527149, upload-time = "2025-05-17T21:30:29.788Z" }, + { url = "https://files.pythonhosted.org/packages/a3/dd/4b822569d6b96c39d1215dbae0582fd99954dcbcf0c1a13c61783feaca3f/numpy-2.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3", size = 12904620, upload-time = "2025-05-17T21:30:48.994Z" }, + { url = "https://files.pythonhosted.org/packages/da/a8/4f83e2aa666a9fbf56d6118faaaf5f1974d456b1823fda0a176eff722839/numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae", size = 21176963, upload-time = "2025-05-17T21:31:19.36Z" }, + { url = "https://files.pythonhosted.org/packages/b3/2b/64e1affc7972decb74c9e29e5649fac940514910960ba25cd9af4488b66c/numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a", size = 14406743, upload-time = "2025-05-17T21:31:41.087Z" }, + { url = "https://files.pythonhosted.org/packages/4a/9f/0121e375000b5e50ffdd8b25bf78d8e1a5aa4cca3f185d41265198c7b834/numpy-2.2.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42", size = 5352616, upload-time = "2025-05-17T21:31:50.072Z" }, + { url = "https://files.pythonhosted.org/packages/31/0d/b48c405c91693635fbe2dcd7bc84a33a602add5f63286e024d3b6741411c/numpy-2.2.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491", size = 6889579, upload-time = "2025-05-17T21:32:01.712Z" }, + { url = "https://files.pythonhosted.org/packages/52/b8/7f0554d49b565d0171eab6e99001846882000883998e7b7d9f0d98b1f934/numpy-2.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a", size = 14312005, upload-time = "2025-05-17T21:32:23.332Z" }, + { url = "https://files.pythonhosted.org/packages/b3/dd/2238b898e51bd6d389b7389ffb20d7f4c10066d80351187ec8e303a5a475/numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf", size = 16821570, upload-time = "2025-05-17T21:32:47.991Z" }, + { url = "https://files.pythonhosted.org/packages/83/6c/44d0325722cf644f191042bf47eedad61c1e6df2432ed65cbe28509d404e/numpy-2.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1", size = 15818548, upload-time = "2025-05-17T21:33:11.728Z" }, + { url = "https://files.pythonhosted.org/packages/ae/9d/81e8216030ce66be25279098789b665d49ff19eef08bfa8cb96d4957f422/numpy-2.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab", size = 18620521, upload-time = "2025-05-17T21:33:39.139Z" }, + { url = "https://files.pythonhosted.org/packages/6a/fd/e19617b9530b031db51b0926eed5345ce8ddc669bb3bc0044b23e275ebe8/numpy-2.2.6-cp311-cp311-win32.whl", hash = "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47", size = 6525866, upload-time = "2025-05-17T21:33:50.273Z" }, + { url = "https://files.pythonhosted.org/packages/31/0a/f354fb7176b81747d870f7991dc763e157a934c717b67b58456bc63da3df/numpy-2.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303", size = 12907455, upload-time = "2025-05-17T21:34:09.135Z" }, + { url = "https://files.pythonhosted.org/packages/82/5d/c00588b6cf18e1da539b45d3598d3557084990dcc4331960c15ee776ee41/numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff", size = 20875348, upload-time = "2025-05-17T21:34:39.648Z" }, + { url = "https://files.pythonhosted.org/packages/66/ee/560deadcdde6c2f90200450d5938f63a34b37e27ebff162810f716f6a230/numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c", size = 14119362, upload-time = "2025-05-17T21:35:01.241Z" }, + { url = "https://files.pythonhosted.org/packages/3c/65/4baa99f1c53b30adf0acd9a5519078871ddde8d2339dc5a7fde80d9d87da/numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3", size = 5084103, upload-time = "2025-05-17T21:35:10.622Z" }, + { url = "https://files.pythonhosted.org/packages/cc/89/e5a34c071a0570cc40c9a54eb472d113eea6d002e9ae12bb3a8407fb912e/numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282", size = 6625382, upload-time = "2025-05-17T21:35:21.414Z" }, + { url = "https://files.pythonhosted.org/packages/f8/35/8c80729f1ff76b3921d5c9487c7ac3de9b2a103b1cd05e905b3090513510/numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87", size = 14018462, upload-time = "2025-05-17T21:35:42.174Z" }, + { url = "https://files.pythonhosted.org/packages/8c/3d/1e1db36cfd41f895d266b103df00ca5b3cbe965184df824dec5c08c6b803/numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249", size = 16527618, upload-time = "2025-05-17T21:36:06.711Z" }, + { url = "https://files.pythonhosted.org/packages/61/c6/03ed30992602c85aa3cd95b9070a514f8b3c33e31124694438d88809ae36/numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49", size = 15505511, upload-time = "2025-05-17T21:36:29.965Z" }, + { url = "https://files.pythonhosted.org/packages/b7/25/5761d832a81df431e260719ec45de696414266613c9ee268394dd5ad8236/numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de", size = 18313783, upload-time = "2025-05-17T21:36:56.883Z" }, + { url = "https://files.pythonhosted.org/packages/57/0a/72d5a3527c5ebffcd47bde9162c39fae1f90138c961e5296491ce778e682/numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4", size = 6246506, upload-time = "2025-05-17T21:37:07.368Z" }, + { url = "https://files.pythonhosted.org/packages/36/fa/8c9210162ca1b88529ab76b41ba02d433fd54fecaf6feb70ef9f124683f1/numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2", size = 12614190, upload-time = "2025-05-17T21:37:26.213Z" }, + { url = "https://files.pythonhosted.org/packages/f9/5c/6657823f4f594f72b5471f1db1ab12e26e890bb2e41897522d134d2a3e81/numpy-2.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84", size = 20867828, upload-time = "2025-05-17T21:37:56.699Z" }, + { url = "https://files.pythonhosted.org/packages/dc/9e/14520dc3dadf3c803473bd07e9b2bd1b69bc583cb2497b47000fed2fa92f/numpy-2.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b", size = 14143006, upload-time = "2025-05-17T21:38:18.291Z" }, + { url = "https://files.pythonhosted.org/packages/4f/06/7e96c57d90bebdce9918412087fc22ca9851cceaf5567a45c1f404480e9e/numpy-2.2.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d", size = 5076765, upload-time = "2025-05-17T21:38:27.319Z" }, + { url = "https://files.pythonhosted.org/packages/73/ed/63d920c23b4289fdac96ddbdd6132e9427790977d5457cd132f18e76eae0/numpy-2.2.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566", size = 6617736, upload-time = "2025-05-17T21:38:38.141Z" }, + { url = "https://files.pythonhosted.org/packages/85/c5/e19c8f99d83fd377ec8c7e0cf627a8049746da54afc24ef0a0cb73d5dfb5/numpy-2.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f", size = 14010719, upload-time = "2025-05-17T21:38:58.433Z" }, + { url = "https://files.pythonhosted.org/packages/19/49/4df9123aafa7b539317bf6d342cb6d227e49f7a35b99c287a6109b13dd93/numpy-2.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f", size = 16526072, upload-time = "2025-05-17T21:39:22.638Z" }, + { url = "https://files.pythonhosted.org/packages/b2/6c/04b5f47f4f32f7c2b0e7260442a8cbcf8168b0e1a41ff1495da42f42a14f/numpy-2.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868", size = 15503213, upload-time = "2025-05-17T21:39:45.865Z" }, + { url = "https://files.pythonhosted.org/packages/17/0a/5cd92e352c1307640d5b6fec1b2ffb06cd0dabe7d7b8227f97933d378422/numpy-2.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d", size = 18316632, upload-time = "2025-05-17T21:40:13.331Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3b/5cba2b1d88760ef86596ad0f3d484b1cbff7c115ae2429678465057c5155/numpy-2.2.6-cp313-cp313-win32.whl", hash = "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd", size = 6244532, upload-time = "2025-05-17T21:43:46.099Z" }, + { url = "https://files.pythonhosted.org/packages/cb/3b/d58c12eafcb298d4e6d0d40216866ab15f59e55d148a5658bb3132311fcf/numpy-2.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c", size = 12610885, upload-time = "2025-05-17T21:44:05.145Z" }, + { url = "https://files.pythonhosted.org/packages/6b/9e/4bf918b818e516322db999ac25d00c75788ddfd2d2ade4fa66f1f38097e1/numpy-2.2.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6", size = 20963467, upload-time = "2025-05-17T21:40:44Z" }, + { url = "https://files.pythonhosted.org/packages/61/66/d2de6b291507517ff2e438e13ff7b1e2cdbdb7cb40b3ed475377aece69f9/numpy-2.2.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda", size = 14225144, upload-time = "2025-05-17T21:41:05.695Z" }, + { url = "https://files.pythonhosted.org/packages/e4/25/480387655407ead912e28ba3a820bc69af9adf13bcbe40b299d454ec011f/numpy-2.2.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40", size = 5200217, upload-time = "2025-05-17T21:41:15.903Z" }, + { url = "https://files.pythonhosted.org/packages/aa/4a/6e313b5108f53dcbf3aca0c0f3e9c92f4c10ce57a0a721851f9785872895/numpy-2.2.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8", size = 6712014, upload-time = "2025-05-17T21:41:27.321Z" }, + { url = "https://files.pythonhosted.org/packages/b7/30/172c2d5c4be71fdf476e9de553443cf8e25feddbe185e0bd88b096915bcc/numpy-2.2.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f", size = 14077935, upload-time = "2025-05-17T21:41:49.738Z" }, + { url = "https://files.pythonhosted.org/packages/12/fb/9e743f8d4e4d3c710902cf87af3512082ae3d43b945d5d16563f26ec251d/numpy-2.2.6-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa", size = 16600122, upload-time = "2025-05-17T21:42:14.046Z" }, + { url = "https://files.pythonhosted.org/packages/12/75/ee20da0e58d3a66f204f38916757e01e33a9737d0b22373b3eb5a27358f9/numpy-2.2.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571", size = 15586143, upload-time = "2025-05-17T21:42:37.464Z" }, + { url = "https://files.pythonhosted.org/packages/76/95/bef5b37f29fc5e739947e9ce5179ad402875633308504a52d188302319c8/numpy-2.2.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1", size = 18385260, upload-time = "2025-05-17T21:43:05.189Z" }, + { url = "https://files.pythonhosted.org/packages/09/04/f2f83279d287407cf36a7a8053a5abe7be3622a4363337338f2585e4afda/numpy-2.2.6-cp313-cp313t-win32.whl", hash = "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff", size = 6377225, upload-time = "2025-05-17T21:43:16.254Z" }, + { url = "https://files.pythonhosted.org/packages/67/0e/35082d13c09c02c011cf21570543d202ad929d961c02a147493cb0c2bdf5/numpy-2.2.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06", size = 12771374, upload-time = "2025-05-17T21:43:35.479Z" }, + { url = "https://files.pythonhosted.org/packages/9e/3b/d94a75f4dbf1ef5d321523ecac21ef23a3cd2ac8b78ae2aac40873590229/numpy-2.2.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d", size = 21040391, upload-time = "2025-05-17T21:44:35.948Z" }, + { url = "https://files.pythonhosted.org/packages/17/f4/09b2fa1b58f0fb4f7c7963a1649c64c4d315752240377ed74d9cd878f7b5/numpy-2.2.6-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db", size = 6786754, upload-time = "2025-05-17T21:44:47.446Z" }, + { url = "https://files.pythonhosted.org/packages/af/30/feba75f143bdc868a1cc3f44ccfa6c4b9ec522b36458e738cd00f67b573f/numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543", size = 16643476, upload-time = "2025-05-17T21:45:11.871Z" }, + { url = "https://files.pythonhosted.org/packages/37/48/ac2a9584402fb6c0cd5b5d1a91dcf176b15760130dd386bbafdbfe3640bf/numpy-2.2.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00", size = 12812666, upload-time = "2025-05-17T21:45:31.426Z" }, ] [[package]] @@ -1757,58 +1759,58 @@ resolution-markers = [ "python_full_version >= '3.12'", "python_full_version == '3.11.*'", ] -sdist = { url = "https://files.pythonhosted.org/packages/2e/19/d7c972dfe90a353dbd3efbbe1d14a5951de80c99c9dc1b93cd998d51dc0f/numpy-2.3.1.tar.gz", hash = "sha256:1ec9ae20a4226da374362cca3c62cd753faf2f951440b0e3b98e93c235441d2b", size = 20390372 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b0/c7/87c64d7ab426156530676000c94784ef55676df2f13b2796f97722464124/numpy-2.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6ea9e48336a402551f52cd8f593343699003d2353daa4b72ce8d34f66b722070", size = 21199346 }, - { url = "https://files.pythonhosted.org/packages/58/0e/0966c2f44beeac12af8d836e5b5f826a407cf34c45cb73ddcdfce9f5960b/numpy-2.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5ccb7336eaf0e77c1635b232c141846493a588ec9ea777a7c24d7166bb8533ae", size = 14361143 }, - { url = "https://files.pythonhosted.org/packages/7d/31/6e35a247acb1bfc19226791dfc7d4c30002cd4e620e11e58b0ddf836fe52/numpy-2.3.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:0bb3a4a61e1d327e035275d2a993c96fa786e4913aa089843e6a2d9dd205c66a", size = 5378989 }, - { url = "https://files.pythonhosted.org/packages/b0/25/93b621219bb6f5a2d4e713a824522c69ab1f06a57cd571cda70e2e31af44/numpy-2.3.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:e344eb79dab01f1e838ebb67aab09965fb271d6da6b00adda26328ac27d4a66e", size = 6912890 }, - { url = "https://files.pythonhosted.org/packages/ef/60/6b06ed98d11fb32e27fb59468b42383f3877146d3ee639f733776b6ac596/numpy-2.3.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:467db865b392168ceb1ef1ffa6f5a86e62468c43e0cfb4ab6da667ede10e58db", size = 14569032 }, - { url = "https://files.pythonhosted.org/packages/75/c9/9bec03675192077467a9c7c2bdd1f2e922bd01d3a69b15c3a0fdcd8548f6/numpy-2.3.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:afed2ce4a84f6b0fc6c1ce734ff368cbf5a5e24e8954a338f3bdffa0718adffb", size = 16930354 }, - { url = "https://files.pythonhosted.org/packages/6a/e2/5756a00cabcf50a3f527a0c968b2b4881c62b1379223931853114fa04cda/numpy-2.3.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0025048b3c1557a20bc80d06fdeb8cc7fc193721484cca82b2cfa072fec71a93", size = 15879605 }, - { url = "https://files.pythonhosted.org/packages/ff/86/a471f65f0a86f1ca62dcc90b9fa46174dd48f50214e5446bc16a775646c5/numpy-2.3.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a5ee121b60aa509679b682819c602579e1df14a5b07fe95671c8849aad8f2115", size = 18666994 }, - { url = "https://files.pythonhosted.org/packages/43/a6/482a53e469b32be6500aaf61cfafd1de7a0b0d484babf679209c3298852e/numpy-2.3.1-cp311-cp311-win32.whl", hash = "sha256:a8b740f5579ae4585831b3cf0e3b0425c667274f82a484866d2adf9570539369", size = 6603672 }, - { url = "https://files.pythonhosted.org/packages/6b/fb/bb613f4122c310a13ec67585c70e14b03bfc7ebabd24f4d5138b97371d7c/numpy-2.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:d4580adadc53311b163444f877e0789f1c8861e2698f6b2a4ca852fda154f3ff", size = 13024015 }, - { url = "https://files.pythonhosted.org/packages/51/58/2d842825af9a0c041aca246dc92eb725e1bc5e1c9ac89712625db0c4e11c/numpy-2.3.1-cp311-cp311-win_arm64.whl", hash = "sha256:ec0bdafa906f95adc9a0c6f26a4871fa753f25caaa0e032578a30457bff0af6a", size = 10456989 }, - { url = "https://files.pythonhosted.org/packages/c6/56/71ad5022e2f63cfe0ca93559403d0edef14aea70a841d640bd13cdba578e/numpy-2.3.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2959d8f268f3d8ee402b04a9ec4bb7604555aeacf78b360dc4ec27f1d508177d", size = 20896664 }, - { url = "https://files.pythonhosted.org/packages/25/65/2db52ba049813670f7f987cc5db6dac9be7cd95e923cc6832b3d32d87cef/numpy-2.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:762e0c0c6b56bdedfef9a8e1d4538556438288c4276901ea008ae44091954e29", size = 14131078 }, - { url = "https://files.pythonhosted.org/packages/57/dd/28fa3c17b0e751047ac928c1e1b6990238faad76e9b147e585b573d9d1bd/numpy-2.3.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:867ef172a0976aaa1f1d1b63cf2090de8b636a7674607d514505fb7276ab08fc", size = 5112554 }, - { url = "https://files.pythonhosted.org/packages/c9/fc/84ea0cba8e760c4644b708b6819d91784c290288c27aca916115e3311d17/numpy-2.3.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:4e602e1b8682c2b833af89ba641ad4176053aaa50f5cacda1a27004352dde943", size = 6646560 }, - { url = "https://files.pythonhosted.org/packages/61/b2/512b0c2ddec985ad1e496b0bd853eeb572315c0f07cd6997473ced8f15e2/numpy-2.3.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:8e333040d069eba1652fb08962ec5b76af7f2c7bce1df7e1418c8055cf776f25", size = 14260638 }, - { url = "https://files.pythonhosted.org/packages/6e/45/c51cb248e679a6c6ab14b7a8e3ead3f4a3fe7425fc7a6f98b3f147bec532/numpy-2.3.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:e7cbf5a5eafd8d230a3ce356d892512185230e4781a361229bd902ff403bc660", size = 16632729 }, - { url = "https://files.pythonhosted.org/packages/e4/ff/feb4be2e5c09a3da161b412019caf47183099cbea1132fd98061808c2df2/numpy-2.3.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5f1b8f26d1086835f442286c1d9b64bb3974b0b1e41bb105358fd07d20872952", size = 15565330 }, - { url = "https://files.pythonhosted.org/packages/bc/6d/ceafe87587101e9ab0d370e4f6e5f3f3a85b9a697f2318738e5e7e176ce3/numpy-2.3.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ee8340cb48c9b7a5899d1149eece41ca535513a9698098edbade2a8e7a84da77", size = 18361734 }, - { url = "https://files.pythonhosted.org/packages/2b/19/0fb49a3ea088be691f040c9bf1817e4669a339d6e98579f91859b902c636/numpy-2.3.1-cp312-cp312-win32.whl", hash = "sha256:e772dda20a6002ef7061713dc1e2585bc1b534e7909b2030b5a46dae8ff077ab", size = 6320411 }, - { url = "https://files.pythonhosted.org/packages/b1/3e/e28f4c1dd9e042eb57a3eb652f200225e311b608632bc727ae378623d4f8/numpy-2.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:cfecc7822543abdea6de08758091da655ea2210b8ffa1faf116b940693d3df76", size = 12734973 }, - { url = "https://files.pythonhosted.org/packages/04/a8/8a5e9079dc722acf53522b8f8842e79541ea81835e9b5483388701421073/numpy-2.3.1-cp312-cp312-win_arm64.whl", hash = "sha256:7be91b2239af2658653c5bb6f1b8bccafaf08226a258caf78ce44710a0160d30", size = 10191491 }, - { url = "https://files.pythonhosted.org/packages/d4/bd/35ad97006d8abff8631293f8ea6adf07b0108ce6fec68da3c3fcca1197f2/numpy-2.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:25a1992b0a3fdcdaec9f552ef10d8103186f5397ab45e2d25f8ac51b1a6b97e8", size = 20889381 }, - { url = "https://files.pythonhosted.org/packages/f1/4f/df5923874d8095b6062495b39729178eef4a922119cee32a12ee1bd4664c/numpy-2.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7dea630156d39b02a63c18f508f85010230409db5b2927ba59c8ba4ab3e8272e", size = 14152726 }, - { url = "https://files.pythonhosted.org/packages/8c/0f/a1f269b125806212a876f7efb049b06c6f8772cf0121139f97774cd95626/numpy-2.3.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:bada6058dd886061f10ea15f230ccf7dfff40572e99fef440a4a857c8728c9c0", size = 5105145 }, - { url = "https://files.pythonhosted.org/packages/6d/63/a7f7fd5f375b0361682f6ffbf686787e82b7bbd561268e4f30afad2bb3c0/numpy-2.3.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:a894f3816eb17b29e4783e5873f92faf55b710c2519e5c351767c51f79d8526d", size = 6639409 }, - { url = "https://files.pythonhosted.org/packages/bf/0d/1854a4121af895aab383f4aa233748f1df4671ef331d898e32426756a8a6/numpy-2.3.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:18703df6c4a4fee55fd3d6e5a253d01c5d33a295409b03fda0c86b3ca2ff41a1", size = 14257630 }, - { url = "https://files.pythonhosted.org/packages/50/30/af1b277b443f2fb08acf1c55ce9d68ee540043f158630d62cef012750f9f/numpy-2.3.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:5902660491bd7a48b2ec16c23ccb9124b8abfd9583c5fdfa123fe6b421e03de1", size = 16627546 }, - { url = "https://files.pythonhosted.org/packages/6e/ec/3b68220c277e463095342d254c61be8144c31208db18d3fd8ef02712bcd6/numpy-2.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:36890eb9e9d2081137bd78d29050ba63b8dab95dff7912eadf1185e80074b2a0", size = 15562538 }, - { url = "https://files.pythonhosted.org/packages/77/2b/4014f2bcc4404484021c74d4c5ee8eb3de7e3f7ac75f06672f8dcf85140a/numpy-2.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a780033466159c2270531e2b8ac063704592a0bc62ec4a1b991c7c40705eb0e8", size = 18360327 }, - { url = "https://files.pythonhosted.org/packages/40/8d/2ddd6c9b30fcf920837b8672f6c65590c7d92e43084c25fc65edc22e93ca/numpy-2.3.1-cp313-cp313-win32.whl", hash = "sha256:39bff12c076812595c3a306f22bfe49919c5513aa1e0e70fac756a0be7c2a2b8", size = 6312330 }, - { url = "https://files.pythonhosted.org/packages/dd/c8/beaba449925988d415efccb45bf977ff8327a02f655090627318f6398c7b/numpy-2.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d5ee6eec45f08ce507a6570e06f2f879b374a552087a4179ea7838edbcbfa42", size = 12731565 }, - { url = "https://files.pythonhosted.org/packages/0b/c3/5c0c575d7ec78c1126998071f58facfc124006635da75b090805e642c62e/numpy-2.3.1-cp313-cp313-win_arm64.whl", hash = "sha256:0c4d9e0a8368db90f93bd192bfa771ace63137c3488d198ee21dfb8e7771916e", size = 10190262 }, - { url = "https://files.pythonhosted.org/packages/ea/19/a029cd335cf72f79d2644dcfc22d90f09caa86265cbbde3b5702ccef6890/numpy-2.3.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:b0b5397374f32ec0649dd98c652a1798192042e715df918c20672c62fb52d4b8", size = 20987593 }, - { url = "https://files.pythonhosted.org/packages/25/91/8ea8894406209107d9ce19b66314194675d31761fe2cb3c84fe2eeae2f37/numpy-2.3.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c5bdf2015ccfcee8253fb8be695516ac4457c743473a43290fd36eba6a1777eb", size = 14300523 }, - { url = "https://files.pythonhosted.org/packages/a6/7f/06187b0066eefc9e7ce77d5f2ddb4e314a55220ad62dd0bfc9f2c44bac14/numpy-2.3.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d70f20df7f08b90a2062c1f07737dd340adccf2068d0f1b9b3d56e2038979fee", size = 5227993 }, - { url = "https://files.pythonhosted.org/packages/e8/ec/a926c293c605fa75e9cfb09f1e4840098ed46d2edaa6e2152ee35dc01ed3/numpy-2.3.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:2fb86b7e58f9ac50e1e9dd1290154107e47d1eef23a0ae9145ded06ea606f992", size = 6736652 }, - { url = "https://files.pythonhosted.org/packages/e3/62/d68e52fb6fde5586650d4c0ce0b05ff3a48ad4df4ffd1b8866479d1d671d/numpy-2.3.1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:23ab05b2d241f76cb883ce8b9a93a680752fbfcbd51c50eff0b88b979e471d8c", size = 14331561 }, - { url = "https://files.pythonhosted.org/packages/fc/ec/b74d3f2430960044bdad6900d9f5edc2dc0fb8bf5a0be0f65287bf2cbe27/numpy-2.3.1-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:ce2ce9e5de4703a673e705183f64fd5da5bf36e7beddcb63a25ee2286e71ca48", size = 16693349 }, - { url = "https://files.pythonhosted.org/packages/0d/15/def96774b9d7eb198ddadfcbd20281b20ebb510580419197e225f5c55c3e/numpy-2.3.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c4913079974eeb5c16ccfd2b1f09354b8fed7e0d6f2cab933104a09a6419b1ee", size = 15642053 }, - { url = "https://files.pythonhosted.org/packages/2b/57/c3203974762a759540c6ae71d0ea2341c1fa41d84e4971a8e76d7141678a/numpy-2.3.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:010ce9b4f00d5c036053ca684c77441f2f2c934fd23bee058b4d6f196efd8280", size = 18434184 }, - { url = "https://files.pythonhosted.org/packages/22/8a/ccdf201457ed8ac6245187850aff4ca56a79edbea4829f4e9f14d46fa9a5/numpy-2.3.1-cp313-cp313t-win32.whl", hash = "sha256:6269b9edfe32912584ec496d91b00b6d34282ca1d07eb10e82dfc780907d6c2e", size = 6440678 }, - { url = "https://files.pythonhosted.org/packages/f1/7e/7f431d8bd8eb7e03d79294aed238b1b0b174b3148570d03a8a8a8f6a0da9/numpy-2.3.1-cp313-cp313t-win_amd64.whl", hash = "sha256:2a809637460e88a113e186e87f228d74ae2852a2e0c44de275263376f17b5bdc", size = 12870697 }, - { url = "https://files.pythonhosted.org/packages/d4/ca/af82bf0fad4c3e573c6930ed743b5308492ff19917c7caaf2f9b6f9e2e98/numpy-2.3.1-cp313-cp313t-win_arm64.whl", hash = "sha256:eccb9a159db9aed60800187bc47a6d3451553f0e1b08b068d8b277ddfbb9b244", size = 10260376 }, - { url = "https://files.pythonhosted.org/packages/e8/34/facc13b9b42ddca30498fc51f7f73c3d0f2be179943a4b4da8686e259740/numpy-2.3.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ad506d4b09e684394c42c966ec1527f6ebc25da7f4da4b1b056606ffe446b8a3", size = 21070637 }, - { url = "https://files.pythonhosted.org/packages/65/b6/41b705d9dbae04649b529fc9bd3387664c3281c7cd78b404a4efe73dcc45/numpy-2.3.1-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:ebb8603d45bc86bbd5edb0d63e52c5fd9e7945d3a503b77e486bd88dde67a19b", size = 5304087 }, - { url = "https://files.pythonhosted.org/packages/7a/b4/fe3ac1902bff7a4934a22d49e1c9d71a623204d654d4cc43c6e8fe337fcb/numpy-2.3.1-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:15aa4c392ac396e2ad3d0a2680c0f0dee420f9fed14eef09bdb9450ee6dcb7b7", size = 6817588 }, - { url = "https://files.pythonhosted.org/packages/ae/ee/89bedf69c36ace1ac8f59e97811c1f5031e179a37e4821c3a230bf750142/numpy-2.3.1-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c6e0bf9d1a2f50d2b65a7cf56db37c095af17b59f6c132396f7c6d5dd76484df", size = 14399010 }, - { url = "https://files.pythonhosted.org/packages/15/08/e00e7070ede29b2b176165eba18d6f9784d5349be3c0c1218338e79c27fd/numpy-2.3.1-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:eabd7e8740d494ce2b4ea0ff05afa1b7b291e978c0ae075487c51e8bd93c0c68", size = 16752042 }, - { url = "https://files.pythonhosted.org/packages/48/6b/1c6b515a83d5564b1698a61efa245727c8feecf308f4091f565988519d20/numpy-2.3.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:e610832418a2bc09d974cc9fecebfa51e9532d6190223bc5ef6a7402ebf3b5cb", size = 12927246 }, +sdist = { url = "https://files.pythonhosted.org/packages/2e/19/d7c972dfe90a353dbd3efbbe1d14a5951de80c99c9dc1b93cd998d51dc0f/numpy-2.3.1.tar.gz", hash = "sha256:1ec9ae20a4226da374362cca3c62cd753faf2f951440b0e3b98e93c235441d2b", size = 20390372, upload-time = "2025-06-21T12:28:33.469Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b0/c7/87c64d7ab426156530676000c94784ef55676df2f13b2796f97722464124/numpy-2.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6ea9e48336a402551f52cd8f593343699003d2353daa4b72ce8d34f66b722070", size = 21199346, upload-time = "2025-06-21T11:47:47.57Z" }, + { url = "https://files.pythonhosted.org/packages/58/0e/0966c2f44beeac12af8d836e5b5f826a407cf34c45cb73ddcdfce9f5960b/numpy-2.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5ccb7336eaf0e77c1635b232c141846493a588ec9ea777a7c24d7166bb8533ae", size = 14361143, upload-time = "2025-06-21T11:48:10.766Z" }, + { url = "https://files.pythonhosted.org/packages/7d/31/6e35a247acb1bfc19226791dfc7d4c30002cd4e620e11e58b0ddf836fe52/numpy-2.3.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:0bb3a4a61e1d327e035275d2a993c96fa786e4913aa089843e6a2d9dd205c66a", size = 5378989, upload-time = "2025-06-21T11:48:19.998Z" }, + { url = "https://files.pythonhosted.org/packages/b0/25/93b621219bb6f5a2d4e713a824522c69ab1f06a57cd571cda70e2e31af44/numpy-2.3.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:e344eb79dab01f1e838ebb67aab09965fb271d6da6b00adda26328ac27d4a66e", size = 6912890, upload-time = "2025-06-21T11:48:31.376Z" }, + { url = "https://files.pythonhosted.org/packages/ef/60/6b06ed98d11fb32e27fb59468b42383f3877146d3ee639f733776b6ac596/numpy-2.3.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:467db865b392168ceb1ef1ffa6f5a86e62468c43e0cfb4ab6da667ede10e58db", size = 14569032, upload-time = "2025-06-21T11:48:52.563Z" }, + { url = "https://files.pythonhosted.org/packages/75/c9/9bec03675192077467a9c7c2bdd1f2e922bd01d3a69b15c3a0fdcd8548f6/numpy-2.3.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:afed2ce4a84f6b0fc6c1ce734ff368cbf5a5e24e8954a338f3bdffa0718adffb", size = 16930354, upload-time = "2025-06-21T11:49:17.473Z" }, + { url = "https://files.pythonhosted.org/packages/6a/e2/5756a00cabcf50a3f527a0c968b2b4881c62b1379223931853114fa04cda/numpy-2.3.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0025048b3c1557a20bc80d06fdeb8cc7fc193721484cca82b2cfa072fec71a93", size = 15879605, upload-time = "2025-06-21T11:49:41.161Z" }, + { url = "https://files.pythonhosted.org/packages/ff/86/a471f65f0a86f1ca62dcc90b9fa46174dd48f50214e5446bc16a775646c5/numpy-2.3.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a5ee121b60aa509679b682819c602579e1df14a5b07fe95671c8849aad8f2115", size = 18666994, upload-time = "2025-06-21T11:50:08.516Z" }, + { url = "https://files.pythonhosted.org/packages/43/a6/482a53e469b32be6500aaf61cfafd1de7a0b0d484babf679209c3298852e/numpy-2.3.1-cp311-cp311-win32.whl", hash = "sha256:a8b740f5579ae4585831b3cf0e3b0425c667274f82a484866d2adf9570539369", size = 6603672, upload-time = "2025-06-21T11:50:19.584Z" }, + { url = "https://files.pythonhosted.org/packages/6b/fb/bb613f4122c310a13ec67585c70e14b03bfc7ebabd24f4d5138b97371d7c/numpy-2.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:d4580adadc53311b163444f877e0789f1c8861e2698f6b2a4ca852fda154f3ff", size = 13024015, upload-time = "2025-06-21T11:50:39.139Z" }, + { url = "https://files.pythonhosted.org/packages/51/58/2d842825af9a0c041aca246dc92eb725e1bc5e1c9ac89712625db0c4e11c/numpy-2.3.1-cp311-cp311-win_arm64.whl", hash = "sha256:ec0bdafa906f95adc9a0c6f26a4871fa753f25caaa0e032578a30457bff0af6a", size = 10456989, upload-time = "2025-06-21T11:50:55.616Z" }, + { url = "https://files.pythonhosted.org/packages/c6/56/71ad5022e2f63cfe0ca93559403d0edef14aea70a841d640bd13cdba578e/numpy-2.3.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2959d8f268f3d8ee402b04a9ec4bb7604555aeacf78b360dc4ec27f1d508177d", size = 20896664, upload-time = "2025-06-21T12:15:30.845Z" }, + { url = "https://files.pythonhosted.org/packages/25/65/2db52ba049813670f7f987cc5db6dac9be7cd95e923cc6832b3d32d87cef/numpy-2.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:762e0c0c6b56bdedfef9a8e1d4538556438288c4276901ea008ae44091954e29", size = 14131078, upload-time = "2025-06-21T12:15:52.23Z" }, + { url = "https://files.pythonhosted.org/packages/57/dd/28fa3c17b0e751047ac928c1e1b6990238faad76e9b147e585b573d9d1bd/numpy-2.3.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:867ef172a0976aaa1f1d1b63cf2090de8b636a7674607d514505fb7276ab08fc", size = 5112554, upload-time = "2025-06-21T12:16:01.434Z" }, + { url = "https://files.pythonhosted.org/packages/c9/fc/84ea0cba8e760c4644b708b6819d91784c290288c27aca916115e3311d17/numpy-2.3.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:4e602e1b8682c2b833af89ba641ad4176053aaa50f5cacda1a27004352dde943", size = 6646560, upload-time = "2025-06-21T12:16:11.895Z" }, + { url = "https://files.pythonhosted.org/packages/61/b2/512b0c2ddec985ad1e496b0bd853eeb572315c0f07cd6997473ced8f15e2/numpy-2.3.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:8e333040d069eba1652fb08962ec5b76af7f2c7bce1df7e1418c8055cf776f25", size = 14260638, upload-time = "2025-06-21T12:16:32.611Z" }, + { url = "https://files.pythonhosted.org/packages/6e/45/c51cb248e679a6c6ab14b7a8e3ead3f4a3fe7425fc7a6f98b3f147bec532/numpy-2.3.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:e7cbf5a5eafd8d230a3ce356d892512185230e4781a361229bd902ff403bc660", size = 16632729, upload-time = "2025-06-21T12:16:57.439Z" }, + { url = "https://files.pythonhosted.org/packages/e4/ff/feb4be2e5c09a3da161b412019caf47183099cbea1132fd98061808c2df2/numpy-2.3.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5f1b8f26d1086835f442286c1d9b64bb3974b0b1e41bb105358fd07d20872952", size = 15565330, upload-time = "2025-06-21T12:17:20.638Z" }, + { url = "https://files.pythonhosted.org/packages/bc/6d/ceafe87587101e9ab0d370e4f6e5f3f3a85b9a697f2318738e5e7e176ce3/numpy-2.3.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ee8340cb48c9b7a5899d1149eece41ca535513a9698098edbade2a8e7a84da77", size = 18361734, upload-time = "2025-06-21T12:17:47.938Z" }, + { url = "https://files.pythonhosted.org/packages/2b/19/0fb49a3ea088be691f040c9bf1817e4669a339d6e98579f91859b902c636/numpy-2.3.1-cp312-cp312-win32.whl", hash = "sha256:e772dda20a6002ef7061713dc1e2585bc1b534e7909b2030b5a46dae8ff077ab", size = 6320411, upload-time = "2025-06-21T12:17:58.475Z" }, + { url = "https://files.pythonhosted.org/packages/b1/3e/e28f4c1dd9e042eb57a3eb652f200225e311b608632bc727ae378623d4f8/numpy-2.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:cfecc7822543abdea6de08758091da655ea2210b8ffa1faf116b940693d3df76", size = 12734973, upload-time = "2025-06-21T12:18:17.601Z" }, + { url = "https://files.pythonhosted.org/packages/04/a8/8a5e9079dc722acf53522b8f8842e79541ea81835e9b5483388701421073/numpy-2.3.1-cp312-cp312-win_arm64.whl", hash = "sha256:7be91b2239af2658653c5bb6f1b8bccafaf08226a258caf78ce44710a0160d30", size = 10191491, upload-time = "2025-06-21T12:18:33.585Z" }, + { url = "https://files.pythonhosted.org/packages/d4/bd/35ad97006d8abff8631293f8ea6adf07b0108ce6fec68da3c3fcca1197f2/numpy-2.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:25a1992b0a3fdcdaec9f552ef10d8103186f5397ab45e2d25f8ac51b1a6b97e8", size = 20889381, upload-time = "2025-06-21T12:19:04.103Z" }, + { url = "https://files.pythonhosted.org/packages/f1/4f/df5923874d8095b6062495b39729178eef4a922119cee32a12ee1bd4664c/numpy-2.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7dea630156d39b02a63c18f508f85010230409db5b2927ba59c8ba4ab3e8272e", size = 14152726, upload-time = "2025-06-21T12:19:25.599Z" }, + { url = "https://files.pythonhosted.org/packages/8c/0f/a1f269b125806212a876f7efb049b06c6f8772cf0121139f97774cd95626/numpy-2.3.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:bada6058dd886061f10ea15f230ccf7dfff40572e99fef440a4a857c8728c9c0", size = 5105145, upload-time = "2025-06-21T12:19:34.782Z" }, + { url = "https://files.pythonhosted.org/packages/6d/63/a7f7fd5f375b0361682f6ffbf686787e82b7bbd561268e4f30afad2bb3c0/numpy-2.3.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:a894f3816eb17b29e4783e5873f92faf55b710c2519e5c351767c51f79d8526d", size = 6639409, upload-time = "2025-06-21T12:19:45.228Z" }, + { url = "https://files.pythonhosted.org/packages/bf/0d/1854a4121af895aab383f4aa233748f1df4671ef331d898e32426756a8a6/numpy-2.3.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:18703df6c4a4fee55fd3d6e5a253d01c5d33a295409b03fda0c86b3ca2ff41a1", size = 14257630, upload-time = "2025-06-21T12:20:06.544Z" }, + { url = "https://files.pythonhosted.org/packages/50/30/af1b277b443f2fb08acf1c55ce9d68ee540043f158630d62cef012750f9f/numpy-2.3.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:5902660491bd7a48b2ec16c23ccb9124b8abfd9583c5fdfa123fe6b421e03de1", size = 16627546, upload-time = "2025-06-21T12:20:31.002Z" }, + { url = "https://files.pythonhosted.org/packages/6e/ec/3b68220c277e463095342d254c61be8144c31208db18d3fd8ef02712bcd6/numpy-2.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:36890eb9e9d2081137bd78d29050ba63b8dab95dff7912eadf1185e80074b2a0", size = 15562538, upload-time = "2025-06-21T12:20:54.322Z" }, + { url = "https://files.pythonhosted.org/packages/77/2b/4014f2bcc4404484021c74d4c5ee8eb3de7e3f7ac75f06672f8dcf85140a/numpy-2.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a780033466159c2270531e2b8ac063704592a0bc62ec4a1b991c7c40705eb0e8", size = 18360327, upload-time = "2025-06-21T12:21:21.053Z" }, + { url = "https://files.pythonhosted.org/packages/40/8d/2ddd6c9b30fcf920837b8672f6c65590c7d92e43084c25fc65edc22e93ca/numpy-2.3.1-cp313-cp313-win32.whl", hash = "sha256:39bff12c076812595c3a306f22bfe49919c5513aa1e0e70fac756a0be7c2a2b8", size = 6312330, upload-time = "2025-06-21T12:25:07.447Z" }, + { url = "https://files.pythonhosted.org/packages/dd/c8/beaba449925988d415efccb45bf977ff8327a02f655090627318f6398c7b/numpy-2.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d5ee6eec45f08ce507a6570e06f2f879b374a552087a4179ea7838edbcbfa42", size = 12731565, upload-time = "2025-06-21T12:25:26.444Z" }, + { url = "https://files.pythonhosted.org/packages/0b/c3/5c0c575d7ec78c1126998071f58facfc124006635da75b090805e642c62e/numpy-2.3.1-cp313-cp313-win_arm64.whl", hash = "sha256:0c4d9e0a8368db90f93bd192bfa771ace63137c3488d198ee21dfb8e7771916e", size = 10190262, upload-time = "2025-06-21T12:25:42.196Z" }, + { url = "https://files.pythonhosted.org/packages/ea/19/a029cd335cf72f79d2644dcfc22d90f09caa86265cbbde3b5702ccef6890/numpy-2.3.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:b0b5397374f32ec0649dd98c652a1798192042e715df918c20672c62fb52d4b8", size = 20987593, upload-time = "2025-06-21T12:21:51.664Z" }, + { url = "https://files.pythonhosted.org/packages/25/91/8ea8894406209107d9ce19b66314194675d31761fe2cb3c84fe2eeae2f37/numpy-2.3.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c5bdf2015ccfcee8253fb8be695516ac4457c743473a43290fd36eba6a1777eb", size = 14300523, upload-time = "2025-06-21T12:22:13.583Z" }, + { url = "https://files.pythonhosted.org/packages/a6/7f/06187b0066eefc9e7ce77d5f2ddb4e314a55220ad62dd0bfc9f2c44bac14/numpy-2.3.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d70f20df7f08b90a2062c1f07737dd340adccf2068d0f1b9b3d56e2038979fee", size = 5227993, upload-time = "2025-06-21T12:22:22.53Z" }, + { url = "https://files.pythonhosted.org/packages/e8/ec/a926c293c605fa75e9cfb09f1e4840098ed46d2edaa6e2152ee35dc01ed3/numpy-2.3.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:2fb86b7e58f9ac50e1e9dd1290154107e47d1eef23a0ae9145ded06ea606f992", size = 6736652, upload-time = "2025-06-21T12:22:33.629Z" }, + { url = "https://files.pythonhosted.org/packages/e3/62/d68e52fb6fde5586650d4c0ce0b05ff3a48ad4df4ffd1b8866479d1d671d/numpy-2.3.1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:23ab05b2d241f76cb883ce8b9a93a680752fbfcbd51c50eff0b88b979e471d8c", size = 14331561, upload-time = "2025-06-21T12:22:55.056Z" }, + { url = "https://files.pythonhosted.org/packages/fc/ec/b74d3f2430960044bdad6900d9f5edc2dc0fb8bf5a0be0f65287bf2cbe27/numpy-2.3.1-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:ce2ce9e5de4703a673e705183f64fd5da5bf36e7beddcb63a25ee2286e71ca48", size = 16693349, upload-time = "2025-06-21T12:23:20.53Z" }, + { url = "https://files.pythonhosted.org/packages/0d/15/def96774b9d7eb198ddadfcbd20281b20ebb510580419197e225f5c55c3e/numpy-2.3.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c4913079974eeb5c16ccfd2b1f09354b8fed7e0d6f2cab933104a09a6419b1ee", size = 15642053, upload-time = "2025-06-21T12:23:43.697Z" }, + { url = "https://files.pythonhosted.org/packages/2b/57/c3203974762a759540c6ae71d0ea2341c1fa41d84e4971a8e76d7141678a/numpy-2.3.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:010ce9b4f00d5c036053ca684c77441f2f2c934fd23bee058b4d6f196efd8280", size = 18434184, upload-time = "2025-06-21T12:24:10.708Z" }, + { url = "https://files.pythonhosted.org/packages/22/8a/ccdf201457ed8ac6245187850aff4ca56a79edbea4829f4e9f14d46fa9a5/numpy-2.3.1-cp313-cp313t-win32.whl", hash = "sha256:6269b9edfe32912584ec496d91b00b6d34282ca1d07eb10e82dfc780907d6c2e", size = 6440678, upload-time = "2025-06-21T12:24:21.596Z" }, + { url = "https://files.pythonhosted.org/packages/f1/7e/7f431d8bd8eb7e03d79294aed238b1b0b174b3148570d03a8a8a8f6a0da9/numpy-2.3.1-cp313-cp313t-win_amd64.whl", hash = "sha256:2a809637460e88a113e186e87f228d74ae2852a2e0c44de275263376f17b5bdc", size = 12870697, upload-time = "2025-06-21T12:24:40.644Z" }, + { url = "https://files.pythonhosted.org/packages/d4/ca/af82bf0fad4c3e573c6930ed743b5308492ff19917c7caaf2f9b6f9e2e98/numpy-2.3.1-cp313-cp313t-win_arm64.whl", hash = "sha256:eccb9a159db9aed60800187bc47a6d3451553f0e1b08b068d8b277ddfbb9b244", size = 10260376, upload-time = "2025-06-21T12:24:56.884Z" }, + { url = "https://files.pythonhosted.org/packages/e8/34/facc13b9b42ddca30498fc51f7f73c3d0f2be179943a4b4da8686e259740/numpy-2.3.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ad506d4b09e684394c42c966ec1527f6ebc25da7f4da4b1b056606ffe446b8a3", size = 21070637, upload-time = "2025-06-21T12:26:12.518Z" }, + { url = "https://files.pythonhosted.org/packages/65/b6/41b705d9dbae04649b529fc9bd3387664c3281c7cd78b404a4efe73dcc45/numpy-2.3.1-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:ebb8603d45bc86bbd5edb0d63e52c5fd9e7945d3a503b77e486bd88dde67a19b", size = 5304087, upload-time = "2025-06-21T12:26:22.294Z" }, + { url = "https://files.pythonhosted.org/packages/7a/b4/fe3ac1902bff7a4934a22d49e1c9d71a623204d654d4cc43c6e8fe337fcb/numpy-2.3.1-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:15aa4c392ac396e2ad3d0a2680c0f0dee420f9fed14eef09bdb9450ee6dcb7b7", size = 6817588, upload-time = "2025-06-21T12:26:32.939Z" }, + { url = "https://files.pythonhosted.org/packages/ae/ee/89bedf69c36ace1ac8f59e97811c1f5031e179a37e4821c3a230bf750142/numpy-2.3.1-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c6e0bf9d1a2f50d2b65a7cf56db37c095af17b59f6c132396f7c6d5dd76484df", size = 14399010, upload-time = "2025-06-21T12:26:54.086Z" }, + { url = "https://files.pythonhosted.org/packages/15/08/e00e7070ede29b2b176165eba18d6f9784d5349be3c0c1218338e79c27fd/numpy-2.3.1-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:eabd7e8740d494ce2b4ea0ff05afa1b7b291e978c0ae075487c51e8bd93c0c68", size = 16752042, upload-time = "2025-06-21T12:27:19.018Z" }, + { url = "https://files.pythonhosted.org/packages/48/6b/1c6b515a83d5564b1698a61efa245727c8feecf308f4091f565988519d20/numpy-2.3.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:e610832418a2bc09d974cc9fecebfa51e9532d6190223bc5ef6a7402ebf3b5cb", size = 12927246, upload-time = "2025-06-21T12:27:38.618Z" }, ] [[package]] @@ -1818,28 +1820,28 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/04/2c/c1b2ea64cc3ccde4de0dbc52bdff1e08f90798df207b9ed0a24bc0995608/onepassword_sdk-0.3.1.tar.gz", hash = "sha256:ddc6ecd7c7b33fbafe1673196605f309b4b2dfee5a6d2e137a4d18860eb07c3b", size = 29160996 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/53/92/65eee7fd76720ceb9cde929dbdff8404d2cb215f1b82e76833d8184aaee9/onepassword_sdk-0.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3e77d22d02a7481850ce5a75916da395ba9fa746d1a6e595e2e029c57a0c6c4b", size = 6035549 }, - { url = "https://files.pythonhosted.org/packages/4a/41/ec27de9b878f3092c5e74d27de94fd0fb839d0946a4d0e370dedc888b89e/onepassword_sdk-0.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9d83100635949d5111bb555eea5cbc0a5dc72ce6275e07ce4892cc3841405738", size = 5518634 }, - { url = "https://files.pythonhosted.org/packages/02/d9/b3d4477a7d0407111196e836da3feaa9ba64f0cfbf8e2368dbe05ae947a6/onepassword_sdk-0.3.1-cp310-cp310-manylinux_2_32_aarch64.whl", hash = "sha256:fce00e847486b9d36999c86cacb6c3e6b3cc32f5774b2e98b8a16b4705d3a315", size = 6012082 }, - { url = "https://files.pythonhosted.org/packages/35/81/6ae18a51e84eb2d4748c598352199c859c0df59f3af1728870db2bba25fd/onepassword_sdk-0.3.1-cp310-cp310-manylinux_2_32_x86_64.whl", hash = "sha256:4ac86ca4c91ee005a7da6c765b10030a2cc3890d392d51e28a07fd8046e15ca2", size = 6351722 }, - { url = "https://files.pythonhosted.org/packages/cc/79/6c9726a6f94eaaa1f3b94e75381b765fba89679eb7ba1469202d0acc3226/onepassword_sdk-0.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:98de0a1a647495892d577a1bf1336780cdadaef3a05f16a0d747844fc9f85b97", size = 5506879 }, - { url = "https://files.pythonhosted.org/packages/2f/f5/0b871309c313a6c55f1e4ee025598a623c45d4c315240845af971655d0ff/onepassword_sdk-0.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a85524be5a07095800c6f75ae3279a7265c350fdc4a686ab5f2ca555f4ed39b8", size = 6035550 }, - { url = "https://files.pythonhosted.org/packages/c1/00/74a2b740de19c88f865298d2f125253d18a0d8cfb1c87cdc881fc80853e7/onepassword_sdk-0.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5c69bffd73e1a3361710e7d8c982930ac6409cb84e0e8acd7540797f8ae9640c", size = 5518633 }, - { url = "https://files.pythonhosted.org/packages/43/da/d523dd961a4a4774ac54deb09778f8813d459435d8cae177cf5b8eae50e2/onepassword_sdk-0.3.1-cp311-cp311-manylinux_2_32_aarch64.whl", hash = "sha256:97755c6cae8091a91f9ce80d6028b7631c9da339227dd586e5f502c154dd63c9", size = 6012083 }, - { url = "https://files.pythonhosted.org/packages/ae/25/b7cbf576f4b2049447c669d09defa481e6eaf43124824ca7eb649f1cd82f/onepassword_sdk-0.3.1-cp311-cp311-manylinux_2_32_x86_64.whl", hash = "sha256:4289b2d7665c7d2ae9e03e6a949da29e5982eaaea0c3670034e39257f81988d0", size = 6351724 }, - { url = "https://files.pythonhosted.org/packages/fa/83/58aab05ff9676f0e6a1a4a5fa5e5020de47357dada10d1bccd4421040832/onepassword_sdk-0.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:2fb75b218592e0b7e530b443c5fc96f15af539da712b04bfdf7c74b3c9eb275f", size = 5506879 }, - { url = "https://files.pythonhosted.org/packages/51/ca/a0d526f93446d3c301ed793a7b504604a3847c038fb2e189687e49e40a92/onepassword_sdk-0.3.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d57cb37d7a49f140e99892542de8f7e027602ff35ebdc45a74d9d15b671056e4", size = 6035549 }, - { url = "https://files.pythonhosted.org/packages/fd/ab/611b565492985f02f073970c5737653439da1a98c9fc26e6a21dec598477/onepassword_sdk-0.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9611ca7f831bce0e8d7280551a289cbc326e5041537c713b985bef2d5cdc324d", size = 5518637 }, - { url = "https://files.pythonhosted.org/packages/23/a6/00c3a2b5a6c6637aa4959b01f77399b714e394b50e8c14fa0a0e766233a1/onepassword_sdk-0.3.1-cp312-cp312-manylinux_2_32_aarch64.whl", hash = "sha256:8368b9325fe835f0bfbee4e084486ee39185349ea5e2ec712d9531d56d5b74ef", size = 6012083 }, - { url = "https://files.pythonhosted.org/packages/f8/eb/39b92ba242d32aee3e7705db831e1691713a9a87c6dc32d2bda3682c4ff7/onepassword_sdk-0.3.1-cp312-cp312-manylinux_2_32_x86_64.whl", hash = "sha256:cb69b84ee6d23a2ce83648a94dce499dfac96941ad4bed7801ca69857608877d", size = 6351723 }, - { url = "https://files.pythonhosted.org/packages/2a/02/3063b562aae6a1972a0a8938fe4fc464631419847a409589667dd3cbf6ba/onepassword_sdk-0.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:b4e6a035af990d172da24fb65146f91b46c61f4216f67392c4595c4de23f48b5", size = 5506879 }, - { url = "https://files.pythonhosted.org/packages/24/7a/a7e67af03afdf431882a696d522dff6ee6b43f91ff5d806f8deb557bcc4a/onepassword_sdk-0.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6d6d5182734ca711fb15e0b652fd0609ca57f7175d2a21aa5e5e8b182b3b67ce", size = 6035551 }, - { url = "https://files.pythonhosted.org/packages/f8/a6/4bd09f0ccbaa2436799f647ae30602bb70b5a9c81399c048f9af1de20d61/onepassword_sdk-0.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bce94596cdd3f1d5258ced1fe99076361b3807dde110263a3c3350824a99bd46", size = 5518634 }, - { url = "https://files.pythonhosted.org/packages/95/45/ebe066360aac1bcbbf8e37a7ff3d73e4dc882d3de249e8ebfd6892c389b3/onepassword_sdk-0.3.1-cp313-cp313-manylinux_2_32_aarch64.whl", hash = "sha256:39f0adea83d8659d64a00be9c744053c215660eda14838de82040f2eb800216e", size = 6012081 }, - { url = "https://files.pythonhosted.org/packages/63/b2/e8acb8bd5e3599b4204f3a9b11171c1f9ce503dfa46405c4f689d9f2021a/onepassword_sdk-0.3.1-cp313-cp313-manylinux_2_32_x86_64.whl", hash = "sha256:38a4f337a1d6f9357ed97a2ceb4b712607be4b5d6278daabb5cdba46bb948b44", size = 6351723 }, - { url = "https://files.pythonhosted.org/packages/c7/1d/91becb8fa0e417c172a5721c06dc403ad2abbbc766e9a8bdeff46bdea6ba/onepassword_sdk-0.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:4a77fa3fdbad03738faf2703387256f53c2c86329bcd9f19ee5725a2075db77b", size = 5506878 }, +sdist = { url = "https://files.pythonhosted.org/packages/04/2c/c1b2ea64cc3ccde4de0dbc52bdff1e08f90798df207b9ed0a24bc0995608/onepassword_sdk-0.3.1.tar.gz", hash = "sha256:ddc6ecd7c7b33fbafe1673196605f309b4b2dfee5a6d2e137a4d18860eb07c3b", size = 29160996, upload-time = "2025-06-11T17:25:20.729Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/53/92/65eee7fd76720ceb9cde929dbdff8404d2cb215f1b82e76833d8184aaee9/onepassword_sdk-0.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3e77d22d02a7481850ce5a75916da395ba9fa746d1a6e595e2e029c57a0c6c4b", size = 6035549, upload-time = "2025-06-11T17:23:03.95Z" }, + { url = "https://files.pythonhosted.org/packages/4a/41/ec27de9b878f3092c5e74d27de94fd0fb839d0946a4d0e370dedc888b89e/onepassword_sdk-0.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9d83100635949d5111bb555eea5cbc0a5dc72ce6275e07ce4892cc3841405738", size = 5518634, upload-time = "2025-06-11T17:23:07.351Z" }, + { url = "https://files.pythonhosted.org/packages/02/d9/b3d4477a7d0407111196e836da3feaa9ba64f0cfbf8e2368dbe05ae947a6/onepassword_sdk-0.3.1-cp310-cp310-manylinux_2_32_aarch64.whl", hash = "sha256:fce00e847486b9d36999c86cacb6c3e6b3cc32f5774b2e98b8a16b4705d3a315", size = 6012082, upload-time = "2025-06-11T17:23:11.397Z" }, + { url = "https://files.pythonhosted.org/packages/35/81/6ae18a51e84eb2d4748c598352199c859c0df59f3af1728870db2bba25fd/onepassword_sdk-0.3.1-cp310-cp310-manylinux_2_32_x86_64.whl", hash = "sha256:4ac86ca4c91ee005a7da6c765b10030a2cc3890d392d51e28a07fd8046e15ca2", size = 6351722, upload-time = "2025-06-11T17:23:17.784Z" }, + { url = "https://files.pythonhosted.org/packages/cc/79/6c9726a6f94eaaa1f3b94e75381b765fba89679eb7ba1469202d0acc3226/onepassword_sdk-0.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:98de0a1a647495892d577a1bf1336780cdadaef3a05f16a0d747844fc9f85b97", size = 5506879, upload-time = "2025-06-11T17:23:23.789Z" }, + { url = "https://files.pythonhosted.org/packages/2f/f5/0b871309c313a6c55f1e4ee025598a623c45d4c315240845af971655d0ff/onepassword_sdk-0.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a85524be5a07095800c6f75ae3279a7265c350fdc4a686ab5f2ca555f4ed39b8", size = 6035550, upload-time = "2025-06-11T17:23:29.546Z" }, + { url = "https://files.pythonhosted.org/packages/c1/00/74a2b740de19c88f865298d2f125253d18a0d8cfb1c87cdc881fc80853e7/onepassword_sdk-0.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5c69bffd73e1a3361710e7d8c982930ac6409cb84e0e8acd7540797f8ae9640c", size = 5518633, upload-time = "2025-06-11T17:23:36.447Z" }, + { url = "https://files.pythonhosted.org/packages/43/da/d523dd961a4a4774ac54deb09778f8813d459435d8cae177cf5b8eae50e2/onepassword_sdk-0.3.1-cp311-cp311-manylinux_2_32_aarch64.whl", hash = "sha256:97755c6cae8091a91f9ce80d6028b7631c9da339227dd586e5f502c154dd63c9", size = 6012083, upload-time = "2025-06-11T17:23:42.443Z" }, + { url = "https://files.pythonhosted.org/packages/ae/25/b7cbf576f4b2049447c669d09defa481e6eaf43124824ca7eb649f1cd82f/onepassword_sdk-0.3.1-cp311-cp311-manylinux_2_32_x86_64.whl", hash = "sha256:4289b2d7665c7d2ae9e03e6a949da29e5982eaaea0c3670034e39257f81988d0", size = 6351724, upload-time = "2025-06-11T17:23:45.919Z" }, + { url = "https://files.pythonhosted.org/packages/fa/83/58aab05ff9676f0e6a1a4a5fa5e5020de47357dada10d1bccd4421040832/onepassword_sdk-0.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:2fb75b218592e0b7e530b443c5fc96f15af539da712b04bfdf7c74b3c9eb275f", size = 5506879, upload-time = "2025-06-11T17:23:49.663Z" }, + { url = "https://files.pythonhosted.org/packages/51/ca/a0d526f93446d3c301ed793a7b504604a3847c038fb2e189687e49e40a92/onepassword_sdk-0.3.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d57cb37d7a49f140e99892542de8f7e027602ff35ebdc45a74d9d15b671056e4", size = 6035549, upload-time = "2025-06-11T17:23:54.504Z" }, + { url = "https://files.pythonhosted.org/packages/fd/ab/611b565492985f02f073970c5737653439da1a98c9fc26e6a21dec598477/onepassword_sdk-0.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9611ca7f831bce0e8d7280551a289cbc326e5041537c713b985bef2d5cdc324d", size = 5518637, upload-time = "2025-06-11T17:23:57.993Z" }, + { url = "https://files.pythonhosted.org/packages/23/a6/00c3a2b5a6c6637aa4959b01f77399b714e394b50e8c14fa0a0e766233a1/onepassword_sdk-0.3.1-cp312-cp312-manylinux_2_32_aarch64.whl", hash = "sha256:8368b9325fe835f0bfbee4e084486ee39185349ea5e2ec712d9531d56d5b74ef", size = 6012083, upload-time = "2025-06-11T17:24:01.591Z" }, + { url = "https://files.pythonhosted.org/packages/f8/eb/39b92ba242d32aee3e7705db831e1691713a9a87c6dc32d2bda3682c4ff7/onepassword_sdk-0.3.1-cp312-cp312-manylinux_2_32_x86_64.whl", hash = "sha256:cb69b84ee6d23a2ce83648a94dce499dfac96941ad4bed7801ca69857608877d", size = 6351723, upload-time = "2025-06-11T17:24:06.435Z" }, + { url = "https://files.pythonhosted.org/packages/2a/02/3063b562aae6a1972a0a8938fe4fc464631419847a409589667dd3cbf6ba/onepassword_sdk-0.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:b4e6a035af990d172da24fb65146f91b46c61f4216f67392c4595c4de23f48b5", size = 5506879, upload-time = "2025-06-11T17:24:11.184Z" }, + { url = "https://files.pythonhosted.org/packages/24/7a/a7e67af03afdf431882a696d522dff6ee6b43f91ff5d806f8deb557bcc4a/onepassword_sdk-0.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6d6d5182734ca711fb15e0b652fd0609ca57f7175d2a21aa5e5e8b182b3b67ce", size = 6035551, upload-time = "2025-06-11T17:24:15.299Z" }, + { url = "https://files.pythonhosted.org/packages/f8/a6/4bd09f0ccbaa2436799f647ae30602bb70b5a9c81399c048f9af1de20d61/onepassword_sdk-0.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bce94596cdd3f1d5258ced1fe99076361b3807dde110263a3c3350824a99bd46", size = 5518634, upload-time = "2025-06-11T17:24:19.599Z" }, + { url = "https://files.pythonhosted.org/packages/95/45/ebe066360aac1bcbbf8e37a7ff3d73e4dc882d3de249e8ebfd6892c389b3/onepassword_sdk-0.3.1-cp313-cp313-manylinux_2_32_aarch64.whl", hash = "sha256:39f0adea83d8659d64a00be9c744053c215660eda14838de82040f2eb800216e", size = 6012081, upload-time = "2025-06-11T17:24:23.921Z" }, + { url = "https://files.pythonhosted.org/packages/63/b2/e8acb8bd5e3599b4204f3a9b11171c1f9ce503dfa46405c4f689d9f2021a/onepassword_sdk-0.3.1-cp313-cp313-manylinux_2_32_x86_64.whl", hash = "sha256:38a4f337a1d6f9357ed97a2ceb4b712607be4b5d6278daabb5cdba46bb948b44", size = 6351723, upload-time = "2025-06-11T17:24:29.546Z" }, + { url = "https://files.pythonhosted.org/packages/c7/1d/91becb8fa0e417c172a5721c06dc403ad2abbbc766e9a8bdeff46bdea6ba/onepassword_sdk-0.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:4a77fa3fdbad03738faf2703387256f53c2c86329bcd9f19ee5725a2075db77b", size = 5506878, upload-time = "2025-06-11T17:24:33.339Z" }, ] [[package]] @@ -1850,9 +1852,9 @@ dependencies = [ { name = "importlib-metadata" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/27/d2/c782c88b8afbf961d6972428821c302bd1e9e7bc361352172f0ca31296e2/opentelemetry_api-1.36.0.tar.gz", hash = "sha256:9a72572b9c416d004d492cbc6e61962c0501eaf945ece9b5a0f56597d8348aa0", size = 64780 } +sdist = { url = "https://files.pythonhosted.org/packages/27/d2/c782c88b8afbf961d6972428821c302bd1e9e7bc361352172f0ca31296e2/opentelemetry_api-1.36.0.tar.gz", hash = "sha256:9a72572b9c416d004d492cbc6e61962c0501eaf945ece9b5a0f56597d8348aa0", size = 64780, upload-time = "2025-07-29T15:12:06.02Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bb/ee/6b08dde0a022c463b88f55ae81149584b125a42183407dc1045c486cc870/opentelemetry_api-1.36.0-py3-none-any.whl", hash = "sha256:02f20bcacf666e1333b6b1f04e647dc1d5111f86b8e510238fcc56d7762cda8c", size = 65564 }, + { url = "https://files.pythonhosted.org/packages/bb/ee/6b08dde0a022c463b88f55ae81149584b125a42183407dc1045c486cc870/opentelemetry_api-1.36.0-py3-none-any.whl", hash = "sha256:02f20bcacf666e1333b6b1f04e647dc1d5111f86b8e510238fcc56d7762cda8c", size = 65564, upload-time = "2025-07-29T15:11:47.998Z" }, ] [[package]] @@ -1862,9 +1864,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-proto" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/34/da/7747e57eb341c59886052d733072bc878424bf20f1d8cf203d508bbece5b/opentelemetry_exporter_otlp_proto_common-1.36.0.tar.gz", hash = "sha256:6c496ccbcbe26b04653cecadd92f73659b814c6e3579af157d8716e5f9f25cbf", size = 20302 } +sdist = { url = "https://files.pythonhosted.org/packages/34/da/7747e57eb341c59886052d733072bc878424bf20f1d8cf203d508bbece5b/opentelemetry_exporter_otlp_proto_common-1.36.0.tar.gz", hash = "sha256:6c496ccbcbe26b04653cecadd92f73659b814c6e3579af157d8716e5f9f25cbf", size = 20302, upload-time = "2025-07-29T15:12:07.71Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d0/ed/22290dca7db78eb32e0101738366b5bbda00d0407f00feffb9bf8c3fdf87/opentelemetry_exporter_otlp_proto_common-1.36.0-py3-none-any.whl", hash = "sha256:0fc002a6ed63eac235ada9aa7056e5492e9a71728214a61745f6ad04b923f840", size = 18349 }, + { url = "https://files.pythonhosted.org/packages/d0/ed/22290dca7db78eb32e0101738366b5bbda00d0407f00feffb9bf8c3fdf87/opentelemetry_exporter_otlp_proto_common-1.36.0-py3-none-any.whl", hash = "sha256:0fc002a6ed63eac235ada9aa7056e5492e9a71728214a61745f6ad04b923f840", size = 18349, upload-time = "2025-07-29T15:11:51.327Z" }, ] [[package]] @@ -1880,9 +1882,9 @@ dependencies = [ { name = "requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/25/85/6632e7e5700ba1ce5b8a065315f92c1e6d787ccc4fb2bdab15139eaefc82/opentelemetry_exporter_otlp_proto_http-1.36.0.tar.gz", hash = "sha256:dd3637f72f774b9fc9608ab1ac479f8b44d09b6fb5b2f3df68a24ad1da7d356e", size = 16213 } +sdist = { url = "https://files.pythonhosted.org/packages/25/85/6632e7e5700ba1ce5b8a065315f92c1e6d787ccc4fb2bdab15139eaefc82/opentelemetry_exporter_otlp_proto_http-1.36.0.tar.gz", hash = "sha256:dd3637f72f774b9fc9608ab1ac479f8b44d09b6fb5b2f3df68a24ad1da7d356e", size = 16213, upload-time = "2025-07-29T15:12:08.932Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7f/41/a680d38b34f8f5ddbd78ed9f0042e1cc712d58ec7531924d71cb1e6c629d/opentelemetry_exporter_otlp_proto_http-1.36.0-py3-none-any.whl", hash = "sha256:3d769f68e2267e7abe4527f70deb6f598f40be3ea34c6adc35789bea94a32902", size = 18752 }, + { url = "https://files.pythonhosted.org/packages/7f/41/a680d38b34f8f5ddbd78ed9f0042e1cc712d58ec7531924d71cb1e6c629d/opentelemetry_exporter_otlp_proto_http-1.36.0-py3-none-any.whl", hash = "sha256:3d769f68e2267e7abe4527f70deb6f598f40be3ea34c6adc35789bea94a32902", size = 18752, upload-time = "2025-07-29T15:11:53.164Z" }, ] [[package]] @@ -1892,9 +1894,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fd/02/f6556142301d136e3b7e95ab8ea6a5d9dc28d879a99f3dd673b5f97dca06/opentelemetry_proto-1.36.0.tar.gz", hash = "sha256:0f10b3c72f74c91e0764a5ec88fd8f1c368ea5d9c64639fb455e2854ef87dd2f", size = 46152 } +sdist = { url = "https://files.pythonhosted.org/packages/fd/02/f6556142301d136e3b7e95ab8ea6a5d9dc28d879a99f3dd673b5f97dca06/opentelemetry_proto-1.36.0.tar.gz", hash = "sha256:0f10b3c72f74c91e0764a5ec88fd8f1c368ea5d9c64639fb455e2854ef87dd2f", size = 46152, upload-time = "2025-07-29T15:12:15.717Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b3/57/3361e06136225be8180e879199caea520f38026f8071366241ac458beb8d/opentelemetry_proto-1.36.0-py3-none-any.whl", hash = "sha256:151b3bf73a09f94afc658497cf77d45a565606f62ce0c17acb08cd9937ca206e", size = 72537 }, + { url = "https://files.pythonhosted.org/packages/b3/57/3361e06136225be8180e879199caea520f38026f8071366241ac458beb8d/opentelemetry_proto-1.36.0-py3-none-any.whl", hash = "sha256:151b3bf73a09f94afc658497cf77d45a565606f62ce0c17acb08cd9937ca206e", size = 72537, upload-time = "2025-07-29T15:12:02.243Z" }, ] [[package]] @@ -1906,9 +1908,9 @@ dependencies = [ { name = "opentelemetry-semantic-conventions" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4c/85/8567a966b85a2d3f971c4d42f781c305b2b91c043724fa08fd37d158e9dc/opentelemetry_sdk-1.36.0.tar.gz", hash = "sha256:19c8c81599f51b71670661ff7495c905d8fdf6976e41622d5245b791b06fa581", size = 162557 } +sdist = { url = "https://files.pythonhosted.org/packages/4c/85/8567a966b85a2d3f971c4d42f781c305b2b91c043724fa08fd37d158e9dc/opentelemetry_sdk-1.36.0.tar.gz", hash = "sha256:19c8c81599f51b71670661ff7495c905d8fdf6976e41622d5245b791b06fa581", size = 162557, upload-time = "2025-07-29T15:12:16.76Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0b/59/7bed362ad1137ba5886dac8439e84cd2df6d087be7c09574ece47ae9b22c/opentelemetry_sdk-1.36.0-py3-none-any.whl", hash = "sha256:19fe048b42e98c5c1ffe85b569b7073576ad4ce0bcb6e9b4c6a39e890a6c45fb", size = 119995 }, + { url = "https://files.pythonhosted.org/packages/0b/59/7bed362ad1137ba5886dac8439e84cd2df6d087be7c09574ece47ae9b22c/opentelemetry_sdk-1.36.0-py3-none-any.whl", hash = "sha256:19fe048b42e98c5c1ffe85b569b7073576ad4ce0bcb6e9b4c6a39e890a6c45fb", size = 119995, upload-time = "2025-07-29T15:12:03.181Z" }, ] [[package]] @@ -1919,27 +1921,27 @@ dependencies = [ { name = "opentelemetry-api" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7e/31/67dfa252ee88476a29200b0255bda8dfc2cf07b56ad66dc9a6221f7dc787/opentelemetry_semantic_conventions-0.57b0.tar.gz", hash = "sha256:609a4a79c7891b4620d64c7aac6898f872d790d75f22019913a660756f27ff32", size = 124225 } +sdist = { url = "https://files.pythonhosted.org/packages/7e/31/67dfa252ee88476a29200b0255bda8dfc2cf07b56ad66dc9a6221f7dc787/opentelemetry_semantic_conventions-0.57b0.tar.gz", hash = "sha256:609a4a79c7891b4620d64c7aac6898f872d790d75f22019913a660756f27ff32", size = 124225, upload-time = "2025-07-29T15:12:17.873Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/05/75/7d591371c6c39c73de5ce5da5a2cc7b72d1d1cd3f8f4638f553c01c37b11/opentelemetry_semantic_conventions-0.57b0-py3-none-any.whl", hash = "sha256:757f7e76293294f124c827e514c2a3144f191ef175b069ce8d1211e1e38e9e78", size = 201627 }, + { url = "https://files.pythonhosted.org/packages/05/75/7d591371c6c39c73de5ce5da5a2cc7b72d1d1cd3f8f4638f553c01c37b11/opentelemetry_semantic_conventions-0.57b0-py3-none-any.whl", hash = "sha256:757f7e76293294f124c827e514c2a3144f191ef175b069ce8d1211e1e38e9e78", size = 201627, upload-time = "2025-07-29T15:12:04.174Z" }, ] [[package]] name = "ordered-set" version = "4.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4c/ca/bfac8bc689799bcca4157e0e0ced07e70ce125193fc2e166d2e685b7e2fe/ordered-set-4.1.0.tar.gz", hash = "sha256:694a8e44c87657c59292ede72891eb91d34131f6531463aab3009191c77364a8", size = 12826 } +sdist = { url = "https://files.pythonhosted.org/packages/4c/ca/bfac8bc689799bcca4157e0e0ced07e70ce125193fc2e166d2e685b7e2fe/ordered-set-4.1.0.tar.gz", hash = "sha256:694a8e44c87657c59292ede72891eb91d34131f6531463aab3009191c77364a8", size = 12826, upload-time = "2022-01-26T14:38:56.6Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/33/55/af02708f230eb77084a299d7b08175cff006dea4f2721074b92cdb0296c0/ordered_set-4.1.0-py3-none-any.whl", hash = "sha256:046e1132c71fcf3330438a539928932caf51ddbc582496833e23de611de14562", size = 7634 }, + { url = "https://files.pythonhosted.org/packages/33/55/af02708f230eb77084a299d7b08175cff006dea4f2721074b92cdb0296c0/ordered_set-4.1.0-py3-none-any.whl", hash = "sha256:046e1132c71fcf3330438a539928932caf51ddbc582496833e23de611de14562", size = 7634, upload-time = "2022-01-26T14:38:48.677Z" }, ] [[package]] name = "packaging" version = "25.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727 } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469 }, + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, ] [[package]] @@ -1953,42 +1955,42 @@ dependencies = [ { name = "pytz" }, { name = "tzdata" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d1/6f/75aa71f8a14267117adeeed5d21b204770189c0a0025acbdc03c337b28fc/pandas-2.3.1.tar.gz", hash = "sha256:0a95b9ac964fe83ce317827f80304d37388ea77616b1425f0ae41c9d2d0d7bb2", size = 4487493 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c4/ca/aa97b47287221fa37a49634532e520300088e290b20d690b21ce3e448143/pandas-2.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:22c2e866f7209ebc3a8f08d75766566aae02bcc91d196935a1d9e59c7b990ac9", size = 11542731 }, - { url = "https://files.pythonhosted.org/packages/80/bf/7938dddc5f01e18e573dcfb0f1b8c9357d9b5fa6ffdee6e605b92efbdff2/pandas-2.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3583d348546201aff730c8c47e49bc159833f971c2899d6097bce68b9112a4f1", size = 10790031 }, - { url = "https://files.pythonhosted.org/packages/ee/2f/9af748366763b2a494fed477f88051dbf06f56053d5c00eba652697e3f94/pandas-2.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f951fbb702dacd390561e0ea45cdd8ecfa7fb56935eb3dd78e306c19104b9b0", size = 11724083 }, - { url = "https://files.pythonhosted.org/packages/2c/95/79ab37aa4c25d1e7df953dde407bb9c3e4ae47d154bc0dd1692f3a6dcf8c/pandas-2.3.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd05b72ec02ebfb993569b4931b2e16fbb4d6ad6ce80224a3ee838387d83a191", size = 12342360 }, - { url = "https://files.pythonhosted.org/packages/75/a7/d65e5d8665c12c3c6ff5edd9709d5836ec9b6f80071b7f4a718c6106e86e/pandas-2.3.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:1b916a627919a247d865aed068eb65eb91a344b13f5b57ab9f610b7716c92de1", size = 13202098 }, - { url = "https://files.pythonhosted.org/packages/65/f3/4c1dbd754dbaa79dbf8b537800cb2fa1a6e534764fef50ab1f7533226c5c/pandas-2.3.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:fe67dc676818c186d5a3d5425250e40f179c2a89145df477dd82945eaea89e97", size = 13837228 }, - { url = "https://files.pythonhosted.org/packages/3f/d6/d7f5777162aa9b48ec3910bca5a58c9b5927cfd9cfde3aa64322f5ba4b9f/pandas-2.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:2eb789ae0274672acbd3c575b0598d213345660120a257b47b5dafdc618aec83", size = 11336561 }, - { url = "https://files.pythonhosted.org/packages/76/1c/ccf70029e927e473a4476c00e0d5b32e623bff27f0402d0a92b7fc29bb9f/pandas-2.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2b0540963d83431f5ce8870ea02a7430adca100cec8a050f0811f8e31035541b", size = 11566608 }, - { url = "https://files.pythonhosted.org/packages/ec/d3/3c37cb724d76a841f14b8f5fe57e5e3645207cc67370e4f84717e8bb7657/pandas-2.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fe7317f578c6a153912bd2292f02e40c1d8f253e93c599e82620c7f69755c74f", size = 10823181 }, - { url = "https://files.pythonhosted.org/packages/8a/4c/367c98854a1251940edf54a4df0826dcacfb987f9068abf3e3064081a382/pandas-2.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e6723a27ad7b244c0c79d8e7007092d7c8f0f11305770e2f4cd778b3ad5f9f85", size = 11793570 }, - { url = "https://files.pythonhosted.org/packages/07/5f/63760ff107bcf5146eee41b38b3985f9055e710a72fdd637b791dea3495c/pandas-2.3.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3462c3735fe19f2638f2c3a40bd94ec2dc5ba13abbb032dd2fa1f540a075509d", size = 12378887 }, - { url = "https://files.pythonhosted.org/packages/15/53/f31a9b4dfe73fe4711c3a609bd8e60238022f48eacedc257cd13ae9327a7/pandas-2.3.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:98bcc8b5bf7afed22cc753a28bc4d9e26e078e777066bc53fac7904ddef9a678", size = 13230957 }, - { url = "https://files.pythonhosted.org/packages/e0/94/6fce6bf85b5056d065e0a7933cba2616dcb48596f7ba3c6341ec4bcc529d/pandas-2.3.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4d544806b485ddf29e52d75b1f559142514e60ef58a832f74fb38e48d757b299", size = 13883883 }, - { url = "https://files.pythonhosted.org/packages/c8/7b/bdcb1ed8fccb63d04bdb7635161d0ec26596d92c9d7a6cce964e7876b6c1/pandas-2.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:b3cd4273d3cb3707b6fffd217204c52ed92859533e31dc03b7c5008aa933aaab", size = 11340212 }, - { url = "https://files.pythonhosted.org/packages/46/de/b8445e0f5d217a99fe0eeb2f4988070908979bec3587c0633e5428ab596c/pandas-2.3.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:689968e841136f9e542020698ee1c4fbe9caa2ed2213ae2388dc7b81721510d3", size = 11588172 }, - { url = "https://files.pythonhosted.org/packages/1e/e0/801cdb3564e65a5ac041ab99ea6f1d802a6c325bb6e58c79c06a3f1cd010/pandas-2.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:025e92411c16cbe5bb2a4abc99732a6b132f439b8aab23a59fa593eb00704232", size = 10717365 }, - { url = "https://files.pythonhosted.org/packages/51/a5/c76a8311833c24ae61a376dbf360eb1b1c9247a5d9c1e8b356563b31b80c/pandas-2.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b7ff55f31c4fcb3e316e8f7fa194566b286d6ac430afec0d461163312c5841e", size = 11280411 }, - { url = "https://files.pythonhosted.org/packages/da/01/e383018feba0a1ead6cf5fe8728e5d767fee02f06a3d800e82c489e5daaf/pandas-2.3.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7dcb79bf373a47d2a40cf7232928eb7540155abbc460925c2c96d2d30b006eb4", size = 11988013 }, - { url = "https://files.pythonhosted.org/packages/5b/14/cec7760d7c9507f11c97d64f29022e12a6cc4fc03ac694535e89f88ad2ec/pandas-2.3.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:56a342b231e8862c96bdb6ab97170e203ce511f4d0429589c8ede1ee8ece48b8", size = 12767210 }, - { url = "https://files.pythonhosted.org/packages/50/b9/6e2d2c6728ed29fb3d4d4d302504fb66f1a543e37eb2e43f352a86365cdf/pandas-2.3.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ca7ed14832bce68baef331f4d7f294411bed8efd032f8109d690df45e00c4679", size = 13440571 }, - { url = "https://files.pythonhosted.org/packages/80/a5/3a92893e7399a691bad7664d977cb5e7c81cf666c81f89ea76ba2bff483d/pandas-2.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:ac942bfd0aca577bef61f2bc8da8147c4ef6879965ef883d8e8d5d2dc3e744b8", size = 10987601 }, - { url = "https://files.pythonhosted.org/packages/32/ed/ff0a67a2c5505e1854e6715586ac6693dd860fbf52ef9f81edee200266e7/pandas-2.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9026bd4a80108fac2239294a15ef9003c4ee191a0f64b90f170b40cfb7cf2d22", size = 11531393 }, - { url = "https://files.pythonhosted.org/packages/c7/db/d8f24a7cc9fb0972adab0cc80b6817e8bef888cfd0024eeb5a21c0bb5c4a/pandas-2.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6de8547d4fdb12421e2d047a2c446c623ff4c11f47fddb6b9169eb98ffba485a", size = 10668750 }, - { url = "https://files.pythonhosted.org/packages/0f/b0/80f6ec783313f1e2356b28b4fd8d2148c378370045da918c73145e6aab50/pandas-2.3.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:782647ddc63c83133b2506912cc6b108140a38a37292102aaa19c81c83db2928", size = 11342004 }, - { url = "https://files.pythonhosted.org/packages/e9/e2/20a317688435470872885e7fc8f95109ae9683dec7c50be29b56911515a5/pandas-2.3.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ba6aff74075311fc88504b1db890187a3cd0f887a5b10f5525f8e2ef55bfdb9", size = 12050869 }, - { url = "https://files.pythonhosted.org/packages/55/79/20d746b0a96c67203a5bee5fb4e00ac49c3e8009a39e1f78de264ecc5729/pandas-2.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e5635178b387bd2ba4ac040f82bc2ef6e6b500483975c4ebacd34bec945fda12", size = 12750218 }, - { url = "https://files.pythonhosted.org/packages/7c/0f/145c8b41e48dbf03dd18fdd7f24f8ba95b8254a97a3379048378f33e7838/pandas-2.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6f3bf5ec947526106399a9e1d26d40ee2b259c66422efdf4de63c848492d91bb", size = 13416763 }, - { url = "https://files.pythonhosted.org/packages/b2/c0/54415af59db5cdd86a3d3bf79863e8cc3fa9ed265f0745254061ac09d5f2/pandas-2.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:1c78cf43c8fde236342a1cb2c34bcff89564a7bfed7e474ed2fffa6aed03a956", size = 10987482 }, - { url = "https://files.pythonhosted.org/packages/48/64/2fd2e400073a1230e13b8cd604c9bc95d9e3b962e5d44088ead2e8f0cfec/pandas-2.3.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8dfc17328e8da77be3cf9f47509e5637ba8f137148ed0e9b5241e1baf526e20a", size = 12029159 }, - { url = "https://files.pythonhosted.org/packages/d8/0a/d84fd79b0293b7ef88c760d7dca69828d867c89b6d9bc52d6a27e4d87316/pandas-2.3.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ec6c851509364c59a5344458ab935e6451b31b818be467eb24b0fe89bd05b6b9", size = 11393287 }, - { url = "https://files.pythonhosted.org/packages/50/ae/ff885d2b6e88f3c7520bb74ba319268b42f05d7e583b5dded9837da2723f/pandas-2.3.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:911580460fc4884d9b05254b38a6bfadddfcc6aaef856fb5859e7ca202e45275", size = 11309381 }, - { url = "https://files.pythonhosted.org/packages/85/86/1fa345fc17caf5d7780d2699985c03dbe186c68fee00b526813939062bb0/pandas-2.3.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f4d6feeba91744872a600e6edbbd5b033005b431d5ae8379abee5bcfa479fab", size = 11883998 }, - { url = "https://files.pythonhosted.org/packages/81/aa/e58541a49b5e6310d89474333e994ee57fea97c8aaa8fc7f00b873059bbf/pandas-2.3.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:fe37e757f462d31a9cd7580236a82f353f5713a80e059a29753cf938c6775d96", size = 12704705 }, - { url = "https://files.pythonhosted.org/packages/d5/f9/07086f5b0f2a19872554abeea7658200824f5835c58a106fa8f2ae96a46c/pandas-2.3.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5db9637dbc24b631ff3707269ae4559bce4b7fd75c1c4d7e13f40edc42df4444", size = 13189044 }, +sdist = { url = "https://files.pythonhosted.org/packages/d1/6f/75aa71f8a14267117adeeed5d21b204770189c0a0025acbdc03c337b28fc/pandas-2.3.1.tar.gz", hash = "sha256:0a95b9ac964fe83ce317827f80304d37388ea77616b1425f0ae41c9d2d0d7bb2", size = 4487493, upload-time = "2025-07-07T19:20:04.079Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c4/ca/aa97b47287221fa37a49634532e520300088e290b20d690b21ce3e448143/pandas-2.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:22c2e866f7209ebc3a8f08d75766566aae02bcc91d196935a1d9e59c7b990ac9", size = 11542731, upload-time = "2025-07-07T19:18:12.619Z" }, + { url = "https://files.pythonhosted.org/packages/80/bf/7938dddc5f01e18e573dcfb0f1b8c9357d9b5fa6ffdee6e605b92efbdff2/pandas-2.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3583d348546201aff730c8c47e49bc159833f971c2899d6097bce68b9112a4f1", size = 10790031, upload-time = "2025-07-07T19:18:16.611Z" }, + { url = "https://files.pythonhosted.org/packages/ee/2f/9af748366763b2a494fed477f88051dbf06f56053d5c00eba652697e3f94/pandas-2.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f951fbb702dacd390561e0ea45cdd8ecfa7fb56935eb3dd78e306c19104b9b0", size = 11724083, upload-time = "2025-07-07T19:18:20.512Z" }, + { url = "https://files.pythonhosted.org/packages/2c/95/79ab37aa4c25d1e7df953dde407bb9c3e4ae47d154bc0dd1692f3a6dcf8c/pandas-2.3.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd05b72ec02ebfb993569b4931b2e16fbb4d6ad6ce80224a3ee838387d83a191", size = 12342360, upload-time = "2025-07-07T19:18:23.194Z" }, + { url = "https://files.pythonhosted.org/packages/75/a7/d65e5d8665c12c3c6ff5edd9709d5836ec9b6f80071b7f4a718c6106e86e/pandas-2.3.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:1b916a627919a247d865aed068eb65eb91a344b13f5b57ab9f610b7716c92de1", size = 13202098, upload-time = "2025-07-07T19:18:25.558Z" }, + { url = "https://files.pythonhosted.org/packages/65/f3/4c1dbd754dbaa79dbf8b537800cb2fa1a6e534764fef50ab1f7533226c5c/pandas-2.3.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:fe67dc676818c186d5a3d5425250e40f179c2a89145df477dd82945eaea89e97", size = 13837228, upload-time = "2025-07-07T19:18:28.344Z" }, + { url = "https://files.pythonhosted.org/packages/3f/d6/d7f5777162aa9b48ec3910bca5a58c9b5927cfd9cfde3aa64322f5ba4b9f/pandas-2.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:2eb789ae0274672acbd3c575b0598d213345660120a257b47b5dafdc618aec83", size = 11336561, upload-time = "2025-07-07T19:18:31.211Z" }, + { url = "https://files.pythonhosted.org/packages/76/1c/ccf70029e927e473a4476c00e0d5b32e623bff27f0402d0a92b7fc29bb9f/pandas-2.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2b0540963d83431f5ce8870ea02a7430adca100cec8a050f0811f8e31035541b", size = 11566608, upload-time = "2025-07-07T19:18:33.86Z" }, + { url = "https://files.pythonhosted.org/packages/ec/d3/3c37cb724d76a841f14b8f5fe57e5e3645207cc67370e4f84717e8bb7657/pandas-2.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fe7317f578c6a153912bd2292f02e40c1d8f253e93c599e82620c7f69755c74f", size = 10823181, upload-time = "2025-07-07T19:18:36.151Z" }, + { url = "https://files.pythonhosted.org/packages/8a/4c/367c98854a1251940edf54a4df0826dcacfb987f9068abf3e3064081a382/pandas-2.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e6723a27ad7b244c0c79d8e7007092d7c8f0f11305770e2f4cd778b3ad5f9f85", size = 11793570, upload-time = "2025-07-07T19:18:38.385Z" }, + { url = "https://files.pythonhosted.org/packages/07/5f/63760ff107bcf5146eee41b38b3985f9055e710a72fdd637b791dea3495c/pandas-2.3.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3462c3735fe19f2638f2c3a40bd94ec2dc5ba13abbb032dd2fa1f540a075509d", size = 12378887, upload-time = "2025-07-07T19:18:41.284Z" }, + { url = "https://files.pythonhosted.org/packages/15/53/f31a9b4dfe73fe4711c3a609bd8e60238022f48eacedc257cd13ae9327a7/pandas-2.3.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:98bcc8b5bf7afed22cc753a28bc4d9e26e078e777066bc53fac7904ddef9a678", size = 13230957, upload-time = "2025-07-07T19:18:44.187Z" }, + { url = "https://files.pythonhosted.org/packages/e0/94/6fce6bf85b5056d065e0a7933cba2616dcb48596f7ba3c6341ec4bcc529d/pandas-2.3.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4d544806b485ddf29e52d75b1f559142514e60ef58a832f74fb38e48d757b299", size = 13883883, upload-time = "2025-07-07T19:18:46.498Z" }, + { url = "https://files.pythonhosted.org/packages/c8/7b/bdcb1ed8fccb63d04bdb7635161d0ec26596d92c9d7a6cce964e7876b6c1/pandas-2.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:b3cd4273d3cb3707b6fffd217204c52ed92859533e31dc03b7c5008aa933aaab", size = 11340212, upload-time = "2025-07-07T19:18:49.293Z" }, + { url = "https://files.pythonhosted.org/packages/46/de/b8445e0f5d217a99fe0eeb2f4988070908979bec3587c0633e5428ab596c/pandas-2.3.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:689968e841136f9e542020698ee1c4fbe9caa2ed2213ae2388dc7b81721510d3", size = 11588172, upload-time = "2025-07-07T19:18:52.054Z" }, + { url = "https://files.pythonhosted.org/packages/1e/e0/801cdb3564e65a5ac041ab99ea6f1d802a6c325bb6e58c79c06a3f1cd010/pandas-2.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:025e92411c16cbe5bb2a4abc99732a6b132f439b8aab23a59fa593eb00704232", size = 10717365, upload-time = "2025-07-07T19:18:54.785Z" }, + { url = "https://files.pythonhosted.org/packages/51/a5/c76a8311833c24ae61a376dbf360eb1b1c9247a5d9c1e8b356563b31b80c/pandas-2.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b7ff55f31c4fcb3e316e8f7fa194566b286d6ac430afec0d461163312c5841e", size = 11280411, upload-time = "2025-07-07T19:18:57.045Z" }, + { url = "https://files.pythonhosted.org/packages/da/01/e383018feba0a1ead6cf5fe8728e5d767fee02f06a3d800e82c489e5daaf/pandas-2.3.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7dcb79bf373a47d2a40cf7232928eb7540155abbc460925c2c96d2d30b006eb4", size = 11988013, upload-time = "2025-07-07T19:18:59.771Z" }, + { url = "https://files.pythonhosted.org/packages/5b/14/cec7760d7c9507f11c97d64f29022e12a6cc4fc03ac694535e89f88ad2ec/pandas-2.3.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:56a342b231e8862c96bdb6ab97170e203ce511f4d0429589c8ede1ee8ece48b8", size = 12767210, upload-time = "2025-07-07T19:19:02.944Z" }, + { url = "https://files.pythonhosted.org/packages/50/b9/6e2d2c6728ed29fb3d4d4d302504fb66f1a543e37eb2e43f352a86365cdf/pandas-2.3.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ca7ed14832bce68baef331f4d7f294411bed8efd032f8109d690df45e00c4679", size = 13440571, upload-time = "2025-07-07T19:19:06.82Z" }, + { url = "https://files.pythonhosted.org/packages/80/a5/3a92893e7399a691bad7664d977cb5e7c81cf666c81f89ea76ba2bff483d/pandas-2.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:ac942bfd0aca577bef61f2bc8da8147c4ef6879965ef883d8e8d5d2dc3e744b8", size = 10987601, upload-time = "2025-07-07T19:19:09.589Z" }, + { url = "https://files.pythonhosted.org/packages/32/ed/ff0a67a2c5505e1854e6715586ac6693dd860fbf52ef9f81edee200266e7/pandas-2.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9026bd4a80108fac2239294a15ef9003c4ee191a0f64b90f170b40cfb7cf2d22", size = 11531393, upload-time = "2025-07-07T19:19:12.245Z" }, + { url = "https://files.pythonhosted.org/packages/c7/db/d8f24a7cc9fb0972adab0cc80b6817e8bef888cfd0024eeb5a21c0bb5c4a/pandas-2.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6de8547d4fdb12421e2d047a2c446c623ff4c11f47fddb6b9169eb98ffba485a", size = 10668750, upload-time = "2025-07-07T19:19:14.612Z" }, + { url = "https://files.pythonhosted.org/packages/0f/b0/80f6ec783313f1e2356b28b4fd8d2148c378370045da918c73145e6aab50/pandas-2.3.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:782647ddc63c83133b2506912cc6b108140a38a37292102aaa19c81c83db2928", size = 11342004, upload-time = "2025-07-07T19:19:16.857Z" }, + { url = "https://files.pythonhosted.org/packages/e9/e2/20a317688435470872885e7fc8f95109ae9683dec7c50be29b56911515a5/pandas-2.3.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ba6aff74075311fc88504b1db890187a3cd0f887a5b10f5525f8e2ef55bfdb9", size = 12050869, upload-time = "2025-07-07T19:19:19.265Z" }, + { url = "https://files.pythonhosted.org/packages/55/79/20d746b0a96c67203a5bee5fb4e00ac49c3e8009a39e1f78de264ecc5729/pandas-2.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e5635178b387bd2ba4ac040f82bc2ef6e6b500483975c4ebacd34bec945fda12", size = 12750218, upload-time = "2025-07-07T19:19:21.547Z" }, + { url = "https://files.pythonhosted.org/packages/7c/0f/145c8b41e48dbf03dd18fdd7f24f8ba95b8254a97a3379048378f33e7838/pandas-2.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6f3bf5ec947526106399a9e1d26d40ee2b259c66422efdf4de63c848492d91bb", size = 13416763, upload-time = "2025-07-07T19:19:23.939Z" }, + { url = "https://files.pythonhosted.org/packages/b2/c0/54415af59db5cdd86a3d3bf79863e8cc3fa9ed265f0745254061ac09d5f2/pandas-2.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:1c78cf43c8fde236342a1cb2c34bcff89564a7bfed7e474ed2fffa6aed03a956", size = 10987482, upload-time = "2025-07-07T19:19:42.699Z" }, + { url = "https://files.pythonhosted.org/packages/48/64/2fd2e400073a1230e13b8cd604c9bc95d9e3b962e5d44088ead2e8f0cfec/pandas-2.3.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8dfc17328e8da77be3cf9f47509e5637ba8f137148ed0e9b5241e1baf526e20a", size = 12029159, upload-time = "2025-07-07T19:19:26.362Z" }, + { url = "https://files.pythonhosted.org/packages/d8/0a/d84fd79b0293b7ef88c760d7dca69828d867c89b6d9bc52d6a27e4d87316/pandas-2.3.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ec6c851509364c59a5344458ab935e6451b31b818be467eb24b0fe89bd05b6b9", size = 11393287, upload-time = "2025-07-07T19:19:29.157Z" }, + { url = "https://files.pythonhosted.org/packages/50/ae/ff885d2b6e88f3c7520bb74ba319268b42f05d7e583b5dded9837da2723f/pandas-2.3.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:911580460fc4884d9b05254b38a6bfadddfcc6aaef856fb5859e7ca202e45275", size = 11309381, upload-time = "2025-07-07T19:19:31.436Z" }, + { url = "https://files.pythonhosted.org/packages/85/86/1fa345fc17caf5d7780d2699985c03dbe186c68fee00b526813939062bb0/pandas-2.3.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f4d6feeba91744872a600e6edbbd5b033005b431d5ae8379abee5bcfa479fab", size = 11883998, upload-time = "2025-07-07T19:19:34.267Z" }, + { url = "https://files.pythonhosted.org/packages/81/aa/e58541a49b5e6310d89474333e994ee57fea97c8aaa8fc7f00b873059bbf/pandas-2.3.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:fe37e757f462d31a9cd7580236a82f353f5713a80e059a29753cf938c6775d96", size = 12704705, upload-time = "2025-07-07T19:19:36.856Z" }, + { url = "https://files.pythonhosted.org/packages/d5/f9/07086f5b0f2a19872554abeea7658200824f5835c58a106fa8f2ae96a46c/pandas-2.3.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5db9637dbc24b631ff3707269ae4559bce4b7fd75c1c4d7e13f40edc42df4444", size = 13189044, upload-time = "2025-07-07T19:19:39.999Z" }, ] [[package]] @@ -2000,27 +2002,27 @@ dependencies = [ { name = "numpy", version = "2.3.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "types-pytz" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ec/df/c1c51c5cec087b8f4d04669308b700e9648745a77cdd0c8c5e16520703ca/pandas_stubs-2.3.0.250703.tar.gz", hash = "sha256:fb6a8478327b16ed65c46b1541de74f5c5947f3601850caf3e885e0140584717", size = 103910 } +sdist = { url = "https://files.pythonhosted.org/packages/ec/df/c1c51c5cec087b8f4d04669308b700e9648745a77cdd0c8c5e16520703ca/pandas_stubs-2.3.0.250703.tar.gz", hash = "sha256:fb6a8478327b16ed65c46b1541de74f5c5947f3601850caf3e885e0140584717", size = 103910, upload-time = "2025-07-02T17:49:11.667Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/75/cb/09d5f9bf7c8659af134ae0ffc1a349038a5d0ff93e45aedc225bde2872a3/pandas_stubs-2.3.0.250703-py3-none-any.whl", hash = "sha256:a9265fc69909f0f7a9cabc5f596d86c9d531499fed86b7838fd3278285d76b81", size = 154719 }, + { url = "https://files.pythonhosted.org/packages/75/cb/09d5f9bf7c8659af134ae0ffc1a349038a5d0ff93e45aedc225bde2872a3/pandas_stubs-2.3.0.250703-py3-none-any.whl", hash = "sha256:a9265fc69909f0f7a9cabc5f596d86c9d531499fed86b7838fd3278285d76b81", size = 154719, upload-time = "2025-07-02T17:49:10.697Z" }, ] [[package]] name = "parsedatetime" version = "2.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a8/20/cb587f6672dbe585d101f590c3871d16e7aec5a576a1694997a3777312ac/parsedatetime-2.6.tar.gz", hash = "sha256:4cb368fbb18a0b7231f4d76119165451c8d2e35951455dfee97c62a87b04d455", size = 60114 } +sdist = { url = "https://files.pythonhosted.org/packages/a8/20/cb587f6672dbe585d101f590c3871d16e7aec5a576a1694997a3777312ac/parsedatetime-2.6.tar.gz", hash = "sha256:4cb368fbb18a0b7231f4d76119165451c8d2e35951455dfee97c62a87b04d455", size = 60114, upload-time = "2020-05-31T23:50:57.443Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9d/a4/3dd804926a42537bf69fb3ebb9fd72a50ba84f807d95df5ae016606c976c/parsedatetime-2.6-py3-none-any.whl", hash = "sha256:cb96edd7016872f58479e35879294258c71437195760746faffedb692aef000b", size = 42548 }, + { url = "https://files.pythonhosted.org/packages/9d/a4/3dd804926a42537bf69fb3ebb9fd72a50ba84f807d95df5ae016606c976c/parsedatetime-2.6-py3-none-any.whl", hash = "sha256:cb96edd7016872f58479e35879294258c71437195760746faffedb692aef000b", size = 42548, upload-time = "2020-05-31T23:50:56.315Z" }, ] [[package]] name = "pathspec" version = "0.12.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043 } +sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191 }, + { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, ] [[package]] @@ -2031,73 +2033,73 @@ dependencies = [ { name = "python-dateutil" }, { name = "tzdata" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/23/7c/009c12b86c7cc6c403aec80f8a4308598dfc5995e5c523a5491faaa3952e/pendulum-3.1.0.tar.gz", hash = "sha256:66f96303560f41d097bee7d2dc98ffca716fbb3a832c4b3062034c2d45865015", size = 85930 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b4/d8/398cd27903a6899d0ae47b896d88e0b15849fc334931a6732e7ce3be9a45/pendulum-3.1.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:aa545a59e6517cf43597455a6fb44daa4a6e08473d67a7ad34e4fa951efb9620", size = 338637 }, - { url = "https://files.pythonhosted.org/packages/aa/9d/a125554919c6db14e189393254c7781ee98ed5a121b6c05652d353b03c12/pendulum-3.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:299df2da6c490ede86bb8d58c65e33d7a2a42479d21475a54b467b03ccb88531", size = 326003 }, - { url = "https://files.pythonhosted.org/packages/53/9f/43a5a902f904e06252c259c2f6cf2dceafbb25aef158df08f79c0089dfd7/pendulum-3.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dbaa66e3ab179a2746eec67462f852a5d555bd709c25030aef38477468dd008e", size = 344335 }, - { url = "https://files.pythonhosted.org/packages/ca/24/00fcd6abd1f7623d2bbcca048b45f01aa8bb6b647e0477c3a8ea6094335c/pendulum-3.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c3907ab3744c32e339c358d88ec80cd35fa2d4b25c77a3c67e6b39e99b7090c5", size = 382169 }, - { url = "https://files.pythonhosted.org/packages/32/bc/20a87f24c26c6c4daf3c69311208b28130b4d19c006da16efc0e55715963/pendulum-3.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8244958c5bc4ed1c47ee84b098ddd95287a3fc59e569ca6e2b664c6396138ec4", size = 436675 }, - { url = "https://files.pythonhosted.org/packages/1d/eb/3b1818a796408a250b8e6cfaa5372b991c0cbec768e02e0f9a226755383d/pendulum-3.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca5722b3993b85ff7dfced48d86b318f863c359877b6badf1a3601e35199ef8f", size = 353728 }, - { url = "https://files.pythonhosted.org/packages/36/23/755ef61f863b2777925171a59509540205b561a9e07ee7de0b5be9226bea/pendulum-3.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5b77a3dc010eea1a4916ef3771163d808bfc3e02b894c37df311287f18e5b764", size = 524465 }, - { url = "https://files.pythonhosted.org/packages/07/1f/a3e5f08890d13d93eee725778bfeaa233db5c55463e526857dffbc1a47e4/pendulum-3.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2d6e1eff4a15fdb8fb3867c5469e691c2465eef002a6a541c47b48a390ff4cf4", size = 525690 }, - { url = "https://files.pythonhosted.org/packages/43/c5/bf8ce472b81e8f5f074e8ba39899d288acce417c2c4a9ec7486d56970e28/pendulum-3.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:73de43ec85b46ac75db848c8e2f3f5d086e90b11cd9c7f029e14c8d748d920e2", size = 260356 }, - { url = "https://files.pythonhosted.org/packages/5e/6e/d28d3c22e6708b819a94c05bd05a3dfaed5c685379e8b6dc4b34b473b942/pendulum-3.1.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:61a03d14f8c64d13b2f7d5859e4b4053c4a7d3b02339f6c71f3e4606bfd67423", size = 338596 }, - { url = "https://files.pythonhosted.org/packages/e1/e6/43324d58021d463c2eeb6146b169d2c935f2f840f9e45ac2d500453d954c/pendulum-3.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e674ed2d158afa5c361e60f1f67872dc55b492a10cacdaa7fcd7b7da5f158f24", size = 325854 }, - { url = "https://files.pythonhosted.org/packages/b0/a7/d2ae79b960bfdea94dab67e2f118697b08bc9e98eb6bd8d32c4d99240da3/pendulum-3.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c75377eb16e58bbe7e03ea89eeea49be6fc5de0934a4aef0e263f8b4fa71bc2", size = 344334 }, - { url = "https://files.pythonhosted.org/packages/96/94/941f071212e23c29aae7def891fb636930c648386e059ce09ea0dcd43933/pendulum-3.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:656b8b0ce070f0f2e5e2668247d3c783c55336534aa1f13bd0969535878955e1", size = 382259 }, - { url = "https://files.pythonhosted.org/packages/51/ad/a78a701656aec00d16fee636704445c23ca11617a0bfe7c3848d1caa5157/pendulum-3.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48962903e6c1afe1f13548cb6252666056086c107d59e3d64795c58c9298bc2e", size = 436361 }, - { url = "https://files.pythonhosted.org/packages/da/93/83f59ccbf4435c29dca8c63a6560fcbe4783079a468a5f91d9f886fd21f0/pendulum-3.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d364ec3f8e65010fefd4b0aaf7be5eb97e5df761b107a06f5e743b7c3f52c311", size = 353653 }, - { url = "https://files.pythonhosted.org/packages/6f/0f/42d6644ec6339b41066f594e52d286162aecd2e9735aaf994d7e00c9e09d/pendulum-3.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:dd52caffc2afb86612ec43bbeb226f204ea12ebff9f3d12f900a7d3097210fcc", size = 524567 }, - { url = "https://files.pythonhosted.org/packages/de/45/d84d909202755ab9d3379e5481fdf70f53344ebefbd68d6f5803ddde98a6/pendulum-3.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d439fccaa35c91f686bd59d30604dab01e8b5c1d0dd66e81648c432fd3f8a539", size = 525571 }, - { url = "https://files.pythonhosted.org/packages/0d/e0/4de160773ce3c2f7843c310db19dd919a0cd02cc1c0384866f63b18a6251/pendulum-3.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:43288773a86d9c5c0ddb645f88f615ff6bd12fd1410b34323662beccb18f3b49", size = 260259 }, - { url = "https://files.pythonhosted.org/packages/c1/7f/ffa278f78112c6c6e5130a702042f52aab5c649ae2edf814df07810bbba5/pendulum-3.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:569ea5072ae0f11d625e03b36d865f8037b76e838a3b621f6967314193896a11", size = 253899 }, - { url = "https://files.pythonhosted.org/packages/7a/d7/b1bfe15a742f2c2713acb1fdc7dc3594ff46ef9418ac6a96fcb12a6ba60b/pendulum-3.1.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:4dfd53e7583ccae138be86d6c0a0b324c7547df2afcec1876943c4d481cf9608", size = 336209 }, - { url = "https://files.pythonhosted.org/packages/eb/87/0392da0c603c828b926d9f7097fbdddaafc01388cb8a00888635d04758c3/pendulum-3.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6a6e06a28f3a7d696546347805536f6f38be458cb79de4f80754430696bea9e6", size = 323130 }, - { url = "https://files.pythonhosted.org/packages/c0/61/95f1eec25796be6dddf71440ee16ec1fd0c573fc61a73bd1ef6daacd529a/pendulum-3.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7e68d6a51880708084afd8958af42dc8c5e819a70a6c6ae903b1c4bfc61e0f25", size = 341509 }, - { url = "https://files.pythonhosted.org/packages/b5/7b/eb0f5e6aa87d5e1b467a1611009dbdc92f0f72425ebf07669bfadd8885a6/pendulum-3.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9e3f1e5da39a7ea7119efda1dd96b529748c1566f8a983412d0908455d606942", size = 378674 }, - { url = "https://files.pythonhosted.org/packages/29/68/5a4c1b5de3e54e16cab21d2ec88f9cd3f18599e96cc90a441c0b0ab6b03f/pendulum-3.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e9af1e5eeddb4ebbe1b1c9afb9fd8077d73416ade42dd61264b3f3b87742e0bb", size = 436133 }, - { url = "https://files.pythonhosted.org/packages/87/5d/f7a1d693e5c0f789185117d5c1d5bee104f5b0d9fbf061d715fb61c840a8/pendulum-3.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20f74aa8029a42e327bfc150472e0e4d2358fa5d795f70460160ba81b94b6945", size = 351232 }, - { url = "https://files.pythonhosted.org/packages/30/77/c97617eb31f1d0554edb073201a294019b9e0a9bd2f73c68e6d8d048cd6b/pendulum-3.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:cf6229e5ee70c2660148523f46c472e677654d0097bec010d6730f08312a4931", size = 521562 }, - { url = "https://files.pythonhosted.org/packages/76/22/0d0ef3393303877e757b848ecef8a9a8c7627e17e7590af82d14633b2cd1/pendulum-3.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:350cabb23bf1aec7c7694b915d3030bff53a2ad4aeabc8c8c0d807c8194113d6", size = 523221 }, - { url = "https://files.pythonhosted.org/packages/99/f3/aefb579aa3cebd6f2866b205fc7a60d33e9a696e9e629024752107dc3cf5/pendulum-3.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:42959341e843077c41d47420f28c3631de054abd64da83f9b956519b5c7a06a7", size = 260502 }, - { url = "https://files.pythonhosted.org/packages/02/74/4332b5d6e34c63d4df8e8eab2249e74c05513b1477757463f7fdca99e9be/pendulum-3.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:006758e2125da2e624493324dfd5d7d1b02b0c44bc39358e18bf0f66d0767f5f", size = 253089 }, - { url = "https://files.pythonhosted.org/packages/8e/1f/af928ba4aa403dac9569f787adcf024005e7654433d71f7a84e608716837/pendulum-3.1.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:28658b0baf4b30eb31d096a375983cfed033e60c0a7bbe94fa23f06cd779b50b", size = 336209 }, - { url = "https://files.pythonhosted.org/packages/b6/16/b010643007ba964c397da7fa622924423883c1bbff1a53f9d1022cd7f024/pendulum-3.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b114dcb99ce511cb8f5495c7b6f0056b2c3dba444ef1ea6e48030d7371bd531a", size = 323132 }, - { url = "https://files.pythonhosted.org/packages/64/19/c3c47aeecb5d9bceb0e89faafd800d39809b696c5b7bba8ec8370ad5052c/pendulum-3.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2404a6a54c80252ea393291f0b7f35525a61abae3d795407f34e118a8f133a18", size = 341509 }, - { url = "https://files.pythonhosted.org/packages/38/cf/c06921ff6b860ff7e62e70b8e5d4dc70e36f5abb66d168bd64d51760bc4e/pendulum-3.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d06999790d9ee9962a1627e469f98568bf7ad1085553fa3c30ed08b3944a14d7", size = 378674 }, - { url = "https://files.pythonhosted.org/packages/62/0b/a43953b9eba11e82612b033ac5133f716f1b76b6108a65da6f408b3cc016/pendulum-3.1.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94751c52f6b7c306734d1044c2c6067a474237e1e5afa2f665d1fbcbbbcf24b3", size = 436133 }, - { url = "https://files.pythonhosted.org/packages/eb/a0/ec3d70b3b96e23ae1d039f132af35e17704c22a8250d1887aaefea4d78a6/pendulum-3.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5553ac27be05e997ec26d7f004cf72788f4ce11fe60bb80dda604a64055b29d0", size = 351232 }, - { url = "https://files.pythonhosted.org/packages/f4/97/aba23f1716b82f6951ba2b1c9178a2d107d1e66c102762a9bf19988547ea/pendulum-3.1.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:f8dee234ca6142bf0514368d01a72945a44685aaa2fc4c14c98d09da9437b620", size = 521563 }, - { url = "https://files.pythonhosted.org/packages/01/33/2c0d5216cc53d16db0c4b3d510f141ee0a540937f8675948541190fbd48b/pendulum-3.1.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:7378084fe54faab4ee481897a00b710876f2e901ded6221671e827a253e643f2", size = 523221 }, - { url = "https://files.pythonhosted.org/packages/51/89/8de955c339c31aeae77fd86d3225509b998c81875e9dba28cb88b8cbf4b3/pendulum-3.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:8539db7ae2c8da430ac2515079e288948c8ebf7eb1edd3e8281b5cdf433040d6", size = 260501 }, - { url = "https://files.pythonhosted.org/packages/15/c3/226a3837363e94f8722461848feec18bfdd7d5172564d53aa3c3397ff01e/pendulum-3.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:1ce26a608e1f7387cd393fba2a129507c4900958d4f47b90757ec17656856571", size = 253087 }, - { url = "https://files.pythonhosted.org/packages/66/10/3258c084653606d2be2c7168998eda4a57cf1559cecb43cf1100000fda5f/pendulum-3.1.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:d2cac744940299d8da41a3ed941aa1e02b5abbc9ae2c525f3aa2ae30c28a86b5", size = 339442 }, - { url = "https://files.pythonhosted.org/packages/98/d5/98a1a10cd1cfb3390fbf070864e9a10de8e70a9d4509832132f4d900d655/pendulum-3.1.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:ffb39c3f3906a9c9a108fa98e5556f18b52d2c6451984bbfe2f14436ec4fc9d4", size = 326609 }, - { url = "https://files.pythonhosted.org/packages/0a/2e/448abdebc11b9c54e190d273cb084162643199fc184cb1bb6bff7900e67f/pendulum-3.1.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebe18b1c2eb364064cc4a68a65900f1465cac47d0891dab82341766bcc05b40c", size = 344777 }, - { url = "https://files.pythonhosted.org/packages/ed/91/ee857bbd51168bf08b89c3a4705c920725eee0f830ccc513b8370f6ce71d/pendulum-3.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9e9b28a35cec9fcd90f224b4878456129a057dbd694fc8266a9393834804995", size = 354404 }, - { url = "https://files.pythonhosted.org/packages/bc/d4/e63a57df65e2b2d10f3aa917a4069be9abf5ac7d56d11336e0510742d8a6/pendulum-3.1.0-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:a3be19b73a9c6a866724419295482f817727e635ccc82f07ae6f818943a1ee96", size = 524948 }, - { url = "https://files.pythonhosted.org/packages/93/87/04e74600c5a5674e5f341b8888b530a9de9b84b31889f80fac3bee3e9e87/pendulum-3.1.0-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:24a53b523819bda4c70245687a589b5ea88711f7caac4be5f276d843fe63076b", size = 526340 }, - { url = "https://files.pythonhosted.org/packages/48/27/d3577a5f6f7d1fbf1138d87ce21ebab363c78642513b991d1c424d658d09/pendulum-3.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:bd701789414fbd0be3c75f46803f31e91140c23821e4bcb0fa2bddcdd051c425", size = 261089 }, - { url = "https://files.pythonhosted.org/packages/6e/23/e98758924d1b3aac11a626268eabf7f3cf177e7837c28d47bf84c64532d0/pendulum-3.1.0-py3-none-any.whl", hash = "sha256:f9178c2a8e291758ade1e8dd6371b1d26d08371b4c7730a6e9a3ef8b16ebae0f", size = 111799 }, +sdist = { url = "https://files.pythonhosted.org/packages/23/7c/009c12b86c7cc6c403aec80f8a4308598dfc5995e5c523a5491faaa3952e/pendulum-3.1.0.tar.gz", hash = "sha256:66f96303560f41d097bee7d2dc98ffca716fbb3a832c4b3062034c2d45865015", size = 85930, upload-time = "2025-04-19T14:30:01.675Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b4/d8/398cd27903a6899d0ae47b896d88e0b15849fc334931a6732e7ce3be9a45/pendulum-3.1.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:aa545a59e6517cf43597455a6fb44daa4a6e08473d67a7ad34e4fa951efb9620", size = 338637, upload-time = "2025-04-19T14:00:56.429Z" }, + { url = "https://files.pythonhosted.org/packages/aa/9d/a125554919c6db14e189393254c7781ee98ed5a121b6c05652d353b03c12/pendulum-3.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:299df2da6c490ede86bb8d58c65e33d7a2a42479d21475a54b467b03ccb88531", size = 326003, upload-time = "2025-04-19T14:00:58.192Z" }, + { url = "https://files.pythonhosted.org/packages/53/9f/43a5a902f904e06252c259c2f6cf2dceafbb25aef158df08f79c0089dfd7/pendulum-3.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dbaa66e3ab179a2746eec67462f852a5d555bd709c25030aef38477468dd008e", size = 344335, upload-time = "2025-04-19T14:00:59.985Z" }, + { url = "https://files.pythonhosted.org/packages/ca/24/00fcd6abd1f7623d2bbcca048b45f01aa8bb6b647e0477c3a8ea6094335c/pendulum-3.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c3907ab3744c32e339c358d88ec80cd35fa2d4b25c77a3c67e6b39e99b7090c5", size = 382169, upload-time = "2025-04-19T14:01:01.411Z" }, + { url = "https://files.pythonhosted.org/packages/32/bc/20a87f24c26c6c4daf3c69311208b28130b4d19c006da16efc0e55715963/pendulum-3.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8244958c5bc4ed1c47ee84b098ddd95287a3fc59e569ca6e2b664c6396138ec4", size = 436675, upload-time = "2025-04-19T14:01:03.068Z" }, + { url = "https://files.pythonhosted.org/packages/1d/eb/3b1818a796408a250b8e6cfaa5372b991c0cbec768e02e0f9a226755383d/pendulum-3.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca5722b3993b85ff7dfced48d86b318f863c359877b6badf1a3601e35199ef8f", size = 353728, upload-time = "2025-04-19T14:01:04.483Z" }, + { url = "https://files.pythonhosted.org/packages/36/23/755ef61f863b2777925171a59509540205b561a9e07ee7de0b5be9226bea/pendulum-3.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5b77a3dc010eea1a4916ef3771163d808bfc3e02b894c37df311287f18e5b764", size = 524465, upload-time = "2025-04-19T14:01:05.865Z" }, + { url = "https://files.pythonhosted.org/packages/07/1f/a3e5f08890d13d93eee725778bfeaa233db5c55463e526857dffbc1a47e4/pendulum-3.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2d6e1eff4a15fdb8fb3867c5469e691c2465eef002a6a541c47b48a390ff4cf4", size = 525690, upload-time = "2025-04-19T14:01:07.707Z" }, + { url = "https://files.pythonhosted.org/packages/43/c5/bf8ce472b81e8f5f074e8ba39899d288acce417c2c4a9ec7486d56970e28/pendulum-3.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:73de43ec85b46ac75db848c8e2f3f5d086e90b11cd9c7f029e14c8d748d920e2", size = 260356, upload-time = "2025-04-19T14:01:09.339Z" }, + { url = "https://files.pythonhosted.org/packages/5e/6e/d28d3c22e6708b819a94c05bd05a3dfaed5c685379e8b6dc4b34b473b942/pendulum-3.1.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:61a03d14f8c64d13b2f7d5859e4b4053c4a7d3b02339f6c71f3e4606bfd67423", size = 338596, upload-time = "2025-04-19T14:01:11.306Z" }, + { url = "https://files.pythonhosted.org/packages/e1/e6/43324d58021d463c2eeb6146b169d2c935f2f840f9e45ac2d500453d954c/pendulum-3.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e674ed2d158afa5c361e60f1f67872dc55b492a10cacdaa7fcd7b7da5f158f24", size = 325854, upload-time = "2025-04-19T14:01:13.156Z" }, + { url = "https://files.pythonhosted.org/packages/b0/a7/d2ae79b960bfdea94dab67e2f118697b08bc9e98eb6bd8d32c4d99240da3/pendulum-3.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c75377eb16e58bbe7e03ea89eeea49be6fc5de0934a4aef0e263f8b4fa71bc2", size = 344334, upload-time = "2025-04-19T14:01:15.151Z" }, + { url = "https://files.pythonhosted.org/packages/96/94/941f071212e23c29aae7def891fb636930c648386e059ce09ea0dcd43933/pendulum-3.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:656b8b0ce070f0f2e5e2668247d3c783c55336534aa1f13bd0969535878955e1", size = 382259, upload-time = "2025-04-19T14:01:16.924Z" }, + { url = "https://files.pythonhosted.org/packages/51/ad/a78a701656aec00d16fee636704445c23ca11617a0bfe7c3848d1caa5157/pendulum-3.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48962903e6c1afe1f13548cb6252666056086c107d59e3d64795c58c9298bc2e", size = 436361, upload-time = "2025-04-19T14:01:18.796Z" }, + { url = "https://files.pythonhosted.org/packages/da/93/83f59ccbf4435c29dca8c63a6560fcbe4783079a468a5f91d9f886fd21f0/pendulum-3.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d364ec3f8e65010fefd4b0aaf7be5eb97e5df761b107a06f5e743b7c3f52c311", size = 353653, upload-time = "2025-04-19T14:01:20.159Z" }, + { url = "https://files.pythonhosted.org/packages/6f/0f/42d6644ec6339b41066f594e52d286162aecd2e9735aaf994d7e00c9e09d/pendulum-3.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:dd52caffc2afb86612ec43bbeb226f204ea12ebff9f3d12f900a7d3097210fcc", size = 524567, upload-time = "2025-04-19T14:01:21.457Z" }, + { url = "https://files.pythonhosted.org/packages/de/45/d84d909202755ab9d3379e5481fdf70f53344ebefbd68d6f5803ddde98a6/pendulum-3.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d439fccaa35c91f686bd59d30604dab01e8b5c1d0dd66e81648c432fd3f8a539", size = 525571, upload-time = "2025-04-19T14:01:23.329Z" }, + { url = "https://files.pythonhosted.org/packages/0d/e0/4de160773ce3c2f7843c310db19dd919a0cd02cc1c0384866f63b18a6251/pendulum-3.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:43288773a86d9c5c0ddb645f88f615ff6bd12fd1410b34323662beccb18f3b49", size = 260259, upload-time = "2025-04-19T14:01:24.689Z" }, + { url = "https://files.pythonhosted.org/packages/c1/7f/ffa278f78112c6c6e5130a702042f52aab5c649ae2edf814df07810bbba5/pendulum-3.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:569ea5072ae0f11d625e03b36d865f8037b76e838a3b621f6967314193896a11", size = 253899, upload-time = "2025-04-19T14:01:26.442Z" }, + { url = "https://files.pythonhosted.org/packages/7a/d7/b1bfe15a742f2c2713acb1fdc7dc3594ff46ef9418ac6a96fcb12a6ba60b/pendulum-3.1.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:4dfd53e7583ccae138be86d6c0a0b324c7547df2afcec1876943c4d481cf9608", size = 336209, upload-time = "2025-04-19T14:01:27.815Z" }, + { url = "https://files.pythonhosted.org/packages/eb/87/0392da0c603c828b926d9f7097fbdddaafc01388cb8a00888635d04758c3/pendulum-3.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6a6e06a28f3a7d696546347805536f6f38be458cb79de4f80754430696bea9e6", size = 323130, upload-time = "2025-04-19T14:01:29.336Z" }, + { url = "https://files.pythonhosted.org/packages/c0/61/95f1eec25796be6dddf71440ee16ec1fd0c573fc61a73bd1ef6daacd529a/pendulum-3.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7e68d6a51880708084afd8958af42dc8c5e819a70a6c6ae903b1c4bfc61e0f25", size = 341509, upload-time = "2025-04-19T14:01:31.1Z" }, + { url = "https://files.pythonhosted.org/packages/b5/7b/eb0f5e6aa87d5e1b467a1611009dbdc92f0f72425ebf07669bfadd8885a6/pendulum-3.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9e3f1e5da39a7ea7119efda1dd96b529748c1566f8a983412d0908455d606942", size = 378674, upload-time = "2025-04-19T14:01:32.974Z" }, + { url = "https://files.pythonhosted.org/packages/29/68/5a4c1b5de3e54e16cab21d2ec88f9cd3f18599e96cc90a441c0b0ab6b03f/pendulum-3.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e9af1e5eeddb4ebbe1b1c9afb9fd8077d73416ade42dd61264b3f3b87742e0bb", size = 436133, upload-time = "2025-04-19T14:01:34.349Z" }, + { url = "https://files.pythonhosted.org/packages/87/5d/f7a1d693e5c0f789185117d5c1d5bee104f5b0d9fbf061d715fb61c840a8/pendulum-3.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20f74aa8029a42e327bfc150472e0e4d2358fa5d795f70460160ba81b94b6945", size = 351232, upload-time = "2025-04-19T14:01:35.669Z" }, + { url = "https://files.pythonhosted.org/packages/30/77/c97617eb31f1d0554edb073201a294019b9e0a9bd2f73c68e6d8d048cd6b/pendulum-3.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:cf6229e5ee70c2660148523f46c472e677654d0097bec010d6730f08312a4931", size = 521562, upload-time = "2025-04-19T14:01:37.05Z" }, + { url = "https://files.pythonhosted.org/packages/76/22/0d0ef3393303877e757b848ecef8a9a8c7627e17e7590af82d14633b2cd1/pendulum-3.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:350cabb23bf1aec7c7694b915d3030bff53a2ad4aeabc8c8c0d807c8194113d6", size = 523221, upload-time = "2025-04-19T14:01:38.444Z" }, + { url = "https://files.pythonhosted.org/packages/99/f3/aefb579aa3cebd6f2866b205fc7a60d33e9a696e9e629024752107dc3cf5/pendulum-3.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:42959341e843077c41d47420f28c3631de054abd64da83f9b956519b5c7a06a7", size = 260502, upload-time = "2025-04-19T14:01:39.814Z" }, + { url = "https://files.pythonhosted.org/packages/02/74/4332b5d6e34c63d4df8e8eab2249e74c05513b1477757463f7fdca99e9be/pendulum-3.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:006758e2125da2e624493324dfd5d7d1b02b0c44bc39358e18bf0f66d0767f5f", size = 253089, upload-time = "2025-04-19T14:01:41.171Z" }, + { url = "https://files.pythonhosted.org/packages/8e/1f/af928ba4aa403dac9569f787adcf024005e7654433d71f7a84e608716837/pendulum-3.1.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:28658b0baf4b30eb31d096a375983cfed033e60c0a7bbe94fa23f06cd779b50b", size = 336209, upload-time = "2025-04-19T14:01:42.775Z" }, + { url = "https://files.pythonhosted.org/packages/b6/16/b010643007ba964c397da7fa622924423883c1bbff1a53f9d1022cd7f024/pendulum-3.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b114dcb99ce511cb8f5495c7b6f0056b2c3dba444ef1ea6e48030d7371bd531a", size = 323132, upload-time = "2025-04-19T14:01:44.577Z" }, + { url = "https://files.pythonhosted.org/packages/64/19/c3c47aeecb5d9bceb0e89faafd800d39809b696c5b7bba8ec8370ad5052c/pendulum-3.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2404a6a54c80252ea393291f0b7f35525a61abae3d795407f34e118a8f133a18", size = 341509, upload-time = "2025-04-19T14:01:46.084Z" }, + { url = "https://files.pythonhosted.org/packages/38/cf/c06921ff6b860ff7e62e70b8e5d4dc70e36f5abb66d168bd64d51760bc4e/pendulum-3.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d06999790d9ee9962a1627e469f98568bf7ad1085553fa3c30ed08b3944a14d7", size = 378674, upload-time = "2025-04-19T14:01:47.727Z" }, + { url = "https://files.pythonhosted.org/packages/62/0b/a43953b9eba11e82612b033ac5133f716f1b76b6108a65da6f408b3cc016/pendulum-3.1.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94751c52f6b7c306734d1044c2c6067a474237e1e5afa2f665d1fbcbbbcf24b3", size = 436133, upload-time = "2025-04-19T14:01:49.126Z" }, + { url = "https://files.pythonhosted.org/packages/eb/a0/ec3d70b3b96e23ae1d039f132af35e17704c22a8250d1887aaefea4d78a6/pendulum-3.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5553ac27be05e997ec26d7f004cf72788f4ce11fe60bb80dda604a64055b29d0", size = 351232, upload-time = "2025-04-19T14:01:50.575Z" }, + { url = "https://files.pythonhosted.org/packages/f4/97/aba23f1716b82f6951ba2b1c9178a2d107d1e66c102762a9bf19988547ea/pendulum-3.1.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:f8dee234ca6142bf0514368d01a72945a44685aaa2fc4c14c98d09da9437b620", size = 521563, upload-time = "2025-04-19T14:01:51.9Z" }, + { url = "https://files.pythonhosted.org/packages/01/33/2c0d5216cc53d16db0c4b3d510f141ee0a540937f8675948541190fbd48b/pendulum-3.1.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:7378084fe54faab4ee481897a00b710876f2e901ded6221671e827a253e643f2", size = 523221, upload-time = "2025-04-19T14:01:53.275Z" }, + { url = "https://files.pythonhosted.org/packages/51/89/8de955c339c31aeae77fd86d3225509b998c81875e9dba28cb88b8cbf4b3/pendulum-3.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:8539db7ae2c8da430ac2515079e288948c8ebf7eb1edd3e8281b5cdf433040d6", size = 260501, upload-time = "2025-04-19T14:01:54.749Z" }, + { url = "https://files.pythonhosted.org/packages/15/c3/226a3837363e94f8722461848feec18bfdd7d5172564d53aa3c3397ff01e/pendulum-3.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:1ce26a608e1f7387cd393fba2a129507c4900958d4f47b90757ec17656856571", size = 253087, upload-time = "2025-04-19T14:01:55.998Z" }, + { url = "https://files.pythonhosted.org/packages/66/10/3258c084653606d2be2c7168998eda4a57cf1559cecb43cf1100000fda5f/pendulum-3.1.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:d2cac744940299d8da41a3ed941aa1e02b5abbc9ae2c525f3aa2ae30c28a86b5", size = 339442, upload-time = "2025-04-19T14:02:12.512Z" }, + { url = "https://files.pythonhosted.org/packages/98/d5/98a1a10cd1cfb3390fbf070864e9a10de8e70a9d4509832132f4d900d655/pendulum-3.1.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:ffb39c3f3906a9c9a108fa98e5556f18b52d2c6451984bbfe2f14436ec4fc9d4", size = 326609, upload-time = "2025-04-19T14:02:13.838Z" }, + { url = "https://files.pythonhosted.org/packages/0a/2e/448abdebc11b9c54e190d273cb084162643199fc184cb1bb6bff7900e67f/pendulum-3.1.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebe18b1c2eb364064cc4a68a65900f1465cac47d0891dab82341766bcc05b40c", size = 344777, upload-time = "2025-04-19T14:02:15.512Z" }, + { url = "https://files.pythonhosted.org/packages/ed/91/ee857bbd51168bf08b89c3a4705c920725eee0f830ccc513b8370f6ce71d/pendulum-3.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9e9b28a35cec9fcd90f224b4878456129a057dbd694fc8266a9393834804995", size = 354404, upload-time = "2025-04-19T14:02:16.91Z" }, + { url = "https://files.pythonhosted.org/packages/bc/d4/e63a57df65e2b2d10f3aa917a4069be9abf5ac7d56d11336e0510742d8a6/pendulum-3.1.0-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:a3be19b73a9c6a866724419295482f817727e635ccc82f07ae6f818943a1ee96", size = 524948, upload-time = "2025-04-19T14:02:18.808Z" }, + { url = "https://files.pythonhosted.org/packages/93/87/04e74600c5a5674e5f341b8888b530a9de9b84b31889f80fac3bee3e9e87/pendulum-3.1.0-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:24a53b523819bda4c70245687a589b5ea88711f7caac4be5f276d843fe63076b", size = 526340, upload-time = "2025-04-19T14:02:20.242Z" }, + { url = "https://files.pythonhosted.org/packages/48/27/d3577a5f6f7d1fbf1138d87ce21ebab363c78642513b991d1c424d658d09/pendulum-3.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:bd701789414fbd0be3c75f46803f31e91140c23821e4bcb0fa2bddcdd051c425", size = 261089, upload-time = "2025-04-19T14:02:21.631Z" }, + { url = "https://files.pythonhosted.org/packages/6e/23/e98758924d1b3aac11a626268eabf7f3cf177e7837c28d47bf84c64532d0/pendulum-3.1.0-py3-none-any.whl", hash = "sha256:f9178c2a8e291758ade1e8dd6371b1d26d08371b4c7730a6e9a3ef8b16ebae0f", size = 111799, upload-time = "2025-04-19T14:02:34.739Z" }, ] [[package]] name = "platformdirs" version = "4.3.8" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fe/8b/3c73abc9c759ecd3f1f7ceff6685840859e8070c4d947c93fae71f6a0bf2/platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", size = 21362 } +sdist = { url = "https://files.pythonhosted.org/packages/fe/8b/3c73abc9c759ecd3f1f7ceff6685840859e8070c4d947c93fae71f6a0bf2/platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", size = 21362, upload-time = "2025-05-07T22:47:42.121Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fe/39/979e8e21520d4e47a0bbe349e2713c0aac6f3d853d0e5b34d76206c439aa/platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4", size = 18567 }, + { url = "https://files.pythonhosted.org/packages/fe/39/979e8e21520d4e47a0bbe349e2713c0aac6f3d853d0e5b34d76206c439aa/platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4", size = 18567, upload-time = "2025-05-07T22:47:40.376Z" }, ] [[package]] name = "pluggy" version = "1.6.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412 } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538 }, + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, ] [[package]] @@ -2112,147 +2114,147 @@ dependencies = [ { name = "six" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0f/e7/027706e2ef332f7b47e8364c8ac78ef69f1d086e7b2716f8764abb54ea66/posthog-6.1.0.tar.gz", hash = "sha256:c3426841743d1f65679563df25920a7ac1e8e028391063faf0d04b8fdc5338be", size = 92650 } +sdist = { url = "https://files.pythonhosted.org/packages/0f/e7/027706e2ef332f7b47e8364c8ac78ef69f1d086e7b2716f8764abb54ea66/posthog-6.1.0.tar.gz", hash = "sha256:c3426841743d1f65679563df25920a7ac1e8e028391063faf0d04b8fdc5338be", size = 92650, upload-time = "2025-07-10T15:04:00.931Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e6/54/9594816fe7597780c2a86f4c77fd9975be0aee07fe62787670a445715cd4/posthog-6.1.0-py3-none-any.whl", hash = "sha256:8d653b3c122f102e8f3d8573fca528d580dba78366e33c0c4b828ae8b2975870", size = 109733 }, + { url = "https://files.pythonhosted.org/packages/e6/54/9594816fe7597780c2a86f4c77fd9975be0aee07fe62787670a445715cd4/posthog-6.1.0-py3-none-any.whl", hash = "sha256:8d653b3c122f102e8f3d8573fca528d580dba78366e33c0c4b828ae8b2975870", size = 109733, upload-time = "2025-07-10T15:03:59.757Z" }, ] [[package]] name = "propcache" version = "0.3.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a6/16/43264e4a779dd8588c21a70f0709665ee8f611211bdd2c87d952cfa7c776/propcache-0.3.2.tar.gz", hash = "sha256:20d7d62e4e7ef05f221e0db2856b979540686342e7dd9973b815599c7057e168", size = 44139 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ab/14/510deed325e262afeb8b360043c5d7c960da7d3ecd6d6f9496c9c56dc7f4/propcache-0.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:22d9962a358aedbb7a2e36187ff273adeaab9743373a272976d2e348d08c7770", size = 73178 }, - { url = "https://files.pythonhosted.org/packages/cd/4e/ad52a7925ff01c1325653a730c7ec3175a23f948f08626a534133427dcff/propcache-0.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0d0fda578d1dc3f77b6b5a5dce3b9ad69a8250a891760a548df850a5e8da87f3", size = 43133 }, - { url = "https://files.pythonhosted.org/packages/63/7c/e9399ba5da7780871db4eac178e9c2e204c23dd3e7d32df202092a1ed400/propcache-0.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3def3da3ac3ce41562d85db655d18ebac740cb3fa4367f11a52b3da9d03a5cc3", size = 43039 }, - { url = "https://files.pythonhosted.org/packages/22/e1/58da211eb8fdc6fc854002387d38f415a6ca5f5c67c1315b204a5d3e9d7a/propcache-0.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9bec58347a5a6cebf239daba9bda37dffec5b8d2ce004d9fe4edef3d2815137e", size = 201903 }, - { url = "https://files.pythonhosted.org/packages/c4/0a/550ea0f52aac455cb90111c8bab995208443e46d925e51e2f6ebdf869525/propcache-0.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55ffda449a507e9fbd4aca1a7d9aa6753b07d6166140e5a18d2ac9bc49eac220", size = 213362 }, - { url = "https://files.pythonhosted.org/packages/5a/af/9893b7d878deda9bb69fcf54600b247fba7317761b7db11fede6e0f28bd0/propcache-0.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64a67fb39229a8a8491dd42f864e5e263155e729c2e7ff723d6e25f596b1e8cb", size = 210525 }, - { url = "https://files.pythonhosted.org/packages/7c/bb/38fd08b278ca85cde36d848091ad2b45954bc5f15cce494bb300b9285831/propcache-0.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9da1cf97b92b51253d5b68cf5a2b9e0dafca095e36b7f2da335e27dc6172a614", size = 198283 }, - { url = "https://files.pythonhosted.org/packages/78/8c/9fe55bd01d362bafb413dfe508c48753111a1e269737fa143ba85693592c/propcache-0.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5f559e127134b07425134b4065be45b166183fdcb433cb6c24c8e4149056ad50", size = 191872 }, - { url = "https://files.pythonhosted.org/packages/54/14/4701c33852937a22584e08abb531d654c8bcf7948a8f87ad0a4822394147/propcache-0.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:aff2e4e06435d61f11a428360a932138d0ec288b0a31dd9bd78d200bd4a2b339", size = 199452 }, - { url = "https://files.pythonhosted.org/packages/16/44/447f2253d859602095356007657ee535e0093215ea0b3d1d6a41d16e5201/propcache-0.3.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:4927842833830942a5d0a56e6f4839bc484785b8e1ce8d287359794818633ba0", size = 191567 }, - { url = "https://files.pythonhosted.org/packages/f2/b3/e4756258749bb2d3b46defcff606a2f47410bab82be5824a67e84015b267/propcache-0.3.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:6107ddd08b02654a30fb8ad7a132021759d750a82578b94cd55ee2772b6ebea2", size = 193015 }, - { url = "https://files.pythonhosted.org/packages/1e/df/e6d3c7574233164b6330b9fd697beeac402afd367280e6dc377bb99b43d9/propcache-0.3.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:70bd8b9cd6b519e12859c99f3fc9a93f375ebd22a50296c3a295028bea73b9e7", size = 204660 }, - { url = "https://files.pythonhosted.org/packages/b2/53/e4d31dd5170b4a0e2e6b730f2385a96410633b4833dc25fe5dffd1f73294/propcache-0.3.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2183111651d710d3097338dd1893fcf09c9f54e27ff1a8795495a16a469cc90b", size = 206105 }, - { url = "https://files.pythonhosted.org/packages/7f/fe/74d54cf9fbe2a20ff786e5f7afcfde446588f0cf15fb2daacfbc267b866c/propcache-0.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:fb075ad271405dcad8e2a7ffc9a750a3bf70e533bd86e89f0603e607b93aa64c", size = 196980 }, - { url = "https://files.pythonhosted.org/packages/22/ec/c469c9d59dada8a7679625e0440b544fe72e99311a4679c279562051f6fc/propcache-0.3.2-cp310-cp310-win32.whl", hash = "sha256:404d70768080d3d3bdb41d0771037da19d8340d50b08e104ca0e7f9ce55fce70", size = 37679 }, - { url = "https://files.pythonhosted.org/packages/38/35/07a471371ac89d418f8d0b699c75ea6dca2041fbda360823de21f6a9ce0a/propcache-0.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:7435d766f978b4ede777002e6b3b6641dd229cd1da8d3d3106a45770365f9ad9", size = 41459 }, - { url = "https://files.pythonhosted.org/packages/80/8d/e8b436717ab9c2cfc23b116d2c297305aa4cd8339172a456d61ebf5669b8/propcache-0.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0b8d2f607bd8f80ddc04088bc2a037fdd17884a6fcadc47a96e334d72f3717be", size = 74207 }, - { url = "https://files.pythonhosted.org/packages/d6/29/1e34000e9766d112171764b9fa3226fa0153ab565d0c242c70e9945318a7/propcache-0.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06766d8f34733416e2e34f46fea488ad5d60726bb9481d3cddf89a6fa2d9603f", size = 43648 }, - { url = "https://files.pythonhosted.org/packages/46/92/1ad5af0df781e76988897da39b5f086c2bf0f028b7f9bd1f409bb05b6874/propcache-0.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a2dc1f4a1df4fecf4e6f68013575ff4af84ef6f478fe5344317a65d38a8e6dc9", size = 43496 }, - { url = "https://files.pythonhosted.org/packages/b3/ce/e96392460f9fb68461fabab3e095cb00c8ddf901205be4eae5ce246e5b7e/propcache-0.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be29c4f4810c5789cf10ddf6af80b041c724e629fa51e308a7a0fb19ed1ef7bf", size = 217288 }, - { url = "https://files.pythonhosted.org/packages/c5/2a/866726ea345299f7ceefc861a5e782b045545ae6940851930a6adaf1fca6/propcache-0.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59d61f6970ecbd8ff2e9360304d5c8876a6abd4530cb752c06586849ac8a9dc9", size = 227456 }, - { url = "https://files.pythonhosted.org/packages/de/03/07d992ccb6d930398689187e1b3c718339a1c06b8b145a8d9650e4726166/propcache-0.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:62180e0b8dbb6b004baec00a7983e4cc52f5ada9cd11f48c3528d8cfa7b96a66", size = 225429 }, - { url = "https://files.pythonhosted.org/packages/5d/e6/116ba39448753b1330f48ab8ba927dcd6cf0baea8a0ccbc512dfb49ba670/propcache-0.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c144ca294a204c470f18cf4c9d78887810d04a3e2fbb30eea903575a779159df", size = 213472 }, - { url = "https://files.pythonhosted.org/packages/a6/85/f01f5d97e54e428885a5497ccf7f54404cbb4f906688a1690cd51bf597dc/propcache-0.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5c2a784234c28854878d68978265617aa6dc0780e53d44b4d67f3651a17a9a2", size = 204480 }, - { url = "https://files.pythonhosted.org/packages/e3/79/7bf5ab9033b8b8194cc3f7cf1aaa0e9c3256320726f64a3e1f113a812dce/propcache-0.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5745bc7acdafa978ca1642891b82c19238eadc78ba2aaa293c6863b304e552d7", size = 214530 }, - { url = "https://files.pythonhosted.org/packages/31/0b/bd3e0c00509b609317df4a18e6b05a450ef2d9a963e1d8bc9c9415d86f30/propcache-0.3.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:c0075bf773d66fa8c9d41f66cc132ecc75e5bb9dd7cce3cfd14adc5ca184cb95", size = 205230 }, - { url = "https://files.pythonhosted.org/packages/7a/23/fae0ff9b54b0de4e819bbe559508da132d5683c32d84d0dc2ccce3563ed4/propcache-0.3.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5f57aa0847730daceff0497f417c9de353c575d8da3579162cc74ac294c5369e", size = 206754 }, - { url = "https://files.pythonhosted.org/packages/b7/7f/ad6a3c22630aaa5f618b4dc3c3598974a72abb4c18e45a50b3cdd091eb2f/propcache-0.3.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:eef914c014bf72d18efb55619447e0aecd5fb7c2e3fa7441e2e5d6099bddff7e", size = 218430 }, - { url = "https://files.pythonhosted.org/packages/5b/2c/ba4f1c0e8a4b4c75910742f0d333759d441f65a1c7f34683b4a74c0ee015/propcache-0.3.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2a4092e8549031e82facf3decdbc0883755d5bbcc62d3aea9d9e185549936dcf", size = 223884 }, - { url = "https://files.pythonhosted.org/packages/88/e4/ebe30fc399e98572019eee82ad0caf512401661985cbd3da5e3140ffa1b0/propcache-0.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:85871b050f174bc0bfb437efbdb68aaf860611953ed12418e4361bc9c392749e", size = 211480 }, - { url = "https://files.pythonhosted.org/packages/96/0a/7d5260b914e01d1d0906f7f38af101f8d8ed0dc47426219eeaf05e8ea7c2/propcache-0.3.2-cp311-cp311-win32.whl", hash = "sha256:36c8d9b673ec57900c3554264e630d45980fd302458e4ac801802a7fd2ef7897", size = 37757 }, - { url = "https://files.pythonhosted.org/packages/e1/2d/89fe4489a884bc0da0c3278c552bd4ffe06a1ace559db5ef02ef24ab446b/propcache-0.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53af8cb6a781b02d2ea079b5b853ba9430fcbe18a8e3ce647d5982a3ff69f39", size = 41500 }, - { url = "https://files.pythonhosted.org/packages/a8/42/9ca01b0a6f48e81615dca4765a8f1dd2c057e0540f6116a27dc5ee01dfb6/propcache-0.3.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8de106b6c84506b31c27168582cd3cb3000a6412c16df14a8628e5871ff83c10", size = 73674 }, - { url = "https://files.pythonhosted.org/packages/af/6e/21293133beb550f9c901bbece755d582bfaf2176bee4774000bd4dd41884/propcache-0.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:28710b0d3975117239c76600ea351934ac7b5ff56e60953474342608dbbb6154", size = 43570 }, - { url = "https://files.pythonhosted.org/packages/0c/c8/0393a0a3a2b8760eb3bde3c147f62b20044f0ddac81e9d6ed7318ec0d852/propcache-0.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce26862344bdf836650ed2487c3d724b00fbfec4233a1013f597b78c1cb73615", size = 43094 }, - { url = "https://files.pythonhosted.org/packages/37/2c/489afe311a690399d04a3e03b069225670c1d489eb7b044a566511c1c498/propcache-0.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bca54bd347a253af2cf4544bbec232ab982f4868de0dd684246b67a51bc6b1db", size = 226958 }, - { url = "https://files.pythonhosted.org/packages/9d/ca/63b520d2f3d418c968bf596839ae26cf7f87bead026b6192d4da6a08c467/propcache-0.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55780d5e9a2ddc59711d727226bb1ba83a22dd32f64ee15594b9392b1f544eb1", size = 234894 }, - { url = "https://files.pythonhosted.org/packages/11/60/1d0ed6fff455a028d678df30cc28dcee7af77fa2b0e6962ce1df95c9a2a9/propcache-0.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:035e631be25d6975ed87ab23153db6a73426a48db688070d925aa27e996fe93c", size = 233672 }, - { url = "https://files.pythonhosted.org/packages/37/7c/54fd5301ef38505ab235d98827207176a5c9b2aa61939b10a460ca53e123/propcache-0.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee6f22b6eaa39297c751d0e80c0d3a454f112f5c6481214fcf4c092074cecd67", size = 224395 }, - { url = "https://files.pythonhosted.org/packages/ee/1a/89a40e0846f5de05fdc6779883bf46ba980e6df4d2ff8fb02643de126592/propcache-0.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ca3aee1aa955438c4dba34fc20a9f390e4c79967257d830f137bd5a8a32ed3b", size = 212510 }, - { url = "https://files.pythonhosted.org/packages/5e/33/ca98368586c9566a6b8d5ef66e30484f8da84c0aac3f2d9aec6d31a11bd5/propcache-0.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7a4f30862869fa2b68380d677cc1c5fcf1e0f2b9ea0cf665812895c75d0ca3b8", size = 222949 }, - { url = "https://files.pythonhosted.org/packages/ba/11/ace870d0aafe443b33b2f0b7efdb872b7c3abd505bfb4890716ad7865e9d/propcache-0.3.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b77ec3c257d7816d9f3700013639db7491a434644c906a2578a11daf13176251", size = 217258 }, - { url = "https://files.pythonhosted.org/packages/5b/d2/86fd6f7adffcfc74b42c10a6b7db721d1d9ca1055c45d39a1a8f2a740a21/propcache-0.3.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cab90ac9d3f14b2d5050928483d3d3b8fb6b4018893fc75710e6aa361ecb2474", size = 213036 }, - { url = "https://files.pythonhosted.org/packages/07/94/2d7d1e328f45ff34a0a284cf5a2847013701e24c2a53117e7c280a4316b3/propcache-0.3.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:0b504d29f3c47cf6b9e936c1852246c83d450e8e063d50562115a6be6d3a2535", size = 227684 }, - { url = "https://files.pythonhosted.org/packages/b7/05/37ae63a0087677e90b1d14710e532ff104d44bc1efa3b3970fff99b891dc/propcache-0.3.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:ce2ac2675a6aa41ddb2a0c9cbff53780a617ac3d43e620f8fd77ba1c84dcfc06", size = 234562 }, - { url = "https://files.pythonhosted.org/packages/a4/7c/3f539fcae630408d0bd8bf3208b9a647ccad10976eda62402a80adf8fc34/propcache-0.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:62b4239611205294cc433845b914131b2a1f03500ff3c1ed093ed216b82621e1", size = 222142 }, - { url = "https://files.pythonhosted.org/packages/7c/d2/34b9eac8c35f79f8a962546b3e97e9d4b990c420ee66ac8255d5d9611648/propcache-0.3.2-cp312-cp312-win32.whl", hash = "sha256:df4a81b9b53449ebc90cc4deefb052c1dd934ba85012aa912c7ea7b7e38b60c1", size = 37711 }, - { url = "https://files.pythonhosted.org/packages/19/61/d582be5d226cf79071681d1b46b848d6cb03d7b70af7063e33a2787eaa03/propcache-0.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:7046e79b989d7fe457bb755844019e10f693752d169076138abf17f31380800c", size = 41479 }, - { url = "https://files.pythonhosted.org/packages/dc/d1/8c747fafa558c603c4ca19d8e20b288aa0c7cda74e9402f50f31eb65267e/propcache-0.3.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ca592ed634a73ca002967458187109265e980422116c0a107cf93d81f95af945", size = 71286 }, - { url = "https://files.pythonhosted.org/packages/61/99/d606cb7986b60d89c36de8a85d58764323b3a5ff07770a99d8e993b3fa73/propcache-0.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9ecb0aad4020e275652ba3975740f241bd12a61f1a784df044cf7477a02bc252", size = 42425 }, - { url = "https://files.pythonhosted.org/packages/8c/96/ef98f91bbb42b79e9bb82bdd348b255eb9d65f14dbbe3b1594644c4073f7/propcache-0.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7f08f1cc28bd2eade7a8a3d2954ccc673bb02062e3e7da09bc75d843386b342f", size = 41846 }, - { url = "https://files.pythonhosted.org/packages/5b/ad/3f0f9a705fb630d175146cd7b1d2bf5555c9beaed54e94132b21aac098a6/propcache-0.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1a342c834734edb4be5ecb1e9fb48cb64b1e2320fccbd8c54bf8da8f2a84c33", size = 208871 }, - { url = "https://files.pythonhosted.org/packages/3a/38/2085cda93d2c8b6ec3e92af2c89489a36a5886b712a34ab25de9fbca7992/propcache-0.3.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a544caaae1ac73f1fecfae70ded3e93728831affebd017d53449e3ac052ac1e", size = 215720 }, - { url = "https://files.pythonhosted.org/packages/61/c1/d72ea2dc83ac7f2c8e182786ab0fc2c7bd123a1ff9b7975bee671866fe5f/propcache-0.3.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:310d11aa44635298397db47a3ebce7db99a4cc4b9bbdfcf6c98a60c8d5261cf1", size = 215203 }, - { url = "https://files.pythonhosted.org/packages/af/81/b324c44ae60c56ef12007105f1460d5c304b0626ab0cc6b07c8f2a9aa0b8/propcache-0.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c1396592321ac83157ac03a2023aa6cc4a3cc3cfdecb71090054c09e5a7cce3", size = 206365 }, - { url = "https://files.pythonhosted.org/packages/09/73/88549128bb89e66d2aff242488f62869014ae092db63ccea53c1cc75a81d/propcache-0.3.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cabf5b5902272565e78197edb682017d21cf3b550ba0460ee473753f28d23c1", size = 196016 }, - { url = "https://files.pythonhosted.org/packages/b9/3f/3bdd14e737d145114a5eb83cb172903afba7242f67c5877f9909a20d948d/propcache-0.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0a2f2235ac46a7aa25bdeb03a9e7060f6ecbd213b1f9101c43b3090ffb971ef6", size = 205596 }, - { url = "https://files.pythonhosted.org/packages/0f/ca/2f4aa819c357d3107c3763d7ef42c03980f9ed5c48c82e01e25945d437c1/propcache-0.3.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:92b69e12e34869a6970fd2f3da91669899994b47c98f5d430b781c26f1d9f387", size = 200977 }, - { url = "https://files.pythonhosted.org/packages/cd/4a/e65276c7477533c59085251ae88505caf6831c0e85ff8b2e31ebcbb949b1/propcache-0.3.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:54e02207c79968ebbdffc169591009f4474dde3b4679e16634d34c9363ff56b4", size = 197220 }, - { url = "https://files.pythonhosted.org/packages/7c/54/fc7152e517cf5578278b242396ce4d4b36795423988ef39bb8cd5bf274c8/propcache-0.3.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4adfb44cb588001f68c5466579d3f1157ca07f7504fc91ec87862e2b8e556b88", size = 210642 }, - { url = "https://files.pythonhosted.org/packages/b9/80/abeb4a896d2767bf5f1ea7b92eb7be6a5330645bd7fb844049c0e4045d9d/propcache-0.3.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fd3e6019dc1261cd0291ee8919dd91fbab7b169bb76aeef6c716833a3f65d206", size = 212789 }, - { url = "https://files.pythonhosted.org/packages/b3/db/ea12a49aa7b2b6d68a5da8293dcf50068d48d088100ac016ad92a6a780e6/propcache-0.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4c181cad81158d71c41a2bce88edce078458e2dd5ffee7eddd6b05da85079f43", size = 205880 }, - { url = "https://files.pythonhosted.org/packages/d1/e5/9076a0bbbfb65d1198007059c65639dfd56266cf8e477a9707e4b1999ff4/propcache-0.3.2-cp313-cp313-win32.whl", hash = "sha256:8a08154613f2249519e549de2330cf8e2071c2887309a7b07fb56098f5170a02", size = 37220 }, - { url = "https://files.pythonhosted.org/packages/d3/f5/b369e026b09a26cd77aa88d8fffd69141d2ae00a2abaaf5380d2603f4b7f/propcache-0.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:e41671f1594fc4ab0a6dec1351864713cb3a279910ae8b58f884a88a0a632c05", size = 40678 }, - { url = "https://files.pythonhosted.org/packages/a4/3a/6ece377b55544941a08d03581c7bc400a3c8cd3c2865900a68d5de79e21f/propcache-0.3.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:9a3cf035bbaf035f109987d9d55dc90e4b0e36e04bbbb95af3055ef17194057b", size = 76560 }, - { url = "https://files.pythonhosted.org/packages/0c/da/64a2bb16418740fa634b0e9c3d29edff1db07f56d3546ca2d86ddf0305e1/propcache-0.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:156c03d07dc1323d8dacaa221fbe028c5c70d16709cdd63502778e6c3ccca1b0", size = 44676 }, - { url = "https://files.pythonhosted.org/packages/36/7b/f025e06ea51cb72c52fb87e9b395cced02786610b60a3ed51da8af017170/propcache-0.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74413c0ba02ba86f55cf60d18daab219f7e531620c15f1e23d95563f505efe7e", size = 44701 }, - { url = "https://files.pythonhosted.org/packages/a4/00/faa1b1b7c3b74fc277f8642f32a4c72ba1d7b2de36d7cdfb676db7f4303e/propcache-0.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f066b437bb3fa39c58ff97ab2ca351db465157d68ed0440abecb21715eb24b28", size = 276934 }, - { url = "https://files.pythonhosted.org/packages/74/ab/935beb6f1756e0476a4d5938ff44bf0d13a055fed880caf93859b4f1baf4/propcache-0.3.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1304b085c83067914721e7e9d9917d41ad87696bf70f0bc7dee450e9c71ad0a", size = 278316 }, - { url = "https://files.pythonhosted.org/packages/f8/9d/994a5c1ce4389610838d1caec74bdf0e98b306c70314d46dbe4fcf21a3e2/propcache-0.3.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ab50cef01b372763a13333b4e54021bdcb291fc9a8e2ccb9c2df98be51bcde6c", size = 282619 }, - { url = "https://files.pythonhosted.org/packages/2b/00/a10afce3d1ed0287cef2e09506d3be9822513f2c1e96457ee369adb9a6cd/propcache-0.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fad3b2a085ec259ad2c2842666b2a0a49dea8463579c606426128925af1ed725", size = 265896 }, - { url = "https://files.pythonhosted.org/packages/2e/a8/2aa6716ffa566ca57c749edb909ad27884680887d68517e4be41b02299f3/propcache-0.3.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:261fa020c1c14deafd54c76b014956e2f86991af198c51139faf41c4d5e83892", size = 252111 }, - { url = "https://files.pythonhosted.org/packages/36/4f/345ca9183b85ac29c8694b0941f7484bf419c7f0fea2d1e386b4f7893eed/propcache-0.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:46d7f8aa79c927e5f987ee3a80205c987717d3659f035c85cf0c3680526bdb44", size = 268334 }, - { url = "https://files.pythonhosted.org/packages/3e/ca/fcd54f78b59e3f97b3b9715501e3147f5340167733d27db423aa321e7148/propcache-0.3.2-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:6d8f3f0eebf73e3c0ff0e7853f68be638b4043c65a70517bb575eff54edd8dbe", size = 255026 }, - { url = "https://files.pythonhosted.org/packages/8b/95/8e6a6bbbd78ac89c30c225210a5c687790e532ba4088afb8c0445b77ef37/propcache-0.3.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:03c89c1b14a5452cf15403e291c0ccd7751d5b9736ecb2c5bab977ad6c5bcd81", size = 250724 }, - { url = "https://files.pythonhosted.org/packages/ee/b0/0dd03616142baba28e8b2d14ce5df6631b4673850a3d4f9c0f9dd714a404/propcache-0.3.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:0cc17efde71e12bbaad086d679ce575268d70bc123a5a71ea7ad76f70ba30bba", size = 268868 }, - { url = "https://files.pythonhosted.org/packages/c5/98/2c12407a7e4fbacd94ddd32f3b1e3d5231e77c30ef7162b12a60e2dd5ce3/propcache-0.3.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:acdf05d00696bc0447e278bb53cb04ca72354e562cf88ea6f9107df8e7fd9770", size = 271322 }, - { url = "https://files.pythonhosted.org/packages/35/91/9cb56efbb428b006bb85db28591e40b7736847b8331d43fe335acf95f6c8/propcache-0.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4445542398bd0b5d32df908031cb1b30d43ac848e20470a878b770ec2dcc6330", size = 265778 }, - { url = "https://files.pythonhosted.org/packages/9a/4c/b0fe775a2bdd01e176b14b574be679d84fc83958335790f7c9a686c1f468/propcache-0.3.2-cp313-cp313t-win32.whl", hash = "sha256:f86e5d7cd03afb3a1db8e9f9f6eff15794e79e791350ac48a8c924e6f439f394", size = 41175 }, - { url = "https://files.pythonhosted.org/packages/a4/ff/47f08595e3d9b5e149c150f88d9714574f1a7cbd89fe2817158a952674bf/propcache-0.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:9704bedf6e7cbe3c65eca4379a9b53ee6a83749f047808cbb5044d40d7d72198", size = 44857 }, - { url = "https://files.pythonhosted.org/packages/cc/35/cc0aaecf278bb4575b8555f2b137de5ab821595ddae9da9d3cd1da4072c7/propcache-0.3.2-py3-none-any.whl", hash = "sha256:98f1ec44fb675f5052cccc8e609c46ed23a35a1cfd18545ad4e29002d858a43f", size = 12663 }, +sdist = { url = "https://files.pythonhosted.org/packages/a6/16/43264e4a779dd8588c21a70f0709665ee8f611211bdd2c87d952cfa7c776/propcache-0.3.2.tar.gz", hash = "sha256:20d7d62e4e7ef05f221e0db2856b979540686342e7dd9973b815599c7057e168", size = 44139, upload-time = "2025-06-09T22:56:06.081Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/14/510deed325e262afeb8b360043c5d7c960da7d3ecd6d6f9496c9c56dc7f4/propcache-0.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:22d9962a358aedbb7a2e36187ff273adeaab9743373a272976d2e348d08c7770", size = 73178, upload-time = "2025-06-09T22:53:40.126Z" }, + { url = "https://files.pythonhosted.org/packages/cd/4e/ad52a7925ff01c1325653a730c7ec3175a23f948f08626a534133427dcff/propcache-0.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0d0fda578d1dc3f77b6b5a5dce3b9ad69a8250a891760a548df850a5e8da87f3", size = 43133, upload-time = "2025-06-09T22:53:41.965Z" }, + { url = "https://files.pythonhosted.org/packages/63/7c/e9399ba5da7780871db4eac178e9c2e204c23dd3e7d32df202092a1ed400/propcache-0.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3def3da3ac3ce41562d85db655d18ebac740cb3fa4367f11a52b3da9d03a5cc3", size = 43039, upload-time = "2025-06-09T22:53:43.268Z" }, + { url = "https://files.pythonhosted.org/packages/22/e1/58da211eb8fdc6fc854002387d38f415a6ca5f5c67c1315b204a5d3e9d7a/propcache-0.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9bec58347a5a6cebf239daba9bda37dffec5b8d2ce004d9fe4edef3d2815137e", size = 201903, upload-time = "2025-06-09T22:53:44.872Z" }, + { url = "https://files.pythonhosted.org/packages/c4/0a/550ea0f52aac455cb90111c8bab995208443e46d925e51e2f6ebdf869525/propcache-0.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55ffda449a507e9fbd4aca1a7d9aa6753b07d6166140e5a18d2ac9bc49eac220", size = 213362, upload-time = "2025-06-09T22:53:46.707Z" }, + { url = "https://files.pythonhosted.org/packages/5a/af/9893b7d878deda9bb69fcf54600b247fba7317761b7db11fede6e0f28bd0/propcache-0.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64a67fb39229a8a8491dd42f864e5e263155e729c2e7ff723d6e25f596b1e8cb", size = 210525, upload-time = "2025-06-09T22:53:48.547Z" }, + { url = "https://files.pythonhosted.org/packages/7c/bb/38fd08b278ca85cde36d848091ad2b45954bc5f15cce494bb300b9285831/propcache-0.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9da1cf97b92b51253d5b68cf5a2b9e0dafca095e36b7f2da335e27dc6172a614", size = 198283, upload-time = "2025-06-09T22:53:50.067Z" }, + { url = "https://files.pythonhosted.org/packages/78/8c/9fe55bd01d362bafb413dfe508c48753111a1e269737fa143ba85693592c/propcache-0.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5f559e127134b07425134b4065be45b166183fdcb433cb6c24c8e4149056ad50", size = 191872, upload-time = "2025-06-09T22:53:51.438Z" }, + { url = "https://files.pythonhosted.org/packages/54/14/4701c33852937a22584e08abb531d654c8bcf7948a8f87ad0a4822394147/propcache-0.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:aff2e4e06435d61f11a428360a932138d0ec288b0a31dd9bd78d200bd4a2b339", size = 199452, upload-time = "2025-06-09T22:53:53.229Z" }, + { url = "https://files.pythonhosted.org/packages/16/44/447f2253d859602095356007657ee535e0093215ea0b3d1d6a41d16e5201/propcache-0.3.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:4927842833830942a5d0a56e6f4839bc484785b8e1ce8d287359794818633ba0", size = 191567, upload-time = "2025-06-09T22:53:54.541Z" }, + { url = "https://files.pythonhosted.org/packages/f2/b3/e4756258749bb2d3b46defcff606a2f47410bab82be5824a67e84015b267/propcache-0.3.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:6107ddd08b02654a30fb8ad7a132021759d750a82578b94cd55ee2772b6ebea2", size = 193015, upload-time = "2025-06-09T22:53:56.44Z" }, + { url = "https://files.pythonhosted.org/packages/1e/df/e6d3c7574233164b6330b9fd697beeac402afd367280e6dc377bb99b43d9/propcache-0.3.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:70bd8b9cd6b519e12859c99f3fc9a93f375ebd22a50296c3a295028bea73b9e7", size = 204660, upload-time = "2025-06-09T22:53:57.839Z" }, + { url = "https://files.pythonhosted.org/packages/b2/53/e4d31dd5170b4a0e2e6b730f2385a96410633b4833dc25fe5dffd1f73294/propcache-0.3.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2183111651d710d3097338dd1893fcf09c9f54e27ff1a8795495a16a469cc90b", size = 206105, upload-time = "2025-06-09T22:53:59.638Z" }, + { url = "https://files.pythonhosted.org/packages/7f/fe/74d54cf9fbe2a20ff786e5f7afcfde446588f0cf15fb2daacfbc267b866c/propcache-0.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:fb075ad271405dcad8e2a7ffc9a750a3bf70e533bd86e89f0603e607b93aa64c", size = 196980, upload-time = "2025-06-09T22:54:01.071Z" }, + { url = "https://files.pythonhosted.org/packages/22/ec/c469c9d59dada8a7679625e0440b544fe72e99311a4679c279562051f6fc/propcache-0.3.2-cp310-cp310-win32.whl", hash = "sha256:404d70768080d3d3bdb41d0771037da19d8340d50b08e104ca0e7f9ce55fce70", size = 37679, upload-time = "2025-06-09T22:54:03.003Z" }, + { url = "https://files.pythonhosted.org/packages/38/35/07a471371ac89d418f8d0b699c75ea6dca2041fbda360823de21f6a9ce0a/propcache-0.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:7435d766f978b4ede777002e6b3b6641dd229cd1da8d3d3106a45770365f9ad9", size = 41459, upload-time = "2025-06-09T22:54:04.134Z" }, + { url = "https://files.pythonhosted.org/packages/80/8d/e8b436717ab9c2cfc23b116d2c297305aa4cd8339172a456d61ebf5669b8/propcache-0.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0b8d2f607bd8f80ddc04088bc2a037fdd17884a6fcadc47a96e334d72f3717be", size = 74207, upload-time = "2025-06-09T22:54:05.399Z" }, + { url = "https://files.pythonhosted.org/packages/d6/29/1e34000e9766d112171764b9fa3226fa0153ab565d0c242c70e9945318a7/propcache-0.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06766d8f34733416e2e34f46fea488ad5d60726bb9481d3cddf89a6fa2d9603f", size = 43648, upload-time = "2025-06-09T22:54:08.023Z" }, + { url = "https://files.pythonhosted.org/packages/46/92/1ad5af0df781e76988897da39b5f086c2bf0f028b7f9bd1f409bb05b6874/propcache-0.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a2dc1f4a1df4fecf4e6f68013575ff4af84ef6f478fe5344317a65d38a8e6dc9", size = 43496, upload-time = "2025-06-09T22:54:09.228Z" }, + { url = "https://files.pythonhosted.org/packages/b3/ce/e96392460f9fb68461fabab3e095cb00c8ddf901205be4eae5ce246e5b7e/propcache-0.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be29c4f4810c5789cf10ddf6af80b041c724e629fa51e308a7a0fb19ed1ef7bf", size = 217288, upload-time = "2025-06-09T22:54:10.466Z" }, + { url = "https://files.pythonhosted.org/packages/c5/2a/866726ea345299f7ceefc861a5e782b045545ae6940851930a6adaf1fca6/propcache-0.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59d61f6970ecbd8ff2e9360304d5c8876a6abd4530cb752c06586849ac8a9dc9", size = 227456, upload-time = "2025-06-09T22:54:11.828Z" }, + { url = "https://files.pythonhosted.org/packages/de/03/07d992ccb6d930398689187e1b3c718339a1c06b8b145a8d9650e4726166/propcache-0.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:62180e0b8dbb6b004baec00a7983e4cc52f5ada9cd11f48c3528d8cfa7b96a66", size = 225429, upload-time = "2025-06-09T22:54:13.823Z" }, + { url = "https://files.pythonhosted.org/packages/5d/e6/116ba39448753b1330f48ab8ba927dcd6cf0baea8a0ccbc512dfb49ba670/propcache-0.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c144ca294a204c470f18cf4c9d78887810d04a3e2fbb30eea903575a779159df", size = 213472, upload-time = "2025-06-09T22:54:15.232Z" }, + { url = "https://files.pythonhosted.org/packages/a6/85/f01f5d97e54e428885a5497ccf7f54404cbb4f906688a1690cd51bf597dc/propcache-0.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5c2a784234c28854878d68978265617aa6dc0780e53d44b4d67f3651a17a9a2", size = 204480, upload-time = "2025-06-09T22:54:17.104Z" }, + { url = "https://files.pythonhosted.org/packages/e3/79/7bf5ab9033b8b8194cc3f7cf1aaa0e9c3256320726f64a3e1f113a812dce/propcache-0.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5745bc7acdafa978ca1642891b82c19238eadc78ba2aaa293c6863b304e552d7", size = 214530, upload-time = "2025-06-09T22:54:18.512Z" }, + { url = "https://files.pythonhosted.org/packages/31/0b/bd3e0c00509b609317df4a18e6b05a450ef2d9a963e1d8bc9c9415d86f30/propcache-0.3.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:c0075bf773d66fa8c9d41f66cc132ecc75e5bb9dd7cce3cfd14adc5ca184cb95", size = 205230, upload-time = "2025-06-09T22:54:19.947Z" }, + { url = "https://files.pythonhosted.org/packages/7a/23/fae0ff9b54b0de4e819bbe559508da132d5683c32d84d0dc2ccce3563ed4/propcache-0.3.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5f57aa0847730daceff0497f417c9de353c575d8da3579162cc74ac294c5369e", size = 206754, upload-time = "2025-06-09T22:54:21.716Z" }, + { url = "https://files.pythonhosted.org/packages/b7/7f/ad6a3c22630aaa5f618b4dc3c3598974a72abb4c18e45a50b3cdd091eb2f/propcache-0.3.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:eef914c014bf72d18efb55619447e0aecd5fb7c2e3fa7441e2e5d6099bddff7e", size = 218430, upload-time = "2025-06-09T22:54:23.17Z" }, + { url = "https://files.pythonhosted.org/packages/5b/2c/ba4f1c0e8a4b4c75910742f0d333759d441f65a1c7f34683b4a74c0ee015/propcache-0.3.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2a4092e8549031e82facf3decdbc0883755d5bbcc62d3aea9d9e185549936dcf", size = 223884, upload-time = "2025-06-09T22:54:25.539Z" }, + { url = "https://files.pythonhosted.org/packages/88/e4/ebe30fc399e98572019eee82ad0caf512401661985cbd3da5e3140ffa1b0/propcache-0.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:85871b050f174bc0bfb437efbdb68aaf860611953ed12418e4361bc9c392749e", size = 211480, upload-time = "2025-06-09T22:54:26.892Z" }, + { url = "https://files.pythonhosted.org/packages/96/0a/7d5260b914e01d1d0906f7f38af101f8d8ed0dc47426219eeaf05e8ea7c2/propcache-0.3.2-cp311-cp311-win32.whl", hash = "sha256:36c8d9b673ec57900c3554264e630d45980fd302458e4ac801802a7fd2ef7897", size = 37757, upload-time = "2025-06-09T22:54:28.241Z" }, + { url = "https://files.pythonhosted.org/packages/e1/2d/89fe4489a884bc0da0c3278c552bd4ffe06a1ace559db5ef02ef24ab446b/propcache-0.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53af8cb6a781b02d2ea079b5b853ba9430fcbe18a8e3ce647d5982a3ff69f39", size = 41500, upload-time = "2025-06-09T22:54:29.4Z" }, + { url = "https://files.pythonhosted.org/packages/a8/42/9ca01b0a6f48e81615dca4765a8f1dd2c057e0540f6116a27dc5ee01dfb6/propcache-0.3.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8de106b6c84506b31c27168582cd3cb3000a6412c16df14a8628e5871ff83c10", size = 73674, upload-time = "2025-06-09T22:54:30.551Z" }, + { url = "https://files.pythonhosted.org/packages/af/6e/21293133beb550f9c901bbece755d582bfaf2176bee4774000bd4dd41884/propcache-0.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:28710b0d3975117239c76600ea351934ac7b5ff56e60953474342608dbbb6154", size = 43570, upload-time = "2025-06-09T22:54:32.296Z" }, + { url = "https://files.pythonhosted.org/packages/0c/c8/0393a0a3a2b8760eb3bde3c147f62b20044f0ddac81e9d6ed7318ec0d852/propcache-0.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce26862344bdf836650ed2487c3d724b00fbfec4233a1013f597b78c1cb73615", size = 43094, upload-time = "2025-06-09T22:54:33.929Z" }, + { url = "https://files.pythonhosted.org/packages/37/2c/489afe311a690399d04a3e03b069225670c1d489eb7b044a566511c1c498/propcache-0.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bca54bd347a253af2cf4544bbec232ab982f4868de0dd684246b67a51bc6b1db", size = 226958, upload-time = "2025-06-09T22:54:35.186Z" }, + { url = "https://files.pythonhosted.org/packages/9d/ca/63b520d2f3d418c968bf596839ae26cf7f87bead026b6192d4da6a08c467/propcache-0.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55780d5e9a2ddc59711d727226bb1ba83a22dd32f64ee15594b9392b1f544eb1", size = 234894, upload-time = "2025-06-09T22:54:36.708Z" }, + { url = "https://files.pythonhosted.org/packages/11/60/1d0ed6fff455a028d678df30cc28dcee7af77fa2b0e6962ce1df95c9a2a9/propcache-0.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:035e631be25d6975ed87ab23153db6a73426a48db688070d925aa27e996fe93c", size = 233672, upload-time = "2025-06-09T22:54:38.062Z" }, + { url = "https://files.pythonhosted.org/packages/37/7c/54fd5301ef38505ab235d98827207176a5c9b2aa61939b10a460ca53e123/propcache-0.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee6f22b6eaa39297c751d0e80c0d3a454f112f5c6481214fcf4c092074cecd67", size = 224395, upload-time = "2025-06-09T22:54:39.634Z" }, + { url = "https://files.pythonhosted.org/packages/ee/1a/89a40e0846f5de05fdc6779883bf46ba980e6df4d2ff8fb02643de126592/propcache-0.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ca3aee1aa955438c4dba34fc20a9f390e4c79967257d830f137bd5a8a32ed3b", size = 212510, upload-time = "2025-06-09T22:54:41.565Z" }, + { url = "https://files.pythonhosted.org/packages/5e/33/ca98368586c9566a6b8d5ef66e30484f8da84c0aac3f2d9aec6d31a11bd5/propcache-0.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7a4f30862869fa2b68380d677cc1c5fcf1e0f2b9ea0cf665812895c75d0ca3b8", size = 222949, upload-time = "2025-06-09T22:54:43.038Z" }, + { url = "https://files.pythonhosted.org/packages/ba/11/ace870d0aafe443b33b2f0b7efdb872b7c3abd505bfb4890716ad7865e9d/propcache-0.3.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b77ec3c257d7816d9f3700013639db7491a434644c906a2578a11daf13176251", size = 217258, upload-time = "2025-06-09T22:54:44.376Z" }, + { url = "https://files.pythonhosted.org/packages/5b/d2/86fd6f7adffcfc74b42c10a6b7db721d1d9ca1055c45d39a1a8f2a740a21/propcache-0.3.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cab90ac9d3f14b2d5050928483d3d3b8fb6b4018893fc75710e6aa361ecb2474", size = 213036, upload-time = "2025-06-09T22:54:46.243Z" }, + { url = "https://files.pythonhosted.org/packages/07/94/2d7d1e328f45ff34a0a284cf5a2847013701e24c2a53117e7c280a4316b3/propcache-0.3.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:0b504d29f3c47cf6b9e936c1852246c83d450e8e063d50562115a6be6d3a2535", size = 227684, upload-time = "2025-06-09T22:54:47.63Z" }, + { url = "https://files.pythonhosted.org/packages/b7/05/37ae63a0087677e90b1d14710e532ff104d44bc1efa3b3970fff99b891dc/propcache-0.3.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:ce2ac2675a6aa41ddb2a0c9cbff53780a617ac3d43e620f8fd77ba1c84dcfc06", size = 234562, upload-time = "2025-06-09T22:54:48.982Z" }, + { url = "https://files.pythonhosted.org/packages/a4/7c/3f539fcae630408d0bd8bf3208b9a647ccad10976eda62402a80adf8fc34/propcache-0.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:62b4239611205294cc433845b914131b2a1f03500ff3c1ed093ed216b82621e1", size = 222142, upload-time = "2025-06-09T22:54:50.424Z" }, + { url = "https://files.pythonhosted.org/packages/7c/d2/34b9eac8c35f79f8a962546b3e97e9d4b990c420ee66ac8255d5d9611648/propcache-0.3.2-cp312-cp312-win32.whl", hash = "sha256:df4a81b9b53449ebc90cc4deefb052c1dd934ba85012aa912c7ea7b7e38b60c1", size = 37711, upload-time = "2025-06-09T22:54:52.072Z" }, + { url = "https://files.pythonhosted.org/packages/19/61/d582be5d226cf79071681d1b46b848d6cb03d7b70af7063e33a2787eaa03/propcache-0.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:7046e79b989d7fe457bb755844019e10f693752d169076138abf17f31380800c", size = 41479, upload-time = "2025-06-09T22:54:53.234Z" }, + { url = "https://files.pythonhosted.org/packages/dc/d1/8c747fafa558c603c4ca19d8e20b288aa0c7cda74e9402f50f31eb65267e/propcache-0.3.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ca592ed634a73ca002967458187109265e980422116c0a107cf93d81f95af945", size = 71286, upload-time = "2025-06-09T22:54:54.369Z" }, + { url = "https://files.pythonhosted.org/packages/61/99/d606cb7986b60d89c36de8a85d58764323b3a5ff07770a99d8e993b3fa73/propcache-0.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9ecb0aad4020e275652ba3975740f241bd12a61f1a784df044cf7477a02bc252", size = 42425, upload-time = "2025-06-09T22:54:55.642Z" }, + { url = "https://files.pythonhosted.org/packages/8c/96/ef98f91bbb42b79e9bb82bdd348b255eb9d65f14dbbe3b1594644c4073f7/propcache-0.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7f08f1cc28bd2eade7a8a3d2954ccc673bb02062e3e7da09bc75d843386b342f", size = 41846, upload-time = "2025-06-09T22:54:57.246Z" }, + { url = "https://files.pythonhosted.org/packages/5b/ad/3f0f9a705fb630d175146cd7b1d2bf5555c9beaed54e94132b21aac098a6/propcache-0.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1a342c834734edb4be5ecb1e9fb48cb64b1e2320fccbd8c54bf8da8f2a84c33", size = 208871, upload-time = "2025-06-09T22:54:58.975Z" }, + { url = "https://files.pythonhosted.org/packages/3a/38/2085cda93d2c8b6ec3e92af2c89489a36a5886b712a34ab25de9fbca7992/propcache-0.3.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a544caaae1ac73f1fecfae70ded3e93728831affebd017d53449e3ac052ac1e", size = 215720, upload-time = "2025-06-09T22:55:00.471Z" }, + { url = "https://files.pythonhosted.org/packages/61/c1/d72ea2dc83ac7f2c8e182786ab0fc2c7bd123a1ff9b7975bee671866fe5f/propcache-0.3.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:310d11aa44635298397db47a3ebce7db99a4cc4b9bbdfcf6c98a60c8d5261cf1", size = 215203, upload-time = "2025-06-09T22:55:01.834Z" }, + { url = "https://files.pythonhosted.org/packages/af/81/b324c44ae60c56ef12007105f1460d5c304b0626ab0cc6b07c8f2a9aa0b8/propcache-0.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c1396592321ac83157ac03a2023aa6cc4a3cc3cfdecb71090054c09e5a7cce3", size = 206365, upload-time = "2025-06-09T22:55:03.199Z" }, + { url = "https://files.pythonhosted.org/packages/09/73/88549128bb89e66d2aff242488f62869014ae092db63ccea53c1cc75a81d/propcache-0.3.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cabf5b5902272565e78197edb682017d21cf3b550ba0460ee473753f28d23c1", size = 196016, upload-time = "2025-06-09T22:55:04.518Z" }, + { url = "https://files.pythonhosted.org/packages/b9/3f/3bdd14e737d145114a5eb83cb172903afba7242f67c5877f9909a20d948d/propcache-0.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0a2f2235ac46a7aa25bdeb03a9e7060f6ecbd213b1f9101c43b3090ffb971ef6", size = 205596, upload-time = "2025-06-09T22:55:05.942Z" }, + { url = "https://files.pythonhosted.org/packages/0f/ca/2f4aa819c357d3107c3763d7ef42c03980f9ed5c48c82e01e25945d437c1/propcache-0.3.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:92b69e12e34869a6970fd2f3da91669899994b47c98f5d430b781c26f1d9f387", size = 200977, upload-time = "2025-06-09T22:55:07.792Z" }, + { url = "https://files.pythonhosted.org/packages/cd/4a/e65276c7477533c59085251ae88505caf6831c0e85ff8b2e31ebcbb949b1/propcache-0.3.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:54e02207c79968ebbdffc169591009f4474dde3b4679e16634d34c9363ff56b4", size = 197220, upload-time = "2025-06-09T22:55:09.173Z" }, + { url = "https://files.pythonhosted.org/packages/7c/54/fc7152e517cf5578278b242396ce4d4b36795423988ef39bb8cd5bf274c8/propcache-0.3.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4adfb44cb588001f68c5466579d3f1157ca07f7504fc91ec87862e2b8e556b88", size = 210642, upload-time = "2025-06-09T22:55:10.62Z" }, + { url = "https://files.pythonhosted.org/packages/b9/80/abeb4a896d2767bf5f1ea7b92eb7be6a5330645bd7fb844049c0e4045d9d/propcache-0.3.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fd3e6019dc1261cd0291ee8919dd91fbab7b169bb76aeef6c716833a3f65d206", size = 212789, upload-time = "2025-06-09T22:55:12.029Z" }, + { url = "https://files.pythonhosted.org/packages/b3/db/ea12a49aa7b2b6d68a5da8293dcf50068d48d088100ac016ad92a6a780e6/propcache-0.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4c181cad81158d71c41a2bce88edce078458e2dd5ffee7eddd6b05da85079f43", size = 205880, upload-time = "2025-06-09T22:55:13.45Z" }, + { url = "https://files.pythonhosted.org/packages/d1/e5/9076a0bbbfb65d1198007059c65639dfd56266cf8e477a9707e4b1999ff4/propcache-0.3.2-cp313-cp313-win32.whl", hash = "sha256:8a08154613f2249519e549de2330cf8e2071c2887309a7b07fb56098f5170a02", size = 37220, upload-time = "2025-06-09T22:55:15.284Z" }, + { url = "https://files.pythonhosted.org/packages/d3/f5/b369e026b09a26cd77aa88d8fffd69141d2ae00a2abaaf5380d2603f4b7f/propcache-0.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:e41671f1594fc4ab0a6dec1351864713cb3a279910ae8b58f884a88a0a632c05", size = 40678, upload-time = "2025-06-09T22:55:16.445Z" }, + { url = "https://files.pythonhosted.org/packages/a4/3a/6ece377b55544941a08d03581c7bc400a3c8cd3c2865900a68d5de79e21f/propcache-0.3.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:9a3cf035bbaf035f109987d9d55dc90e4b0e36e04bbbb95af3055ef17194057b", size = 76560, upload-time = "2025-06-09T22:55:17.598Z" }, + { url = "https://files.pythonhosted.org/packages/0c/da/64a2bb16418740fa634b0e9c3d29edff1db07f56d3546ca2d86ddf0305e1/propcache-0.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:156c03d07dc1323d8dacaa221fbe028c5c70d16709cdd63502778e6c3ccca1b0", size = 44676, upload-time = "2025-06-09T22:55:18.922Z" }, + { url = "https://files.pythonhosted.org/packages/36/7b/f025e06ea51cb72c52fb87e9b395cced02786610b60a3ed51da8af017170/propcache-0.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74413c0ba02ba86f55cf60d18daab219f7e531620c15f1e23d95563f505efe7e", size = 44701, upload-time = "2025-06-09T22:55:20.106Z" }, + { url = "https://files.pythonhosted.org/packages/a4/00/faa1b1b7c3b74fc277f8642f32a4c72ba1d7b2de36d7cdfb676db7f4303e/propcache-0.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f066b437bb3fa39c58ff97ab2ca351db465157d68ed0440abecb21715eb24b28", size = 276934, upload-time = "2025-06-09T22:55:21.5Z" }, + { url = "https://files.pythonhosted.org/packages/74/ab/935beb6f1756e0476a4d5938ff44bf0d13a055fed880caf93859b4f1baf4/propcache-0.3.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1304b085c83067914721e7e9d9917d41ad87696bf70f0bc7dee450e9c71ad0a", size = 278316, upload-time = "2025-06-09T22:55:22.918Z" }, + { url = "https://files.pythonhosted.org/packages/f8/9d/994a5c1ce4389610838d1caec74bdf0e98b306c70314d46dbe4fcf21a3e2/propcache-0.3.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ab50cef01b372763a13333b4e54021bdcb291fc9a8e2ccb9c2df98be51bcde6c", size = 282619, upload-time = "2025-06-09T22:55:24.651Z" }, + { url = "https://files.pythonhosted.org/packages/2b/00/a10afce3d1ed0287cef2e09506d3be9822513f2c1e96457ee369adb9a6cd/propcache-0.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fad3b2a085ec259ad2c2842666b2a0a49dea8463579c606426128925af1ed725", size = 265896, upload-time = "2025-06-09T22:55:26.049Z" }, + { url = "https://files.pythonhosted.org/packages/2e/a8/2aa6716ffa566ca57c749edb909ad27884680887d68517e4be41b02299f3/propcache-0.3.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:261fa020c1c14deafd54c76b014956e2f86991af198c51139faf41c4d5e83892", size = 252111, upload-time = "2025-06-09T22:55:27.381Z" }, + { url = "https://files.pythonhosted.org/packages/36/4f/345ca9183b85ac29c8694b0941f7484bf419c7f0fea2d1e386b4f7893eed/propcache-0.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:46d7f8aa79c927e5f987ee3a80205c987717d3659f035c85cf0c3680526bdb44", size = 268334, upload-time = "2025-06-09T22:55:28.747Z" }, + { url = "https://files.pythonhosted.org/packages/3e/ca/fcd54f78b59e3f97b3b9715501e3147f5340167733d27db423aa321e7148/propcache-0.3.2-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:6d8f3f0eebf73e3c0ff0e7853f68be638b4043c65a70517bb575eff54edd8dbe", size = 255026, upload-time = "2025-06-09T22:55:30.184Z" }, + { url = "https://files.pythonhosted.org/packages/8b/95/8e6a6bbbd78ac89c30c225210a5c687790e532ba4088afb8c0445b77ef37/propcache-0.3.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:03c89c1b14a5452cf15403e291c0ccd7751d5b9736ecb2c5bab977ad6c5bcd81", size = 250724, upload-time = "2025-06-09T22:55:31.646Z" }, + { url = "https://files.pythonhosted.org/packages/ee/b0/0dd03616142baba28e8b2d14ce5df6631b4673850a3d4f9c0f9dd714a404/propcache-0.3.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:0cc17efde71e12bbaad086d679ce575268d70bc123a5a71ea7ad76f70ba30bba", size = 268868, upload-time = "2025-06-09T22:55:33.209Z" }, + { url = "https://files.pythonhosted.org/packages/c5/98/2c12407a7e4fbacd94ddd32f3b1e3d5231e77c30ef7162b12a60e2dd5ce3/propcache-0.3.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:acdf05d00696bc0447e278bb53cb04ca72354e562cf88ea6f9107df8e7fd9770", size = 271322, upload-time = "2025-06-09T22:55:35.065Z" }, + { url = "https://files.pythonhosted.org/packages/35/91/9cb56efbb428b006bb85db28591e40b7736847b8331d43fe335acf95f6c8/propcache-0.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4445542398bd0b5d32df908031cb1b30d43ac848e20470a878b770ec2dcc6330", size = 265778, upload-time = "2025-06-09T22:55:36.45Z" }, + { url = "https://files.pythonhosted.org/packages/9a/4c/b0fe775a2bdd01e176b14b574be679d84fc83958335790f7c9a686c1f468/propcache-0.3.2-cp313-cp313t-win32.whl", hash = "sha256:f86e5d7cd03afb3a1db8e9f9f6eff15794e79e791350ac48a8c924e6f439f394", size = 41175, upload-time = "2025-06-09T22:55:38.436Z" }, + { url = "https://files.pythonhosted.org/packages/a4/ff/47f08595e3d9b5e149c150f88d9714574f1a7cbd89fe2817158a952674bf/propcache-0.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:9704bedf6e7cbe3c65eca4379a9b53ee6a83749f047808cbb5044d40d7d72198", size = 44857, upload-time = "2025-06-09T22:55:39.687Z" }, + { url = "https://files.pythonhosted.org/packages/cc/35/cc0aaecf278bb4575b8555f2b137de5ab821595ddae9da9d3cd1da4072c7/propcache-0.3.2-py3-none-any.whl", hash = "sha256:98f1ec44fb675f5052cccc8e609c46ed23a35a1cfd18545ad4e29002d858a43f", size = 12663, upload-time = "2025-06-09T22:56:04.484Z" }, ] [[package]] name = "protobuf" version = "5.29.5" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/43/29/d09e70352e4e88c9c7a198d5645d7277811448d76c23b00345670f7c8a38/protobuf-5.29.5.tar.gz", hash = "sha256:bc1463bafd4b0929216c35f437a8e28731a2b7fe3d98bb77a600efced5a15c84", size = 425226 } +sdist = { url = "https://files.pythonhosted.org/packages/43/29/d09e70352e4e88c9c7a198d5645d7277811448d76c23b00345670f7c8a38/protobuf-5.29.5.tar.gz", hash = "sha256:bc1463bafd4b0929216c35f437a8e28731a2b7fe3d98bb77a600efced5a15c84", size = 425226, upload-time = "2025-05-28T23:51:59.82Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/11/6e40e9fc5bba02988a214c07cf324595789ca7820160bfd1f8be96e48539/protobuf-5.29.5-cp310-abi3-win32.whl", hash = "sha256:3f1c6468a2cfd102ff4703976138844f78ebd1fb45f49011afc5139e9e283079", size = 422963 }, - { url = "https://files.pythonhosted.org/packages/81/7f/73cefb093e1a2a7c3ffd839e6f9fcafb7a427d300c7f8aef9c64405d8ac6/protobuf-5.29.5-cp310-abi3-win_amd64.whl", hash = "sha256:3f76e3a3675b4a4d867b52e4a5f5b78a2ef9565549d4037e06cf7b0942b1d3fc", size = 434818 }, - { url = "https://files.pythonhosted.org/packages/dd/73/10e1661c21f139f2c6ad9b23040ff36fee624310dc28fba20d33fdae124c/protobuf-5.29.5-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e38c5add5a311f2a6eb0340716ef9b039c1dfa428b28f25a7838ac329204a671", size = 418091 }, - { url = "https://files.pythonhosted.org/packages/6c/04/98f6f8cf5b07ab1294c13f34b4e69b3722bb609c5b701d6c169828f9f8aa/protobuf-5.29.5-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:fa18533a299d7ab6c55a238bf8629311439995f2e7eca5caaff08663606e9015", size = 319824 }, - { url = "https://files.pythonhosted.org/packages/85/e4/07c80521879c2d15f321465ac24c70efe2381378c00bf5e56a0f4fbac8cd/protobuf-5.29.5-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:63848923da3325e1bf7e9003d680ce6e14b07e55d0473253a690c3a8b8fd6e61", size = 319942 }, - { url = "https://files.pythonhosted.org/packages/7e/cc/7e77861000a0691aeea8f4566e5d3aa716f2b1dece4a24439437e41d3d25/protobuf-5.29.5-py3-none-any.whl", hash = "sha256:6cf42630262c59b2d8de33954443d94b746c952b01434fc58a417fdbd2e84bd5", size = 172823 }, + { url = "https://files.pythonhosted.org/packages/5f/11/6e40e9fc5bba02988a214c07cf324595789ca7820160bfd1f8be96e48539/protobuf-5.29.5-cp310-abi3-win32.whl", hash = "sha256:3f1c6468a2cfd102ff4703976138844f78ebd1fb45f49011afc5139e9e283079", size = 422963, upload-time = "2025-05-28T23:51:41.204Z" }, + { url = "https://files.pythonhosted.org/packages/81/7f/73cefb093e1a2a7c3ffd839e6f9fcafb7a427d300c7f8aef9c64405d8ac6/protobuf-5.29.5-cp310-abi3-win_amd64.whl", hash = "sha256:3f76e3a3675b4a4d867b52e4a5f5b78a2ef9565549d4037e06cf7b0942b1d3fc", size = 434818, upload-time = "2025-05-28T23:51:44.297Z" }, + { url = "https://files.pythonhosted.org/packages/dd/73/10e1661c21f139f2c6ad9b23040ff36fee624310dc28fba20d33fdae124c/protobuf-5.29.5-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e38c5add5a311f2a6eb0340716ef9b039c1dfa428b28f25a7838ac329204a671", size = 418091, upload-time = "2025-05-28T23:51:45.907Z" }, + { url = "https://files.pythonhosted.org/packages/6c/04/98f6f8cf5b07ab1294c13f34b4e69b3722bb609c5b701d6c169828f9f8aa/protobuf-5.29.5-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:fa18533a299d7ab6c55a238bf8629311439995f2e7eca5caaff08663606e9015", size = 319824, upload-time = "2025-05-28T23:51:47.545Z" }, + { url = "https://files.pythonhosted.org/packages/85/e4/07c80521879c2d15f321465ac24c70efe2381378c00bf5e56a0f4fbac8cd/protobuf-5.29.5-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:63848923da3325e1bf7e9003d680ce6e14b07e55d0473253a690c3a8b8fd6e61", size = 319942, upload-time = "2025-05-28T23:51:49.11Z" }, + { url = "https://files.pythonhosted.org/packages/7e/cc/7e77861000a0691aeea8f4566e5d3aa716f2b1dece4a24439437e41d3d25/protobuf-5.29.5-py3-none-any.whl", hash = "sha256:6cf42630262c59b2d8de33954443d94b746c952b01434fc58a417fdbd2e84bd5", size = 172823, upload-time = "2025-05-28T23:51:58.157Z" }, ] [[package]] name = "psutil" version = "7.1.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e1/88/bdd0a41e5857d5d703287598cbf08dad90aed56774ea52ae071bae9071b6/psutil-7.1.3.tar.gz", hash = "sha256:6c86281738d77335af7aec228328e944b30930899ea760ecf33a4dba66be5e74", size = 489059 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bd/93/0c49e776b8734fef56ec9c5c57f923922f2cf0497d62e0f419465f28f3d0/psutil-7.1.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0005da714eee687b4b8decd3d6cc7c6db36215c9e74e5ad2264b90c3df7d92dc", size = 239751 }, - { url = "https://files.pythonhosted.org/packages/6f/8d/b31e39c769e70780f007969815195a55c81a63efebdd4dbe9e7a113adb2f/psutil-7.1.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:19644c85dcb987e35eeeaefdc3915d059dac7bd1167cdcdbf27e0ce2df0c08c0", size = 240368 }, - { url = "https://files.pythonhosted.org/packages/62/61/23fd4acc3c9eebbf6b6c78bcd89e5d020cfde4acf0a9233e9d4e3fa698b4/psutil-7.1.3-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:95ef04cf2e5ba0ab9eaafc4a11eaae91b44f4ef5541acd2ee91d9108d00d59a7", size = 287134 }, - { url = "https://files.pythonhosted.org/packages/30/1c/f921a009ea9ceb51aa355cb0cc118f68d354db36eae18174bab63affb3e6/psutil-7.1.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1068c303be3a72f8e18e412c5b2a8f6d31750fb152f9cb106b54090296c9d251", size = 289904 }, - { url = "https://files.pythonhosted.org/packages/a6/82/62d68066e13e46a5116df187d319d1724b3f437ddd0f958756fc052677f4/psutil-7.1.3-cp313-cp313t-win_amd64.whl", hash = "sha256:18349c5c24b06ac5612c0428ec2a0331c26443d259e2a0144a9b24b4395b58fa", size = 249642 }, - { url = "https://files.pythonhosted.org/packages/df/ad/c1cd5fe965c14a0392112f68362cfceb5230819dbb5b1888950d18a11d9f/psutil-7.1.3-cp313-cp313t-win_arm64.whl", hash = "sha256:c525ffa774fe4496282fb0b1187725793de3e7c6b29e41562733cae9ada151ee", size = 245518 }, - { url = "https://files.pythonhosted.org/packages/2e/bb/6670bded3e3236eb4287c7bcdc167e9fae6e1e9286e437f7111caed2f909/psutil-7.1.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:b403da1df4d6d43973dc004d19cee3b848e998ae3154cc8097d139b77156c353", size = 239843 }, - { url = "https://files.pythonhosted.org/packages/b8/66/853d50e75a38c9a7370ddbeefabdd3d3116b9c31ef94dc92c6729bc36bec/psutil-7.1.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ad81425efc5e75da3f39b3e636293360ad8d0b49bed7df824c79764fb4ba9b8b", size = 240369 }, - { url = "https://files.pythonhosted.org/packages/41/bd/313aba97cb5bfb26916dc29cf0646cbe4dd6a89ca69e8c6edce654876d39/psutil-7.1.3-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f33a3702e167783a9213db10ad29650ebf383946e91bc77f28a5eb083496bc9", size = 288210 }, - { url = "https://files.pythonhosted.org/packages/c2/fa/76e3c06e760927a0cfb5705eb38164254de34e9bd86db656d4dbaa228b04/psutil-7.1.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fac9cd332c67f4422504297889da5ab7e05fd11e3c4392140f7370f4208ded1f", size = 291182 }, - { url = "https://files.pythonhosted.org/packages/0f/1d/5774a91607035ee5078b8fd747686ebec28a962f178712de100d00b78a32/psutil-7.1.3-cp314-cp314t-win_amd64.whl", hash = "sha256:3792983e23b69843aea49c8f5b8f115572c5ab64c153bada5270086a2123c7e7", size = 250466 }, - { url = "https://files.pythonhosted.org/packages/00/ca/e426584bacb43a5cb1ac91fae1937f478cd8fbe5e4ff96574e698a2c77cd/psutil-7.1.3-cp314-cp314t-win_arm64.whl", hash = "sha256:31d77fcedb7529f27bb3a0472bea9334349f9a04160e8e6e5020f22c59893264", size = 245756 }, - { url = "https://files.pythonhosted.org/packages/ef/94/46b9154a800253e7ecff5aaacdf8ebf43db99de4a2dfa18575b02548654e/psutil-7.1.3-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2bdbcd0e58ca14996a42adf3621a6244f1bb2e2e528886959c72cf1e326677ab", size = 238359 }, - { url = "https://files.pythonhosted.org/packages/68/3a/9f93cff5c025029a36d9a92fef47220ab4692ee7f2be0fba9f92813d0cb8/psutil-7.1.3-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:bc31fa00f1fbc3c3802141eede66f3a2d51d89716a194bf2cd6fc68310a19880", size = 239171 }, - { url = "https://files.pythonhosted.org/packages/ce/b1/5f49af514f76431ba4eea935b8ad3725cdeb397e9245ab919dbc1d1dc20f/psutil-7.1.3-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3bb428f9f05c1225a558f53e30ccbad9930b11c3fc206836242de1091d3e7dd3", size = 263261 }, - { url = "https://files.pythonhosted.org/packages/e0/95/992c8816a74016eb095e73585d747e0a8ea21a061ed3689474fabb29a395/psutil-7.1.3-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:56d974e02ca2c8eb4812c3f76c30e28836fffc311d55d979f1465c1feeb2b68b", size = 264635 }, - { url = "https://files.pythonhosted.org/packages/55/4c/c3ed1a622b6ae2fd3c945a366e64eb35247a31e4db16cf5095e269e8eb3c/psutil-7.1.3-cp37-abi3-win_amd64.whl", hash = "sha256:f39c2c19fe824b47484b96f9692932248a54c43799a84282cfe58d05a6449efd", size = 247633 }, - { url = "https://files.pythonhosted.org/packages/c9/ad/33b2ccec09bf96c2b2ef3f9a6f66baac8253d7565d8839e024a6b905d45d/psutil-7.1.3-cp37-abi3-win_arm64.whl", hash = "sha256:bd0d69cee829226a761e92f28140bec9a5ee9d5b4fb4b0cc589068dbfff559b1", size = 244608 }, +sdist = { url = "https://files.pythonhosted.org/packages/e1/88/bdd0a41e5857d5d703287598cbf08dad90aed56774ea52ae071bae9071b6/psutil-7.1.3.tar.gz", hash = "sha256:6c86281738d77335af7aec228328e944b30930899ea760ecf33a4dba66be5e74", size = 489059, upload-time = "2025-11-02T12:25:54.619Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bd/93/0c49e776b8734fef56ec9c5c57f923922f2cf0497d62e0f419465f28f3d0/psutil-7.1.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0005da714eee687b4b8decd3d6cc7c6db36215c9e74e5ad2264b90c3df7d92dc", size = 239751, upload-time = "2025-11-02T12:25:58.161Z" }, + { url = "https://files.pythonhosted.org/packages/6f/8d/b31e39c769e70780f007969815195a55c81a63efebdd4dbe9e7a113adb2f/psutil-7.1.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:19644c85dcb987e35eeeaefdc3915d059dac7bd1167cdcdbf27e0ce2df0c08c0", size = 240368, upload-time = "2025-11-02T12:26:00.491Z" }, + { url = "https://files.pythonhosted.org/packages/62/61/23fd4acc3c9eebbf6b6c78bcd89e5d020cfde4acf0a9233e9d4e3fa698b4/psutil-7.1.3-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:95ef04cf2e5ba0ab9eaafc4a11eaae91b44f4ef5541acd2ee91d9108d00d59a7", size = 287134, upload-time = "2025-11-02T12:26:02.613Z" }, + { url = "https://files.pythonhosted.org/packages/30/1c/f921a009ea9ceb51aa355cb0cc118f68d354db36eae18174bab63affb3e6/psutil-7.1.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1068c303be3a72f8e18e412c5b2a8f6d31750fb152f9cb106b54090296c9d251", size = 289904, upload-time = "2025-11-02T12:26:05.207Z" }, + { url = "https://files.pythonhosted.org/packages/a6/82/62d68066e13e46a5116df187d319d1724b3f437ddd0f958756fc052677f4/psutil-7.1.3-cp313-cp313t-win_amd64.whl", hash = "sha256:18349c5c24b06ac5612c0428ec2a0331c26443d259e2a0144a9b24b4395b58fa", size = 249642, upload-time = "2025-11-02T12:26:07.447Z" }, + { url = "https://files.pythonhosted.org/packages/df/ad/c1cd5fe965c14a0392112f68362cfceb5230819dbb5b1888950d18a11d9f/psutil-7.1.3-cp313-cp313t-win_arm64.whl", hash = "sha256:c525ffa774fe4496282fb0b1187725793de3e7c6b29e41562733cae9ada151ee", size = 245518, upload-time = "2025-11-02T12:26:09.719Z" }, + { url = "https://files.pythonhosted.org/packages/2e/bb/6670bded3e3236eb4287c7bcdc167e9fae6e1e9286e437f7111caed2f909/psutil-7.1.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:b403da1df4d6d43973dc004d19cee3b848e998ae3154cc8097d139b77156c353", size = 239843, upload-time = "2025-11-02T12:26:11.968Z" }, + { url = "https://files.pythonhosted.org/packages/b8/66/853d50e75a38c9a7370ddbeefabdd3d3116b9c31ef94dc92c6729bc36bec/psutil-7.1.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ad81425efc5e75da3f39b3e636293360ad8d0b49bed7df824c79764fb4ba9b8b", size = 240369, upload-time = "2025-11-02T12:26:14.358Z" }, + { url = "https://files.pythonhosted.org/packages/41/bd/313aba97cb5bfb26916dc29cf0646cbe4dd6a89ca69e8c6edce654876d39/psutil-7.1.3-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f33a3702e167783a9213db10ad29650ebf383946e91bc77f28a5eb083496bc9", size = 288210, upload-time = "2025-11-02T12:26:16.699Z" }, + { url = "https://files.pythonhosted.org/packages/c2/fa/76e3c06e760927a0cfb5705eb38164254de34e9bd86db656d4dbaa228b04/psutil-7.1.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fac9cd332c67f4422504297889da5ab7e05fd11e3c4392140f7370f4208ded1f", size = 291182, upload-time = "2025-11-02T12:26:18.848Z" }, + { url = "https://files.pythonhosted.org/packages/0f/1d/5774a91607035ee5078b8fd747686ebec28a962f178712de100d00b78a32/psutil-7.1.3-cp314-cp314t-win_amd64.whl", hash = "sha256:3792983e23b69843aea49c8f5b8f115572c5ab64c153bada5270086a2123c7e7", size = 250466, upload-time = "2025-11-02T12:26:21.183Z" }, + { url = "https://files.pythonhosted.org/packages/00/ca/e426584bacb43a5cb1ac91fae1937f478cd8fbe5e4ff96574e698a2c77cd/psutil-7.1.3-cp314-cp314t-win_arm64.whl", hash = "sha256:31d77fcedb7529f27bb3a0472bea9334349f9a04160e8e6e5020f22c59893264", size = 245756, upload-time = "2025-11-02T12:26:23.148Z" }, + { url = "https://files.pythonhosted.org/packages/ef/94/46b9154a800253e7ecff5aaacdf8ebf43db99de4a2dfa18575b02548654e/psutil-7.1.3-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2bdbcd0e58ca14996a42adf3621a6244f1bb2e2e528886959c72cf1e326677ab", size = 238359, upload-time = "2025-11-02T12:26:25.284Z" }, + { url = "https://files.pythonhosted.org/packages/68/3a/9f93cff5c025029a36d9a92fef47220ab4692ee7f2be0fba9f92813d0cb8/psutil-7.1.3-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:bc31fa00f1fbc3c3802141eede66f3a2d51d89716a194bf2cd6fc68310a19880", size = 239171, upload-time = "2025-11-02T12:26:27.23Z" }, + { url = "https://files.pythonhosted.org/packages/ce/b1/5f49af514f76431ba4eea935b8ad3725cdeb397e9245ab919dbc1d1dc20f/psutil-7.1.3-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3bb428f9f05c1225a558f53e30ccbad9930b11c3fc206836242de1091d3e7dd3", size = 263261, upload-time = "2025-11-02T12:26:29.48Z" }, + { url = "https://files.pythonhosted.org/packages/e0/95/992c8816a74016eb095e73585d747e0a8ea21a061ed3689474fabb29a395/psutil-7.1.3-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:56d974e02ca2c8eb4812c3f76c30e28836fffc311d55d979f1465c1feeb2b68b", size = 264635, upload-time = "2025-11-02T12:26:31.74Z" }, + { url = "https://files.pythonhosted.org/packages/55/4c/c3ed1a622b6ae2fd3c945a366e64eb35247a31e4db16cf5095e269e8eb3c/psutil-7.1.3-cp37-abi3-win_amd64.whl", hash = "sha256:f39c2c19fe824b47484b96f9692932248a54c43799a84282cfe58d05a6449efd", size = 247633, upload-time = "2025-11-02T12:26:33.887Z" }, + { url = "https://files.pythonhosted.org/packages/c9/ad/33b2ccec09bf96c2b2ef3f9a6f66baac8253d7565d8839e024a6b905d45d/psutil-7.1.3-cp37-abi3-win_arm64.whl", hash = "sha256:bd0d69cee829226a761e92f28140bec9a5ee9d5b4fb4b0cc589068dbfff559b1", size = 244608, upload-time = "2025-11-02T12:26:36.136Z" }, ] [[package]] name = "pycparser" version = "2.22" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", size = 172736 } +sdist = { url = "https://files.pythonhosted.org/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", size = 172736, upload-time = "2024-03-30T13:22:22.564Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552 }, + { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552, upload-time = "2024-03-30T13:22:20.476Z" }, ] [[package]] @@ -2265,9 +2267,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/00/dd/4325abf92c39ba8623b5af936ddb36ffcfe0beae70405d456ab1fb2f5b8c/pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db", size = 788350 } +sdist = { url = "https://files.pythonhosted.org/packages/00/dd/4325abf92c39ba8623b5af936ddb36ffcfe0beae70405d456ab1fb2f5b8c/pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db", size = 788350, upload-time = "2025-06-14T08:33:17.137Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/c0/ec2b1c8712ca690e5d61979dee872603e92b8a32f94cc1b72d53beab008a/pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b", size = 444782 }, + { url = "https://files.pythonhosted.org/packages/6a/c0/ec2b1c8712ca690e5d61979dee872603e92b8a32f94cc1b72d53beab008a/pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b", size = 444782, upload-time = "2025-06-14T08:33:14.905Z" }, ] [[package]] @@ -2277,84 +2279,84 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/92/b31726561b5dae176c2d2c2dc43a9c5bfba5d32f96f8b4c0a600dd492447/pydantic_core-2.33.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2b3d326aaef0c0399d9afffeb6367d5e26ddc24d351dbc9c636840ac355dc5d8", size = 2028817 }, - { url = "https://files.pythonhosted.org/packages/a3/44/3f0b95fafdaca04a483c4e685fe437c6891001bf3ce8b2fded82b9ea3aa1/pydantic_core-2.33.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e5b2671f05ba48b94cb90ce55d8bdcaaedb8ba00cc5359f6810fc918713983d", size = 1861357 }, - { url = "https://files.pythonhosted.org/packages/30/97/e8f13b55766234caae05372826e8e4b3b96e7b248be3157f53237682e43c/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0069c9acc3f3981b9ff4cdfaf088e98d83440a4c7ea1bc07460af3d4dc22e72d", size = 1898011 }, - { url = "https://files.pythonhosted.org/packages/9b/a3/99c48cf7bafc991cc3ee66fd544c0aae8dc907b752f1dad2d79b1b5a471f/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d53b22f2032c42eaaf025f7c40c2e3b94568ae077a606f006d206a463bc69572", size = 1982730 }, - { url = "https://files.pythonhosted.org/packages/de/8e/a5b882ec4307010a840fb8b58bd9bf65d1840c92eae7534c7441709bf54b/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0405262705a123b7ce9f0b92f123334d67b70fd1f20a9372b907ce1080c7ba02", size = 2136178 }, - { url = "https://files.pythonhosted.org/packages/e4/bb/71e35fc3ed05af6834e890edb75968e2802fe98778971ab5cba20a162315/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4b25d91e288e2c4e0662b8038a28c6a07eaac3e196cfc4ff69de4ea3db992a1b", size = 2736462 }, - { url = "https://files.pythonhosted.org/packages/31/0d/c8f7593e6bc7066289bbc366f2235701dcbebcd1ff0ef8e64f6f239fb47d/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bdfe4b3789761f3bcb4b1ddf33355a71079858958e3a552f16d5af19768fef2", size = 2005652 }, - { url = "https://files.pythonhosted.org/packages/d2/7a/996d8bd75f3eda405e3dd219ff5ff0a283cd8e34add39d8ef9157e722867/pydantic_core-2.33.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:efec8db3266b76ef9607c2c4c419bdb06bf335ae433b80816089ea7585816f6a", size = 2113306 }, - { url = "https://files.pythonhosted.org/packages/ff/84/daf2a6fb2db40ffda6578a7e8c5a6e9c8affb251a05c233ae37098118788/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:031c57d67ca86902726e0fae2214ce6770bbe2f710dc33063187a68744a5ecac", size = 2073720 }, - { url = "https://files.pythonhosted.org/packages/77/fb/2258da019f4825128445ae79456a5499c032b55849dbd5bed78c95ccf163/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:f8de619080e944347f5f20de29a975c2d815d9ddd8be9b9b7268e2e3ef68605a", size = 2244915 }, - { url = "https://files.pythonhosted.org/packages/d8/7a/925ff73756031289468326e355b6fa8316960d0d65f8b5d6b3a3e7866de7/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:73662edf539e72a9440129f231ed3757faab89630d291b784ca99237fb94db2b", size = 2241884 }, - { url = "https://files.pythonhosted.org/packages/0b/b0/249ee6d2646f1cdadcb813805fe76265745c4010cf20a8eba7b0e639d9b2/pydantic_core-2.33.2-cp310-cp310-win32.whl", hash = "sha256:0a39979dcbb70998b0e505fb1556a1d550a0781463ce84ebf915ba293ccb7e22", size = 1910496 }, - { url = "https://files.pythonhosted.org/packages/66/ff/172ba8f12a42d4b552917aa65d1f2328990d3ccfc01d5b7c943ec084299f/pydantic_core-2.33.2-cp310-cp310-win_amd64.whl", hash = "sha256:b0379a2b24882fef529ec3b4987cb5d003b9cda32256024e6fe1586ac45fc640", size = 1955019 }, - { url = "https://files.pythonhosted.org/packages/3f/8d/71db63483d518cbbf290261a1fc2839d17ff89fce7089e08cad07ccfce67/pydantic_core-2.33.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4c5b0a576fb381edd6d27f0a85915c6daf2f8138dc5c267a57c08a62900758c7", size = 2028584 }, - { url = "https://files.pythonhosted.org/packages/24/2f/3cfa7244ae292dd850989f328722d2aef313f74ffc471184dc509e1e4e5a/pydantic_core-2.33.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e799c050df38a639db758c617ec771fd8fb7a5f8eaaa4b27b101f266b216a246", size = 1855071 }, - { url = "https://files.pythonhosted.org/packages/b3/d3/4ae42d33f5e3f50dd467761304be2fa0a9417fbf09735bc2cce003480f2a/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc46a01bf8d62f227d5ecee74178ffc448ff4e5197c756331f71efcc66dc980f", size = 1897823 }, - { url = "https://files.pythonhosted.org/packages/f4/f3/aa5976e8352b7695ff808599794b1fba2a9ae2ee954a3426855935799488/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a144d4f717285c6d9234a66778059f33a89096dfb9b39117663fd8413d582dcc", size = 1983792 }, - { url = "https://files.pythonhosted.org/packages/d5/7a/cda9b5a23c552037717f2b2a5257e9b2bfe45e687386df9591eff7b46d28/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73cf6373c21bc80b2e0dc88444f41ae60b2f070ed02095754eb5a01df12256de", size = 2136338 }, - { url = "https://files.pythonhosted.org/packages/2b/9f/b8f9ec8dd1417eb9da784e91e1667d58a2a4a7b7b34cf4af765ef663a7e5/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dc625f4aa79713512d1976fe9f0bc99f706a9dee21dfd1810b4bbbf228d0e8a", size = 2730998 }, - { url = "https://files.pythonhosted.org/packages/47/bc/cd720e078576bdb8255d5032c5d63ee5c0bf4b7173dd955185a1d658c456/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b21b5549499972441da4758d662aeea93f1923f953e9cbaff14b8b9565aef", size = 2003200 }, - { url = "https://files.pythonhosted.org/packages/ca/22/3602b895ee2cd29d11a2b349372446ae9727c32e78a94b3d588a40fdf187/pydantic_core-2.33.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bdc25f3681f7b78572699569514036afe3c243bc3059d3942624e936ec93450e", size = 2113890 }, - { url = "https://files.pythonhosted.org/packages/ff/e6/e3c5908c03cf00d629eb38393a98fccc38ee0ce8ecce32f69fc7d7b558a7/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fe5b32187cbc0c862ee201ad66c30cf218e5ed468ec8dc1cf49dec66e160cc4d", size = 2073359 }, - { url = "https://files.pythonhosted.org/packages/12/e7/6a36a07c59ebefc8777d1ffdaf5ae71b06b21952582e4b07eba88a421c79/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:bc7aee6f634a6f4a95676fcb5d6559a2c2a390330098dba5e5a5f28a2e4ada30", size = 2245883 }, - { url = "https://files.pythonhosted.org/packages/16/3f/59b3187aaa6cc0c1e6616e8045b284de2b6a87b027cce2ffcea073adf1d2/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:235f45e5dbcccf6bd99f9f472858849f73d11120d76ea8707115415f8e5ebebf", size = 2241074 }, - { url = "https://files.pythonhosted.org/packages/e0/ed/55532bb88f674d5d8f67ab121a2a13c385df382de2a1677f30ad385f7438/pydantic_core-2.33.2-cp311-cp311-win32.whl", hash = "sha256:6368900c2d3ef09b69cb0b913f9f8263b03786e5b2a387706c5afb66800efd51", size = 1910538 }, - { url = "https://files.pythonhosted.org/packages/fe/1b/25b7cccd4519c0b23c2dd636ad39d381abf113085ce4f7bec2b0dc755eb1/pydantic_core-2.33.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e063337ef9e9820c77acc768546325ebe04ee38b08703244c1309cccc4f1bab", size = 1952909 }, - { url = "https://files.pythonhosted.org/packages/49/a9/d809358e49126438055884c4366a1f6227f0f84f635a9014e2deb9b9de54/pydantic_core-2.33.2-cp311-cp311-win_arm64.whl", hash = "sha256:6b99022f1d19bc32a4c2a0d544fc9a76e3be90f0b3f4af413f87d38749300e65", size = 1897786 }, - { url = "https://files.pythonhosted.org/packages/18/8a/2b41c97f554ec8c71f2a8a5f85cb56a8b0956addfe8b0efb5b3d77e8bdc3/pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc", size = 2009000 }, - { url = "https://files.pythonhosted.org/packages/a1/02/6224312aacb3c8ecbaa959897af57181fb6cf3a3d7917fd44d0f2917e6f2/pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7", size = 1847996 }, - { url = "https://files.pythonhosted.org/packages/d6/46/6dcdf084a523dbe0a0be59d054734b86a981726f221f4562aed313dbcb49/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025", size = 1880957 }, - { url = "https://files.pythonhosted.org/packages/ec/6b/1ec2c03837ac00886ba8160ce041ce4e325b41d06a034adbef11339ae422/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011", size = 1964199 }, - { url = "https://files.pythonhosted.org/packages/2d/1d/6bf34d6adb9debd9136bd197ca72642203ce9aaaa85cfcbfcf20f9696e83/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f", size = 2120296 }, - { url = "https://files.pythonhosted.org/packages/e0/94/2bd0aaf5a591e974b32a9f7123f16637776c304471a0ab33cf263cf5591a/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88", size = 2676109 }, - { url = "https://files.pythonhosted.org/packages/f9/41/4b043778cf9c4285d59742281a769eac371b9e47e35f98ad321349cc5d61/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1", size = 2002028 }, - { url = "https://files.pythonhosted.org/packages/cb/d5/7bb781bf2748ce3d03af04d5c969fa1308880e1dca35a9bd94e1a96a922e/pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b", size = 2100044 }, - { url = "https://files.pythonhosted.org/packages/fe/36/def5e53e1eb0ad896785702a5bbfd25eed546cdcf4087ad285021a90ed53/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1", size = 2058881 }, - { url = "https://files.pythonhosted.org/packages/01/6c/57f8d70b2ee57fc3dc8b9610315949837fa8c11d86927b9bb044f8705419/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6", size = 2227034 }, - { url = "https://files.pythonhosted.org/packages/27/b9/9c17f0396a82b3d5cbea4c24d742083422639e7bb1d5bf600e12cb176a13/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea", size = 2234187 }, - { url = "https://files.pythonhosted.org/packages/b0/6a/adf5734ffd52bf86d865093ad70b2ce543415e0e356f6cacabbc0d9ad910/pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290", size = 1892628 }, - { url = "https://files.pythonhosted.org/packages/43/e4/5479fecb3606c1368d496a825d8411e126133c41224c1e7238be58b87d7e/pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2", size = 1955866 }, - { url = "https://files.pythonhosted.org/packages/0d/24/8b11e8b3e2be9dd82df4b11408a67c61bb4dc4f8e11b5b0fc888b38118b5/pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab", size = 1888894 }, - { url = "https://files.pythonhosted.org/packages/46/8c/99040727b41f56616573a28771b1bfa08a3d3fe74d3d513f01251f79f172/pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f", size = 2015688 }, - { url = "https://files.pythonhosted.org/packages/3a/cc/5999d1eb705a6cefc31f0b4a90e9f7fc400539b1a1030529700cc1b51838/pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6", size = 1844808 }, - { url = "https://files.pythonhosted.org/packages/6f/5e/a0a7b8885c98889a18b6e376f344da1ef323d270b44edf8174d6bce4d622/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef", size = 1885580 }, - { url = "https://files.pythonhosted.org/packages/3b/2a/953581f343c7d11a304581156618c3f592435523dd9d79865903272c256a/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a", size = 1973859 }, - { url = "https://files.pythonhosted.org/packages/e6/55/f1a813904771c03a3f97f676c62cca0c0a4138654107c1b61f19c644868b/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916", size = 2120810 }, - { url = "https://files.pythonhosted.org/packages/aa/c3/053389835a996e18853ba107a63caae0b9deb4a276c6b472931ea9ae6e48/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a", size = 2676498 }, - { url = "https://files.pythonhosted.org/packages/eb/3c/f4abd740877a35abade05e437245b192f9d0ffb48bbbbd708df33d3cda37/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d", size = 2000611 }, - { url = "https://files.pythonhosted.org/packages/59/a7/63ef2fed1837d1121a894d0ce88439fe3e3b3e48c7543b2a4479eb99c2bd/pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56", size = 2107924 }, - { url = "https://files.pythonhosted.org/packages/04/8f/2551964ef045669801675f1cfc3b0d74147f4901c3ffa42be2ddb1f0efc4/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5", size = 2063196 }, - { url = "https://files.pythonhosted.org/packages/26/bd/d9602777e77fc6dbb0c7db9ad356e9a985825547dce5ad1d30ee04903918/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e", size = 2236389 }, - { url = "https://files.pythonhosted.org/packages/42/db/0e950daa7e2230423ab342ae918a794964b053bec24ba8af013fc7c94846/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162", size = 2239223 }, - { url = "https://files.pythonhosted.org/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", size = 1900473 }, - { url = "https://files.pythonhosted.org/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", size = 1955269 }, - { url = "https://files.pythonhosted.org/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", size = 1893921 }, - { url = "https://files.pythonhosted.org/packages/a4/7d/e09391c2eebeab681df2b74bfe6c43422fffede8dc74187b2b0bf6fd7571/pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac", size = 1806162 }, - { url = "https://files.pythonhosted.org/packages/f1/3d/847b6b1fed9f8ed3bb95a9ad04fbd0b212e832d4f0f50ff4d9ee5a9f15cf/pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5", size = 1981560 }, - { url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777 }, - { url = "https://files.pythonhosted.org/packages/30/68/373d55e58b7e83ce371691f6eaa7175e3a24b956c44628eb25d7da007917/pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5c4aa4e82353f65e548c476b37e64189783aa5384903bfea4f41580f255fddfa", size = 2023982 }, - { url = "https://files.pythonhosted.org/packages/a4/16/145f54ac08c96a63d8ed6442f9dec17b2773d19920b627b18d4f10a061ea/pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d946c8bf0d5c24bf4fe333af284c59a19358aa3ec18cb3dc4370080da1e8ad29", size = 1858412 }, - { url = "https://files.pythonhosted.org/packages/41/b1/c6dc6c3e2de4516c0bb2c46f6a373b91b5660312342a0cf5826e38ad82fa/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87b31b6846e361ef83fedb187bb5b4372d0da3f7e28d85415efa92d6125d6e6d", size = 1892749 }, - { url = "https://files.pythonhosted.org/packages/12/73/8cd57e20afba760b21b742106f9dbdfa6697f1570b189c7457a1af4cd8a0/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa9d91b338f2df0508606f7009fde642391425189bba6d8c653afd80fd6bb64e", size = 2067527 }, - { url = "https://files.pythonhosted.org/packages/e3/d5/0bb5d988cc019b3cba4a78f2d4b3854427fc47ee8ec8e9eaabf787da239c/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2058a32994f1fde4ca0480ab9d1e75a0e8c87c22b53a3ae66554f9af78f2fe8c", size = 2108225 }, - { url = "https://files.pythonhosted.org/packages/f1/c5/00c02d1571913d496aabf146106ad8239dc132485ee22efe08085084ff7c/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:0e03262ab796d986f978f79c943fc5f620381be7287148b8010b4097f79a39ec", size = 2069490 }, - { url = "https://files.pythonhosted.org/packages/22/a8/dccc38768274d3ed3a59b5d06f59ccb845778687652daa71df0cab4040d7/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:1a8695a8d00c73e50bff9dfda4d540b7dee29ff9b8053e38380426a85ef10052", size = 2237525 }, - { url = "https://files.pythonhosted.org/packages/d4/e7/4f98c0b125dda7cf7ccd14ba936218397b44f50a56dd8c16a3091df116c3/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:fa754d1850735a0b0e03bcffd9d4b4343eb417e47196e4485d9cca326073a42c", size = 2238446 }, - { url = "https://files.pythonhosted.org/packages/ce/91/2ec36480fdb0b783cd9ef6795753c1dea13882f2e68e73bce76ae8c21e6a/pydantic_core-2.33.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a11c8d26a50bfab49002947d3d237abe4d9e4b5bdc8846a63537b6488e197808", size = 2066678 }, - { url = "https://files.pythonhosted.org/packages/7b/27/d4ae6487d73948d6f20dddcd94be4ea43e74349b56eba82e9bdee2d7494c/pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:dd14041875d09cc0f9308e37a6f8b65f5585cf2598a53aa0123df8b129d481f8", size = 2025200 }, - { url = "https://files.pythonhosted.org/packages/f1/b8/b3cb95375f05d33801024079b9392a5ab45267a63400bf1866e7ce0f0de4/pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d87c561733f66531dced0da6e864f44ebf89a8fba55f31407b00c2f7f9449593", size = 1859123 }, - { url = "https://files.pythonhosted.org/packages/05/bc/0d0b5adeda59a261cd30a1235a445bf55c7e46ae44aea28f7bd6ed46e091/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f82865531efd18d6e07a04a17331af02cb7a651583c418df8266f17a63c6612", size = 1892852 }, - { url = "https://files.pythonhosted.org/packages/3e/11/d37bdebbda2e449cb3f519f6ce950927b56d62f0b84fd9cb9e372a26a3d5/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bfb5112df54209d820d7bf9317c7a6c9025ea52e49f46b6a2060104bba37de7", size = 2067484 }, - { url = "https://files.pythonhosted.org/packages/8c/55/1f95f0a05ce72ecb02a8a8a1c3be0579bbc29b1d5ab68f1378b7bebc5057/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:64632ff9d614e5eecfb495796ad51b0ed98c453e447a76bcbeeb69615079fc7e", size = 2108896 }, - { url = "https://files.pythonhosted.org/packages/53/89/2b2de6c81fa131f423246a9109d7b2a375e83968ad0800d6e57d0574629b/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f889f7a40498cc077332c7ab6b4608d296d852182211787d4f3ee377aaae66e8", size = 2069475 }, - { url = "https://files.pythonhosted.org/packages/b8/e9/1f7efbe20d0b2b10f6718944b5d8ece9152390904f29a78e68d4e7961159/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:de4b83bb311557e439b9e186f733f6c645b9417c84e2eb8203f3f820a4b988bf", size = 2239013 }, - { url = "https://files.pythonhosted.org/packages/3c/b2/5309c905a93811524a49b4e031e9851a6b00ff0fb668794472ea7746b448/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:82f68293f055f51b51ea42fafc74b6aad03e70e191799430b90c13d643059ebb", size = 2238715 }, - { url = "https://files.pythonhosted.org/packages/32/56/8a7ca5d2cd2cda1d245d34b1c9a942920a718082ae8e54e5f3e5a58b7add/pydantic_core-2.33.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:329467cecfb529c925cf2bbd4d60d2c509bc2fb52a20c1045bf09bb70971a9c1", size = 2066757 }, +sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195, upload-time = "2025-04-23T18:33:52.104Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/92/b31726561b5dae176c2d2c2dc43a9c5bfba5d32f96f8b4c0a600dd492447/pydantic_core-2.33.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2b3d326aaef0c0399d9afffeb6367d5e26ddc24d351dbc9c636840ac355dc5d8", size = 2028817, upload-time = "2025-04-23T18:30:43.919Z" }, + { url = "https://files.pythonhosted.org/packages/a3/44/3f0b95fafdaca04a483c4e685fe437c6891001bf3ce8b2fded82b9ea3aa1/pydantic_core-2.33.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e5b2671f05ba48b94cb90ce55d8bdcaaedb8ba00cc5359f6810fc918713983d", size = 1861357, upload-time = "2025-04-23T18:30:46.372Z" }, + { url = "https://files.pythonhosted.org/packages/30/97/e8f13b55766234caae05372826e8e4b3b96e7b248be3157f53237682e43c/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0069c9acc3f3981b9ff4cdfaf088e98d83440a4c7ea1bc07460af3d4dc22e72d", size = 1898011, upload-time = "2025-04-23T18:30:47.591Z" }, + { url = "https://files.pythonhosted.org/packages/9b/a3/99c48cf7bafc991cc3ee66fd544c0aae8dc907b752f1dad2d79b1b5a471f/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d53b22f2032c42eaaf025f7c40c2e3b94568ae077a606f006d206a463bc69572", size = 1982730, upload-time = "2025-04-23T18:30:49.328Z" }, + { url = "https://files.pythonhosted.org/packages/de/8e/a5b882ec4307010a840fb8b58bd9bf65d1840c92eae7534c7441709bf54b/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0405262705a123b7ce9f0b92f123334d67b70fd1f20a9372b907ce1080c7ba02", size = 2136178, upload-time = "2025-04-23T18:30:50.907Z" }, + { url = "https://files.pythonhosted.org/packages/e4/bb/71e35fc3ed05af6834e890edb75968e2802fe98778971ab5cba20a162315/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4b25d91e288e2c4e0662b8038a28c6a07eaac3e196cfc4ff69de4ea3db992a1b", size = 2736462, upload-time = "2025-04-23T18:30:52.083Z" }, + { url = "https://files.pythonhosted.org/packages/31/0d/c8f7593e6bc7066289bbc366f2235701dcbebcd1ff0ef8e64f6f239fb47d/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bdfe4b3789761f3bcb4b1ddf33355a71079858958e3a552f16d5af19768fef2", size = 2005652, upload-time = "2025-04-23T18:30:53.389Z" }, + { url = "https://files.pythonhosted.org/packages/d2/7a/996d8bd75f3eda405e3dd219ff5ff0a283cd8e34add39d8ef9157e722867/pydantic_core-2.33.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:efec8db3266b76ef9607c2c4c419bdb06bf335ae433b80816089ea7585816f6a", size = 2113306, upload-time = "2025-04-23T18:30:54.661Z" }, + { url = "https://files.pythonhosted.org/packages/ff/84/daf2a6fb2db40ffda6578a7e8c5a6e9c8affb251a05c233ae37098118788/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:031c57d67ca86902726e0fae2214ce6770bbe2f710dc33063187a68744a5ecac", size = 2073720, upload-time = "2025-04-23T18:30:56.11Z" }, + { url = "https://files.pythonhosted.org/packages/77/fb/2258da019f4825128445ae79456a5499c032b55849dbd5bed78c95ccf163/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:f8de619080e944347f5f20de29a975c2d815d9ddd8be9b9b7268e2e3ef68605a", size = 2244915, upload-time = "2025-04-23T18:30:57.501Z" }, + { url = "https://files.pythonhosted.org/packages/d8/7a/925ff73756031289468326e355b6fa8316960d0d65f8b5d6b3a3e7866de7/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:73662edf539e72a9440129f231ed3757faab89630d291b784ca99237fb94db2b", size = 2241884, upload-time = "2025-04-23T18:30:58.867Z" }, + { url = "https://files.pythonhosted.org/packages/0b/b0/249ee6d2646f1cdadcb813805fe76265745c4010cf20a8eba7b0e639d9b2/pydantic_core-2.33.2-cp310-cp310-win32.whl", hash = "sha256:0a39979dcbb70998b0e505fb1556a1d550a0781463ce84ebf915ba293ccb7e22", size = 1910496, upload-time = "2025-04-23T18:31:00.078Z" }, + { url = "https://files.pythonhosted.org/packages/66/ff/172ba8f12a42d4b552917aa65d1f2328990d3ccfc01d5b7c943ec084299f/pydantic_core-2.33.2-cp310-cp310-win_amd64.whl", hash = "sha256:b0379a2b24882fef529ec3b4987cb5d003b9cda32256024e6fe1586ac45fc640", size = 1955019, upload-time = "2025-04-23T18:31:01.335Z" }, + { url = "https://files.pythonhosted.org/packages/3f/8d/71db63483d518cbbf290261a1fc2839d17ff89fce7089e08cad07ccfce67/pydantic_core-2.33.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4c5b0a576fb381edd6d27f0a85915c6daf2f8138dc5c267a57c08a62900758c7", size = 2028584, upload-time = "2025-04-23T18:31:03.106Z" }, + { url = "https://files.pythonhosted.org/packages/24/2f/3cfa7244ae292dd850989f328722d2aef313f74ffc471184dc509e1e4e5a/pydantic_core-2.33.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e799c050df38a639db758c617ec771fd8fb7a5f8eaaa4b27b101f266b216a246", size = 1855071, upload-time = "2025-04-23T18:31:04.621Z" }, + { url = "https://files.pythonhosted.org/packages/b3/d3/4ae42d33f5e3f50dd467761304be2fa0a9417fbf09735bc2cce003480f2a/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc46a01bf8d62f227d5ecee74178ffc448ff4e5197c756331f71efcc66dc980f", size = 1897823, upload-time = "2025-04-23T18:31:06.377Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f3/aa5976e8352b7695ff808599794b1fba2a9ae2ee954a3426855935799488/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a144d4f717285c6d9234a66778059f33a89096dfb9b39117663fd8413d582dcc", size = 1983792, upload-time = "2025-04-23T18:31:07.93Z" }, + { url = "https://files.pythonhosted.org/packages/d5/7a/cda9b5a23c552037717f2b2a5257e9b2bfe45e687386df9591eff7b46d28/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73cf6373c21bc80b2e0dc88444f41ae60b2f070ed02095754eb5a01df12256de", size = 2136338, upload-time = "2025-04-23T18:31:09.283Z" }, + { url = "https://files.pythonhosted.org/packages/2b/9f/b8f9ec8dd1417eb9da784e91e1667d58a2a4a7b7b34cf4af765ef663a7e5/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dc625f4aa79713512d1976fe9f0bc99f706a9dee21dfd1810b4bbbf228d0e8a", size = 2730998, upload-time = "2025-04-23T18:31:11.7Z" }, + { url = "https://files.pythonhosted.org/packages/47/bc/cd720e078576bdb8255d5032c5d63ee5c0bf4b7173dd955185a1d658c456/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b21b5549499972441da4758d662aeea93f1923f953e9cbaff14b8b9565aef", size = 2003200, upload-time = "2025-04-23T18:31:13.536Z" }, + { url = "https://files.pythonhosted.org/packages/ca/22/3602b895ee2cd29d11a2b349372446ae9727c32e78a94b3d588a40fdf187/pydantic_core-2.33.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bdc25f3681f7b78572699569514036afe3c243bc3059d3942624e936ec93450e", size = 2113890, upload-time = "2025-04-23T18:31:15.011Z" }, + { url = "https://files.pythonhosted.org/packages/ff/e6/e3c5908c03cf00d629eb38393a98fccc38ee0ce8ecce32f69fc7d7b558a7/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fe5b32187cbc0c862ee201ad66c30cf218e5ed468ec8dc1cf49dec66e160cc4d", size = 2073359, upload-time = "2025-04-23T18:31:16.393Z" }, + { url = "https://files.pythonhosted.org/packages/12/e7/6a36a07c59ebefc8777d1ffdaf5ae71b06b21952582e4b07eba88a421c79/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:bc7aee6f634a6f4a95676fcb5d6559a2c2a390330098dba5e5a5f28a2e4ada30", size = 2245883, upload-time = "2025-04-23T18:31:17.892Z" }, + { url = "https://files.pythonhosted.org/packages/16/3f/59b3187aaa6cc0c1e6616e8045b284de2b6a87b027cce2ffcea073adf1d2/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:235f45e5dbcccf6bd99f9f472858849f73d11120d76ea8707115415f8e5ebebf", size = 2241074, upload-time = "2025-04-23T18:31:19.205Z" }, + { url = "https://files.pythonhosted.org/packages/e0/ed/55532bb88f674d5d8f67ab121a2a13c385df382de2a1677f30ad385f7438/pydantic_core-2.33.2-cp311-cp311-win32.whl", hash = "sha256:6368900c2d3ef09b69cb0b913f9f8263b03786e5b2a387706c5afb66800efd51", size = 1910538, upload-time = "2025-04-23T18:31:20.541Z" }, + { url = "https://files.pythonhosted.org/packages/fe/1b/25b7cccd4519c0b23c2dd636ad39d381abf113085ce4f7bec2b0dc755eb1/pydantic_core-2.33.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e063337ef9e9820c77acc768546325ebe04ee38b08703244c1309cccc4f1bab", size = 1952909, upload-time = "2025-04-23T18:31:22.371Z" }, + { url = "https://files.pythonhosted.org/packages/49/a9/d809358e49126438055884c4366a1f6227f0f84f635a9014e2deb9b9de54/pydantic_core-2.33.2-cp311-cp311-win_arm64.whl", hash = "sha256:6b99022f1d19bc32a4c2a0d544fc9a76e3be90f0b3f4af413f87d38749300e65", size = 1897786, upload-time = "2025-04-23T18:31:24.161Z" }, + { url = "https://files.pythonhosted.org/packages/18/8a/2b41c97f554ec8c71f2a8a5f85cb56a8b0956addfe8b0efb5b3d77e8bdc3/pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc", size = 2009000, upload-time = "2025-04-23T18:31:25.863Z" }, + { url = "https://files.pythonhosted.org/packages/a1/02/6224312aacb3c8ecbaa959897af57181fb6cf3a3d7917fd44d0f2917e6f2/pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7", size = 1847996, upload-time = "2025-04-23T18:31:27.341Z" }, + { url = "https://files.pythonhosted.org/packages/d6/46/6dcdf084a523dbe0a0be59d054734b86a981726f221f4562aed313dbcb49/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025", size = 1880957, upload-time = "2025-04-23T18:31:28.956Z" }, + { url = "https://files.pythonhosted.org/packages/ec/6b/1ec2c03837ac00886ba8160ce041ce4e325b41d06a034adbef11339ae422/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011", size = 1964199, upload-time = "2025-04-23T18:31:31.025Z" }, + { url = "https://files.pythonhosted.org/packages/2d/1d/6bf34d6adb9debd9136bd197ca72642203ce9aaaa85cfcbfcf20f9696e83/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f", size = 2120296, upload-time = "2025-04-23T18:31:32.514Z" }, + { url = "https://files.pythonhosted.org/packages/e0/94/2bd0aaf5a591e974b32a9f7123f16637776c304471a0ab33cf263cf5591a/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88", size = 2676109, upload-time = "2025-04-23T18:31:33.958Z" }, + { url = "https://files.pythonhosted.org/packages/f9/41/4b043778cf9c4285d59742281a769eac371b9e47e35f98ad321349cc5d61/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1", size = 2002028, upload-time = "2025-04-23T18:31:39.095Z" }, + { url = "https://files.pythonhosted.org/packages/cb/d5/7bb781bf2748ce3d03af04d5c969fa1308880e1dca35a9bd94e1a96a922e/pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b", size = 2100044, upload-time = "2025-04-23T18:31:41.034Z" }, + { url = "https://files.pythonhosted.org/packages/fe/36/def5e53e1eb0ad896785702a5bbfd25eed546cdcf4087ad285021a90ed53/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1", size = 2058881, upload-time = "2025-04-23T18:31:42.757Z" }, + { url = "https://files.pythonhosted.org/packages/01/6c/57f8d70b2ee57fc3dc8b9610315949837fa8c11d86927b9bb044f8705419/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6", size = 2227034, upload-time = "2025-04-23T18:31:44.304Z" }, + { url = "https://files.pythonhosted.org/packages/27/b9/9c17f0396a82b3d5cbea4c24d742083422639e7bb1d5bf600e12cb176a13/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea", size = 2234187, upload-time = "2025-04-23T18:31:45.891Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6a/adf5734ffd52bf86d865093ad70b2ce543415e0e356f6cacabbc0d9ad910/pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290", size = 1892628, upload-time = "2025-04-23T18:31:47.819Z" }, + { url = "https://files.pythonhosted.org/packages/43/e4/5479fecb3606c1368d496a825d8411e126133c41224c1e7238be58b87d7e/pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2", size = 1955866, upload-time = "2025-04-23T18:31:49.635Z" }, + { url = "https://files.pythonhosted.org/packages/0d/24/8b11e8b3e2be9dd82df4b11408a67c61bb4dc4f8e11b5b0fc888b38118b5/pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab", size = 1888894, upload-time = "2025-04-23T18:31:51.609Z" }, + { url = "https://files.pythonhosted.org/packages/46/8c/99040727b41f56616573a28771b1bfa08a3d3fe74d3d513f01251f79f172/pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f", size = 2015688, upload-time = "2025-04-23T18:31:53.175Z" }, + { url = "https://files.pythonhosted.org/packages/3a/cc/5999d1eb705a6cefc31f0b4a90e9f7fc400539b1a1030529700cc1b51838/pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6", size = 1844808, upload-time = "2025-04-23T18:31:54.79Z" }, + { url = "https://files.pythonhosted.org/packages/6f/5e/a0a7b8885c98889a18b6e376f344da1ef323d270b44edf8174d6bce4d622/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef", size = 1885580, upload-time = "2025-04-23T18:31:57.393Z" }, + { url = "https://files.pythonhosted.org/packages/3b/2a/953581f343c7d11a304581156618c3f592435523dd9d79865903272c256a/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a", size = 1973859, upload-time = "2025-04-23T18:31:59.065Z" }, + { url = "https://files.pythonhosted.org/packages/e6/55/f1a813904771c03a3f97f676c62cca0c0a4138654107c1b61f19c644868b/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916", size = 2120810, upload-time = "2025-04-23T18:32:00.78Z" }, + { url = "https://files.pythonhosted.org/packages/aa/c3/053389835a996e18853ba107a63caae0b9deb4a276c6b472931ea9ae6e48/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a", size = 2676498, upload-time = "2025-04-23T18:32:02.418Z" }, + { url = "https://files.pythonhosted.org/packages/eb/3c/f4abd740877a35abade05e437245b192f9d0ffb48bbbbd708df33d3cda37/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d", size = 2000611, upload-time = "2025-04-23T18:32:04.152Z" }, + { url = "https://files.pythonhosted.org/packages/59/a7/63ef2fed1837d1121a894d0ce88439fe3e3b3e48c7543b2a4479eb99c2bd/pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56", size = 2107924, upload-time = "2025-04-23T18:32:06.129Z" }, + { url = "https://files.pythonhosted.org/packages/04/8f/2551964ef045669801675f1cfc3b0d74147f4901c3ffa42be2ddb1f0efc4/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5", size = 2063196, upload-time = "2025-04-23T18:32:08.178Z" }, + { url = "https://files.pythonhosted.org/packages/26/bd/d9602777e77fc6dbb0c7db9ad356e9a985825547dce5ad1d30ee04903918/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e", size = 2236389, upload-time = "2025-04-23T18:32:10.242Z" }, + { url = "https://files.pythonhosted.org/packages/42/db/0e950daa7e2230423ab342ae918a794964b053bec24ba8af013fc7c94846/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162", size = 2239223, upload-time = "2025-04-23T18:32:12.382Z" }, + { url = "https://files.pythonhosted.org/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", size = 1900473, upload-time = "2025-04-23T18:32:14.034Z" }, + { url = "https://files.pythonhosted.org/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", size = 1955269, upload-time = "2025-04-23T18:32:15.783Z" }, + { url = "https://files.pythonhosted.org/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", size = 1893921, upload-time = "2025-04-23T18:32:18.473Z" }, + { url = "https://files.pythonhosted.org/packages/a4/7d/e09391c2eebeab681df2b74bfe6c43422fffede8dc74187b2b0bf6fd7571/pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac", size = 1806162, upload-time = "2025-04-23T18:32:20.188Z" }, + { url = "https://files.pythonhosted.org/packages/f1/3d/847b6b1fed9f8ed3bb95a9ad04fbd0b212e832d4f0f50ff4d9ee5a9f15cf/pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5", size = 1981560, upload-time = "2025-04-23T18:32:22.354Z" }, + { url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777, upload-time = "2025-04-23T18:32:25.088Z" }, + { url = "https://files.pythonhosted.org/packages/30/68/373d55e58b7e83ce371691f6eaa7175e3a24b956c44628eb25d7da007917/pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5c4aa4e82353f65e548c476b37e64189783aa5384903bfea4f41580f255fddfa", size = 2023982, upload-time = "2025-04-23T18:32:53.14Z" }, + { url = "https://files.pythonhosted.org/packages/a4/16/145f54ac08c96a63d8ed6442f9dec17b2773d19920b627b18d4f10a061ea/pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d946c8bf0d5c24bf4fe333af284c59a19358aa3ec18cb3dc4370080da1e8ad29", size = 1858412, upload-time = "2025-04-23T18:32:55.52Z" }, + { url = "https://files.pythonhosted.org/packages/41/b1/c6dc6c3e2de4516c0bb2c46f6a373b91b5660312342a0cf5826e38ad82fa/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87b31b6846e361ef83fedb187bb5b4372d0da3f7e28d85415efa92d6125d6e6d", size = 1892749, upload-time = "2025-04-23T18:32:57.546Z" }, + { url = "https://files.pythonhosted.org/packages/12/73/8cd57e20afba760b21b742106f9dbdfa6697f1570b189c7457a1af4cd8a0/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa9d91b338f2df0508606f7009fde642391425189bba6d8c653afd80fd6bb64e", size = 2067527, upload-time = "2025-04-23T18:32:59.771Z" }, + { url = "https://files.pythonhosted.org/packages/e3/d5/0bb5d988cc019b3cba4a78f2d4b3854427fc47ee8ec8e9eaabf787da239c/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2058a32994f1fde4ca0480ab9d1e75a0e8c87c22b53a3ae66554f9af78f2fe8c", size = 2108225, upload-time = "2025-04-23T18:33:04.51Z" }, + { url = "https://files.pythonhosted.org/packages/f1/c5/00c02d1571913d496aabf146106ad8239dc132485ee22efe08085084ff7c/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:0e03262ab796d986f978f79c943fc5f620381be7287148b8010b4097f79a39ec", size = 2069490, upload-time = "2025-04-23T18:33:06.391Z" }, + { url = "https://files.pythonhosted.org/packages/22/a8/dccc38768274d3ed3a59b5d06f59ccb845778687652daa71df0cab4040d7/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:1a8695a8d00c73e50bff9dfda4d540b7dee29ff9b8053e38380426a85ef10052", size = 2237525, upload-time = "2025-04-23T18:33:08.44Z" }, + { url = "https://files.pythonhosted.org/packages/d4/e7/4f98c0b125dda7cf7ccd14ba936218397b44f50a56dd8c16a3091df116c3/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:fa754d1850735a0b0e03bcffd9d4b4343eb417e47196e4485d9cca326073a42c", size = 2238446, upload-time = "2025-04-23T18:33:10.313Z" }, + { url = "https://files.pythonhosted.org/packages/ce/91/2ec36480fdb0b783cd9ef6795753c1dea13882f2e68e73bce76ae8c21e6a/pydantic_core-2.33.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a11c8d26a50bfab49002947d3d237abe4d9e4b5bdc8846a63537b6488e197808", size = 2066678, upload-time = "2025-04-23T18:33:12.224Z" }, + { url = "https://files.pythonhosted.org/packages/7b/27/d4ae6487d73948d6f20dddcd94be4ea43e74349b56eba82e9bdee2d7494c/pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:dd14041875d09cc0f9308e37a6f8b65f5585cf2598a53aa0123df8b129d481f8", size = 2025200, upload-time = "2025-04-23T18:33:14.199Z" }, + { url = "https://files.pythonhosted.org/packages/f1/b8/b3cb95375f05d33801024079b9392a5ab45267a63400bf1866e7ce0f0de4/pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d87c561733f66531dced0da6e864f44ebf89a8fba55f31407b00c2f7f9449593", size = 1859123, upload-time = "2025-04-23T18:33:16.555Z" }, + { url = "https://files.pythonhosted.org/packages/05/bc/0d0b5adeda59a261cd30a1235a445bf55c7e46ae44aea28f7bd6ed46e091/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f82865531efd18d6e07a04a17331af02cb7a651583c418df8266f17a63c6612", size = 1892852, upload-time = "2025-04-23T18:33:18.513Z" }, + { url = "https://files.pythonhosted.org/packages/3e/11/d37bdebbda2e449cb3f519f6ce950927b56d62f0b84fd9cb9e372a26a3d5/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bfb5112df54209d820d7bf9317c7a6c9025ea52e49f46b6a2060104bba37de7", size = 2067484, upload-time = "2025-04-23T18:33:20.475Z" }, + { url = "https://files.pythonhosted.org/packages/8c/55/1f95f0a05ce72ecb02a8a8a1c3be0579bbc29b1d5ab68f1378b7bebc5057/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:64632ff9d614e5eecfb495796ad51b0ed98c453e447a76bcbeeb69615079fc7e", size = 2108896, upload-time = "2025-04-23T18:33:22.501Z" }, + { url = "https://files.pythonhosted.org/packages/53/89/2b2de6c81fa131f423246a9109d7b2a375e83968ad0800d6e57d0574629b/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f889f7a40498cc077332c7ab6b4608d296d852182211787d4f3ee377aaae66e8", size = 2069475, upload-time = "2025-04-23T18:33:24.528Z" }, + { url = "https://files.pythonhosted.org/packages/b8/e9/1f7efbe20d0b2b10f6718944b5d8ece9152390904f29a78e68d4e7961159/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:de4b83bb311557e439b9e186f733f6c645b9417c84e2eb8203f3f820a4b988bf", size = 2239013, upload-time = "2025-04-23T18:33:26.621Z" }, + { url = "https://files.pythonhosted.org/packages/3c/b2/5309c905a93811524a49b4e031e9851a6b00ff0fb668794472ea7746b448/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:82f68293f055f51b51ea42fafc74b6aad03e70e191799430b90c13d643059ebb", size = 2238715, upload-time = "2025-04-23T18:33:28.656Z" }, + { url = "https://files.pythonhosted.org/packages/32/56/8a7ca5d2cd2cda1d245d34b1c9a942920a718082ae8e54e5f3e5a58b7add/pydantic_core-2.33.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:329467cecfb529c925cf2bbd4d60d2c509bc2fb52a20c1045bf09bb70971a9c1", size = 2066757, upload-time = "2025-04-23T18:33:30.645Z" }, ] [[package]] @@ -2366,27 +2368,27 @@ dependencies = [ { name = "python-dotenv" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/68/85/1ea668bbab3c50071ca613c6ab30047fb36ab0da1b92fa8f17bbc38fd36c/pydantic_settings-2.10.1.tar.gz", hash = "sha256:06f0062169818d0f5524420a360d632d5857b83cffd4d42fe29597807a1614ee", size = 172583 } +sdist = { url = "https://files.pythonhosted.org/packages/68/85/1ea668bbab3c50071ca613c6ab30047fb36ab0da1b92fa8f17bbc38fd36c/pydantic_settings-2.10.1.tar.gz", hash = "sha256:06f0062169818d0f5524420a360d632d5857b83cffd4d42fe29597807a1614ee", size = 172583, upload-time = "2025-06-24T13:26:46.841Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/58/f0/427018098906416f580e3cf1366d3b1abfb408a0652e9f31600c24a1903c/pydantic_settings-2.10.1-py3-none-any.whl", hash = "sha256:a60952460b99cf661dc25c29c0ef171721f98bfcb52ef8d9ea4c943d7c8cc796", size = 45235 }, + { url = "https://files.pythonhosted.org/packages/58/f0/427018098906416f580e3cf1366d3b1abfb408a0652e9f31600c24a1903c/pydantic_settings-2.10.1-py3-none-any.whl", hash = "sha256:a60952460b99cf661dc25c29c0ef171721f98bfcb52ef8d9ea4c943d7c8cc796", size = 45235, upload-time = "2025-06-24T13:26:45.485Z" }, ] [[package]] name = "pygments" version = "2.19.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631 } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217 }, + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, ] [[package]] name = "pyproject-hooks" version = "1.2.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e7/82/28175b2414effca1cdac8dc99f76d660e7a4fb0ceefa4b4ab8f5f6742925/pyproject_hooks-1.2.0.tar.gz", hash = "sha256:1e859bd5c40fae9448642dd871adf459e5e2084186e8d2c2a79a824c970da1f8", size = 19228 } +sdist = { url = "https://files.pythonhosted.org/packages/e7/82/28175b2414effca1cdac8dc99f76d660e7a4fb0ceefa4b4ab8f5f6742925/pyproject_hooks-1.2.0.tar.gz", hash = "sha256:1e859bd5c40fae9448642dd871adf459e5e2084186e8d2c2a79a824c970da1f8", size = 19228, upload-time = "2024-09-29T09:24:13.293Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl", hash = "sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913", size = 10216 }, + { url = "https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl", hash = "sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913", size = 10216, upload-time = "2024-09-29T09:24:11.978Z" }, ] [[package]] @@ -2402,9 +2404,9 @@ dependencies = [ { name = "pygments" }, { name = "tomli", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/08/ba/45911d754e8eba3d5a841a5ce61a65a685ff1798421ac054f85aa8747dfb/pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c", size = 1517714 } +sdist = { url = "https://files.pythonhosted.org/packages/08/ba/45911d754e8eba3d5a841a5ce61a65a685ff1798421ac054f85aa8747dfb/pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c", size = 1517714, upload-time = "2025-06-18T05:48:06.109Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474 }, + { url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474, upload-time = "2025-06-18T05:48:03.955Z" }, ] [[package]] @@ -2414,9 +2416,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pytest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d0/d4/14f53324cb1a6381bef29d698987625d80052bb33932d8e7cbf9b337b17c/pytest_asyncio-1.0.0.tar.gz", hash = "sha256:d15463d13f4456e1ead2594520216b225a16f781e144f8fdf6c5bb4667c48b3f", size = 46960 } +sdist = { url = "https://files.pythonhosted.org/packages/d0/d4/14f53324cb1a6381bef29d698987625d80052bb33932d8e7cbf9b337b17c/pytest_asyncio-1.0.0.tar.gz", hash = "sha256:d15463d13f4456e1ead2594520216b225a16f781e144f8fdf6c5bb4667c48b3f", size = 46960, upload-time = "2025-05-26T04:54:40.484Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/30/05/ce271016e351fddc8399e546f6e23761967ee09c8c568bbfbecb0c150171/pytest_asyncio-1.0.0-py3-none-any.whl", hash = "sha256:4f024da9f1ef945e680dc68610b52550e36590a67fd31bb3b4943979a1f90ef3", size = 15976 }, + { url = "https://files.pythonhosted.org/packages/30/05/ce271016e351fddc8399e546f6e23761967ee09c8c568bbfbecb0c150171/pytest_asyncio-1.0.0-py3-none-any.whl", hash = "sha256:4f024da9f1ef945e680dc68610b52550e36590a67fd31bb3b4943979a1f90ef3", size = 15976, upload-time = "2025-05-26T04:54:39.035Z" }, ] [[package]] @@ -2428,9 +2430,9 @@ dependencies = [ { name = "pluggy" }, { name = "pytest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/18/99/668cade231f434aaa59bbfbf49469068d2ddd945000621d3d165d2e7dd7b/pytest_cov-6.2.1.tar.gz", hash = "sha256:25cc6cc0a5358204b8108ecedc51a9b57b34cc6b8c967cc2c01a4e00d8a67da2", size = 69432 } +sdist = { url = "https://files.pythonhosted.org/packages/18/99/668cade231f434aaa59bbfbf49469068d2ddd945000621d3d165d2e7dd7b/pytest_cov-6.2.1.tar.gz", hash = "sha256:25cc6cc0a5358204b8108ecedc51a9b57b34cc6b8c967cc2c01a4e00d8a67da2", size = 69432, upload-time = "2025-06-12T10:47:47.684Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bc/16/4ea354101abb1287856baa4af2732be351c7bee728065aed451b678153fd/pytest_cov-6.2.1-py3-none-any.whl", hash = "sha256:f5bc4c23f42f1cdd23c70b1dab1bbaef4fc505ba950d53e0081d0730dd7e86d5", size = 24644 }, + { url = "https://files.pythonhosted.org/packages/bc/16/4ea354101abb1287856baa4af2732be351c7bee728065aed451b678153fd/pytest_cov-6.2.1-py3-none-any.whl", hash = "sha256:f5bc4c23f42f1cdd23c70b1dab1bbaef4fc505ba950d53e0081d0730dd7e86d5", size = 24644, upload-time = "2025-06-12T10:47:45.932Z" }, ] [[package]] @@ -2440,9 +2442,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pytest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/71/28/67172c96ba684058a4d24ffe144d64783d2a270d0af0d9e792737bddc75c/pytest_mock-3.14.1.tar.gz", hash = "sha256:159e9edac4c451ce77a5cdb9fc5d1100708d2dd4ba3c3df572f14097351af80e", size = 33241 } +sdist = { url = "https://files.pythonhosted.org/packages/71/28/67172c96ba684058a4d24ffe144d64783d2a270d0af0d9e792737bddc75c/pytest_mock-3.14.1.tar.gz", hash = "sha256:159e9edac4c451ce77a5cdb9fc5d1100708d2dd4ba3c3df572f14097351af80e", size = 33241, upload-time = "2025-05-26T13:58:45.167Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b2/05/77b60e520511c53d1c1ca75f1930c7dd8e971d0c4379b7f4b3f9644685ba/pytest_mock-3.14.1-py3-none-any.whl", hash = "sha256:178aefcd11307d874b4cd3100344e7e2d888d9791a6a1d9bfe90fbc1b74fd1d0", size = 9923 }, + { url = "https://files.pythonhosted.org/packages/b2/05/77b60e520511c53d1c1ca75f1930c7dd8e971d0c4379b7f4b3f9644685ba/pytest_mock-3.14.1-py3-none-any.whl", hash = "sha256:178aefcd11307d874b4cd3100344e7e2d888d9791a6a1d9bfe90fbc1b74fd1d0", size = 9923, upload-time = "2025-05-26T13:58:43.487Z" }, ] [[package]] @@ -2452,9 +2454,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pytest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ac/82/4c9ecabab13363e72d880f2fb504c5f750433b2b6f16e99f4ec21ada284c/pytest_timeout-2.4.0.tar.gz", hash = "sha256:7e68e90b01f9eff71332b25001f85c75495fc4e3a836701876183c4bcfd0540a", size = 17973 } +sdist = { url = "https://files.pythonhosted.org/packages/ac/82/4c9ecabab13363e72d880f2fb504c5f750433b2b6f16e99f4ec21ada284c/pytest_timeout-2.4.0.tar.gz", hash = "sha256:7e68e90b01f9eff71332b25001f85c75495fc4e3a836701876183c4bcfd0540a", size = 17973, upload-time = "2025-05-05T19:44:34.99Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/b6/3127540ecdf1464a00e5a01ee60a1b09175f6913f0644ac748494d9c4b21/pytest_timeout-2.4.0-py3-none-any.whl", hash = "sha256:c42667e5cdadb151aeb5b26d114aff6bdf5a907f176a007a30b940d3d865b5c2", size = 14382 }, + { url = "https://files.pythonhosted.org/packages/fa/b6/3127540ecdf1464a00e5a01ee60a1b09175f6913f0644ac748494d9c4b21/pytest_timeout-2.4.0-py3-none-any.whl", hash = "sha256:c42667e5cdadb151aeb5b26d114aff6bdf5a907f176a007a30b940d3d865b5c2", size = 14382, upload-time = "2025-05-05T19:44:33.502Z" }, ] [[package]] @@ -2464,27 +2466,27 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "six" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432 } +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892 }, + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, ] [[package]] name = "python-dotenv" version = "1.1.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f6/b0/4bc07ccd3572a2f9df7e6782f52b0c6c90dcbb803ac4a167702d7d0dfe1e/python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab", size = 41978 } +sdist = { url = "https://files.pythonhosted.org/packages/f6/b0/4bc07ccd3572a2f9df7e6782f52b0c6c90dcbb803ac4a167702d7d0dfe1e/python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab", size = 41978, upload-time = "2025-06-24T04:21:07.341Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556 }, + { url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556, upload-time = "2025-06-24T04:21:06.073Z" }, ] [[package]] name = "python-multipart" version = "0.0.20" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f3/87/f44d7c9f274c7ee665a29b885ec97089ec5dc034c7f3fafa03da9e39a09e/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13", size = 37158 } +sdist = { url = "https://files.pythonhosted.org/packages/f3/87/f44d7c9f274c7ee665a29b885ec97089ec5dc034c7f3fafa03da9e39a09e/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13", size = 37158, upload-time = "2024-12-16T19:45:46.972Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546 }, + { url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546, upload-time = "2024-12-16T19:45:44.423Z" }, ] [[package]] @@ -2494,27 +2496,27 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "text-unidecode" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/87/c7/5e1547c44e31da50a460df93af11a535ace568ef89d7a811069ead340c4a/python-slugify-8.0.4.tar.gz", hash = "sha256:59202371d1d05b54a9e7720c5e038f928f45daaffe41dd10822f3907b937c856", size = 10921 } +sdist = { url = "https://files.pythonhosted.org/packages/87/c7/5e1547c44e31da50a460df93af11a535ace568ef89d7a811069ead340c4a/python-slugify-8.0.4.tar.gz", hash = "sha256:59202371d1d05b54a9e7720c5e038f928f45daaffe41dd10822f3907b937c856", size = 10921, upload-time = "2024-02-08T18:32:45.488Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a4/62/02da182e544a51a5c3ccf4b03ab79df279f9c60c5e82d5e8bec7ca26ac11/python_slugify-8.0.4-py2.py3-none-any.whl", hash = "sha256:276540b79961052b66b7d116620b36518847f52d5fd9e3a70164fc8c50faa6b8", size = 10051 }, + { url = "https://files.pythonhosted.org/packages/a4/62/02da182e544a51a5c3ccf4b03ab79df279f9c60c5e82d5e8bec7ca26ac11/python_slugify-8.0.4-py2.py3-none-any.whl", hash = "sha256:276540b79961052b66b7d116620b36518847f52d5fd9e3a70164fc8c50faa6b8", size = 10051, upload-time = "2024-02-08T18:32:43.911Z" }, ] [[package]] name = "pytimeparse" version = "1.1.8" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/37/5d/231f5f33c81e09682708fb323f9e4041408d8223e2f0fb9742843328778f/pytimeparse-1.1.8.tar.gz", hash = "sha256:e86136477be924d7e670646a98561957e8ca7308d44841e21f5ddea757556a0a", size = 9403 } +sdist = { url = "https://files.pythonhosted.org/packages/37/5d/231f5f33c81e09682708fb323f9e4041408d8223e2f0fb9742843328778f/pytimeparse-1.1.8.tar.gz", hash = "sha256:e86136477be924d7e670646a98561957e8ca7308d44841e21f5ddea757556a0a", size = 9403, upload-time = "2018-05-18T17:40:42.76Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1b/b4/afd75551a3b910abd1d922dbd45e49e5deeb4d47dc50209ce489ba9844dd/pytimeparse-1.1.8-py2.py3-none-any.whl", hash = "sha256:04b7be6cc8bd9f5647a6325444926c3ac34ee6bc7e69da4367ba282f076036bd", size = 9969 }, + { url = "https://files.pythonhosted.org/packages/1b/b4/afd75551a3b910abd1d922dbd45e49e5deeb4d47dc50209ce489ba9844dd/pytimeparse-1.1.8-py2.py3-none-any.whl", hash = "sha256:04b7be6cc8bd9f5647a6325444926c3ac34ee6bc7e69da4367ba282f076036bd", size = 9969, upload-time = "2018-05-18T17:40:41.28Z" }, ] [[package]] name = "pytz" version = "2025.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884 } +sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225 }, + { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, ] [[package]] @@ -2522,71 +2524,71 @@ name = "pywin32" version = "310" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/95/da/a5f38fffbba2fb99aa4aa905480ac4b8e83ca486659ac8c95bce47fb5276/pywin32-310-cp310-cp310-win32.whl", hash = "sha256:6dd97011efc8bf51d6793a82292419eba2c71cf8e7250cfac03bba284454abc1", size = 8848240 }, - { url = "https://files.pythonhosted.org/packages/aa/fe/d873a773324fa565619ba555a82c9dabd677301720f3660a731a5d07e49a/pywin32-310-cp310-cp310-win_amd64.whl", hash = "sha256:c3e78706e4229b915a0821941a84e7ef420bf2b77e08c9dae3c76fd03fd2ae3d", size = 9601854 }, - { url = "https://files.pythonhosted.org/packages/3c/84/1a8e3d7a15490d28a5d816efa229ecb4999cdc51a7c30dd8914f669093b8/pywin32-310-cp310-cp310-win_arm64.whl", hash = "sha256:33babed0cf0c92a6f94cc6cc13546ab24ee13e3e800e61ed87609ab91e4c8213", size = 8522963 }, - { url = "https://files.pythonhosted.org/packages/f7/b1/68aa2986129fb1011dabbe95f0136f44509afaf072b12b8f815905a39f33/pywin32-310-cp311-cp311-win32.whl", hash = "sha256:1e765f9564e83011a63321bb9d27ec456a0ed90d3732c4b2e312b855365ed8bd", size = 8784284 }, - { url = "https://files.pythonhosted.org/packages/b3/bd/d1592635992dd8db5bb8ace0551bc3a769de1ac8850200cfa517e72739fb/pywin32-310-cp311-cp311-win_amd64.whl", hash = "sha256:126298077a9d7c95c53823934f000599f66ec9296b09167810eb24875f32689c", size = 9520748 }, - { url = "https://files.pythonhosted.org/packages/90/b1/ac8b1ffce6603849eb45a91cf126c0fa5431f186c2e768bf56889c46f51c/pywin32-310-cp311-cp311-win_arm64.whl", hash = "sha256:19ec5fc9b1d51c4350be7bb00760ffce46e6c95eaf2f0b2f1150657b1a43c582", size = 8455941 }, - { url = "https://files.pythonhosted.org/packages/6b/ec/4fdbe47932f671d6e348474ea35ed94227fb5df56a7c30cbbb42cd396ed0/pywin32-310-cp312-cp312-win32.whl", hash = "sha256:8a75a5cc3893e83a108c05d82198880704c44bbaee4d06e442e471d3c9ea4f3d", size = 8796239 }, - { url = "https://files.pythonhosted.org/packages/e3/e5/b0627f8bb84e06991bea89ad8153a9e50ace40b2e1195d68e9dff6b03d0f/pywin32-310-cp312-cp312-win_amd64.whl", hash = "sha256:bf5c397c9a9a19a6f62f3fb821fbf36cac08f03770056711f765ec1503972060", size = 9503839 }, - { url = "https://files.pythonhosted.org/packages/1f/32/9ccf53748df72301a89713936645a664ec001abd35ecc8578beda593d37d/pywin32-310-cp312-cp312-win_arm64.whl", hash = "sha256:2349cc906eae872d0663d4d6290d13b90621eaf78964bb1578632ff20e152966", size = 8459470 }, - { url = "https://files.pythonhosted.org/packages/1c/09/9c1b978ffc4ae53999e89c19c77ba882d9fce476729f23ef55211ea1c034/pywin32-310-cp313-cp313-win32.whl", hash = "sha256:5d241a659c496ada3253cd01cfaa779b048e90ce4b2b38cd44168ad555ce74ab", size = 8794384 }, - { url = "https://files.pythonhosted.org/packages/45/3c/b4640f740ffebadd5d34df35fecba0e1cfef8fde9f3e594df91c28ad9b50/pywin32-310-cp313-cp313-win_amd64.whl", hash = "sha256:667827eb3a90208ddbdcc9e860c81bde63a135710e21e4cb3348968e4bd5249e", size = 9503039 }, - { url = "https://files.pythonhosted.org/packages/b4/f4/f785020090fb050e7fb6d34b780f2231f302609dc964672f72bfaeb59a28/pywin32-310-cp313-cp313-win_arm64.whl", hash = "sha256:e308f831de771482b7cf692a1f308f8fca701b2d8f9dde6cc440c7da17e47b33", size = 8458152 }, + { url = "https://files.pythonhosted.org/packages/95/da/a5f38fffbba2fb99aa4aa905480ac4b8e83ca486659ac8c95bce47fb5276/pywin32-310-cp310-cp310-win32.whl", hash = "sha256:6dd97011efc8bf51d6793a82292419eba2c71cf8e7250cfac03bba284454abc1", size = 8848240, upload-time = "2025-03-17T00:55:46.783Z" }, + { url = "https://files.pythonhosted.org/packages/aa/fe/d873a773324fa565619ba555a82c9dabd677301720f3660a731a5d07e49a/pywin32-310-cp310-cp310-win_amd64.whl", hash = "sha256:c3e78706e4229b915a0821941a84e7ef420bf2b77e08c9dae3c76fd03fd2ae3d", size = 9601854, upload-time = "2025-03-17T00:55:48.783Z" }, + { url = "https://files.pythonhosted.org/packages/3c/84/1a8e3d7a15490d28a5d816efa229ecb4999cdc51a7c30dd8914f669093b8/pywin32-310-cp310-cp310-win_arm64.whl", hash = "sha256:33babed0cf0c92a6f94cc6cc13546ab24ee13e3e800e61ed87609ab91e4c8213", size = 8522963, upload-time = "2025-03-17T00:55:50.969Z" }, + { url = "https://files.pythonhosted.org/packages/f7/b1/68aa2986129fb1011dabbe95f0136f44509afaf072b12b8f815905a39f33/pywin32-310-cp311-cp311-win32.whl", hash = "sha256:1e765f9564e83011a63321bb9d27ec456a0ed90d3732c4b2e312b855365ed8bd", size = 8784284, upload-time = "2025-03-17T00:55:53.124Z" }, + { url = "https://files.pythonhosted.org/packages/b3/bd/d1592635992dd8db5bb8ace0551bc3a769de1ac8850200cfa517e72739fb/pywin32-310-cp311-cp311-win_amd64.whl", hash = "sha256:126298077a9d7c95c53823934f000599f66ec9296b09167810eb24875f32689c", size = 9520748, upload-time = "2025-03-17T00:55:55.203Z" }, + { url = "https://files.pythonhosted.org/packages/90/b1/ac8b1ffce6603849eb45a91cf126c0fa5431f186c2e768bf56889c46f51c/pywin32-310-cp311-cp311-win_arm64.whl", hash = "sha256:19ec5fc9b1d51c4350be7bb00760ffce46e6c95eaf2f0b2f1150657b1a43c582", size = 8455941, upload-time = "2025-03-17T00:55:57.048Z" }, + { url = "https://files.pythonhosted.org/packages/6b/ec/4fdbe47932f671d6e348474ea35ed94227fb5df56a7c30cbbb42cd396ed0/pywin32-310-cp312-cp312-win32.whl", hash = "sha256:8a75a5cc3893e83a108c05d82198880704c44bbaee4d06e442e471d3c9ea4f3d", size = 8796239, upload-time = "2025-03-17T00:55:58.807Z" }, + { url = "https://files.pythonhosted.org/packages/e3/e5/b0627f8bb84e06991bea89ad8153a9e50ace40b2e1195d68e9dff6b03d0f/pywin32-310-cp312-cp312-win_amd64.whl", hash = "sha256:bf5c397c9a9a19a6f62f3fb821fbf36cac08f03770056711f765ec1503972060", size = 9503839, upload-time = "2025-03-17T00:56:00.8Z" }, + { url = "https://files.pythonhosted.org/packages/1f/32/9ccf53748df72301a89713936645a664ec001abd35ecc8578beda593d37d/pywin32-310-cp312-cp312-win_arm64.whl", hash = "sha256:2349cc906eae872d0663d4d6290d13b90621eaf78964bb1578632ff20e152966", size = 8459470, upload-time = "2025-03-17T00:56:02.601Z" }, + { url = "https://files.pythonhosted.org/packages/1c/09/9c1b978ffc4ae53999e89c19c77ba882d9fce476729f23ef55211ea1c034/pywin32-310-cp313-cp313-win32.whl", hash = "sha256:5d241a659c496ada3253cd01cfaa779b048e90ce4b2b38cd44168ad555ce74ab", size = 8794384, upload-time = "2025-03-17T00:56:04.383Z" }, + { url = "https://files.pythonhosted.org/packages/45/3c/b4640f740ffebadd5d34df35fecba0e1cfef8fde9f3e594df91c28ad9b50/pywin32-310-cp313-cp313-win_amd64.whl", hash = "sha256:667827eb3a90208ddbdcc9e860c81bde63a135710e21e4cb3348968e4bd5249e", size = 9503039, upload-time = "2025-03-17T00:56:06.207Z" }, + { url = "https://files.pythonhosted.org/packages/b4/f4/f785020090fb050e7fb6d34b780f2231f302609dc964672f72bfaeb59a28/pywin32-310-cp313-cp313-win_arm64.whl", hash = "sha256:e308f831de771482b7cf692a1f308f8fca701b2d8f9dde6cc440c7da17e47b33", size = 8458152, upload-time = "2025-03-17T00:56:07.819Z" }, ] [[package]] name = "pywin32-ctypes" version = "0.2.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/85/9f/01a1a99704853cb63f253eea009390c88e7131c67e66a0a02099a8c917cb/pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755", size = 29471 } +sdist = { url = "https://files.pythonhosted.org/packages/85/9f/01a1a99704853cb63f253eea009390c88e7131c67e66a0a02099a8c917cb/pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755", size = 29471, upload-time = "2024-08-14T10:15:34.626Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/de/3d/8161f7711c017e01ac9f008dfddd9410dff3674334c233bde66e7ba65bbf/pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8", size = 30756 }, + { url = "https://files.pythonhosted.org/packages/de/3d/8161f7711c017e01ac9f008dfddd9410dff3674334c233bde66e7ba65bbf/pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8", size = 30756, upload-time = "2024-08-14T10:15:33.187Z" }, ] [[package]] name = "pyyaml" version = "6.0.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9b/95/a3fac87cb7158e231b5a6012e438c647e1a87f09f8e0d123acec8ab8bf71/PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", size = 184199 }, - { url = "https://files.pythonhosted.org/packages/c7/7a/68bd47624dab8fd4afbfd3c48e3b79efe09098ae941de5b58abcbadff5cb/PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", size = 171758 }, - { url = "https://files.pythonhosted.org/packages/49/ee/14c54df452143b9ee9f0f29074d7ca5516a36edb0b4cc40c3f280131656f/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", size = 718463 }, - { url = "https://files.pythonhosted.org/packages/4d/61/de363a97476e766574650d742205be468921a7b532aa2499fcd886b62530/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", size = 719280 }, - { url = "https://files.pythonhosted.org/packages/6b/4e/1523cb902fd98355e2e9ea5e5eb237cbc5f3ad5f3075fa65087aa0ecb669/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", size = 751239 }, - { url = "https://files.pythonhosted.org/packages/b7/33/5504b3a9a4464893c32f118a9cc045190a91637b119a9c881da1cf6b7a72/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", size = 695802 }, - { url = "https://files.pythonhosted.org/packages/5c/20/8347dcabd41ef3a3cdc4f7b7a2aff3d06598c8779faa189cdbf878b626a4/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", size = 720527 }, - { url = "https://files.pythonhosted.org/packages/be/aa/5afe99233fb360d0ff37377145a949ae258aaab831bde4792b32650a4378/PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", size = 144052 }, - { url = "https://files.pythonhosted.org/packages/b5/84/0fa4b06f6d6c958d207620fc60005e241ecedceee58931bb20138e1e5776/PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", size = 161774 }, - { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612 }, - { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040 }, - { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829 }, - { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167 }, - { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952 }, - { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301 }, - { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638 }, - { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850 }, - { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980 }, - { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873 }, - { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302 }, - { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154 }, - { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223 }, - { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542 }, - { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164 }, - { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611 }, - { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591 }, - { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338 }, - { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309 }, - { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679 }, - { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428 }, - { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361 }, - { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523 }, - { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660 }, - { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597 }, - { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527 }, - { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 }, +sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload-time = "2024-08-06T20:33:50.674Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/95/a3fac87cb7158e231b5a6012e438c647e1a87f09f8e0d123acec8ab8bf71/PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", size = 184199, upload-time = "2024-08-06T20:31:40.178Z" }, + { url = "https://files.pythonhosted.org/packages/c7/7a/68bd47624dab8fd4afbfd3c48e3b79efe09098ae941de5b58abcbadff5cb/PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", size = 171758, upload-time = "2024-08-06T20:31:42.173Z" }, + { url = "https://files.pythonhosted.org/packages/49/ee/14c54df452143b9ee9f0f29074d7ca5516a36edb0b4cc40c3f280131656f/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", size = 718463, upload-time = "2024-08-06T20:31:44.263Z" }, + { url = "https://files.pythonhosted.org/packages/4d/61/de363a97476e766574650d742205be468921a7b532aa2499fcd886b62530/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", size = 719280, upload-time = "2024-08-06T20:31:50.199Z" }, + { url = "https://files.pythonhosted.org/packages/6b/4e/1523cb902fd98355e2e9ea5e5eb237cbc5f3ad5f3075fa65087aa0ecb669/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", size = 751239, upload-time = "2024-08-06T20:31:52.292Z" }, + { url = "https://files.pythonhosted.org/packages/b7/33/5504b3a9a4464893c32f118a9cc045190a91637b119a9c881da1cf6b7a72/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", size = 695802, upload-time = "2024-08-06T20:31:53.836Z" }, + { url = "https://files.pythonhosted.org/packages/5c/20/8347dcabd41ef3a3cdc4f7b7a2aff3d06598c8779faa189cdbf878b626a4/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", size = 720527, upload-time = "2024-08-06T20:31:55.565Z" }, + { url = "https://files.pythonhosted.org/packages/be/aa/5afe99233fb360d0ff37377145a949ae258aaab831bde4792b32650a4378/PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", size = 144052, upload-time = "2024-08-06T20:31:56.914Z" }, + { url = "https://files.pythonhosted.org/packages/b5/84/0fa4b06f6d6c958d207620fc60005e241ecedceee58931bb20138e1e5776/PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", size = 161774, upload-time = "2024-08-06T20:31:58.304Z" }, + { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612, upload-time = "2024-08-06T20:32:03.408Z" }, + { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040, upload-time = "2024-08-06T20:32:04.926Z" }, + { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829, upload-time = "2024-08-06T20:32:06.459Z" }, + { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167, upload-time = "2024-08-06T20:32:08.338Z" }, + { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952, upload-time = "2024-08-06T20:32:14.124Z" }, + { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301, upload-time = "2024-08-06T20:32:16.17Z" }, + { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638, upload-time = "2024-08-06T20:32:18.555Z" }, + { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850, upload-time = "2024-08-06T20:32:19.889Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980, upload-time = "2024-08-06T20:32:21.273Z" }, + { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873, upload-time = "2024-08-06T20:32:25.131Z" }, + { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302, upload-time = "2024-08-06T20:32:26.511Z" }, + { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154, upload-time = "2024-08-06T20:32:28.363Z" }, + { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223, upload-time = "2024-08-06T20:32:30.058Z" }, + { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542, upload-time = "2024-08-06T20:32:31.881Z" }, + { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164, upload-time = "2024-08-06T20:32:37.083Z" }, + { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611, upload-time = "2024-08-06T20:32:38.898Z" }, + { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591, upload-time = "2024-08-06T20:32:40.241Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338, upload-time = "2024-08-06T20:32:41.93Z" }, + { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309, upload-time = "2024-08-06T20:32:43.4Z" }, + { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679, upload-time = "2024-08-06T20:32:44.801Z" }, + { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428, upload-time = "2024-08-06T20:32:46.432Z" }, + { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361, upload-time = "2024-08-06T20:32:51.188Z" }, + { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523, upload-time = "2024-08-06T20:32:53.019Z" }, + { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660, upload-time = "2024-08-06T20:32:54.708Z" }, + { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597, upload-time = "2024-08-06T20:32:56.985Z" }, + { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527, upload-time = "2024-08-06T20:33:03.001Z" }, + { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload-time = "2024-08-06T20:33:04.33Z" }, ] [[package]] @@ -2598,9 +2600,9 @@ dependencies = [ { name = "nh3" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5a/a9/104ec9234c8448c4379768221ea6df01260cd6c2ce13182d4eac531c8342/readme_renderer-44.0.tar.gz", hash = "sha256:8712034eabbfa6805cacf1402b4eeb2a73028f72d1166d6f5cb7f9c047c5d1e1", size = 32056 } +sdist = { url = "https://files.pythonhosted.org/packages/5a/a9/104ec9234c8448c4379768221ea6df01260cd6c2ce13182d4eac531c8342/readme_renderer-44.0.tar.gz", hash = "sha256:8712034eabbfa6805cacf1402b4eeb2a73028f72d1166d6f5cb7f9c047c5d1e1", size = 32056, upload-time = "2024-07-08T15:00:57.805Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e1/67/921ec3024056483db83953ae8e48079ad62b92db7880013ca77632921dd0/readme_renderer-44.0-py3-none-any.whl", hash = "sha256:2fbca89b81a08526aadf1357a8c2ae889ec05fb03f5da67f9769c9a592166151", size = 13310 }, + { url = "https://files.pythonhosted.org/packages/e1/67/921ec3024056483db83953ae8e48079ad62b92db7880013ca77632921dd0/readme_renderer-44.0-py3-none-any.whl", hash = "sha256:2fbca89b81a08526aadf1357a8c2ae889ec05fb03f5da67f9769c9a592166151", size = 13310, upload-time = "2024-07-08T15:00:56.577Z" }, ] [[package]] @@ -2612,9 +2614,9 @@ dependencies = [ { name = "rpds-py" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2f/db/98b5c277be99dd18bfd91dd04e1b759cad18d1a338188c936e92f921c7e2/referencing-0.36.2.tar.gz", hash = "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa", size = 74744 } +sdist = { url = "https://files.pythonhosted.org/packages/2f/db/98b5c277be99dd18bfd91dd04e1b759cad18d1a338188c936e92f921c7e2/referencing-0.36.2.tar.gz", hash = "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa", size = 74744, upload-time = "2025-01-25T08:48:16.138Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c1/b1/3baf80dc6d2b7bc27a95a67752d0208e410351e3feb4eb78de5f77454d8d/referencing-0.36.2-py3-none-any.whl", hash = "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0", size = 26775 }, + { url = "https://files.pythonhosted.org/packages/c1/b1/3baf80dc6d2b7bc27a95a67752d0208e410351e3feb4eb78de5f77454d8d/referencing-0.36.2-py3-none-any.whl", hash = "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0", size = 26775, upload-time = "2025-01-25T08:48:14.241Z" }, ] [[package]] @@ -2627,9 +2629,9 @@ dependencies = [ { name = "idna" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e1/0a/929373653770d8a0d7ea76c37de6e41f11eb07559b103b1c02cafb3f7cf8/requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422", size = 135258 } +sdist = { url = "https://files.pythonhosted.org/packages/e1/0a/929373653770d8a0d7ea76c37de6e41f11eb07559b103b1c02cafb3f7cf8/requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422", size = 135258, upload-time = "2025-06-09T16:43:07.34Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7c/e4/56027c4a6b4ae70ca9de302488c5ca95ad4a39e190093d6c1a8ace08341b/requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", size = 64847 }, + { url = "https://files.pythonhosted.org/packages/7c/e4/56027c4a6b4ae70ca9de302488c5ca95ad4a39e190093d6c1a8ace08341b/requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", size = 64847, upload-time = "2025-06-09T16:43:05.728Z" }, ] [[package]] @@ -2639,18 +2641,18 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f3/61/d7545dafb7ac2230c70d38d31cbfe4cc64f7144dc41f6e4e4b78ecd9f5bb/requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6", size = 206888 } +sdist = { url = "https://files.pythonhosted.org/packages/f3/61/d7545dafb7ac2230c70d38d31cbfe4cc64f7144dc41f6e4e4b78ecd9f5bb/requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6", size = 206888, upload-time = "2023-05-01T04:11:33.229Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06", size = 54481 }, + { url = "https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06", size = 54481, upload-time = "2023-05-01T04:11:28.427Z" }, ] [[package]] name = "rfc3986" version = "2.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/85/40/1520d68bfa07ab5a6f065a186815fb6610c86fe957bc065754e47f7b0840/rfc3986-2.0.0.tar.gz", hash = "sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c", size = 49026 } +sdist = { url = "https://files.pythonhosted.org/packages/85/40/1520d68bfa07ab5a6f065a186815fb6610c86fe957bc065754e47f7b0840/rfc3986-2.0.0.tar.gz", hash = "sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c", size = 49026, upload-time = "2022-01-10T00:52:30.832Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ff/9a/9afaade874b2fa6c752c36f1548f718b5b83af81ed9b76628329dab81c1b/rfc3986-2.0.0-py2.py3-none-any.whl", hash = "sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd", size = 31326 }, + { url = "https://files.pythonhosted.org/packages/ff/9a/9afaade874b2fa6c752c36f1548f718b5b83af81ed9b76628329dab81c1b/rfc3986-2.0.0-py2.py3-none-any.whl", hash = "sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd", size = 31326, upload-time = "2022-01-10T00:52:29.594Z" }, ] [[package]] @@ -2662,160 +2664,160 @@ dependencies = [ { name = "pygments" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a1/53/830aa4c3066a8ab0ae9a9955976fb770fe9c6102117c8ec4ab3ea62d89e8/rich-14.0.0.tar.gz", hash = "sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725", size = 224078 } +sdist = { url = "https://files.pythonhosted.org/packages/a1/53/830aa4c3066a8ab0ae9a9955976fb770fe9c6102117c8ec4ab3ea62d89e8/rich-14.0.0.tar.gz", hash = "sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725", size = 224078, upload-time = "2025-03-30T14:15:14.23Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0d/9b/63f4c7ebc259242c89b3acafdb37b41d1185c07ff0011164674e9076b491/rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0", size = 243229 }, + { url = "https://files.pythonhosted.org/packages/0d/9b/63f4c7ebc259242c89b3acafdb37b41d1185c07ff0011164674e9076b491/rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0", size = 243229, upload-time = "2025-03-30T14:15:12.283Z" }, ] [[package]] name = "rpds-py" version = "0.26.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a5/aa/4456d84bbb54adc6a916fb10c9b374f78ac840337644e4a5eda229c81275/rpds_py-0.26.0.tar.gz", hash = "sha256:20dae58a859b0906f0685642e591056f1e787f3a8b39c8e8749a45dc7d26bdb0", size = 27385 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b9/31/1459645f036c3dfeacef89e8e5825e430c77dde8489f3b99eaafcd4a60f5/rpds_py-0.26.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:4c70c70f9169692b36307a95f3d8c0a9fcd79f7b4a383aad5eaa0e9718b79b37", size = 372466 }, - { url = "https://files.pythonhosted.org/packages/dd/ff/3d0727f35836cc8773d3eeb9a46c40cc405854e36a8d2e951f3a8391c976/rpds_py-0.26.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:777c62479d12395bfb932944e61e915741e364c843afc3196b694db3d669fcd0", size = 357825 }, - { url = "https://files.pythonhosted.org/packages/bf/ce/badc5e06120a54099ae287fa96d82cbb650a5f85cf247ffe19c7b157fd1f/rpds_py-0.26.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec671691e72dff75817386aa02d81e708b5a7ec0dec6669ec05213ff6b77e1bd", size = 381530 }, - { url = "https://files.pythonhosted.org/packages/1e/a5/fa5d96a66c95d06c62d7a30707b6a4cfec696ab8ae280ee7be14e961e118/rpds_py-0.26.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6a1cb5d6ce81379401bbb7f6dbe3d56de537fb8235979843f0d53bc2e9815a79", size = 396933 }, - { url = "https://files.pythonhosted.org/packages/00/a7/7049d66750f18605c591a9db47d4a059e112a0c9ff8de8daf8fa0f446bba/rpds_py-0.26.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4f789e32fa1fb6a7bf890e0124e7b42d1e60d28ebff57fe806719abb75f0e9a3", size = 513973 }, - { url = "https://files.pythonhosted.org/packages/0e/f1/528d02c7d6b29d29fac8fd784b354d3571cc2153f33f842599ef0cf20dd2/rpds_py-0.26.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c55b0a669976cf258afd718de3d9ad1b7d1fe0a91cd1ab36f38b03d4d4aeaaf", size = 402293 }, - { url = "https://files.pythonhosted.org/packages/15/93/fde36cd6e4685df2cd08508f6c45a841e82f5bb98c8d5ecf05649522acb5/rpds_py-0.26.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c70d9ec912802ecfd6cd390dadb34a9578b04f9bcb8e863d0a7598ba5e9e7ccc", size = 383787 }, - { url = "https://files.pythonhosted.org/packages/69/f2/5007553aaba1dcae5d663143683c3dfd03d9395289f495f0aebc93e90f24/rpds_py-0.26.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3021933c2cb7def39d927b9862292e0f4c75a13d7de70eb0ab06efed4c508c19", size = 416312 }, - { url = "https://files.pythonhosted.org/packages/8f/a7/ce52c75c1e624a79e48a69e611f1c08844564e44c85db2b6f711d76d10ce/rpds_py-0.26.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8a7898b6ca3b7d6659e55cdac825a2e58c638cbf335cde41f4619e290dd0ad11", size = 558403 }, - { url = "https://files.pythonhosted.org/packages/79/d5/e119db99341cc75b538bf4cb80504129fa22ce216672fb2c28e4a101f4d9/rpds_py-0.26.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:12bff2ad9447188377f1b2794772f91fe68bb4bbfa5a39d7941fbebdbf8c500f", size = 588323 }, - { url = "https://files.pythonhosted.org/packages/93/94/d28272a0b02f5fe24c78c20e13bbcb95f03dc1451b68e7830ca040c60bd6/rpds_py-0.26.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:191aa858f7d4902e975d4cf2f2d9243816c91e9605070aeb09c0a800d187e323", size = 554541 }, - { url = "https://files.pythonhosted.org/packages/93/e0/8c41166602f1b791da892d976057eba30685486d2e2c061ce234679c922b/rpds_py-0.26.0-cp310-cp310-win32.whl", hash = "sha256:b37a04d9f52cb76b6b78f35109b513f6519efb481d8ca4c321f6a3b9580b3f45", size = 220442 }, - { url = "https://files.pythonhosted.org/packages/87/f0/509736bb752a7ab50fb0270c2a4134d671a7b3038030837e5536c3de0e0b/rpds_py-0.26.0-cp310-cp310-win_amd64.whl", hash = "sha256:38721d4c9edd3eb6670437d8d5e2070063f305bfa2d5aa4278c51cedcd508a84", size = 231314 }, - { url = "https://files.pythonhosted.org/packages/09/4c/4ee8f7e512030ff79fda1df3243c88d70fc874634e2dbe5df13ba4210078/rpds_py-0.26.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:9e8cb77286025bdb21be2941d64ac6ca016130bfdcd228739e8ab137eb4406ed", size = 372610 }, - { url = "https://files.pythonhosted.org/packages/fa/9d/3dc16be00f14fc1f03c71b1d67c8df98263ab2710a2fbd65a6193214a527/rpds_py-0.26.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5e09330b21d98adc8ccb2dbb9fc6cb434e8908d4c119aeaa772cb1caab5440a0", size = 358032 }, - { url = "https://files.pythonhosted.org/packages/e7/5a/7f1bf8f045da2866324a08ae80af63e64e7bfaf83bd31f865a7b91a58601/rpds_py-0.26.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c9c1b92b774b2e68d11193dc39620d62fd8ab33f0a3c77ecdabe19c179cdbc1", size = 381525 }, - { url = "https://files.pythonhosted.org/packages/45/8a/04479398c755a066ace10e3d158866beb600867cacae194c50ffa783abd0/rpds_py-0.26.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:824e6d3503ab990d7090768e4dfd9e840837bae057f212ff9f4f05ec6d1975e7", size = 397089 }, - { url = "https://files.pythonhosted.org/packages/72/88/9203f47268db488a1b6d469d69c12201ede776bb728b9d9f29dbfd7df406/rpds_py-0.26.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8ad7fd2258228bf288f2331f0a6148ad0186b2e3643055ed0db30990e59817a6", size = 514255 }, - { url = "https://files.pythonhosted.org/packages/f5/b4/01ce5d1e853ddf81fbbd4311ab1eff0b3cf162d559288d10fd127e2588b5/rpds_py-0.26.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0dc23bbb3e06ec1ea72d515fb572c1fea59695aefbffb106501138762e1e915e", size = 402283 }, - { url = "https://files.pythonhosted.org/packages/34/a2/004c99936997bfc644d590a9defd9e9c93f8286568f9c16cdaf3e14429a7/rpds_py-0.26.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d80bf832ac7b1920ee29a426cdca335f96a2b5caa839811803e999b41ba9030d", size = 383881 }, - { url = "https://files.pythonhosted.org/packages/05/1b/ef5fba4a8f81ce04c427bfd96223f92f05e6cd72291ce9d7523db3b03a6c/rpds_py-0.26.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0919f38f5542c0a87e7b4afcafab6fd2c15386632d249e9a087498571250abe3", size = 415822 }, - { url = "https://files.pythonhosted.org/packages/16/80/5c54195aec456b292f7bd8aa61741c8232964063fd8a75fdde9c1e982328/rpds_py-0.26.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d422b945683e409000c888e384546dbab9009bb92f7c0b456e217988cf316107", size = 558347 }, - { url = "https://files.pythonhosted.org/packages/f2/1c/1845c1b1fd6d827187c43afe1841d91678d7241cbdb5420a4c6de180a538/rpds_py-0.26.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:77a7711fa562ba2da1aa757e11024ad6d93bad6ad7ede5afb9af144623e5f76a", size = 587956 }, - { url = "https://files.pythonhosted.org/packages/2e/ff/9e979329dd131aa73a438c077252ddabd7df6d1a7ad7b9aacf6261f10faa/rpds_py-0.26.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:238e8c8610cb7c29460e37184f6799547f7e09e6a9bdbdab4e8edb90986a2318", size = 554363 }, - { url = "https://files.pythonhosted.org/packages/00/8b/d78cfe034b71ffbe72873a136e71acc7a831a03e37771cfe59f33f6de8a2/rpds_py-0.26.0-cp311-cp311-win32.whl", hash = "sha256:893b022bfbdf26d7bedb083efeea624e8550ca6eb98bf7fea30211ce95b9201a", size = 220123 }, - { url = "https://files.pythonhosted.org/packages/94/c1/3c8c94c7dd3905dbfde768381ce98778500a80db9924731d87ddcdb117e9/rpds_py-0.26.0-cp311-cp311-win_amd64.whl", hash = "sha256:87a5531de9f71aceb8af041d72fc4cab4943648d91875ed56d2e629bef6d4c03", size = 231732 }, - { url = "https://files.pythonhosted.org/packages/67/93/e936fbed1b734eabf36ccb5d93c6a2e9246fbb13c1da011624b7286fae3e/rpds_py-0.26.0-cp311-cp311-win_arm64.whl", hash = "sha256:de2713f48c1ad57f89ac25b3cb7daed2156d8e822cf0eca9b96a6f990718cc41", size = 221917 }, - { url = "https://files.pythonhosted.org/packages/ea/86/90eb87c6f87085868bd077c7a9938006eb1ce19ed4d06944a90d3560fce2/rpds_py-0.26.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:894514d47e012e794f1350f076c427d2347ebf82f9b958d554d12819849a369d", size = 363933 }, - { url = "https://files.pythonhosted.org/packages/63/78/4469f24d34636242c924626082b9586f064ada0b5dbb1e9d096ee7a8e0c6/rpds_py-0.26.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc921b96fa95a097add244da36a1d9e4f3039160d1d30f1b35837bf108c21136", size = 350447 }, - { url = "https://files.pythonhosted.org/packages/ad/91/c448ed45efdfdade82348d5e7995e15612754826ea640afc20915119734f/rpds_py-0.26.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e1157659470aa42a75448b6e943c895be8c70531c43cb78b9ba990778955582", size = 384711 }, - { url = "https://files.pythonhosted.org/packages/ec/43/e5c86fef4be7f49828bdd4ecc8931f0287b1152c0bb0163049b3218740e7/rpds_py-0.26.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:521ccf56f45bb3a791182dc6b88ae5f8fa079dd705ee42138c76deb1238e554e", size = 400865 }, - { url = "https://files.pythonhosted.org/packages/55/34/e00f726a4d44f22d5c5fe2e5ddd3ac3d7fd3f74a175607781fbdd06fe375/rpds_py-0.26.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9def736773fd56b305c0eef698be5192c77bfa30d55a0e5885f80126c4831a15", size = 517763 }, - { url = "https://files.pythonhosted.org/packages/52/1c/52dc20c31b147af724b16104500fba13e60123ea0334beba7b40e33354b4/rpds_py-0.26.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cdad4ea3b4513b475e027be79e5a0ceac8ee1c113a1a11e5edc3c30c29f964d8", size = 406651 }, - { url = "https://files.pythonhosted.org/packages/2e/77/87d7bfabfc4e821caa35481a2ff6ae0b73e6a391bb6b343db2c91c2b9844/rpds_py-0.26.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82b165b07f416bdccf5c84546a484cc8f15137ca38325403864bfdf2b5b72f6a", size = 386079 }, - { url = "https://files.pythonhosted.org/packages/e3/d4/7f2200c2d3ee145b65b3cddc4310d51f7da6a26634f3ac87125fd789152a/rpds_py-0.26.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d04cab0a54b9dba4d278fe955a1390da3cf71f57feb78ddc7cb67cbe0bd30323", size = 421379 }, - { url = "https://files.pythonhosted.org/packages/ae/13/9fdd428b9c820869924ab62236b8688b122baa22d23efdd1c566938a39ba/rpds_py-0.26.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:79061ba1a11b6a12743a2b0f72a46aa2758613d454aa6ba4f5a265cc48850158", size = 562033 }, - { url = "https://files.pythonhosted.org/packages/f3/e1/b69686c3bcbe775abac3a4c1c30a164a2076d28df7926041f6c0eb5e8d28/rpds_py-0.26.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:f405c93675d8d4c5ac87364bb38d06c988e11028a64b52a47158a355079661f3", size = 591639 }, - { url = "https://files.pythonhosted.org/packages/5c/c9/1e3d8c8863c84a90197ac577bbc3d796a92502124c27092413426f670990/rpds_py-0.26.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dafd4c44b74aa4bed4b250f1aed165b8ef5de743bcca3b88fc9619b6087093d2", size = 557105 }, - { url = "https://files.pythonhosted.org/packages/9f/c5/90c569649057622959f6dcc40f7b516539608a414dfd54b8d77e3b201ac0/rpds_py-0.26.0-cp312-cp312-win32.whl", hash = "sha256:3da5852aad63fa0c6f836f3359647870e21ea96cf433eb393ffa45263a170d44", size = 223272 }, - { url = "https://files.pythonhosted.org/packages/7d/16/19f5d9f2a556cfed454eebe4d354c38d51c20f3db69e7b4ce6cff904905d/rpds_py-0.26.0-cp312-cp312-win_amd64.whl", hash = "sha256:cf47cfdabc2194a669dcf7a8dbba62e37a04c5041d2125fae0233b720da6f05c", size = 234995 }, - { url = "https://files.pythonhosted.org/packages/83/f0/7935e40b529c0e752dfaa7880224771b51175fce08b41ab4a92eb2fbdc7f/rpds_py-0.26.0-cp312-cp312-win_arm64.whl", hash = "sha256:20ab1ae4fa534f73647aad289003f1104092890849e0266271351922ed5574f8", size = 223198 }, - { url = "https://files.pythonhosted.org/packages/6a/67/bb62d0109493b12b1c6ab00de7a5566aa84c0e44217c2d94bee1bd370da9/rpds_py-0.26.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:696764a5be111b036256c0b18cd29783fab22154690fc698062fc1b0084b511d", size = 363917 }, - { url = "https://files.pythonhosted.org/packages/4b/f3/34e6ae1925a5706c0f002a8d2d7f172373b855768149796af87bd65dcdb9/rpds_py-0.26.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1e6c15d2080a63aaed876e228efe4f814bc7889c63b1e112ad46fdc8b368b9e1", size = 350073 }, - { url = "https://files.pythonhosted.org/packages/75/83/1953a9d4f4e4de7fd0533733e041c28135f3c21485faaef56a8aadbd96b5/rpds_py-0.26.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:390e3170babf42462739a93321e657444f0862c6d722a291accc46f9d21ed04e", size = 384214 }, - { url = "https://files.pythonhosted.org/packages/48/0e/983ed1b792b3322ea1d065e67f4b230f3b96025f5ce3878cc40af09b7533/rpds_py-0.26.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7da84c2c74c0f5bc97d853d9e17bb83e2dcafcff0dc48286916001cc114379a1", size = 400113 }, - { url = "https://files.pythonhosted.org/packages/69/7f/36c0925fff6f660a80be259c5b4f5e53a16851f946eb080351d057698528/rpds_py-0.26.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c5fe114a6dd480a510b6d3661d09d67d1622c4bf20660a474507aaee7eeeee9", size = 515189 }, - { url = "https://files.pythonhosted.org/packages/13/45/cbf07fc03ba7a9b54662c9badb58294ecfb24f828b9732970bd1a431ed5c/rpds_py-0.26.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3100b3090269f3a7ea727b06a6080d4eb7439dca4c0e91a07c5d133bb1727ea7", size = 406998 }, - { url = "https://files.pythonhosted.org/packages/6c/b0/8fa5e36e58657997873fd6a1cf621285ca822ca75b4b3434ead047daa307/rpds_py-0.26.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c03c9b0c64afd0320ae57de4c982801271c0c211aa2d37f3003ff5feb75bb04", size = 385903 }, - { url = "https://files.pythonhosted.org/packages/4b/f7/b25437772f9f57d7a9fbd73ed86d0dcd76b4c7c6998348c070d90f23e315/rpds_py-0.26.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5963b72ccd199ade6ee493723d18a3f21ba7d5b957017607f815788cef50eaf1", size = 419785 }, - { url = "https://files.pythonhosted.org/packages/a7/6b/63ffa55743dfcb4baf2e9e77a0b11f7f97ed96a54558fcb5717a4b2cd732/rpds_py-0.26.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9da4e873860ad5bab3291438525cae80169daecbfafe5657f7f5fb4d6b3f96b9", size = 561329 }, - { url = "https://files.pythonhosted.org/packages/2f/07/1f4f5e2886c480a2346b1e6759c00278b8a69e697ae952d82ae2e6ee5db0/rpds_py-0.26.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:5afaddaa8e8c7f1f7b4c5c725c0070b6eed0228f705b90a1732a48e84350f4e9", size = 590875 }, - { url = "https://files.pythonhosted.org/packages/cc/bc/e6639f1b91c3a55f8c41b47d73e6307051b6e246254a827ede730624c0f8/rpds_py-0.26.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4916dc96489616a6f9667e7526af8fa693c0fdb4f3acb0e5d9f4400eb06a47ba", size = 556636 }, - { url = "https://files.pythonhosted.org/packages/05/4c/b3917c45566f9f9a209d38d9b54a1833f2bb1032a3e04c66f75726f28876/rpds_py-0.26.0-cp313-cp313-win32.whl", hash = "sha256:2a343f91b17097c546b93f7999976fd6c9d5900617aa848c81d794e062ab302b", size = 222663 }, - { url = "https://files.pythonhosted.org/packages/e0/0b/0851bdd6025775aaa2365bb8de0697ee2558184c800bfef8d7aef5ccde58/rpds_py-0.26.0-cp313-cp313-win_amd64.whl", hash = "sha256:0a0b60701f2300c81b2ac88a5fb893ccfa408e1c4a555a77f908a2596eb875a5", size = 234428 }, - { url = "https://files.pythonhosted.org/packages/ed/e8/a47c64ed53149c75fb581e14a237b7b7cd18217e969c30d474d335105622/rpds_py-0.26.0-cp313-cp313-win_arm64.whl", hash = "sha256:257d011919f133a4746958257f2c75238e3ff54255acd5e3e11f3ff41fd14256", size = 222571 }, - { url = "https://files.pythonhosted.org/packages/89/bf/3d970ba2e2bcd17d2912cb42874107390f72873e38e79267224110de5e61/rpds_py-0.26.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:529c8156d7506fba5740e05da8795688f87119cce330c244519cf706a4a3d618", size = 360475 }, - { url = "https://files.pythonhosted.org/packages/82/9f/283e7e2979fc4ec2d8ecee506d5a3675fce5ed9b4b7cb387ea5d37c2f18d/rpds_py-0.26.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f53ec51f9d24e9638a40cabb95078ade8c99251945dad8d57bf4aabe86ecee35", size = 346692 }, - { url = "https://files.pythonhosted.org/packages/e3/03/7e50423c04d78daf391da3cc4330bdb97042fc192a58b186f2d5deb7befd/rpds_py-0.26.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab504c4d654e4a29558eaa5bb8cea5fdc1703ea60a8099ffd9c758472cf913f", size = 379415 }, - { url = "https://files.pythonhosted.org/packages/57/00/d11ee60d4d3b16808432417951c63df803afb0e0fc672b5e8d07e9edaaae/rpds_py-0.26.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fd0641abca296bc1a00183fe44f7fced8807ed49d501f188faa642d0e4975b83", size = 391783 }, - { url = "https://files.pythonhosted.org/packages/08/b3/1069c394d9c0d6d23c5b522e1f6546b65793a22950f6e0210adcc6f97c3e/rpds_py-0.26.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:69b312fecc1d017b5327afa81d4da1480f51c68810963a7336d92203dbb3d4f1", size = 512844 }, - { url = "https://files.pythonhosted.org/packages/08/3b/c4fbf0926800ed70b2c245ceca99c49f066456755f5d6eb8863c2c51e6d0/rpds_py-0.26.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c741107203954f6fc34d3066d213d0a0c40f7bb5aafd698fb39888af277c70d8", size = 402105 }, - { url = "https://files.pythonhosted.org/packages/1c/b0/db69b52ca07413e568dae9dc674627a22297abb144c4d6022c6d78f1e5cc/rpds_py-0.26.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc3e55a7db08dc9a6ed5fb7103019d2c1a38a349ac41901f9f66d7f95750942f", size = 383440 }, - { url = "https://files.pythonhosted.org/packages/4c/e1/c65255ad5b63903e56b3bb3ff9dcc3f4f5c3badde5d08c741ee03903e951/rpds_py-0.26.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9e851920caab2dbcae311fd28f4313c6953993893eb5c1bb367ec69d9a39e7ed", size = 412759 }, - { url = "https://files.pythonhosted.org/packages/e4/22/bb731077872377a93c6e93b8a9487d0406c70208985831034ccdeed39c8e/rpds_py-0.26.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:dfbf280da5f876d0b00c81f26bedce274e72a678c28845453885a9b3c22ae632", size = 556032 }, - { url = "https://files.pythonhosted.org/packages/e0/8b/393322ce7bac5c4530fb96fc79cc9ea2f83e968ff5f6e873f905c493e1c4/rpds_py-0.26.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:1cc81d14ddfa53d7f3906694d35d54d9d3f850ef8e4e99ee68bc0d1e5fed9a9c", size = 585416 }, - { url = "https://files.pythonhosted.org/packages/49/ae/769dc372211835bf759319a7aae70525c6eb523e3371842c65b7ef41c9c6/rpds_py-0.26.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dca83c498b4650a91efcf7b88d669b170256bf8017a5db6f3e06c2bf031f57e0", size = 554049 }, - { url = "https://files.pythonhosted.org/packages/6b/f9/4c43f9cc203d6ba44ce3146246cdc38619d92c7bd7bad4946a3491bd5b70/rpds_py-0.26.0-cp313-cp313t-win32.whl", hash = "sha256:4d11382bcaf12f80b51d790dee295c56a159633a8e81e6323b16e55d81ae37e9", size = 218428 }, - { url = "https://files.pythonhosted.org/packages/7e/8b/9286b7e822036a4a977f2f1e851c7345c20528dbd56b687bb67ed68a8ede/rpds_py-0.26.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ff110acded3c22c033e637dd8896e411c7d3a11289b2edf041f86663dbc791e9", size = 231524 }, - { url = "https://files.pythonhosted.org/packages/55/07/029b7c45db910c74e182de626dfdae0ad489a949d84a468465cd0ca36355/rpds_py-0.26.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:da619979df60a940cd434084355c514c25cf8eb4cf9a508510682f6c851a4f7a", size = 364292 }, - { url = "https://files.pythonhosted.org/packages/13/d1/9b3d3f986216b4d1f584878dca15ce4797aaf5d372d738974ba737bf68d6/rpds_py-0.26.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ea89a2458a1a75f87caabefe789c87539ea4e43b40f18cff526052e35bbb4fdf", size = 350334 }, - { url = "https://files.pythonhosted.org/packages/18/98/16d5e7bc9ec715fa9668731d0cf97f6b032724e61696e2db3d47aeb89214/rpds_py-0.26.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:feac1045b3327a45944e7dcbeb57530339f6b17baff154df51ef8b0da34c8c12", size = 384875 }, - { url = "https://files.pythonhosted.org/packages/f9/13/aa5e2b1ec5ab0e86a5c464d53514c0467bec6ba2507027d35fc81818358e/rpds_py-0.26.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b818a592bd69bfe437ee8368603d4a2d928c34cffcdf77c2e761a759ffd17d20", size = 399993 }, - { url = "https://files.pythonhosted.org/packages/17/03/8021810b0e97923abdbab6474c8b77c69bcb4b2c58330777df9ff69dc559/rpds_py-0.26.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a8b0dd8648709b62d9372fc00a57466f5fdeefed666afe3fea5a6c9539a0331", size = 516683 }, - { url = "https://files.pythonhosted.org/packages/dc/b1/da8e61c87c2f3d836954239fdbbfb477bb7b54d74974d8f6fcb34342d166/rpds_py-0.26.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6d3498ad0df07d81112aa6ec6c95a7e7b1ae00929fb73e7ebee0f3faaeabad2f", size = 408825 }, - { url = "https://files.pythonhosted.org/packages/38/bc/1fc173edaaa0e52c94b02a655db20697cb5fa954ad5a8e15a2c784c5cbdd/rpds_py-0.26.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24a4146ccb15be237fdef10f331c568e1b0e505f8c8c9ed5d67759dac58ac246", size = 387292 }, - { url = "https://files.pythonhosted.org/packages/7c/eb/3a9bb4bd90867d21916f253caf4f0d0be7098671b6715ad1cead9fe7bab9/rpds_py-0.26.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a9a63785467b2d73635957d32a4f6e73d5e4df497a16a6392fa066b753e87387", size = 420435 }, - { url = "https://files.pythonhosted.org/packages/cd/16/e066dcdb56f5632713445271a3f8d3d0b426d51ae9c0cca387799df58b02/rpds_py-0.26.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:de4ed93a8c91debfd5a047be327b7cc8b0cc6afe32a716bbbc4aedca9e2a83af", size = 562410 }, - { url = "https://files.pythonhosted.org/packages/60/22/ddbdec7eb82a0dc2e455be44c97c71c232983e21349836ce9f272e8a3c29/rpds_py-0.26.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:caf51943715b12af827696ec395bfa68f090a4c1a1d2509eb4e2cb69abbbdb33", size = 590724 }, - { url = "https://files.pythonhosted.org/packages/2c/b4/95744085e65b7187d83f2fcb0bef70716a1ea0a9e5d8f7f39a86e5d83424/rpds_py-0.26.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4a59e5bc386de021f56337f757301b337d7ab58baa40174fb150accd480bc953", size = 558285 }, - { url = "https://files.pythonhosted.org/packages/37/37/6309a75e464d1da2559446f9c811aa4d16343cebe3dbb73701e63f760caa/rpds_py-0.26.0-cp314-cp314-win32.whl", hash = "sha256:92c8db839367ef16a662478f0a2fe13e15f2227da3c1430a782ad0f6ee009ec9", size = 223459 }, - { url = "https://files.pythonhosted.org/packages/d9/6f/8e9c11214c46098b1d1391b7e02b70bb689ab963db3b19540cba17315291/rpds_py-0.26.0-cp314-cp314-win_amd64.whl", hash = "sha256:b0afb8cdd034150d4d9f53926226ed27ad15b7f465e93d7468caaf5eafae0d37", size = 236083 }, - { url = "https://files.pythonhosted.org/packages/47/af/9c4638994dd623d51c39892edd9d08e8be8220a4b7e874fa02c2d6e91955/rpds_py-0.26.0-cp314-cp314-win_arm64.whl", hash = "sha256:ca3f059f4ba485d90c8dc75cb5ca897e15325e4e609812ce57f896607c1c0867", size = 223291 }, - { url = "https://files.pythonhosted.org/packages/4d/db/669a241144460474aab03e254326b32c42def83eb23458a10d163cb9b5ce/rpds_py-0.26.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:5afea17ab3a126006dc2f293b14ffc7ef3c85336cf451564a0515ed7648033da", size = 361445 }, - { url = "https://files.pythonhosted.org/packages/3b/2d/133f61cc5807c6c2fd086a46df0eb8f63a23f5df8306ff9f6d0fd168fecc/rpds_py-0.26.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:69f0c0a3df7fd3a7eec50a00396104bb9a843ea6d45fcc31c2d5243446ffd7a7", size = 347206 }, - { url = "https://files.pythonhosted.org/packages/05/bf/0e8fb4c05f70273469eecf82f6ccf37248558526a45321644826555db31b/rpds_py-0.26.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:801a71f70f9813e82d2513c9a96532551fce1e278ec0c64610992c49c04c2dad", size = 380330 }, - { url = "https://files.pythonhosted.org/packages/d4/a8/060d24185d8b24d3923322f8d0ede16df4ade226a74e747b8c7c978e3dd3/rpds_py-0.26.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:df52098cde6d5e02fa75c1f6244f07971773adb4a26625edd5c18fee906fa84d", size = 392254 }, - { url = "https://files.pythonhosted.org/packages/b9/7b/7c2e8a9ee3e6bc0bae26bf29f5219955ca2fbb761dca996a83f5d2f773fe/rpds_py-0.26.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9bc596b30f86dc6f0929499c9e574601679d0341a0108c25b9b358a042f51bca", size = 516094 }, - { url = "https://files.pythonhosted.org/packages/75/d6/f61cafbed8ba1499b9af9f1777a2a199cd888f74a96133d8833ce5eaa9c5/rpds_py-0.26.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9dfbe56b299cf5875b68eb6f0ebaadc9cac520a1989cac0db0765abfb3709c19", size = 402889 }, - { url = "https://files.pythonhosted.org/packages/92/19/c8ac0a8a8df2dd30cdec27f69298a5c13e9029500d6d76718130f5e5be10/rpds_py-0.26.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac64f4b2bdb4ea622175c9ab7cf09444e412e22c0e02e906978b3b488af5fde8", size = 384301 }, - { url = "https://files.pythonhosted.org/packages/41/e1/6b1859898bc292a9ce5776016c7312b672da00e25cec74d7beced1027286/rpds_py-0.26.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:181ef9b6bbf9845a264f9aa45c31836e9f3c1f13be565d0d010e964c661d1e2b", size = 412891 }, - { url = "https://files.pythonhosted.org/packages/ef/b9/ceb39af29913c07966a61367b3c08b4f71fad841e32c6b59a129d5974698/rpds_py-0.26.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:49028aa684c144ea502a8e847d23aed5e4c2ef7cadfa7d5eaafcb40864844b7a", size = 557044 }, - { url = "https://files.pythonhosted.org/packages/2f/27/35637b98380731a521f8ec4f3fd94e477964f04f6b2f8f7af8a2d889a4af/rpds_py-0.26.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:e5d524d68a474a9688336045bbf76cb0def88549c1b2ad9dbfec1fb7cfbe9170", size = 585774 }, - { url = "https://files.pythonhosted.org/packages/52/d9/3f0f105420fecd18551b678c9a6ce60bd23986098b252a56d35781b3e7e9/rpds_py-0.26.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c1851f429b822831bd2edcbe0cfd12ee9ea77868f8d3daf267b189371671c80e", size = 554886 }, - { url = "https://files.pythonhosted.org/packages/6b/c5/347c056a90dc8dd9bc240a08c527315008e1b5042e7a4cf4ac027be9d38a/rpds_py-0.26.0-cp314-cp314t-win32.whl", hash = "sha256:7bdb17009696214c3b66bb3590c6d62e14ac5935e53e929bcdbc5a495987a84f", size = 219027 }, - { url = "https://files.pythonhosted.org/packages/75/04/5302cea1aa26d886d34cadbf2dc77d90d7737e576c0065f357b96dc7a1a6/rpds_py-0.26.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f14440b9573a6f76b4ee4770c13f0b5921f71dde3b6fcb8dabbefd13b7fe05d7", size = 232821 }, - { url = "https://files.pythonhosted.org/packages/ef/9a/1f033b0b31253d03d785b0cd905bc127e555ab496ea6b4c7c2e1f951f2fd/rpds_py-0.26.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3c0909c5234543ada2515c05dc08595b08d621ba919629e94427e8e03539c958", size = 373226 }, - { url = "https://files.pythonhosted.org/packages/58/29/5f88023fd6aaaa8ca3c4a6357ebb23f6f07da6079093ccf27c99efce87db/rpds_py-0.26.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:c1fb0cda2abcc0ac62f64e2ea4b4e64c57dfd6b885e693095460c61bde7bb18e", size = 359230 }, - { url = "https://files.pythonhosted.org/packages/6c/6c/13eaebd28b439da6964dde22712b52e53fe2824af0223b8e403249d10405/rpds_py-0.26.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84d142d2d6cf9b31c12aa4878d82ed3b2324226270b89b676ac62ccd7df52d08", size = 382363 }, - { url = "https://files.pythonhosted.org/packages/55/fc/3bb9c486b06da19448646f96147796de23c5811ef77cbfc26f17307b6a9d/rpds_py-0.26.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a547e21c5610b7e9093d870be50682a6a6cf180d6da0f42c47c306073bfdbbf6", size = 397146 }, - { url = "https://files.pythonhosted.org/packages/15/18/9d1b79eb4d18e64ba8bba9e7dec6f9d6920b639f22f07ee9368ca35d4673/rpds_py-0.26.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:35e9a70a0f335371275cdcd08bc5b8051ac494dd58bff3bbfb421038220dc871", size = 514804 }, - { url = "https://files.pythonhosted.org/packages/4f/5a/175ad7191bdbcd28785204621b225ad70e85cdfd1e09cc414cb554633b21/rpds_py-0.26.0-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0dfa6115c6def37905344d56fb54c03afc49104e2ca473d5dedec0f6606913b4", size = 402820 }, - { url = "https://files.pythonhosted.org/packages/11/45/6a67ecf6d61c4d4aff4bc056e864eec4b2447787e11d1c2c9a0242c6e92a/rpds_py-0.26.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:313cfcd6af1a55a286a3c9a25f64af6d0e46cf60bc5798f1db152d97a216ff6f", size = 384567 }, - { url = "https://files.pythonhosted.org/packages/a1/ba/16589da828732b46454c61858950a78fe4c931ea4bf95f17432ffe64b241/rpds_py-0.26.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f7bf2496fa563c046d05e4d232d7b7fd61346e2402052064b773e5c378bf6f73", size = 416520 }, - { url = "https://files.pythonhosted.org/packages/81/4b/00092999fc7c0c266045e984d56b7314734cc400a6c6dc4d61a35f135a9d/rpds_py-0.26.0-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:aa81873e2c8c5aa616ab8e017a481a96742fdf9313c40f14338ca7dbf50cb55f", size = 559362 }, - { url = "https://files.pythonhosted.org/packages/96/0c/43737053cde1f93ac4945157f7be1428724ab943e2132a0d235a7e161d4e/rpds_py-0.26.0-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:68ffcf982715f5b5b7686bdd349ff75d422e8f22551000c24b30eaa1b7f7ae84", size = 588113 }, - { url = "https://files.pythonhosted.org/packages/46/46/8e38f6161466e60a997ed7e9951ae5de131dedc3cf778ad35994b4af823d/rpds_py-0.26.0-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:6188de70e190847bb6db3dc3981cbadff87d27d6fe9b4f0e18726d55795cee9b", size = 555429 }, - { url = "https://files.pythonhosted.org/packages/2c/ac/65da605e9f1dd643ebe615d5bbd11b6efa1d69644fc4bf623ea5ae385a82/rpds_py-0.26.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:1c962145c7473723df9722ba4c058de12eb5ebedcb4e27e7d902920aa3831ee8", size = 231950 }, - { url = "https://files.pythonhosted.org/packages/51/f2/b5c85b758a00c513bb0389f8fc8e61eb5423050c91c958cdd21843faa3e6/rpds_py-0.26.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f61a9326f80ca59214d1cceb0a09bb2ece5b2563d4e0cd37bfd5515c28510674", size = 373505 }, - { url = "https://files.pythonhosted.org/packages/23/e0/25db45e391251118e915e541995bb5f5ac5691a3b98fb233020ba53afc9b/rpds_py-0.26.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:183f857a53bcf4b1b42ef0f57ca553ab56bdd170e49d8091e96c51c3d69ca696", size = 359468 }, - { url = "https://files.pythonhosted.org/packages/0b/73/dd5ee6075bb6491be3a646b301dfd814f9486d924137a5098e61f0487e16/rpds_py-0.26.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:941c1cfdf4799d623cf3aa1d326a6b4fdb7a5799ee2687f3516738216d2262fb", size = 382680 }, - { url = "https://files.pythonhosted.org/packages/2f/10/84b522ff58763a5c443f5bcedc1820240e454ce4e620e88520f04589e2ea/rpds_py-0.26.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72a8d9564a717ee291f554eeb4bfeafe2309d5ec0aa6c475170bdab0f9ee8e88", size = 397035 }, - { url = "https://files.pythonhosted.org/packages/06/ea/8667604229a10a520fcbf78b30ccc278977dcc0627beb7ea2c96b3becef0/rpds_py-0.26.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:511d15193cbe013619dd05414c35a7dedf2088fcee93c6bbb7c77859765bd4e8", size = 514922 }, - { url = "https://files.pythonhosted.org/packages/24/e6/9ed5b625c0661c4882fc8cdf302bf8e96c73c40de99c31e0b95ed37d508c/rpds_py-0.26.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aea1f9741b603a8d8fedb0ed5502c2bc0accbc51f43e2ad1337fe7259c2b77a5", size = 402822 }, - { url = "https://files.pythonhosted.org/packages/8a/58/212c7b6fd51946047fb45d3733da27e2fa8f7384a13457c874186af691b1/rpds_py-0.26.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4019a9d473c708cf2f16415688ef0b4639e07abaa569d72f74745bbeffafa2c7", size = 384336 }, - { url = "https://files.pythonhosted.org/packages/aa/f5/a40ba78748ae8ebf4934d4b88e77b98497378bc2c24ba55ebe87a4e87057/rpds_py-0.26.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:093d63b4b0f52d98ebae33b8c50900d3d67e0666094b1be7a12fffd7f65de74b", size = 416871 }, - { url = "https://files.pythonhosted.org/packages/d5/a6/33b1fc0c9f7dcfcfc4a4353daa6308b3ece22496ceece348b3e7a7559a09/rpds_py-0.26.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:2abe21d8ba64cded53a2a677e149ceb76dcf44284202d737178afe7ba540c1eb", size = 559439 }, - { url = "https://files.pythonhosted.org/packages/71/2d/ceb3f9c12f8cfa56d34995097f6cd99da1325642c60d1b6680dd9df03ed8/rpds_py-0.26.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:4feb7511c29f8442cbbc28149a92093d32e815a28aa2c50d333826ad2a20fdf0", size = 588380 }, - { url = "https://files.pythonhosted.org/packages/c8/ed/9de62c2150ca8e2e5858acf3f4f4d0d180a38feef9fdab4078bea63d8dba/rpds_py-0.26.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:e99685fc95d386da368013e7fb4269dd39c30d99f812a8372d62f244f662709c", size = 555334 }, +sdist = { url = "https://files.pythonhosted.org/packages/a5/aa/4456d84bbb54adc6a916fb10c9b374f78ac840337644e4a5eda229c81275/rpds_py-0.26.0.tar.gz", hash = "sha256:20dae58a859b0906f0685642e591056f1e787f3a8b39c8e8749a45dc7d26bdb0", size = 27385, upload-time = "2025-07-01T15:57:13.958Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/31/1459645f036c3dfeacef89e8e5825e430c77dde8489f3b99eaafcd4a60f5/rpds_py-0.26.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:4c70c70f9169692b36307a95f3d8c0a9fcd79f7b4a383aad5eaa0e9718b79b37", size = 372466, upload-time = "2025-07-01T15:53:40.55Z" }, + { url = "https://files.pythonhosted.org/packages/dd/ff/3d0727f35836cc8773d3eeb9a46c40cc405854e36a8d2e951f3a8391c976/rpds_py-0.26.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:777c62479d12395bfb932944e61e915741e364c843afc3196b694db3d669fcd0", size = 357825, upload-time = "2025-07-01T15:53:42.247Z" }, + { url = "https://files.pythonhosted.org/packages/bf/ce/badc5e06120a54099ae287fa96d82cbb650a5f85cf247ffe19c7b157fd1f/rpds_py-0.26.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec671691e72dff75817386aa02d81e708b5a7ec0dec6669ec05213ff6b77e1bd", size = 381530, upload-time = "2025-07-01T15:53:43.585Z" }, + { url = "https://files.pythonhosted.org/packages/1e/a5/fa5d96a66c95d06c62d7a30707b6a4cfec696ab8ae280ee7be14e961e118/rpds_py-0.26.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6a1cb5d6ce81379401bbb7f6dbe3d56de537fb8235979843f0d53bc2e9815a79", size = 396933, upload-time = "2025-07-01T15:53:45.78Z" }, + { url = "https://files.pythonhosted.org/packages/00/a7/7049d66750f18605c591a9db47d4a059e112a0c9ff8de8daf8fa0f446bba/rpds_py-0.26.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4f789e32fa1fb6a7bf890e0124e7b42d1e60d28ebff57fe806719abb75f0e9a3", size = 513973, upload-time = "2025-07-01T15:53:47.085Z" }, + { url = "https://files.pythonhosted.org/packages/0e/f1/528d02c7d6b29d29fac8fd784b354d3571cc2153f33f842599ef0cf20dd2/rpds_py-0.26.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c55b0a669976cf258afd718de3d9ad1b7d1fe0a91cd1ab36f38b03d4d4aeaaf", size = 402293, upload-time = "2025-07-01T15:53:48.117Z" }, + { url = "https://files.pythonhosted.org/packages/15/93/fde36cd6e4685df2cd08508f6c45a841e82f5bb98c8d5ecf05649522acb5/rpds_py-0.26.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c70d9ec912802ecfd6cd390dadb34a9578b04f9bcb8e863d0a7598ba5e9e7ccc", size = 383787, upload-time = "2025-07-01T15:53:50.874Z" }, + { url = "https://files.pythonhosted.org/packages/69/f2/5007553aaba1dcae5d663143683c3dfd03d9395289f495f0aebc93e90f24/rpds_py-0.26.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3021933c2cb7def39d927b9862292e0f4c75a13d7de70eb0ab06efed4c508c19", size = 416312, upload-time = "2025-07-01T15:53:52.046Z" }, + { url = "https://files.pythonhosted.org/packages/8f/a7/ce52c75c1e624a79e48a69e611f1c08844564e44c85db2b6f711d76d10ce/rpds_py-0.26.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8a7898b6ca3b7d6659e55cdac825a2e58c638cbf335cde41f4619e290dd0ad11", size = 558403, upload-time = "2025-07-01T15:53:53.192Z" }, + { url = "https://files.pythonhosted.org/packages/79/d5/e119db99341cc75b538bf4cb80504129fa22ce216672fb2c28e4a101f4d9/rpds_py-0.26.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:12bff2ad9447188377f1b2794772f91fe68bb4bbfa5a39d7941fbebdbf8c500f", size = 588323, upload-time = "2025-07-01T15:53:54.336Z" }, + { url = "https://files.pythonhosted.org/packages/93/94/d28272a0b02f5fe24c78c20e13bbcb95f03dc1451b68e7830ca040c60bd6/rpds_py-0.26.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:191aa858f7d4902e975d4cf2f2d9243816c91e9605070aeb09c0a800d187e323", size = 554541, upload-time = "2025-07-01T15:53:55.469Z" }, + { url = "https://files.pythonhosted.org/packages/93/e0/8c41166602f1b791da892d976057eba30685486d2e2c061ce234679c922b/rpds_py-0.26.0-cp310-cp310-win32.whl", hash = "sha256:b37a04d9f52cb76b6b78f35109b513f6519efb481d8ca4c321f6a3b9580b3f45", size = 220442, upload-time = "2025-07-01T15:53:56.524Z" }, + { url = "https://files.pythonhosted.org/packages/87/f0/509736bb752a7ab50fb0270c2a4134d671a7b3038030837e5536c3de0e0b/rpds_py-0.26.0-cp310-cp310-win_amd64.whl", hash = "sha256:38721d4c9edd3eb6670437d8d5e2070063f305bfa2d5aa4278c51cedcd508a84", size = 231314, upload-time = "2025-07-01T15:53:57.842Z" }, + { url = "https://files.pythonhosted.org/packages/09/4c/4ee8f7e512030ff79fda1df3243c88d70fc874634e2dbe5df13ba4210078/rpds_py-0.26.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:9e8cb77286025bdb21be2941d64ac6ca016130bfdcd228739e8ab137eb4406ed", size = 372610, upload-time = "2025-07-01T15:53:58.844Z" }, + { url = "https://files.pythonhosted.org/packages/fa/9d/3dc16be00f14fc1f03c71b1d67c8df98263ab2710a2fbd65a6193214a527/rpds_py-0.26.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5e09330b21d98adc8ccb2dbb9fc6cb434e8908d4c119aeaa772cb1caab5440a0", size = 358032, upload-time = "2025-07-01T15:53:59.985Z" }, + { url = "https://files.pythonhosted.org/packages/e7/5a/7f1bf8f045da2866324a08ae80af63e64e7bfaf83bd31f865a7b91a58601/rpds_py-0.26.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c9c1b92b774b2e68d11193dc39620d62fd8ab33f0a3c77ecdabe19c179cdbc1", size = 381525, upload-time = "2025-07-01T15:54:01.162Z" }, + { url = "https://files.pythonhosted.org/packages/45/8a/04479398c755a066ace10e3d158866beb600867cacae194c50ffa783abd0/rpds_py-0.26.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:824e6d3503ab990d7090768e4dfd9e840837bae057f212ff9f4f05ec6d1975e7", size = 397089, upload-time = "2025-07-01T15:54:02.319Z" }, + { url = "https://files.pythonhosted.org/packages/72/88/9203f47268db488a1b6d469d69c12201ede776bb728b9d9f29dbfd7df406/rpds_py-0.26.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8ad7fd2258228bf288f2331f0a6148ad0186b2e3643055ed0db30990e59817a6", size = 514255, upload-time = "2025-07-01T15:54:03.38Z" }, + { url = "https://files.pythonhosted.org/packages/f5/b4/01ce5d1e853ddf81fbbd4311ab1eff0b3cf162d559288d10fd127e2588b5/rpds_py-0.26.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0dc23bbb3e06ec1ea72d515fb572c1fea59695aefbffb106501138762e1e915e", size = 402283, upload-time = "2025-07-01T15:54:04.923Z" }, + { url = "https://files.pythonhosted.org/packages/34/a2/004c99936997bfc644d590a9defd9e9c93f8286568f9c16cdaf3e14429a7/rpds_py-0.26.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d80bf832ac7b1920ee29a426cdca335f96a2b5caa839811803e999b41ba9030d", size = 383881, upload-time = "2025-07-01T15:54:06.482Z" }, + { url = "https://files.pythonhosted.org/packages/05/1b/ef5fba4a8f81ce04c427bfd96223f92f05e6cd72291ce9d7523db3b03a6c/rpds_py-0.26.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0919f38f5542c0a87e7b4afcafab6fd2c15386632d249e9a087498571250abe3", size = 415822, upload-time = "2025-07-01T15:54:07.605Z" }, + { url = "https://files.pythonhosted.org/packages/16/80/5c54195aec456b292f7bd8aa61741c8232964063fd8a75fdde9c1e982328/rpds_py-0.26.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d422b945683e409000c888e384546dbab9009bb92f7c0b456e217988cf316107", size = 558347, upload-time = "2025-07-01T15:54:08.591Z" }, + { url = "https://files.pythonhosted.org/packages/f2/1c/1845c1b1fd6d827187c43afe1841d91678d7241cbdb5420a4c6de180a538/rpds_py-0.26.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:77a7711fa562ba2da1aa757e11024ad6d93bad6ad7ede5afb9af144623e5f76a", size = 587956, upload-time = "2025-07-01T15:54:09.963Z" }, + { url = "https://files.pythonhosted.org/packages/2e/ff/9e979329dd131aa73a438c077252ddabd7df6d1a7ad7b9aacf6261f10faa/rpds_py-0.26.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:238e8c8610cb7c29460e37184f6799547f7e09e6a9bdbdab4e8edb90986a2318", size = 554363, upload-time = "2025-07-01T15:54:11.073Z" }, + { url = "https://files.pythonhosted.org/packages/00/8b/d78cfe034b71ffbe72873a136e71acc7a831a03e37771cfe59f33f6de8a2/rpds_py-0.26.0-cp311-cp311-win32.whl", hash = "sha256:893b022bfbdf26d7bedb083efeea624e8550ca6eb98bf7fea30211ce95b9201a", size = 220123, upload-time = "2025-07-01T15:54:12.382Z" }, + { url = "https://files.pythonhosted.org/packages/94/c1/3c8c94c7dd3905dbfde768381ce98778500a80db9924731d87ddcdb117e9/rpds_py-0.26.0-cp311-cp311-win_amd64.whl", hash = "sha256:87a5531de9f71aceb8af041d72fc4cab4943648d91875ed56d2e629bef6d4c03", size = 231732, upload-time = "2025-07-01T15:54:13.434Z" }, + { url = "https://files.pythonhosted.org/packages/67/93/e936fbed1b734eabf36ccb5d93c6a2e9246fbb13c1da011624b7286fae3e/rpds_py-0.26.0-cp311-cp311-win_arm64.whl", hash = "sha256:de2713f48c1ad57f89ac25b3cb7daed2156d8e822cf0eca9b96a6f990718cc41", size = 221917, upload-time = "2025-07-01T15:54:14.559Z" }, + { url = "https://files.pythonhosted.org/packages/ea/86/90eb87c6f87085868bd077c7a9938006eb1ce19ed4d06944a90d3560fce2/rpds_py-0.26.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:894514d47e012e794f1350f076c427d2347ebf82f9b958d554d12819849a369d", size = 363933, upload-time = "2025-07-01T15:54:15.734Z" }, + { url = "https://files.pythonhosted.org/packages/63/78/4469f24d34636242c924626082b9586f064ada0b5dbb1e9d096ee7a8e0c6/rpds_py-0.26.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc921b96fa95a097add244da36a1d9e4f3039160d1d30f1b35837bf108c21136", size = 350447, upload-time = "2025-07-01T15:54:16.922Z" }, + { url = "https://files.pythonhosted.org/packages/ad/91/c448ed45efdfdade82348d5e7995e15612754826ea640afc20915119734f/rpds_py-0.26.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e1157659470aa42a75448b6e943c895be8c70531c43cb78b9ba990778955582", size = 384711, upload-time = "2025-07-01T15:54:18.101Z" }, + { url = "https://files.pythonhosted.org/packages/ec/43/e5c86fef4be7f49828bdd4ecc8931f0287b1152c0bb0163049b3218740e7/rpds_py-0.26.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:521ccf56f45bb3a791182dc6b88ae5f8fa079dd705ee42138c76deb1238e554e", size = 400865, upload-time = "2025-07-01T15:54:19.295Z" }, + { url = "https://files.pythonhosted.org/packages/55/34/e00f726a4d44f22d5c5fe2e5ddd3ac3d7fd3f74a175607781fbdd06fe375/rpds_py-0.26.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9def736773fd56b305c0eef698be5192c77bfa30d55a0e5885f80126c4831a15", size = 517763, upload-time = "2025-07-01T15:54:20.858Z" }, + { url = "https://files.pythonhosted.org/packages/52/1c/52dc20c31b147af724b16104500fba13e60123ea0334beba7b40e33354b4/rpds_py-0.26.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cdad4ea3b4513b475e027be79e5a0ceac8ee1c113a1a11e5edc3c30c29f964d8", size = 406651, upload-time = "2025-07-01T15:54:22.508Z" }, + { url = "https://files.pythonhosted.org/packages/2e/77/87d7bfabfc4e821caa35481a2ff6ae0b73e6a391bb6b343db2c91c2b9844/rpds_py-0.26.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82b165b07f416bdccf5c84546a484cc8f15137ca38325403864bfdf2b5b72f6a", size = 386079, upload-time = "2025-07-01T15:54:23.987Z" }, + { url = "https://files.pythonhosted.org/packages/e3/d4/7f2200c2d3ee145b65b3cddc4310d51f7da6a26634f3ac87125fd789152a/rpds_py-0.26.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d04cab0a54b9dba4d278fe955a1390da3cf71f57feb78ddc7cb67cbe0bd30323", size = 421379, upload-time = "2025-07-01T15:54:25.073Z" }, + { url = "https://files.pythonhosted.org/packages/ae/13/9fdd428b9c820869924ab62236b8688b122baa22d23efdd1c566938a39ba/rpds_py-0.26.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:79061ba1a11b6a12743a2b0f72a46aa2758613d454aa6ba4f5a265cc48850158", size = 562033, upload-time = "2025-07-01T15:54:26.225Z" }, + { url = "https://files.pythonhosted.org/packages/f3/e1/b69686c3bcbe775abac3a4c1c30a164a2076d28df7926041f6c0eb5e8d28/rpds_py-0.26.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:f405c93675d8d4c5ac87364bb38d06c988e11028a64b52a47158a355079661f3", size = 591639, upload-time = "2025-07-01T15:54:27.424Z" }, + { url = "https://files.pythonhosted.org/packages/5c/c9/1e3d8c8863c84a90197ac577bbc3d796a92502124c27092413426f670990/rpds_py-0.26.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dafd4c44b74aa4bed4b250f1aed165b8ef5de743bcca3b88fc9619b6087093d2", size = 557105, upload-time = "2025-07-01T15:54:29.93Z" }, + { url = "https://files.pythonhosted.org/packages/9f/c5/90c569649057622959f6dcc40f7b516539608a414dfd54b8d77e3b201ac0/rpds_py-0.26.0-cp312-cp312-win32.whl", hash = "sha256:3da5852aad63fa0c6f836f3359647870e21ea96cf433eb393ffa45263a170d44", size = 223272, upload-time = "2025-07-01T15:54:31.128Z" }, + { url = "https://files.pythonhosted.org/packages/7d/16/19f5d9f2a556cfed454eebe4d354c38d51c20f3db69e7b4ce6cff904905d/rpds_py-0.26.0-cp312-cp312-win_amd64.whl", hash = "sha256:cf47cfdabc2194a669dcf7a8dbba62e37a04c5041d2125fae0233b720da6f05c", size = 234995, upload-time = "2025-07-01T15:54:32.195Z" }, + { url = "https://files.pythonhosted.org/packages/83/f0/7935e40b529c0e752dfaa7880224771b51175fce08b41ab4a92eb2fbdc7f/rpds_py-0.26.0-cp312-cp312-win_arm64.whl", hash = "sha256:20ab1ae4fa534f73647aad289003f1104092890849e0266271351922ed5574f8", size = 223198, upload-time = "2025-07-01T15:54:33.271Z" }, + { url = "https://files.pythonhosted.org/packages/6a/67/bb62d0109493b12b1c6ab00de7a5566aa84c0e44217c2d94bee1bd370da9/rpds_py-0.26.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:696764a5be111b036256c0b18cd29783fab22154690fc698062fc1b0084b511d", size = 363917, upload-time = "2025-07-01T15:54:34.755Z" }, + { url = "https://files.pythonhosted.org/packages/4b/f3/34e6ae1925a5706c0f002a8d2d7f172373b855768149796af87bd65dcdb9/rpds_py-0.26.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1e6c15d2080a63aaed876e228efe4f814bc7889c63b1e112ad46fdc8b368b9e1", size = 350073, upload-time = "2025-07-01T15:54:36.292Z" }, + { url = "https://files.pythonhosted.org/packages/75/83/1953a9d4f4e4de7fd0533733e041c28135f3c21485faaef56a8aadbd96b5/rpds_py-0.26.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:390e3170babf42462739a93321e657444f0862c6d722a291accc46f9d21ed04e", size = 384214, upload-time = "2025-07-01T15:54:37.469Z" }, + { url = "https://files.pythonhosted.org/packages/48/0e/983ed1b792b3322ea1d065e67f4b230f3b96025f5ce3878cc40af09b7533/rpds_py-0.26.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7da84c2c74c0f5bc97d853d9e17bb83e2dcafcff0dc48286916001cc114379a1", size = 400113, upload-time = "2025-07-01T15:54:38.954Z" }, + { url = "https://files.pythonhosted.org/packages/69/7f/36c0925fff6f660a80be259c5b4f5e53a16851f946eb080351d057698528/rpds_py-0.26.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c5fe114a6dd480a510b6d3661d09d67d1622c4bf20660a474507aaee7eeeee9", size = 515189, upload-time = "2025-07-01T15:54:40.57Z" }, + { url = "https://files.pythonhosted.org/packages/13/45/cbf07fc03ba7a9b54662c9badb58294ecfb24f828b9732970bd1a431ed5c/rpds_py-0.26.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3100b3090269f3a7ea727b06a6080d4eb7439dca4c0e91a07c5d133bb1727ea7", size = 406998, upload-time = "2025-07-01T15:54:43.025Z" }, + { url = "https://files.pythonhosted.org/packages/6c/b0/8fa5e36e58657997873fd6a1cf621285ca822ca75b4b3434ead047daa307/rpds_py-0.26.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c03c9b0c64afd0320ae57de4c982801271c0c211aa2d37f3003ff5feb75bb04", size = 385903, upload-time = "2025-07-01T15:54:44.752Z" }, + { url = "https://files.pythonhosted.org/packages/4b/f7/b25437772f9f57d7a9fbd73ed86d0dcd76b4c7c6998348c070d90f23e315/rpds_py-0.26.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5963b72ccd199ade6ee493723d18a3f21ba7d5b957017607f815788cef50eaf1", size = 419785, upload-time = "2025-07-01T15:54:46.043Z" }, + { url = "https://files.pythonhosted.org/packages/a7/6b/63ffa55743dfcb4baf2e9e77a0b11f7f97ed96a54558fcb5717a4b2cd732/rpds_py-0.26.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9da4e873860ad5bab3291438525cae80169daecbfafe5657f7f5fb4d6b3f96b9", size = 561329, upload-time = "2025-07-01T15:54:47.64Z" }, + { url = "https://files.pythonhosted.org/packages/2f/07/1f4f5e2886c480a2346b1e6759c00278b8a69e697ae952d82ae2e6ee5db0/rpds_py-0.26.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:5afaddaa8e8c7f1f7b4c5c725c0070b6eed0228f705b90a1732a48e84350f4e9", size = 590875, upload-time = "2025-07-01T15:54:48.9Z" }, + { url = "https://files.pythonhosted.org/packages/cc/bc/e6639f1b91c3a55f8c41b47d73e6307051b6e246254a827ede730624c0f8/rpds_py-0.26.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4916dc96489616a6f9667e7526af8fa693c0fdb4f3acb0e5d9f4400eb06a47ba", size = 556636, upload-time = "2025-07-01T15:54:50.619Z" }, + { url = "https://files.pythonhosted.org/packages/05/4c/b3917c45566f9f9a209d38d9b54a1833f2bb1032a3e04c66f75726f28876/rpds_py-0.26.0-cp313-cp313-win32.whl", hash = "sha256:2a343f91b17097c546b93f7999976fd6c9d5900617aa848c81d794e062ab302b", size = 222663, upload-time = "2025-07-01T15:54:52.023Z" }, + { url = "https://files.pythonhosted.org/packages/e0/0b/0851bdd6025775aaa2365bb8de0697ee2558184c800bfef8d7aef5ccde58/rpds_py-0.26.0-cp313-cp313-win_amd64.whl", hash = "sha256:0a0b60701f2300c81b2ac88a5fb893ccfa408e1c4a555a77f908a2596eb875a5", size = 234428, upload-time = "2025-07-01T15:54:53.692Z" }, + { url = "https://files.pythonhosted.org/packages/ed/e8/a47c64ed53149c75fb581e14a237b7b7cd18217e969c30d474d335105622/rpds_py-0.26.0-cp313-cp313-win_arm64.whl", hash = "sha256:257d011919f133a4746958257f2c75238e3ff54255acd5e3e11f3ff41fd14256", size = 222571, upload-time = "2025-07-01T15:54:54.822Z" }, + { url = "https://files.pythonhosted.org/packages/89/bf/3d970ba2e2bcd17d2912cb42874107390f72873e38e79267224110de5e61/rpds_py-0.26.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:529c8156d7506fba5740e05da8795688f87119cce330c244519cf706a4a3d618", size = 360475, upload-time = "2025-07-01T15:54:56.228Z" }, + { url = "https://files.pythonhosted.org/packages/82/9f/283e7e2979fc4ec2d8ecee506d5a3675fce5ed9b4b7cb387ea5d37c2f18d/rpds_py-0.26.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f53ec51f9d24e9638a40cabb95078ade8c99251945dad8d57bf4aabe86ecee35", size = 346692, upload-time = "2025-07-01T15:54:58.561Z" }, + { url = "https://files.pythonhosted.org/packages/e3/03/7e50423c04d78daf391da3cc4330bdb97042fc192a58b186f2d5deb7befd/rpds_py-0.26.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab504c4d654e4a29558eaa5bb8cea5fdc1703ea60a8099ffd9c758472cf913f", size = 379415, upload-time = "2025-07-01T15:54:59.751Z" }, + { url = "https://files.pythonhosted.org/packages/57/00/d11ee60d4d3b16808432417951c63df803afb0e0fc672b5e8d07e9edaaae/rpds_py-0.26.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fd0641abca296bc1a00183fe44f7fced8807ed49d501f188faa642d0e4975b83", size = 391783, upload-time = "2025-07-01T15:55:00.898Z" }, + { url = "https://files.pythonhosted.org/packages/08/b3/1069c394d9c0d6d23c5b522e1f6546b65793a22950f6e0210adcc6f97c3e/rpds_py-0.26.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:69b312fecc1d017b5327afa81d4da1480f51c68810963a7336d92203dbb3d4f1", size = 512844, upload-time = "2025-07-01T15:55:02.201Z" }, + { url = "https://files.pythonhosted.org/packages/08/3b/c4fbf0926800ed70b2c245ceca99c49f066456755f5d6eb8863c2c51e6d0/rpds_py-0.26.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c741107203954f6fc34d3066d213d0a0c40f7bb5aafd698fb39888af277c70d8", size = 402105, upload-time = "2025-07-01T15:55:03.698Z" }, + { url = "https://files.pythonhosted.org/packages/1c/b0/db69b52ca07413e568dae9dc674627a22297abb144c4d6022c6d78f1e5cc/rpds_py-0.26.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc3e55a7db08dc9a6ed5fb7103019d2c1a38a349ac41901f9f66d7f95750942f", size = 383440, upload-time = "2025-07-01T15:55:05.398Z" }, + { url = "https://files.pythonhosted.org/packages/4c/e1/c65255ad5b63903e56b3bb3ff9dcc3f4f5c3badde5d08c741ee03903e951/rpds_py-0.26.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9e851920caab2dbcae311fd28f4313c6953993893eb5c1bb367ec69d9a39e7ed", size = 412759, upload-time = "2025-07-01T15:55:08.316Z" }, + { url = "https://files.pythonhosted.org/packages/e4/22/bb731077872377a93c6e93b8a9487d0406c70208985831034ccdeed39c8e/rpds_py-0.26.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:dfbf280da5f876d0b00c81f26bedce274e72a678c28845453885a9b3c22ae632", size = 556032, upload-time = "2025-07-01T15:55:09.52Z" }, + { url = "https://files.pythonhosted.org/packages/e0/8b/393322ce7bac5c4530fb96fc79cc9ea2f83e968ff5f6e873f905c493e1c4/rpds_py-0.26.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:1cc81d14ddfa53d7f3906694d35d54d9d3f850ef8e4e99ee68bc0d1e5fed9a9c", size = 585416, upload-time = "2025-07-01T15:55:11.216Z" }, + { url = "https://files.pythonhosted.org/packages/49/ae/769dc372211835bf759319a7aae70525c6eb523e3371842c65b7ef41c9c6/rpds_py-0.26.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dca83c498b4650a91efcf7b88d669b170256bf8017a5db6f3e06c2bf031f57e0", size = 554049, upload-time = "2025-07-01T15:55:13.004Z" }, + { url = "https://files.pythonhosted.org/packages/6b/f9/4c43f9cc203d6ba44ce3146246cdc38619d92c7bd7bad4946a3491bd5b70/rpds_py-0.26.0-cp313-cp313t-win32.whl", hash = "sha256:4d11382bcaf12f80b51d790dee295c56a159633a8e81e6323b16e55d81ae37e9", size = 218428, upload-time = "2025-07-01T15:55:14.486Z" }, + { url = "https://files.pythonhosted.org/packages/7e/8b/9286b7e822036a4a977f2f1e851c7345c20528dbd56b687bb67ed68a8ede/rpds_py-0.26.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ff110acded3c22c033e637dd8896e411c7d3a11289b2edf041f86663dbc791e9", size = 231524, upload-time = "2025-07-01T15:55:15.745Z" }, + { url = "https://files.pythonhosted.org/packages/55/07/029b7c45db910c74e182de626dfdae0ad489a949d84a468465cd0ca36355/rpds_py-0.26.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:da619979df60a940cd434084355c514c25cf8eb4cf9a508510682f6c851a4f7a", size = 364292, upload-time = "2025-07-01T15:55:17.001Z" }, + { url = "https://files.pythonhosted.org/packages/13/d1/9b3d3f986216b4d1f584878dca15ce4797aaf5d372d738974ba737bf68d6/rpds_py-0.26.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ea89a2458a1a75f87caabefe789c87539ea4e43b40f18cff526052e35bbb4fdf", size = 350334, upload-time = "2025-07-01T15:55:18.922Z" }, + { url = "https://files.pythonhosted.org/packages/18/98/16d5e7bc9ec715fa9668731d0cf97f6b032724e61696e2db3d47aeb89214/rpds_py-0.26.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:feac1045b3327a45944e7dcbeb57530339f6b17baff154df51ef8b0da34c8c12", size = 384875, upload-time = "2025-07-01T15:55:20.399Z" }, + { url = "https://files.pythonhosted.org/packages/f9/13/aa5e2b1ec5ab0e86a5c464d53514c0467bec6ba2507027d35fc81818358e/rpds_py-0.26.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b818a592bd69bfe437ee8368603d4a2d928c34cffcdf77c2e761a759ffd17d20", size = 399993, upload-time = "2025-07-01T15:55:21.729Z" }, + { url = "https://files.pythonhosted.org/packages/17/03/8021810b0e97923abdbab6474c8b77c69bcb4b2c58330777df9ff69dc559/rpds_py-0.26.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a8b0dd8648709b62d9372fc00a57466f5fdeefed666afe3fea5a6c9539a0331", size = 516683, upload-time = "2025-07-01T15:55:22.918Z" }, + { url = "https://files.pythonhosted.org/packages/dc/b1/da8e61c87c2f3d836954239fdbbfb477bb7b54d74974d8f6fcb34342d166/rpds_py-0.26.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6d3498ad0df07d81112aa6ec6c95a7e7b1ae00929fb73e7ebee0f3faaeabad2f", size = 408825, upload-time = "2025-07-01T15:55:24.207Z" }, + { url = "https://files.pythonhosted.org/packages/38/bc/1fc173edaaa0e52c94b02a655db20697cb5fa954ad5a8e15a2c784c5cbdd/rpds_py-0.26.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24a4146ccb15be237fdef10f331c568e1b0e505f8c8c9ed5d67759dac58ac246", size = 387292, upload-time = "2025-07-01T15:55:25.554Z" }, + { url = "https://files.pythonhosted.org/packages/7c/eb/3a9bb4bd90867d21916f253caf4f0d0be7098671b6715ad1cead9fe7bab9/rpds_py-0.26.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a9a63785467b2d73635957d32a4f6e73d5e4df497a16a6392fa066b753e87387", size = 420435, upload-time = "2025-07-01T15:55:27.798Z" }, + { url = "https://files.pythonhosted.org/packages/cd/16/e066dcdb56f5632713445271a3f8d3d0b426d51ae9c0cca387799df58b02/rpds_py-0.26.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:de4ed93a8c91debfd5a047be327b7cc8b0cc6afe32a716bbbc4aedca9e2a83af", size = 562410, upload-time = "2025-07-01T15:55:29.057Z" }, + { url = "https://files.pythonhosted.org/packages/60/22/ddbdec7eb82a0dc2e455be44c97c71c232983e21349836ce9f272e8a3c29/rpds_py-0.26.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:caf51943715b12af827696ec395bfa68f090a4c1a1d2509eb4e2cb69abbbdb33", size = 590724, upload-time = "2025-07-01T15:55:30.719Z" }, + { url = "https://files.pythonhosted.org/packages/2c/b4/95744085e65b7187d83f2fcb0bef70716a1ea0a9e5d8f7f39a86e5d83424/rpds_py-0.26.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4a59e5bc386de021f56337f757301b337d7ab58baa40174fb150accd480bc953", size = 558285, upload-time = "2025-07-01T15:55:31.981Z" }, + { url = "https://files.pythonhosted.org/packages/37/37/6309a75e464d1da2559446f9c811aa4d16343cebe3dbb73701e63f760caa/rpds_py-0.26.0-cp314-cp314-win32.whl", hash = "sha256:92c8db839367ef16a662478f0a2fe13e15f2227da3c1430a782ad0f6ee009ec9", size = 223459, upload-time = "2025-07-01T15:55:33.312Z" }, + { url = "https://files.pythonhosted.org/packages/d9/6f/8e9c11214c46098b1d1391b7e02b70bb689ab963db3b19540cba17315291/rpds_py-0.26.0-cp314-cp314-win_amd64.whl", hash = "sha256:b0afb8cdd034150d4d9f53926226ed27ad15b7f465e93d7468caaf5eafae0d37", size = 236083, upload-time = "2025-07-01T15:55:34.933Z" }, + { url = "https://files.pythonhosted.org/packages/47/af/9c4638994dd623d51c39892edd9d08e8be8220a4b7e874fa02c2d6e91955/rpds_py-0.26.0-cp314-cp314-win_arm64.whl", hash = "sha256:ca3f059f4ba485d90c8dc75cb5ca897e15325e4e609812ce57f896607c1c0867", size = 223291, upload-time = "2025-07-01T15:55:36.202Z" }, + { url = "https://files.pythonhosted.org/packages/4d/db/669a241144460474aab03e254326b32c42def83eb23458a10d163cb9b5ce/rpds_py-0.26.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:5afea17ab3a126006dc2f293b14ffc7ef3c85336cf451564a0515ed7648033da", size = 361445, upload-time = "2025-07-01T15:55:37.483Z" }, + { url = "https://files.pythonhosted.org/packages/3b/2d/133f61cc5807c6c2fd086a46df0eb8f63a23f5df8306ff9f6d0fd168fecc/rpds_py-0.26.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:69f0c0a3df7fd3a7eec50a00396104bb9a843ea6d45fcc31c2d5243446ffd7a7", size = 347206, upload-time = "2025-07-01T15:55:38.828Z" }, + { url = "https://files.pythonhosted.org/packages/05/bf/0e8fb4c05f70273469eecf82f6ccf37248558526a45321644826555db31b/rpds_py-0.26.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:801a71f70f9813e82d2513c9a96532551fce1e278ec0c64610992c49c04c2dad", size = 380330, upload-time = "2025-07-01T15:55:40.175Z" }, + { url = "https://files.pythonhosted.org/packages/d4/a8/060d24185d8b24d3923322f8d0ede16df4ade226a74e747b8c7c978e3dd3/rpds_py-0.26.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:df52098cde6d5e02fa75c1f6244f07971773adb4a26625edd5c18fee906fa84d", size = 392254, upload-time = "2025-07-01T15:55:42.015Z" }, + { url = "https://files.pythonhosted.org/packages/b9/7b/7c2e8a9ee3e6bc0bae26bf29f5219955ca2fbb761dca996a83f5d2f773fe/rpds_py-0.26.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9bc596b30f86dc6f0929499c9e574601679d0341a0108c25b9b358a042f51bca", size = 516094, upload-time = "2025-07-01T15:55:43.603Z" }, + { url = "https://files.pythonhosted.org/packages/75/d6/f61cafbed8ba1499b9af9f1777a2a199cd888f74a96133d8833ce5eaa9c5/rpds_py-0.26.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9dfbe56b299cf5875b68eb6f0ebaadc9cac520a1989cac0db0765abfb3709c19", size = 402889, upload-time = "2025-07-01T15:55:45.275Z" }, + { url = "https://files.pythonhosted.org/packages/92/19/c8ac0a8a8df2dd30cdec27f69298a5c13e9029500d6d76718130f5e5be10/rpds_py-0.26.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac64f4b2bdb4ea622175c9ab7cf09444e412e22c0e02e906978b3b488af5fde8", size = 384301, upload-time = "2025-07-01T15:55:47.098Z" }, + { url = "https://files.pythonhosted.org/packages/41/e1/6b1859898bc292a9ce5776016c7312b672da00e25cec74d7beced1027286/rpds_py-0.26.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:181ef9b6bbf9845a264f9aa45c31836e9f3c1f13be565d0d010e964c661d1e2b", size = 412891, upload-time = "2025-07-01T15:55:48.412Z" }, + { url = "https://files.pythonhosted.org/packages/ef/b9/ceb39af29913c07966a61367b3c08b4f71fad841e32c6b59a129d5974698/rpds_py-0.26.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:49028aa684c144ea502a8e847d23aed5e4c2ef7cadfa7d5eaafcb40864844b7a", size = 557044, upload-time = "2025-07-01T15:55:49.816Z" }, + { url = "https://files.pythonhosted.org/packages/2f/27/35637b98380731a521f8ec4f3fd94e477964f04f6b2f8f7af8a2d889a4af/rpds_py-0.26.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:e5d524d68a474a9688336045bbf76cb0def88549c1b2ad9dbfec1fb7cfbe9170", size = 585774, upload-time = "2025-07-01T15:55:51.192Z" }, + { url = "https://files.pythonhosted.org/packages/52/d9/3f0f105420fecd18551b678c9a6ce60bd23986098b252a56d35781b3e7e9/rpds_py-0.26.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c1851f429b822831bd2edcbe0cfd12ee9ea77868f8d3daf267b189371671c80e", size = 554886, upload-time = "2025-07-01T15:55:52.541Z" }, + { url = "https://files.pythonhosted.org/packages/6b/c5/347c056a90dc8dd9bc240a08c527315008e1b5042e7a4cf4ac027be9d38a/rpds_py-0.26.0-cp314-cp314t-win32.whl", hash = "sha256:7bdb17009696214c3b66bb3590c6d62e14ac5935e53e929bcdbc5a495987a84f", size = 219027, upload-time = "2025-07-01T15:55:53.874Z" }, + { url = "https://files.pythonhosted.org/packages/75/04/5302cea1aa26d886d34cadbf2dc77d90d7737e576c0065f357b96dc7a1a6/rpds_py-0.26.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f14440b9573a6f76b4ee4770c13f0b5921f71dde3b6fcb8dabbefd13b7fe05d7", size = 232821, upload-time = "2025-07-01T15:55:55.167Z" }, + { url = "https://files.pythonhosted.org/packages/ef/9a/1f033b0b31253d03d785b0cd905bc127e555ab496ea6b4c7c2e1f951f2fd/rpds_py-0.26.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3c0909c5234543ada2515c05dc08595b08d621ba919629e94427e8e03539c958", size = 373226, upload-time = "2025-07-01T15:56:16.578Z" }, + { url = "https://files.pythonhosted.org/packages/58/29/5f88023fd6aaaa8ca3c4a6357ebb23f6f07da6079093ccf27c99efce87db/rpds_py-0.26.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:c1fb0cda2abcc0ac62f64e2ea4b4e64c57dfd6b885e693095460c61bde7bb18e", size = 359230, upload-time = "2025-07-01T15:56:17.978Z" }, + { url = "https://files.pythonhosted.org/packages/6c/6c/13eaebd28b439da6964dde22712b52e53fe2824af0223b8e403249d10405/rpds_py-0.26.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84d142d2d6cf9b31c12aa4878d82ed3b2324226270b89b676ac62ccd7df52d08", size = 382363, upload-time = "2025-07-01T15:56:19.977Z" }, + { url = "https://files.pythonhosted.org/packages/55/fc/3bb9c486b06da19448646f96147796de23c5811ef77cbfc26f17307b6a9d/rpds_py-0.26.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a547e21c5610b7e9093d870be50682a6a6cf180d6da0f42c47c306073bfdbbf6", size = 397146, upload-time = "2025-07-01T15:56:21.39Z" }, + { url = "https://files.pythonhosted.org/packages/15/18/9d1b79eb4d18e64ba8bba9e7dec6f9d6920b639f22f07ee9368ca35d4673/rpds_py-0.26.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:35e9a70a0f335371275cdcd08bc5b8051ac494dd58bff3bbfb421038220dc871", size = 514804, upload-time = "2025-07-01T15:56:22.78Z" }, + { url = "https://files.pythonhosted.org/packages/4f/5a/175ad7191bdbcd28785204621b225ad70e85cdfd1e09cc414cb554633b21/rpds_py-0.26.0-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0dfa6115c6def37905344d56fb54c03afc49104e2ca473d5dedec0f6606913b4", size = 402820, upload-time = "2025-07-01T15:56:24.584Z" }, + { url = "https://files.pythonhosted.org/packages/11/45/6a67ecf6d61c4d4aff4bc056e864eec4b2447787e11d1c2c9a0242c6e92a/rpds_py-0.26.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:313cfcd6af1a55a286a3c9a25f64af6d0e46cf60bc5798f1db152d97a216ff6f", size = 384567, upload-time = "2025-07-01T15:56:26.064Z" }, + { url = "https://files.pythonhosted.org/packages/a1/ba/16589da828732b46454c61858950a78fe4c931ea4bf95f17432ffe64b241/rpds_py-0.26.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f7bf2496fa563c046d05e4d232d7b7fd61346e2402052064b773e5c378bf6f73", size = 416520, upload-time = "2025-07-01T15:56:27.608Z" }, + { url = "https://files.pythonhosted.org/packages/81/4b/00092999fc7c0c266045e984d56b7314734cc400a6c6dc4d61a35f135a9d/rpds_py-0.26.0-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:aa81873e2c8c5aa616ab8e017a481a96742fdf9313c40f14338ca7dbf50cb55f", size = 559362, upload-time = "2025-07-01T15:56:29.078Z" }, + { url = "https://files.pythonhosted.org/packages/96/0c/43737053cde1f93ac4945157f7be1428724ab943e2132a0d235a7e161d4e/rpds_py-0.26.0-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:68ffcf982715f5b5b7686bdd349ff75d422e8f22551000c24b30eaa1b7f7ae84", size = 588113, upload-time = "2025-07-01T15:56:30.485Z" }, + { url = "https://files.pythonhosted.org/packages/46/46/8e38f6161466e60a997ed7e9951ae5de131dedc3cf778ad35994b4af823d/rpds_py-0.26.0-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:6188de70e190847bb6db3dc3981cbadff87d27d6fe9b4f0e18726d55795cee9b", size = 555429, upload-time = "2025-07-01T15:56:31.956Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ac/65da605e9f1dd643ebe615d5bbd11b6efa1d69644fc4bf623ea5ae385a82/rpds_py-0.26.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:1c962145c7473723df9722ba4c058de12eb5ebedcb4e27e7d902920aa3831ee8", size = 231950, upload-time = "2025-07-01T15:56:33.337Z" }, + { url = "https://files.pythonhosted.org/packages/51/f2/b5c85b758a00c513bb0389f8fc8e61eb5423050c91c958cdd21843faa3e6/rpds_py-0.26.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f61a9326f80ca59214d1cceb0a09bb2ece5b2563d4e0cd37bfd5515c28510674", size = 373505, upload-time = "2025-07-01T15:56:34.716Z" }, + { url = "https://files.pythonhosted.org/packages/23/e0/25db45e391251118e915e541995bb5f5ac5691a3b98fb233020ba53afc9b/rpds_py-0.26.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:183f857a53bcf4b1b42ef0f57ca553ab56bdd170e49d8091e96c51c3d69ca696", size = 359468, upload-time = "2025-07-01T15:56:36.219Z" }, + { url = "https://files.pythonhosted.org/packages/0b/73/dd5ee6075bb6491be3a646b301dfd814f9486d924137a5098e61f0487e16/rpds_py-0.26.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:941c1cfdf4799d623cf3aa1d326a6b4fdb7a5799ee2687f3516738216d2262fb", size = 382680, upload-time = "2025-07-01T15:56:37.644Z" }, + { url = "https://files.pythonhosted.org/packages/2f/10/84b522ff58763a5c443f5bcedc1820240e454ce4e620e88520f04589e2ea/rpds_py-0.26.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72a8d9564a717ee291f554eeb4bfeafe2309d5ec0aa6c475170bdab0f9ee8e88", size = 397035, upload-time = "2025-07-01T15:56:39.241Z" }, + { url = "https://files.pythonhosted.org/packages/06/ea/8667604229a10a520fcbf78b30ccc278977dcc0627beb7ea2c96b3becef0/rpds_py-0.26.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:511d15193cbe013619dd05414c35a7dedf2088fcee93c6bbb7c77859765bd4e8", size = 514922, upload-time = "2025-07-01T15:56:40.645Z" }, + { url = "https://files.pythonhosted.org/packages/24/e6/9ed5b625c0661c4882fc8cdf302bf8e96c73c40de99c31e0b95ed37d508c/rpds_py-0.26.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aea1f9741b603a8d8fedb0ed5502c2bc0accbc51f43e2ad1337fe7259c2b77a5", size = 402822, upload-time = "2025-07-01T15:56:42.137Z" }, + { url = "https://files.pythonhosted.org/packages/8a/58/212c7b6fd51946047fb45d3733da27e2fa8f7384a13457c874186af691b1/rpds_py-0.26.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4019a9d473c708cf2f16415688ef0b4639e07abaa569d72f74745bbeffafa2c7", size = 384336, upload-time = "2025-07-01T15:56:44.239Z" }, + { url = "https://files.pythonhosted.org/packages/aa/f5/a40ba78748ae8ebf4934d4b88e77b98497378bc2c24ba55ebe87a4e87057/rpds_py-0.26.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:093d63b4b0f52d98ebae33b8c50900d3d67e0666094b1be7a12fffd7f65de74b", size = 416871, upload-time = "2025-07-01T15:56:46.284Z" }, + { url = "https://files.pythonhosted.org/packages/d5/a6/33b1fc0c9f7dcfcfc4a4353daa6308b3ece22496ceece348b3e7a7559a09/rpds_py-0.26.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:2abe21d8ba64cded53a2a677e149ceb76dcf44284202d737178afe7ba540c1eb", size = 559439, upload-time = "2025-07-01T15:56:48.549Z" }, + { url = "https://files.pythonhosted.org/packages/71/2d/ceb3f9c12f8cfa56d34995097f6cd99da1325642c60d1b6680dd9df03ed8/rpds_py-0.26.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:4feb7511c29f8442cbbc28149a92093d32e815a28aa2c50d333826ad2a20fdf0", size = 588380, upload-time = "2025-07-01T15:56:50.086Z" }, + { url = "https://files.pythonhosted.org/packages/c8/ed/9de62c2150ca8e2e5858acf3f4f4d0d180a38feef9fdab4078bea63d8dba/rpds_py-0.26.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:e99685fc95d386da368013e7fb4269dd39c30d99f812a8372d62f244f662709c", size = 555334, upload-time = "2025-07-01T15:56:51.703Z" }, ] [[package]] name = "ruff" version = "0.12.8" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4b/da/5bd7565be729e86e1442dad2c9a364ceeff82227c2dece7c29697a9795eb/ruff-0.12.8.tar.gz", hash = "sha256:4cb3a45525176e1009b2b64126acf5f9444ea59066262791febf55e40493a033", size = 5242373 } +sdist = { url = "https://files.pythonhosted.org/packages/4b/da/5bd7565be729e86e1442dad2c9a364ceeff82227c2dece7c29697a9795eb/ruff-0.12.8.tar.gz", hash = "sha256:4cb3a45525176e1009b2b64126acf5f9444ea59066262791febf55e40493a033", size = 5242373, upload-time = "2025-08-07T19:05:47.268Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c9/1e/c843bfa8ad1114fab3eb2b78235dda76acd66384c663a4e0415ecc13aa1e/ruff-0.12.8-py3-none-linux_armv6l.whl", hash = "sha256:63cb5a5e933fc913e5823a0dfdc3c99add73f52d139d6cd5cc8639d0e0465513", size = 11675315 }, - { url = "https://files.pythonhosted.org/packages/24/ee/af6e5c2a8ca3a81676d5480a1025494fd104b8896266502bb4de2a0e8388/ruff-0.12.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9a9bbe28f9f551accf84a24c366c1aa8774d6748438b47174f8e8565ab9dedbc", size = 12456653 }, - { url = "https://files.pythonhosted.org/packages/99/9d/e91f84dfe3866fa648c10512904991ecc326fd0b66578b324ee6ecb8f725/ruff-0.12.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:2fae54e752a3150f7ee0e09bce2e133caf10ce9d971510a9b925392dc98d2fec", size = 11659690 }, - { url = "https://files.pythonhosted.org/packages/fe/ac/a363d25ec53040408ebdd4efcee929d48547665858ede0505d1d8041b2e5/ruff-0.12.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c0acbcf01206df963d9331b5838fb31f3b44fa979ee7fa368b9b9057d89f4a53", size = 11896923 }, - { url = "https://files.pythonhosted.org/packages/58/9f/ea356cd87c395f6ade9bb81365bd909ff60860975ca1bc39f0e59de3da37/ruff-0.12.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ae3e7504666ad4c62f9ac8eedb52a93f9ebdeb34742b8b71cd3cccd24912719f", size = 11477612 }, - { url = "https://files.pythonhosted.org/packages/1a/46/92e8fa3c9dcfd49175225c09053916cb97bb7204f9f899c2f2baca69e450/ruff-0.12.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cb82efb5d35d07497813a1c5647867390a7d83304562607f3579602fa3d7d46f", size = 13182745 }, - { url = "https://files.pythonhosted.org/packages/5e/c4/f2176a310f26e6160deaf661ef60db6c3bb62b7a35e57ae28f27a09a7d63/ruff-0.12.8-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:dbea798fc0065ad0b84a2947b0aff4233f0cb30f226f00a2c5850ca4393de609", size = 14206885 }, - { url = "https://files.pythonhosted.org/packages/87/9d/98e162f3eeeb6689acbedbae5050b4b3220754554526c50c292b611d3a63/ruff-0.12.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:49ebcaccc2bdad86fd51b7864e3d808aad404aab8df33d469b6e65584656263a", size = 13639381 }, - { url = "https://files.pythonhosted.org/packages/81/4e/1b7478b072fcde5161b48f64774d6edd59d6d198e4ba8918d9f4702b8043/ruff-0.12.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ac9c570634b98c71c88cb17badd90f13fc076a472ba6ef1d113d8ed3df109fb", size = 12613271 }, - { url = "https://files.pythonhosted.org/packages/e8/67/0c3c9179a3ad19791ef1b8f7138aa27d4578c78700551c60d9260b2c660d/ruff-0.12.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:560e0cd641e45591a3e42cb50ef61ce07162b9c233786663fdce2d8557d99818", size = 12847783 }, - { url = "https://files.pythonhosted.org/packages/4e/2a/0b6ac3dd045acf8aa229b12c9c17bb35508191b71a14904baf99573a21bd/ruff-0.12.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:71c83121512e7743fba5a8848c261dcc454cafb3ef2934a43f1b7a4eb5a447ea", size = 11702672 }, - { url = "https://files.pythonhosted.org/packages/9d/ee/f9fdc9f341b0430110de8b39a6ee5fa68c5706dc7c0aa940817947d6937e/ruff-0.12.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:de4429ef2ba091ecddedd300f4c3f24bca875d3d8b23340728c3cb0da81072c3", size = 11440626 }, - { url = "https://files.pythonhosted.org/packages/89/fb/b3aa2d482d05f44e4d197d1de5e3863feb13067b22c571b9561085c999dc/ruff-0.12.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a2cab5f60d5b65b50fba39a8950c8746df1627d54ba1197f970763917184b161", size = 12462162 }, - { url = "https://files.pythonhosted.org/packages/18/9f/5c5d93e1d00d854d5013c96e1a92c33b703a0332707a7cdbd0a4880a84fb/ruff-0.12.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:45c32487e14f60b88aad6be9fd5da5093dbefb0e3e1224131cb1d441d7cb7d46", size = 12913212 }, - { url = "https://files.pythonhosted.org/packages/71/13/ab9120add1c0e4604c71bfc2e4ef7d63bebece0cfe617013da289539cef8/ruff-0.12.8-py3-none-win32.whl", hash = "sha256:daf3475060a617fd5bc80638aeaf2f5937f10af3ec44464e280a9d2218e720d3", size = 11694382 }, - { url = "https://files.pythonhosted.org/packages/f6/dc/a2873b7c5001c62f46266685863bee2888caf469d1edac84bf3242074be2/ruff-0.12.8-py3-none-win_amd64.whl", hash = "sha256:7209531f1a1fcfbe8e46bcd7ab30e2f43604d8ba1c49029bb420b103d0b5f76e", size = 12740482 }, - { url = "https://files.pythonhosted.org/packages/cb/5c/799a1efb8b5abab56e8a9f2a0b72d12bd64bb55815e9476c7d0a2887d2f7/ruff-0.12.8-py3-none-win_arm64.whl", hash = "sha256:c90e1a334683ce41b0e7a04f41790c429bf5073b62c1ae701c9dc5b3d14f0749", size = 11884718 }, + { url = "https://files.pythonhosted.org/packages/c9/1e/c843bfa8ad1114fab3eb2b78235dda76acd66384c663a4e0415ecc13aa1e/ruff-0.12.8-py3-none-linux_armv6l.whl", hash = "sha256:63cb5a5e933fc913e5823a0dfdc3c99add73f52d139d6cd5cc8639d0e0465513", size = 11675315, upload-time = "2025-08-07T19:05:06.15Z" }, + { url = "https://files.pythonhosted.org/packages/24/ee/af6e5c2a8ca3a81676d5480a1025494fd104b8896266502bb4de2a0e8388/ruff-0.12.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9a9bbe28f9f551accf84a24c366c1aa8774d6748438b47174f8e8565ab9dedbc", size = 12456653, upload-time = "2025-08-07T19:05:09.759Z" }, + { url = "https://files.pythonhosted.org/packages/99/9d/e91f84dfe3866fa648c10512904991ecc326fd0b66578b324ee6ecb8f725/ruff-0.12.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:2fae54e752a3150f7ee0e09bce2e133caf10ce9d971510a9b925392dc98d2fec", size = 11659690, upload-time = "2025-08-07T19:05:12.551Z" }, + { url = "https://files.pythonhosted.org/packages/fe/ac/a363d25ec53040408ebdd4efcee929d48547665858ede0505d1d8041b2e5/ruff-0.12.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c0acbcf01206df963d9331b5838fb31f3b44fa979ee7fa368b9b9057d89f4a53", size = 11896923, upload-time = "2025-08-07T19:05:14.821Z" }, + { url = "https://files.pythonhosted.org/packages/58/9f/ea356cd87c395f6ade9bb81365bd909ff60860975ca1bc39f0e59de3da37/ruff-0.12.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ae3e7504666ad4c62f9ac8eedb52a93f9ebdeb34742b8b71cd3cccd24912719f", size = 11477612, upload-time = "2025-08-07T19:05:16.712Z" }, + { url = "https://files.pythonhosted.org/packages/1a/46/92e8fa3c9dcfd49175225c09053916cb97bb7204f9f899c2f2baca69e450/ruff-0.12.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cb82efb5d35d07497813a1c5647867390a7d83304562607f3579602fa3d7d46f", size = 13182745, upload-time = "2025-08-07T19:05:18.709Z" }, + { url = "https://files.pythonhosted.org/packages/5e/c4/f2176a310f26e6160deaf661ef60db6c3bb62b7a35e57ae28f27a09a7d63/ruff-0.12.8-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:dbea798fc0065ad0b84a2947b0aff4233f0cb30f226f00a2c5850ca4393de609", size = 14206885, upload-time = "2025-08-07T19:05:21.025Z" }, + { url = "https://files.pythonhosted.org/packages/87/9d/98e162f3eeeb6689acbedbae5050b4b3220754554526c50c292b611d3a63/ruff-0.12.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:49ebcaccc2bdad86fd51b7864e3d808aad404aab8df33d469b6e65584656263a", size = 13639381, upload-time = "2025-08-07T19:05:23.423Z" }, + { url = "https://files.pythonhosted.org/packages/81/4e/1b7478b072fcde5161b48f64774d6edd59d6d198e4ba8918d9f4702b8043/ruff-0.12.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ac9c570634b98c71c88cb17badd90f13fc076a472ba6ef1d113d8ed3df109fb", size = 12613271, upload-time = "2025-08-07T19:05:25.507Z" }, + { url = "https://files.pythonhosted.org/packages/e8/67/0c3c9179a3ad19791ef1b8f7138aa27d4578c78700551c60d9260b2c660d/ruff-0.12.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:560e0cd641e45591a3e42cb50ef61ce07162b9c233786663fdce2d8557d99818", size = 12847783, upload-time = "2025-08-07T19:05:28.14Z" }, + { url = "https://files.pythonhosted.org/packages/4e/2a/0b6ac3dd045acf8aa229b12c9c17bb35508191b71a14904baf99573a21bd/ruff-0.12.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:71c83121512e7743fba5a8848c261dcc454cafb3ef2934a43f1b7a4eb5a447ea", size = 11702672, upload-time = "2025-08-07T19:05:30.413Z" }, + { url = "https://files.pythonhosted.org/packages/9d/ee/f9fdc9f341b0430110de8b39a6ee5fa68c5706dc7c0aa940817947d6937e/ruff-0.12.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:de4429ef2ba091ecddedd300f4c3f24bca875d3d8b23340728c3cb0da81072c3", size = 11440626, upload-time = "2025-08-07T19:05:32.492Z" }, + { url = "https://files.pythonhosted.org/packages/89/fb/b3aa2d482d05f44e4d197d1de5e3863feb13067b22c571b9561085c999dc/ruff-0.12.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a2cab5f60d5b65b50fba39a8950c8746df1627d54ba1197f970763917184b161", size = 12462162, upload-time = "2025-08-07T19:05:34.449Z" }, + { url = "https://files.pythonhosted.org/packages/18/9f/5c5d93e1d00d854d5013c96e1a92c33b703a0332707a7cdbd0a4880a84fb/ruff-0.12.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:45c32487e14f60b88aad6be9fd5da5093dbefb0e3e1224131cb1d441d7cb7d46", size = 12913212, upload-time = "2025-08-07T19:05:36.541Z" }, + { url = "https://files.pythonhosted.org/packages/71/13/ab9120add1c0e4604c71bfc2e4ef7d63bebece0cfe617013da289539cef8/ruff-0.12.8-py3-none-win32.whl", hash = "sha256:daf3475060a617fd5bc80638aeaf2f5937f10af3ec44464e280a9d2218e720d3", size = 11694382, upload-time = "2025-08-07T19:05:38.468Z" }, + { url = "https://files.pythonhosted.org/packages/f6/dc/a2873b7c5001c62f46266685863bee2888caf469d1edac84bf3242074be2/ruff-0.12.8-py3-none-win_amd64.whl", hash = "sha256:7209531f1a1fcfbe8e46bcd7ab30e2f43604d8ba1c49029bb420b103d0b5f76e", size = 12740482, upload-time = "2025-08-07T19:05:40.391Z" }, + { url = "https://files.pythonhosted.org/packages/cb/5c/799a1efb8b5abab56e8a9f2a0b72d12bd64bb55815e9476c7d0a2887d2f7/ruff-0.12.8-py3-none-win_arm64.whl", hash = "sha256:c90e1a334683ce41b0e7a04f41790c429bf5073b62c1ae701c9dc5b3d14f0749", size = 11884718, upload-time = "2025-08-07T19:05:42.866Z" }, ] [[package]] @@ -2826,27 +2828,27 @@ dependencies = [ { name = "cryptography" }, { name = "jeepney" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/53/a4/f48c9d79cb507ed1373477dbceaba7401fd8a23af63b837fa61f1dcd3691/SecretStorage-3.3.3.tar.gz", hash = "sha256:2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77", size = 19739 } +sdist = { url = "https://files.pythonhosted.org/packages/53/a4/f48c9d79cb507ed1373477dbceaba7401fd8a23af63b837fa61f1dcd3691/SecretStorage-3.3.3.tar.gz", hash = "sha256:2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77", size = 19739, upload-time = "2022-08-13T16:22:46.976Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/54/24/b4293291fa1dd830f353d2cb163295742fa87f179fcc8a20a306a81978b7/SecretStorage-3.3.3-py3-none-any.whl", hash = "sha256:f356e6628222568e3af06f2eba8df495efa13b3b63081dafd4f7d9a7b7bc9f99", size = 15221 }, + { url = "https://files.pythonhosted.org/packages/54/24/b4293291fa1dd830f353d2cb163295742fa87f179fcc8a20a306a81978b7/SecretStorage-3.3.3-py3-none-any.whl", hash = "sha256:f356e6628222568e3af06f2eba8df495efa13b3b63081dafd4f7d9a7b7bc9f99", size = 15221, upload-time = "2022-08-13T16:22:44.457Z" }, ] [[package]] name = "six" version = "1.17.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031 } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050 }, + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, ] [[package]] name = "sniffio" version = "1.3.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 }, + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, ] [[package]] @@ -2857,18 +2859,18 @@ dependencies = [ { name = "requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ff/77/1ab6e5bafb9c80d8128f065a355377a04ac5b3c38eb719d920a9909d346e/snowplow_tracker-1.1.0.tar.gz", hash = "sha256:95d8fdc8bd542fd12a0b9a076852239cbaf0599eda8721deaf5f93f7138fe755", size = 34135 } +sdist = { url = "https://files.pythonhosted.org/packages/ff/77/1ab6e5bafb9c80d8128f065a355377a04ac5b3c38eb719d920a9909d346e/snowplow_tracker-1.1.0.tar.gz", hash = "sha256:95d8fdc8bd542fd12a0b9a076852239cbaf0599eda8721deaf5f93f7138fe755", size = 34135, upload-time = "2025-02-21T10:58:48.112Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/78/10/1c76269cbf2d6e127f4415044d9ddb0295858230678bbf4bfba905593c82/snowplow_tracker-1.1.0-py3-none-any.whl", hash = "sha256:24ea32ddac9cca547421bf9ab162f5f33c00711c6ef118ad5f78093cee962224", size = 44128 }, + { url = "https://files.pythonhosted.org/packages/78/10/1c76269cbf2d6e127f4415044d9ddb0295858230678bbf4bfba905593c82/snowplow_tracker-1.1.0-py3-none-any.whl", hash = "sha256:24ea32ddac9cca547421bf9ab162f5f33c00711c6ef118ad5f78093cee962224", size = 44128, upload-time = "2025-02-21T10:58:45.818Z" }, ] [[package]] name = "sqlparse" version = "0.5.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e5/40/edede8dd6977b0d3da179a342c198ed100dd2aba4be081861ee5911e4da4/sqlparse-0.5.3.tar.gz", hash = "sha256:09f67787f56a0b16ecdbde1bfc7f5d9c3371ca683cfeaa8e6ff60b4807ec9272", size = 84999 } +sdist = { url = "https://files.pythonhosted.org/packages/e5/40/edede8dd6977b0d3da179a342c198ed100dd2aba4be081861ee5911e4da4/sqlparse-0.5.3.tar.gz", hash = "sha256:09f67787f56a0b16ecdbde1bfc7f5d9c3371ca683cfeaa8e6ff60b4807ec9272", size = 84999, upload-time = "2024-12-10T12:05:30.728Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a9/5c/bfd6bd0bf979426d405cc6e71eceb8701b148b16c21d2dc3c261efc61c7b/sqlparse-0.5.3-py3-none-any.whl", hash = "sha256:cf2196ed3418f3ba5de6af7e82c694a9fbdbfecccdfc72e281548517081f16ca", size = 44415 }, + { url = "https://files.pythonhosted.org/packages/a9/5c/bfd6bd0bf979426d405cc6e71eceb8701b148b16c21d2dc3c261efc61c7b/sqlparse-0.5.3-py3-none-any.whl", hash = "sha256:cf2196ed3418f3ba5de6af7e82c694a9fbdbfecccdfc72e281548517081f16ca", size = 44415, upload-time = "2024-12-10T12:05:27.824Z" }, ] [[package]] @@ -2878,9 +2880,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/07/3e/eae74d8d33e3262bae0a7e023bb43d8bdd27980aa3557333f4632611151f/sse_starlette-2.4.1.tar.gz", hash = "sha256:7c8a800a1ca343e9165fc06bbda45c78e4c6166320707ae30b416c42da070926", size = 18635 } +sdist = { url = "https://files.pythonhosted.org/packages/07/3e/eae74d8d33e3262bae0a7e023bb43d8bdd27980aa3557333f4632611151f/sse_starlette-2.4.1.tar.gz", hash = "sha256:7c8a800a1ca343e9165fc06bbda45c78e4c6166320707ae30b416c42da070926", size = 18635, upload-time = "2025-07-06T09:41:33.631Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e4/f1/6c7eaa8187ba789a6dd6d74430307478d2a91c23a5452ab339b6fbe15a08/sse_starlette-2.4.1-py3-none-any.whl", hash = "sha256:08b77ea898ab1a13a428b2b6f73cfe6d0e607a7b4e15b9bb23e4a37b087fd39a", size = 10824 }, + { url = "https://files.pythonhosted.org/packages/e4/f1/6c7eaa8187ba789a6dd6d74430307478d2a91c23a5452ab339b6fbe15a08/sse_starlette-2.4.1-py3-none-any.whl", hash = "sha256:08b77ea898ab1a13a428b2b6f73cfe6d0e607a7b4e15b9bb23e4a37b087fd39a", size = 10824, upload-time = "2025-07-06T09:41:32.321Z" }, ] [[package]] @@ -2891,57 +2893,57 @@ dependencies = [ { name = "anyio" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0a/69/662169fdb92fb96ec3eaee218cf540a629d629c86d7993d9651226a6789b/starlette-0.47.1.tar.gz", hash = "sha256:aef012dd2b6be325ffa16698f9dc533614fb1cebd593a906b90dc1025529a79b", size = 2583072 } +sdist = { url = "https://files.pythonhosted.org/packages/0a/69/662169fdb92fb96ec3eaee218cf540a629d629c86d7993d9651226a6789b/starlette-0.47.1.tar.gz", hash = "sha256:aef012dd2b6be325ffa16698f9dc533614fb1cebd593a906b90dc1025529a79b", size = 2583072, upload-time = "2025-06-21T04:03:17.337Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/82/95/38ef0cd7fa11eaba6a99b3c4f5ac948d8bc6ff199aabd327a29cc000840c/starlette-0.47.1-py3-none-any.whl", hash = "sha256:5e11c9f5c7c3f24959edbf2dffdc01bba860228acf657129467d8a7468591527", size = 72747 }, + { url = "https://files.pythonhosted.org/packages/82/95/38ef0cd7fa11eaba6a99b3c4f5ac948d8bc6ff199aabd327a29cc000840c/starlette-0.47.1-py3-none-any.whl", hash = "sha256:5e11c9f5c7c3f24959edbf2dffdc01bba860228acf657129467d8a7468591527", size = 72747, upload-time = "2025-06-21T04:03:15.705Z" }, ] [[package]] name = "text-unidecode" version = "1.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ab/e2/e9a00f0ccb71718418230718b3d900e71a5d16e701a3dae079a21e9cd8f8/text-unidecode-1.3.tar.gz", hash = "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93", size = 76885 } +sdist = { url = "https://files.pythonhosted.org/packages/ab/e2/e9a00f0ccb71718418230718b3d900e71a5d16e701a3dae079a21e9cd8f8/text-unidecode-1.3.tar.gz", hash = "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93", size = 76885, upload-time = "2019-08-30T21:36:45.405Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a6/a5/c0b6468d3824fe3fde30dbb5e1f687b291608f9473681bbf7dabbf5a87d7/text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8", size = 78154 }, + { url = "https://files.pythonhosted.org/packages/a6/a5/c0b6468d3824fe3fde30dbb5e1f687b291608f9473681bbf7dabbf5a87d7/text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8", size = 78154, upload-time = "2019-08-30T21:37:03.543Z" }, ] [[package]] name = "tomli" version = "2.2.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077 }, - { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429 }, - { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067 }, - { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030 }, - { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898 }, - { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894 }, - { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319 }, - { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273 }, - { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310 }, - { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309 }, - { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762 }, - { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453 }, - { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486 }, - { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349 }, - { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159 }, - { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243 }, - { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645 }, - { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584 }, - { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875 }, - { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418 }, - { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708 }, - { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582 }, - { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543 }, - { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691 }, - { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170 }, - { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530 }, - { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666 }, - { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954 }, - { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724 }, - { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383 }, - { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257 }, +sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175, upload-time = "2024-11-27T22:38:36.873Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077, upload-time = "2024-11-27T22:37:54.956Z" }, + { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429, upload-time = "2024-11-27T22:37:56.698Z" }, + { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067, upload-time = "2024-11-27T22:37:57.63Z" }, + { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030, upload-time = "2024-11-27T22:37:59.344Z" }, + { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898, upload-time = "2024-11-27T22:38:00.429Z" }, + { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894, upload-time = "2024-11-27T22:38:02.094Z" }, + { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319, upload-time = "2024-11-27T22:38:03.206Z" }, + { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273, upload-time = "2024-11-27T22:38:04.217Z" }, + { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310, upload-time = "2024-11-27T22:38:05.908Z" }, + { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309, upload-time = "2024-11-27T22:38:06.812Z" }, + { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762, upload-time = "2024-11-27T22:38:07.731Z" }, + { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453, upload-time = "2024-11-27T22:38:09.384Z" }, + { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486, upload-time = "2024-11-27T22:38:10.329Z" }, + { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349, upload-time = "2024-11-27T22:38:11.443Z" }, + { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159, upload-time = "2024-11-27T22:38:13.099Z" }, + { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243, upload-time = "2024-11-27T22:38:14.766Z" }, + { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645, upload-time = "2024-11-27T22:38:15.843Z" }, + { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584, upload-time = "2024-11-27T22:38:17.645Z" }, + { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875, upload-time = "2024-11-27T22:38:19.159Z" }, + { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418, upload-time = "2024-11-27T22:38:20.064Z" }, + { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708, upload-time = "2024-11-27T22:38:21.659Z" }, + { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582, upload-time = "2024-11-27T22:38:22.693Z" }, + { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543, upload-time = "2024-11-27T22:38:24.367Z" }, + { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691, upload-time = "2024-11-27T22:38:26.081Z" }, + { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170, upload-time = "2024-11-27T22:38:27.921Z" }, + { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530, upload-time = "2024-11-27T22:38:29.591Z" }, + { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666, upload-time = "2024-11-27T22:38:30.639Z" }, + { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954, upload-time = "2024-11-27T22:38:31.702Z" }, + { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724, upload-time = "2024-11-27T22:38:32.837Z" }, + { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383, upload-time = "2024-11-27T22:38:34.455Z" }, + { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" }, ] [[package]] @@ -2959,9 +2961,9 @@ dependencies = [ { name = "rich" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c8/a2/6df94fc5c8e2170d21d7134a565c3a8fb84f9797c1dd65a5976aaf714418/twine-6.1.0.tar.gz", hash = "sha256:be324f6272eff91d07ee93f251edf232fc647935dd585ac003539b42404a8dbd", size = 168404 } +sdist = { url = "https://files.pythonhosted.org/packages/c8/a2/6df94fc5c8e2170d21d7134a565c3a8fb84f9797c1dd65a5976aaf714418/twine-6.1.0.tar.gz", hash = "sha256:be324f6272eff91d07ee93f251edf232fc647935dd585ac003539b42404a8dbd", size = 168404, upload-time = "2025-01-21T18:45:26.758Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7c/b6/74e927715a285743351233f33ea3c684528a0d374d2e43ff9ce9585b73fe/twine-6.1.0-py3-none-any.whl", hash = "sha256:a47f973caf122930bf0fbbf17f80b83bc1602c9ce393c7845f289a3001dc5384", size = 40791 }, + { url = "https://files.pythonhosted.org/packages/7c/b6/74e927715a285743351233f33ea3c684528a0d374d2e43ff9ce9585b73fe/twine-6.1.0-py3-none-any.whl", hash = "sha256:a47f973caf122930bf0fbbf17f80b83bc1602c9ce393c7845f289a3001dc5384", size = 40791, upload-time = "2025-01-21T18:45:24.584Z" }, ] [[package]] @@ -2971,45 +2973,45 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "referencing" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e2/78/0b2102e988c221851f4bb98f3b008b52e52b56ef01379e10f640e033d31a/types_jsonschema-4.25.0.20250809.tar.gz", hash = "sha256:83c2a0ed5365c731a68d6e815e2063ea22ccf4547a74b1d5ed0ac234dd3de86e", size = 15423 } +sdist = { url = "https://files.pythonhosted.org/packages/e2/78/0b2102e988c221851f4bb98f3b008b52e52b56ef01379e10f640e033d31a/types_jsonschema-4.25.0.20250809.tar.gz", hash = "sha256:83c2a0ed5365c731a68d6e815e2063ea22ccf4547a74b1d5ed0ac234dd3de86e", size = 15423, upload-time = "2025-08-09T03:17:07.207Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/db/f0/6b0d5d48e9e8c8515055ed69808abea557e66cadf6d3c638a1c3cd9f908c/types_jsonschema-4.25.0.20250809-py3-none-any.whl", hash = "sha256:dcd0fee69feb0f0763555c0307f0c5d58cc0c1a55984e66a04f4ef4ae1efb507", size = 15722 }, + { url = "https://files.pythonhosted.org/packages/db/f0/6b0d5d48e9e8c8515055ed69808abea557e66cadf6d3c638a1c3cd9f908c/types_jsonschema-4.25.0.20250809-py3-none-any.whl", hash = "sha256:dcd0fee69feb0f0763555c0307f0c5d58cc0c1a55984e66a04f4ef4ae1efb507", size = 15722, upload-time = "2025-08-09T03:17:06.41Z" }, ] [[package]] name = "types-psutil" version = "7.0.0.20251111" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1a/ba/4f48c927f38c7a4d6f7ff65cde91c49d28a95a56e00ec19b2813e1e0b1c1/types_psutil-7.0.0.20251111.tar.gz", hash = "sha256:d109ee2da4c0a9b69b8cefc46e195db8cf0fc0200b6641480df71e7f3f51a239", size = 20287 } +sdist = { url = "https://files.pythonhosted.org/packages/1a/ba/4f48c927f38c7a4d6f7ff65cde91c49d28a95a56e00ec19b2813e1e0b1c1/types_psutil-7.0.0.20251111.tar.gz", hash = "sha256:d109ee2da4c0a9b69b8cefc46e195db8cf0fc0200b6641480df71e7f3f51a239", size = 20287, upload-time = "2025-11-11T03:06:37.482Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fb/bc/b081d10fbd933cdf839109707a693c668a174e2276d64159a582a9cebd3f/types_psutil-7.0.0.20251111-py3-none-any.whl", hash = "sha256:85ba00205dcfa3c73685122e5a360205d2fbc9b56f942b591027bf401ce0cc47", size = 23052 }, + { url = "https://files.pythonhosted.org/packages/fb/bc/b081d10fbd933cdf839109707a693c668a174e2276d64159a582a9cebd3f/types_psutil-7.0.0.20251111-py3-none-any.whl", hash = "sha256:85ba00205dcfa3c73685122e5a360205d2fbc9b56f942b591027bf401ce0cc47", size = 23052, upload-time = "2025-11-11T03:06:36.011Z" }, ] [[package]] name = "types-pytz" version = "2025.2.0.20250809" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/07/e2/c774f754de26848f53f05defff5bb21dd9375a059d1ba5b5ea943cf8206e/types_pytz-2025.2.0.20250809.tar.gz", hash = "sha256:222e32e6a29bb28871f8834e8785e3801f2dc4441c715cd2082b271eecbe21e5", size = 10876 } +sdist = { url = "https://files.pythonhosted.org/packages/07/e2/c774f754de26848f53f05defff5bb21dd9375a059d1ba5b5ea943cf8206e/types_pytz-2025.2.0.20250809.tar.gz", hash = "sha256:222e32e6a29bb28871f8834e8785e3801f2dc4441c715cd2082b271eecbe21e5", size = 10876, upload-time = "2025-08-09T03:14:17.453Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/db/d0/91c24fe54e565f2344d7a6821e6c6bb099841ef09007ea6321a0bac0f808/types_pytz-2025.2.0.20250809-py3-none-any.whl", hash = "sha256:4f55ed1b43e925cf851a756fe1707e0f5deeb1976e15bf844bcaa025e8fbd0db", size = 10095 }, + { url = "https://files.pythonhosted.org/packages/db/d0/91c24fe54e565f2344d7a6821e6c6bb099841ef09007ea6321a0bac0f808/types_pytz-2025.2.0.20250809-py3-none-any.whl", hash = "sha256:4f55ed1b43e925cf851a756fe1707e0f5deeb1976e15bf844bcaa025e8fbd0db", size = 10095, upload-time = "2025-08-09T03:14:16.674Z" }, ] [[package]] name = "types-pyyaml" version = "6.0.12.20250516" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4e/22/59e2aeb48ceeee1f7cd4537db9568df80d62bdb44a7f9e743502ea8aab9c/types_pyyaml-6.0.12.20250516.tar.gz", hash = "sha256:9f21a70216fc0fa1b216a8176db5f9e0af6eb35d2f2932acb87689d03a5bf6ba", size = 17378 } +sdist = { url = "https://files.pythonhosted.org/packages/4e/22/59e2aeb48ceeee1f7cd4537db9568df80d62bdb44a7f9e743502ea8aab9c/types_pyyaml-6.0.12.20250516.tar.gz", hash = "sha256:9f21a70216fc0fa1b216a8176db5f9e0af6eb35d2f2932acb87689d03a5bf6ba", size = 17378, upload-time = "2025-05-16T03:08:04.897Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/99/5f/e0af6f7f6a260d9af67e1db4f54d732abad514252a7a378a6c4d17dd1036/types_pyyaml-6.0.12.20250516-py3-none-any.whl", hash = "sha256:8478208feaeb53a34cb5d970c56a7cd76b72659442e733e268a94dc72b2d0530", size = 20312 }, + { url = "https://files.pythonhosted.org/packages/99/5f/e0af6f7f6a260d9af67e1db4f54d732abad514252a7a378a6c4d17dd1036/types_pyyaml-6.0.12.20250516-py3-none-any.whl", hash = "sha256:8478208feaeb53a34cb5d970c56a7cd76b72659442e733e268a94dc72b2d0530", size = 20312, upload-time = "2025-05-16T03:08:04.019Z" }, ] [[package]] name = "typing-extensions" version = "4.14.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/98/5a/da40306b885cc8c09109dc2e1abd358d5684b1425678151cdaed4731c822/typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", size = 107673 } +sdist = { url = "https://files.pythonhosted.org/packages/98/5a/da40306b885cc8c09109dc2e1abd358d5684b1425678151cdaed4731c822/typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", size = 107673, upload-time = "2025-07-04T13:28:34.16Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b5/00/d631e67a838026495268c2f6884f3711a15a9a2a96cd244fdaea53b823fb/typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76", size = 43906 }, + { url = "https://files.pythonhosted.org/packages/b5/00/d631e67a838026495268c2f6884f3711a15a9a2a96cd244fdaea53b823fb/typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76", size = 43906, upload-time = "2025-07-04T13:28:32.743Z" }, ] [[package]] @@ -3019,27 +3021,27 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f8/b1/0c11f5058406b3af7609f121aaa6b609744687f1d158b3c3a5bf4cc94238/typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28", size = 75726 } +sdist = { url = "https://files.pythonhosted.org/packages/f8/b1/0c11f5058406b3af7609f121aaa6b609744687f1d158b3c3a5bf4cc94238/typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28", size = 75726, upload-time = "2025-05-21T18:55:23.885Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552 }, + { url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552, upload-time = "2025-05-21T18:55:22.152Z" }, ] [[package]] name = "tzdata" version = "2025.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380 } +sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839 }, + { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" }, ] [[package]] name = "urllib3" version = "2.5.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185 } +sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795 }, + { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, ] [[package]] @@ -3051,9 +3053,9 @@ dependencies = [ { name = "h11" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5e/42/e0e305207bb88c6b8d3061399c6a961ffe5fbb7e2aa63c9234df7259e9cd/uvicorn-0.35.0.tar.gz", hash = "sha256:bc662f087f7cf2ce11a1d7fd70b90c9f98ef2e2831556dd078d131b96cc94a01", size = 78473 } +sdist = { url = "https://files.pythonhosted.org/packages/5e/42/e0e305207bb88c6b8d3061399c6a961ffe5fbb7e2aa63c9234df7259e9cd/uvicorn-0.35.0.tar.gz", hash = "sha256:bc662f087f7cf2ce11a1d7fd70b90c9f98ef2e2831556dd078d131b96cc94a01", size = 78473, upload-time = "2025-06-28T16:15:46.058Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/e2/dc81b1bd1dcfe91735810265e9d26bc8ec5da45b4c0f6237e286819194c3/uvicorn-0.35.0-py3-none-any.whl", hash = "sha256:197535216b25ff9b785e29a0b79199f55222193d47f820816e7da751e9bc8d4a", size = 66406 }, + { url = "https://files.pythonhosted.org/packages/d2/e2/dc81b1bd1dcfe91735810265e9d26bc8ec5da45b4c0f6237e286819194c3/uvicorn-0.35.0-py3-none-any.whl", hash = "sha256:197535216b25ff9b785e29a0b79199f55222193d47f820816e7da751e9bc8d4a", size = 66406, upload-time = "2025-06-28T16:15:44.816Z" }, ] [package.optional-dependencies] @@ -3071,44 +3073,44 @@ standard = [ name = "uvloop" 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 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/eb/14/ecceb239b65adaaf7fde510aa8bd534075695d1e5f8dadfa32b5723d9cfb/uvloop-0.22.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ef6f0d4cc8a9fa1f6a910230cd53545d9a14479311e87e3cb225495952eb672c", size = 1343335 }, - { url = "https://files.pythonhosted.org/packages/ba/ae/6f6f9af7f590b319c94532b9567409ba11f4fa71af1148cab1bf48a07048/uvloop-0.22.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7cd375a12b71d33d46af85a3343b35d98e8116134ba404bd657b3b1d15988792", size = 742903 }, - { url = "https://files.pythonhosted.org/packages/09/bd/3667151ad0702282a1f4d5d29288fce8a13c8b6858bf0978c219cd52b231/uvloop-0.22.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ac33ed96229b7790eb729702751c0e93ac5bc3bcf52ae9eccbff30da09194b86", size = 3648499 }, - { url = "https://files.pythonhosted.org/packages/b3/f6/21657bb3beb5f8c57ce8be3b83f653dd7933c2fd00545ed1b092d464799a/uvloop-0.22.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:481c990a7abe2c6f4fc3d98781cc9426ebd7f03a9aaa7eb03d3bfc68ac2a46bd", size = 3700133 }, - { url = "https://files.pythonhosted.org/packages/09/e0/604f61d004ded805f24974c87ddd8374ef675644f476f01f1df90e4cdf72/uvloop-0.22.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a592b043a47ad17911add5fbd087c76716d7c9ccc1d64ec9249ceafd735f03c2", size = 3512681 }, - { url = "https://files.pythonhosted.org/packages/bb/ce/8491fd370b0230deb5eac69c7aae35b3be527e25a911c0acdffb922dc1cd/uvloop-0.22.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1489cf791aa7b6e8c8be1c5a080bae3a672791fcb4e9e12249b05862a2ca9cec", size = 3615261 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { url = "https://files.pythonhosted.org/packages/5f/6f/e62b4dfc7ad6518e7eff2516f680d02a0f6eb62c0c212e152ca708a0085e/uvloop-0.22.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b5b1ac819a3f946d3b2ee07f09149578ae76066d70b44df3fa990add49a82e4", size = 4426307 }, - { url = "https://files.pythonhosted.org/packages/90/60/97362554ac21e20e81bcef1150cb2a7e4ffdaf8ea1e5b2e8bf7a053caa18/uvloop-0.22.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e047cc068570bac9866237739607d1313b9253c3051ad84738cbb095be0537b2", size = 4131970 }, - { url = "https://files.pythonhosted.org/packages/99/39/6b3f7d234ba3964c428a6e40006340f53ba37993f46ed6e111c6e9141d18/uvloop-0.22.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:512fec6815e2dd45161054592441ef76c830eddaad55c8aa30952e6fe1ed07c0", size = 4296343 }, - { url = "https://files.pythonhosted.org/packages/89/8c/182a2a593195bfd39842ea68ebc084e20c850806117213f5a299dfc513d9/uvloop-0.22.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:561577354eb94200d75aca23fbde86ee11be36b00e52a4eaf8f50fb0c86b7705", size = 1358611 }, - { url = "https://files.pythonhosted.org/packages/d2/14/e301ee96a6dc95224b6f1162cd3312f6d1217be3907b79173b06785f2fe7/uvloop-0.22.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cdf5192ab3e674ca26da2eada35b288d2fa49fdd0f357a19f0e7c4e7d5077c8", size = 751811 }, - { url = "https://files.pythonhosted.org/packages/b7/02/654426ce265ac19e2980bfd9ea6590ca96a56f10c76e63801a2df01c0486/uvloop-0.22.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e2ea3d6190a2968f4a14a23019d3b16870dd2190cd69c8180f7c632d21de68d", size = 4288562 }, - { url = "https://files.pythonhosted.org/packages/15/c0/0be24758891ef825f2065cd5db8741aaddabe3e248ee6acc5e8a80f04005/uvloop-0.22.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0530a5fbad9c9e4ee3f2b33b148c6a64d47bbad8000ea63704fa8260f4cf728e", size = 4366890 }, - { url = "https://files.pythonhosted.org/packages/d2/53/8369e5219a5855869bcee5f4d317f6da0e2c669aecf0ef7d371e3d084449/uvloop-0.22.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bc5ef13bbc10b5335792360623cc378d52d7e62c2de64660616478c32cd0598e", size = 4119472 }, - { url = "https://files.pythonhosted.org/packages/f8/ba/d69adbe699b768f6b29a5eec7b47dd610bd17a69de51b251126a801369ea/uvloop-0.22.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1f38ec5e3f18c8a10ded09742f7fb8de0108796eb673f30ce7762ce1b8550cad", size = 4239051 }, - { url = "https://files.pythonhosted.org/packages/90/cd/b62bdeaa429758aee8de8b00ac0dd26593a9de93d302bff3d21439e9791d/uvloop-0.22.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3879b88423ec7e97cd4eba2a443aa26ed4e59b45e6b76aabf13fe2f27023a142", size = 1362067 }, - { url = "https://files.pythonhosted.org/packages/0d/f8/a132124dfda0777e489ca86732e85e69afcd1ff7686647000050ba670689/uvloop-0.22.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4baa86acedf1d62115c1dc6ad1e17134476688f08c6efd8a2ab076e815665c74", size = 752423 }, - { url = "https://files.pythonhosted.org/packages/a3/94/94af78c156f88da4b3a733773ad5ba0b164393e357cc4bd0ab2e2677a7d6/uvloop-0.22.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:297c27d8003520596236bdb2335e6b3f649480bd09e00d1e3a99144b691d2a35", size = 4272437 }, - { url = "https://files.pythonhosted.org/packages/b5/35/60249e9fd07b32c665192cec7af29e06c7cd96fa1d08b84f012a56a0b38e/uvloop-0.22.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c1955d5a1dd43198244d47664a5858082a3239766a839b2102a269aaff7a4e25", size = 4292101 }, - { url = "https://files.pythonhosted.org/packages/02/62/67d382dfcb25d0a98ce73c11ed1a6fba5037a1a1d533dcbb7cab033a2636/uvloop-0.22.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b31dc2fccbd42adc73bc4e7cdbae4fc5086cf378979e53ca5d0301838c5682c6", size = 4114158 }, - { url = "https://files.pythonhosted.org/packages/f0/7a/f1171b4a882a5d13c8b7576f348acfe6074d72eaf52cccef752f748d4a9f/uvloop-0.22.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:93f617675b2d03af4e72a5333ef89450dfaa5321303ede6e67ba9c9d26878079", size = 4177360 }, - { url = "https://files.pythonhosted.org/packages/79/7b/b01414f31546caf0919da80ad57cbfe24c56b151d12af68cee1b04922ca8/uvloop-0.22.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:37554f70528f60cad66945b885eb01f1bb514f132d92b6eeed1c90fd54ed6289", size = 1454790 }, - { url = "https://files.pythonhosted.org/packages/d4/31/0bb232318dd838cad3fa8fb0c68c8b40e1145b32025581975e18b11fab40/uvloop-0.22.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b76324e2dc033a0b2f435f33eb88ff9913c156ef78e153fb210e03c13da746b3", size = 796783 }, - { url = "https://files.pythonhosted.org/packages/42/38/c9b09f3271a7a723a5de69f8e237ab8e7803183131bc57c890db0b6bb872/uvloop-0.22.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:badb4d8e58ee08dad957002027830d5c3b06aea446a6a3744483c2b3b745345c", size = 4647548 }, - { url = "https://files.pythonhosted.org/packages/c1/37/945b4ca0ac27e3dc4952642d4c900edd030b3da6c9634875af6e13ae80e5/uvloop-0.22.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b91328c72635f6f9e0282e4a57da7470c7350ab1c9f48546c0f2866205349d21", size = 4467065 }, - { url = "https://files.pythonhosted.org/packages/97/cc/48d232f33d60e2e2e0b42f4e73455b146b76ebe216487e862700457fbf3c/uvloop-0.22.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:daf620c2995d193449393d6c62131b3fbd40a63bf7b307a1527856ace637fe88", size = 4328384 }, - { url = "https://files.pythonhosted.org/packages/e4/16/c1fd27e9549f3c4baf1dc9c20c456cd2f822dbf8de9f463824b0c0357e06/uvloop-0.22.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6cde23eeda1a25c75b2e07d39970f3374105d5eafbaab2a4482be82f272d5a5e", size = 4296730 }, +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/eb/14/ecceb239b65adaaf7fde510aa8bd534075695d1e5f8dadfa32b5723d9cfb/uvloop-0.22.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ef6f0d4cc8a9fa1f6a910230cd53545d9a14479311e87e3cb225495952eb672c", size = 1343335, upload-time = "2025-10-16T22:16:11.43Z" }, + { url = "https://files.pythonhosted.org/packages/ba/ae/6f6f9af7f590b319c94532b9567409ba11f4fa71af1148cab1bf48a07048/uvloop-0.22.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7cd375a12b71d33d46af85a3343b35d98e8116134ba404bd657b3b1d15988792", size = 742903, upload-time = "2025-10-16T22:16:12.979Z" }, + { url = "https://files.pythonhosted.org/packages/09/bd/3667151ad0702282a1f4d5d29288fce8a13c8b6858bf0978c219cd52b231/uvloop-0.22.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ac33ed96229b7790eb729702751c0e93ac5bc3bcf52ae9eccbff30da09194b86", size = 3648499, upload-time = "2025-10-16T22:16:14.451Z" }, + { url = "https://files.pythonhosted.org/packages/b3/f6/21657bb3beb5f8c57ce8be3b83f653dd7933c2fd00545ed1b092d464799a/uvloop-0.22.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:481c990a7abe2c6f4fc3d98781cc9426ebd7f03a9aaa7eb03d3bfc68ac2a46bd", size = 3700133, upload-time = "2025-10-16T22:16:16.272Z" }, + { url = "https://files.pythonhosted.org/packages/09/e0/604f61d004ded805f24974c87ddd8374ef675644f476f01f1df90e4cdf72/uvloop-0.22.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a592b043a47ad17911add5fbd087c76716d7c9ccc1d64ec9249ceafd735f03c2", size = 3512681, upload-time = "2025-10-16T22:16:18.07Z" }, + { url = "https://files.pythonhosted.org/packages/bb/ce/8491fd370b0230deb5eac69c7aae35b3be527e25a911c0acdffb922dc1cd/uvloop-0.22.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1489cf791aa7b6e8c8be1c5a080bae3a672791fcb4e9e12249b05862a2ca9cec", size = 3615261, upload-time = "2025-10-16T22:16:19.596Z" }, + { 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" }, + { url = "https://files.pythonhosted.org/packages/5f/6f/e62b4dfc7ad6518e7eff2516f680d02a0f6eb62c0c212e152ca708a0085e/uvloop-0.22.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b5b1ac819a3f946d3b2ee07f09149578ae76066d70b44df3fa990add49a82e4", size = 4426307, upload-time = "2025-10-16T22:16:32.917Z" }, + { url = "https://files.pythonhosted.org/packages/90/60/97362554ac21e20e81bcef1150cb2a7e4ffdaf8ea1e5b2e8bf7a053caa18/uvloop-0.22.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e047cc068570bac9866237739607d1313b9253c3051ad84738cbb095be0537b2", size = 4131970, upload-time = "2025-10-16T22:16:34.015Z" }, + { url = "https://files.pythonhosted.org/packages/99/39/6b3f7d234ba3964c428a6e40006340f53ba37993f46ed6e111c6e9141d18/uvloop-0.22.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:512fec6815e2dd45161054592441ef76c830eddaad55c8aa30952e6fe1ed07c0", size = 4296343, upload-time = "2025-10-16T22:16:35.149Z" }, + { url = "https://files.pythonhosted.org/packages/89/8c/182a2a593195bfd39842ea68ebc084e20c850806117213f5a299dfc513d9/uvloop-0.22.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:561577354eb94200d75aca23fbde86ee11be36b00e52a4eaf8f50fb0c86b7705", size = 1358611, upload-time = "2025-10-16T22:16:36.833Z" }, + { url = "https://files.pythonhosted.org/packages/d2/14/e301ee96a6dc95224b6f1162cd3312f6d1217be3907b79173b06785f2fe7/uvloop-0.22.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cdf5192ab3e674ca26da2eada35b288d2fa49fdd0f357a19f0e7c4e7d5077c8", size = 751811, upload-time = "2025-10-16T22:16:38.275Z" }, + { url = "https://files.pythonhosted.org/packages/b7/02/654426ce265ac19e2980bfd9ea6590ca96a56f10c76e63801a2df01c0486/uvloop-0.22.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e2ea3d6190a2968f4a14a23019d3b16870dd2190cd69c8180f7c632d21de68d", size = 4288562, upload-time = "2025-10-16T22:16:39.375Z" }, + { url = "https://files.pythonhosted.org/packages/15/c0/0be24758891ef825f2065cd5db8741aaddabe3e248ee6acc5e8a80f04005/uvloop-0.22.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0530a5fbad9c9e4ee3f2b33b148c6a64d47bbad8000ea63704fa8260f4cf728e", size = 4366890, upload-time = "2025-10-16T22:16:40.547Z" }, + { url = "https://files.pythonhosted.org/packages/d2/53/8369e5219a5855869bcee5f4d317f6da0e2c669aecf0ef7d371e3d084449/uvloop-0.22.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bc5ef13bbc10b5335792360623cc378d52d7e62c2de64660616478c32cd0598e", size = 4119472, upload-time = "2025-10-16T22:16:41.694Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ba/d69adbe699b768f6b29a5eec7b47dd610bd17a69de51b251126a801369ea/uvloop-0.22.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1f38ec5e3f18c8a10ded09742f7fb8de0108796eb673f30ce7762ce1b8550cad", size = 4239051, upload-time = "2025-10-16T22:16:43.224Z" }, + { url = "https://files.pythonhosted.org/packages/90/cd/b62bdeaa429758aee8de8b00ac0dd26593a9de93d302bff3d21439e9791d/uvloop-0.22.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3879b88423ec7e97cd4eba2a443aa26ed4e59b45e6b76aabf13fe2f27023a142", size = 1362067, upload-time = "2025-10-16T22:16:44.503Z" }, + { url = "https://files.pythonhosted.org/packages/0d/f8/a132124dfda0777e489ca86732e85e69afcd1ff7686647000050ba670689/uvloop-0.22.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4baa86acedf1d62115c1dc6ad1e17134476688f08c6efd8a2ab076e815665c74", size = 752423, upload-time = "2025-10-16T22:16:45.968Z" }, + { url = "https://files.pythonhosted.org/packages/a3/94/94af78c156f88da4b3a733773ad5ba0b164393e357cc4bd0ab2e2677a7d6/uvloop-0.22.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:297c27d8003520596236bdb2335e6b3f649480bd09e00d1e3a99144b691d2a35", size = 4272437, upload-time = "2025-10-16T22:16:47.451Z" }, + { url = "https://files.pythonhosted.org/packages/b5/35/60249e9fd07b32c665192cec7af29e06c7cd96fa1d08b84f012a56a0b38e/uvloop-0.22.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c1955d5a1dd43198244d47664a5858082a3239766a839b2102a269aaff7a4e25", size = 4292101, upload-time = "2025-10-16T22:16:49.318Z" }, + { url = "https://files.pythonhosted.org/packages/02/62/67d382dfcb25d0a98ce73c11ed1a6fba5037a1a1d533dcbb7cab033a2636/uvloop-0.22.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b31dc2fccbd42adc73bc4e7cdbae4fc5086cf378979e53ca5d0301838c5682c6", size = 4114158, upload-time = "2025-10-16T22:16:50.517Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/f1171b4a882a5d13c8b7576f348acfe6074d72eaf52cccef752f748d4a9f/uvloop-0.22.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:93f617675b2d03af4e72a5333ef89450dfaa5321303ede6e67ba9c9d26878079", size = 4177360, upload-time = "2025-10-16T22:16:52.646Z" }, + { url = "https://files.pythonhosted.org/packages/79/7b/b01414f31546caf0919da80ad57cbfe24c56b151d12af68cee1b04922ca8/uvloop-0.22.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:37554f70528f60cad66945b885eb01f1bb514f132d92b6eeed1c90fd54ed6289", size = 1454790, upload-time = "2025-10-16T22:16:54.355Z" }, + { url = "https://files.pythonhosted.org/packages/d4/31/0bb232318dd838cad3fa8fb0c68c8b40e1145b32025581975e18b11fab40/uvloop-0.22.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b76324e2dc033a0b2f435f33eb88ff9913c156ef78e153fb210e03c13da746b3", size = 796783, upload-time = "2025-10-16T22:16:55.906Z" }, + { url = "https://files.pythonhosted.org/packages/42/38/c9b09f3271a7a723a5de69f8e237ab8e7803183131bc57c890db0b6bb872/uvloop-0.22.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:badb4d8e58ee08dad957002027830d5c3b06aea446a6a3744483c2b3b745345c", size = 4647548, upload-time = "2025-10-16T22:16:57.008Z" }, + { url = "https://files.pythonhosted.org/packages/c1/37/945b4ca0ac27e3dc4952642d4c900edd030b3da6c9634875af6e13ae80e5/uvloop-0.22.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b91328c72635f6f9e0282e4a57da7470c7350ab1c9f48546c0f2866205349d21", size = 4467065, upload-time = "2025-10-16T22:16:58.206Z" }, + { url = "https://files.pythonhosted.org/packages/97/cc/48d232f33d60e2e2e0b42f4e73455b146b76ebe216487e862700457fbf3c/uvloop-0.22.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:daf620c2995d193449393d6c62131b3fbd40a63bf7b307a1527856ace637fe88", size = 4328384, upload-time = "2025-10-16T22:16:59.36Z" }, + { url = "https://files.pythonhosted.org/packages/e4/16/c1fd27e9549f3c4baf1dc9c20c456cd2f822dbf8de9f463824b0c0357e06/uvloop-0.22.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6cde23eeda1a25c75b2e07d39970f3374105d5eafbaab2a4482be82f272d5a5e", size = 4296730, upload-time = "2025-10-16T22:17:00.744Z" }, ] [[package]] @@ -3118,159 +3120,159 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c2/c9/8869df9b2a2d6c59d79220a4db37679e74f807c559ffe5265e08b227a210/watchfiles-1.1.1.tar.gz", hash = "sha256:a173cb5c16c4f40ab19cecf48a534c409f7ea983ab8fed0741304a1c0a31b3f2", size = 94440 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a7/1a/206e8cf2dd86fddf939165a57b4df61607a1e0add2785f170a3f616b7d9f/watchfiles-1.1.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:eef58232d32daf2ac67f42dea51a2c80f0d03379075d44a587051e63cc2e368c", size = 407318 }, - { url = "https://files.pythonhosted.org/packages/b3/0f/abaf5262b9c496b5dad4ed3c0e799cbecb1f8ea512ecb6ddd46646a9fca3/watchfiles-1.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:03fa0f5237118a0c5e496185cafa92878568b652a2e9a9382a5151b1a0380a43", size = 394478 }, - { url = "https://files.pythonhosted.org/packages/b1/04/9cc0ba88697b34b755371f5ace8d3a4d9a15719c07bdc7bd13d7d8c6a341/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8ca65483439f9c791897f7db49202301deb6e15fe9f8fe2fed555bf986d10c31", size = 449894 }, - { url = "https://files.pythonhosted.org/packages/d2/9c/eda4615863cd8621e89aed4df680d8c3ec3da6a4cf1da113c17decd87c7f/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f0ab1c1af0cb38e3f598244c17919fb1a84d1629cc08355b0074b6d7f53138ac", size = 459065 }, - { url = "https://files.pythonhosted.org/packages/84/13/f28b3f340157d03cbc8197629bc109d1098764abe1e60874622a0be5c112/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3bc570d6c01c206c46deb6e935a260be44f186a2f05179f52f7fcd2be086a94d", size = 488377 }, - { url = "https://files.pythonhosted.org/packages/86/93/cfa597fa9389e122488f7ffdbd6db505b3b915ca7435ecd7542e855898c2/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e84087b432b6ac94778de547e08611266f1f8ffad28c0ee4c82e028b0fc5966d", size = 595837 }, - { url = "https://files.pythonhosted.org/packages/57/1e/68c1ed5652b48d89fc24d6af905d88ee4f82fa8bc491e2666004e307ded1/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:620bae625f4cb18427b1bb1a2d9426dc0dd5a5ba74c7c2cdb9de405f7b129863", size = 473456 }, - { url = "https://files.pythonhosted.org/packages/d5/dc/1a680b7458ffa3b14bb64878112aefc8f2e4f73c5af763cbf0bd43100658/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:544364b2b51a9b0c7000a4b4b02f90e9423d97fbbf7e06689236443ebcad81ab", size = 455614 }, - { url = "https://files.pythonhosted.org/packages/61/a5/3d782a666512e01eaa6541a72ebac1d3aae191ff4a31274a66b8dd85760c/watchfiles-1.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:bbe1ef33d45bc71cf21364df962af171f96ecaeca06bd9e3d0b583efb12aec82", size = 630690 }, - { url = "https://files.pythonhosted.org/packages/9b/73/bb5f38590e34687b2a9c47a244aa4dd50c56a825969c92c9c5fc7387cea1/watchfiles-1.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1a0bb430adb19ef49389e1ad368450193a90038b5b752f4ac089ec6942c4dff4", size = 622459 }, - { url = "https://files.pythonhosted.org/packages/f1/ac/c9bb0ec696e07a20bd58af5399aeadaef195fb2c73d26baf55180fe4a942/watchfiles-1.1.1-cp310-cp310-win32.whl", hash = "sha256:3f6d37644155fb5beca5378feb8c1708d5783145f2a0f1c4d5a061a210254844", size = 272663 }, - { url = "https://files.pythonhosted.org/packages/11/a0/a60c5a7c2ec59fa062d9a9c61d02e3b6abd94d32aac2d8344c4bdd033326/watchfiles-1.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:a36d8efe0f290835fd0f33da35042a1bb5dc0e83cbc092dcf69bce442579e88e", size = 287453 }, - { url = "https://files.pythonhosted.org/packages/1f/f8/2c5f479fb531ce2f0564eda479faecf253d886b1ab3630a39b7bf7362d46/watchfiles-1.1.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:f57b396167a2565a4e8b5e56a5a1c537571733992b226f4f1197d79e94cf0ae5", size = 406529 }, - { url = "https://files.pythonhosted.org/packages/fe/cd/f515660b1f32f65df671ddf6f85bfaca621aee177712874dc30a97397977/watchfiles-1.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:421e29339983e1bebc281fab40d812742268ad057db4aee8c4d2bce0af43b741", size = 394384 }, - { url = "https://files.pythonhosted.org/packages/7b/c3/28b7dc99733eab43fca2d10f55c86e03bd6ab11ca31b802abac26b23d161/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e43d39a741e972bab5d8100b5cdacf69db64e34eb19b6e9af162bccf63c5cc6", size = 448789 }, - { url = "https://files.pythonhosted.org/packages/4a/24/33e71113b320030011c8e4316ccca04194bf0cbbaeee207f00cbc7d6b9f5/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f537afb3276d12814082a2e9b242bdcf416c2e8fd9f799a737990a1dbe906e5b", size = 460521 }, - { url = "https://files.pythonhosted.org/packages/f4/c3/3c9a55f255aa57b91579ae9e98c88704955fa9dac3e5614fb378291155df/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b2cd9e04277e756a2e2d2543d65d1e2166d6fd4c9b183f8808634fda23f17b14", size = 488722 }, - { url = "https://files.pythonhosted.org/packages/49/36/506447b73eb46c120169dc1717fe2eff07c234bb3232a7200b5f5bd816e9/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5f3f58818dc0b07f7d9aa7fe9eb1037aecb9700e63e1f6acfed13e9fef648f5d", size = 596088 }, - { url = "https://files.pythonhosted.org/packages/82/ab/5f39e752a9838ec4d52e9b87c1e80f1ee3ccdbe92e183c15b6577ab9de16/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9bb9f66367023ae783551042d31b1d7fd422e8289eedd91f26754a66f44d5cff", size = 472923 }, - { url = "https://files.pythonhosted.org/packages/af/b9/a419292f05e302dea372fa7e6fda5178a92998411f8581b9830d28fb9edb/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aebfd0861a83e6c3d1110b78ad54704486555246e542be3e2bb94195eabb2606", size = 456080 }, - { url = "https://files.pythonhosted.org/packages/b0/c3/d5932fd62bde1a30c36e10c409dc5d54506726f08cb3e1d8d0ba5e2bc8db/watchfiles-1.1.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5fac835b4ab3c6487b5dbad78c4b3724e26bcc468e886f8ba8cc4306f68f6701", size = 629432 }, - { url = "https://files.pythonhosted.org/packages/f7/77/16bddd9779fafb795f1a94319dc965209c5641db5bf1edbbccace6d1b3c0/watchfiles-1.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:399600947b170270e80134ac854e21b3ccdefa11a9529a3decc1327088180f10", size = 623046 }, - { url = "https://files.pythonhosted.org/packages/46/ef/f2ecb9a0f342b4bfad13a2787155c6ee7ce792140eac63a34676a2feeef2/watchfiles-1.1.1-cp311-cp311-win32.whl", hash = "sha256:de6da501c883f58ad50db3a32ad397b09ad29865b5f26f64c24d3e3281685849", size = 271473 }, - { url = "https://files.pythonhosted.org/packages/94/bc/f42d71125f19731ea435c3948cad148d31a64fccde3867e5ba4edee901f9/watchfiles-1.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:35c53bd62a0b885bf653ebf6b700d1bf05debb78ad9292cf2a942b23513dc4c4", size = 287598 }, - { url = "https://files.pythonhosted.org/packages/57/c9/a30f897351f95bbbfb6abcadafbaca711ce1162f4db95fc908c98a9165f3/watchfiles-1.1.1-cp311-cp311-win_arm64.whl", hash = "sha256:57ca5281a8b5e27593cb7d82c2ac927ad88a96ed406aa446f6344e4328208e9e", size = 277210 }, - { url = "https://files.pythonhosted.org/packages/74/d5/f039e7e3c639d9b1d09b07ea412a6806d38123f0508e5f9b48a87b0a76cc/watchfiles-1.1.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:8c89f9f2f740a6b7dcc753140dd5e1ab9215966f7a3530d0c0705c83b401bd7d", size = 404745 }, - { url = "https://files.pythonhosted.org/packages/a5/96/a881a13aa1349827490dab2d363c8039527060cfcc2c92cc6d13d1b1049e/watchfiles-1.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bd404be08018c37350f0d6e34676bd1e2889990117a2b90070b3007f172d0610", size = 391769 }, - { url = "https://files.pythonhosted.org/packages/4b/5b/d3b460364aeb8da471c1989238ea0e56bec24b6042a68046adf3d9ddb01c/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8526e8f916bb5b9a0a777c8317c23ce65de259422bba5b31325a6fa6029d33af", size = 449374 }, - { url = "https://files.pythonhosted.org/packages/b9/44/5769cb62d4ed055cb17417c0a109a92f007114a4e07f30812a73a4efdb11/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2edc3553362b1c38d9f06242416a5d8e9fe235c204a4072e988ce2e5bb1f69f6", size = 459485 }, - { url = "https://files.pythonhosted.org/packages/19/0c/286b6301ded2eccd4ffd0041a1b726afda999926cf720aab63adb68a1e36/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30f7da3fb3f2844259cba4720c3fc7138eb0f7b659c38f3bfa65084c7fc7abce", size = 488813 }, - { url = "https://files.pythonhosted.org/packages/c7/2b/8530ed41112dd4a22f4dcfdb5ccf6a1baad1ff6eed8dc5a5f09e7e8c41c7/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8979280bdafff686ba5e4d8f97840f929a87ed9cdf133cbbd42f7766774d2aa", size = 594816 }, - { url = "https://files.pythonhosted.org/packages/ce/d2/f5f9fb49489f184f18470d4f99f4e862a4b3e9ac2865688eb2099e3d837a/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dcc5c24523771db3a294c77d94771abcfcb82a0e0ee8efd910c37c59ec1b31bb", size = 475186 }, - { url = "https://files.pythonhosted.org/packages/cf/68/5707da262a119fb06fbe214d82dd1fe4a6f4af32d2d14de368d0349eb52a/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db5d7ae38ff20153d542460752ff397fcf5c96090c1230803713cf3147a6803", size = 456812 }, - { url = "https://files.pythonhosted.org/packages/66/ab/3cbb8756323e8f9b6f9acb9ef4ec26d42b2109bce830cc1f3468df20511d/watchfiles-1.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:28475ddbde92df1874b6c5c8aaeb24ad5be47a11f87cde5a28ef3835932e3e94", size = 630196 }, - { url = "https://files.pythonhosted.org/packages/78/46/7152ec29b8335f80167928944a94955015a345440f524d2dfe63fc2f437b/watchfiles-1.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:36193ed342f5b9842edd3532729a2ad55c4160ffcfa3700e0d54be496b70dd43", size = 622657 }, - { url = "https://files.pythonhosted.org/packages/0a/bf/95895e78dd75efe9a7f31733607f384b42eb5feb54bd2eb6ed57cc2e94f4/watchfiles-1.1.1-cp312-cp312-win32.whl", hash = "sha256:859e43a1951717cc8de7f4c77674a6d389b106361585951d9e69572823f311d9", size = 272042 }, - { url = "https://files.pythonhosted.org/packages/87/0a/90eb755f568de2688cb220171c4191df932232c20946966c27a59c400850/watchfiles-1.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:91d4c9a823a8c987cce8fa2690923b069966dabb196dd8d137ea2cede885fde9", size = 288410 }, - { url = "https://files.pythonhosted.org/packages/36/76/f322701530586922fbd6723c4f91ace21364924822a8772c549483abed13/watchfiles-1.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:a625815d4a2bdca61953dbba5a39d60164451ef34c88d751f6c368c3ea73d404", size = 278209 }, - { url = "https://files.pythonhosted.org/packages/bb/f4/f750b29225fe77139f7ae5de89d4949f5a99f934c65a1f1c0b248f26f747/watchfiles-1.1.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:130e4876309e8686a5e37dba7d5e9bc77e6ed908266996ca26572437a5271e18", size = 404321 }, - { url = "https://files.pythonhosted.org/packages/2b/f9/f07a295cde762644aa4c4bb0f88921d2d141af45e735b965fb2e87858328/watchfiles-1.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5f3bde70f157f84ece3765b42b4a52c6ac1a50334903c6eaf765362f6ccca88a", size = 391783 }, - { url = "https://files.pythonhosted.org/packages/bc/11/fc2502457e0bea39a5c958d86d2cb69e407a4d00b85735ca724bfa6e0d1a/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14e0b1fe858430fc0251737ef3824c54027bedb8c37c38114488b8e131cf8219", size = 449279 }, - { url = "https://files.pythonhosted.org/packages/e3/1f/d66bc15ea0b728df3ed96a539c777acfcad0eb78555ad9efcaa1274688f0/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f27db948078f3823a6bb3b465180db8ebecf26dd5dae6f6180bd87383b6b4428", size = 459405 }, - { url = "https://files.pythonhosted.org/packages/be/90/9f4a65c0aec3ccf032703e6db02d89a157462fbb2cf20dd415128251cac0/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:059098c3a429f62fc98e8ec62b982230ef2c8df68c79e826e37b895bc359a9c0", size = 488976 }, - { url = "https://files.pythonhosted.org/packages/37/57/ee347af605d867f712be7029bb94c8c071732a4b44792e3176fa3c612d39/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfb5862016acc9b869bb57284e6cb35fdf8e22fe59f7548858e2f971d045f150", size = 595506 }, - { url = "https://files.pythonhosted.org/packages/a8/78/cc5ab0b86c122047f75e8fc471c67a04dee395daf847d3e59381996c8707/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:319b27255aacd9923b8a276bb14d21a5f7ff82564c744235fc5eae58d95422ae", size = 474936 }, - { url = "https://files.pythonhosted.org/packages/62/da/def65b170a3815af7bd40a3e7010bf6ab53089ef1b75d05dd5385b87cf08/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c755367e51db90e75b19454b680903631d41f9e3607fbd941d296a020c2d752d", size = 456147 }, - { url = "https://files.pythonhosted.org/packages/57/99/da6573ba71166e82d288d4df0839128004c67d2778d3b566c138695f5c0b/watchfiles-1.1.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c22c776292a23bfc7237a98f791b9ad3144b02116ff10d820829ce62dff46d0b", size = 630007 }, - { url = "https://files.pythonhosted.org/packages/a8/51/7439c4dd39511368849eb1e53279cd3454b4a4dbace80bab88feeb83c6b5/watchfiles-1.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:3a476189be23c3686bc2f4321dd501cb329c0a0469e77b7b534ee10129ae6374", size = 622280 }, - { url = "https://files.pythonhosted.org/packages/95/9c/8ed97d4bba5db6fdcdb2b298d3898f2dd5c20f6b73aee04eabe56c59677e/watchfiles-1.1.1-cp313-cp313-win32.whl", hash = "sha256:bf0a91bfb5574a2f7fc223cf95eeea79abfefa404bf1ea5e339c0c1560ae99a0", size = 272056 }, - { url = "https://files.pythonhosted.org/packages/1f/f3/c14e28429f744a260d8ceae18bf58c1d5fa56b50d006a7a9f80e1882cb0d/watchfiles-1.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:52e06553899e11e8074503c8e716d574adeeb7e68913115c4b3653c53f9bae42", size = 288162 }, - { url = "https://files.pythonhosted.org/packages/dc/61/fe0e56c40d5cd29523e398d31153218718c5786b5e636d9ae8ae79453d27/watchfiles-1.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:ac3cc5759570cd02662b15fbcd9d917f7ecd47efe0d6b40474eafd246f91ea18", size = 277909 }, - { url = "https://files.pythonhosted.org/packages/79/42/e0a7d749626f1e28c7108a99fb9bf524b501bbbeb9b261ceecde644d5a07/watchfiles-1.1.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:563b116874a9a7ce6f96f87cd0b94f7faf92d08d0021e837796f0a14318ef8da", size = 403389 }, - { url = "https://files.pythonhosted.org/packages/15/49/08732f90ce0fbbc13913f9f215c689cfc9ced345fb1bcd8829a50007cc8d/watchfiles-1.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3ad9fe1dae4ab4212d8c91e80b832425e24f421703b5a42ef2e4a1e215aff051", size = 389964 }, - { url = "https://files.pythonhosted.org/packages/27/0d/7c315d4bd5f2538910491a0393c56bf70d333d51bc5b34bee8e68e8cea19/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce70f96a46b894b36eba678f153f052967a0d06d5b5a19b336ab0dbbd029f73e", size = 448114 }, - { url = "https://files.pythonhosted.org/packages/c3/24/9e096de47a4d11bc4df41e9d1e61776393eac4cb6eb11b3e23315b78b2cc/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cb467c999c2eff23a6417e58d75e5828716f42ed8289fe6b77a7e5a91036ca70", size = 460264 }, - { url = "https://files.pythonhosted.org/packages/cc/0f/e8dea6375f1d3ba5fcb0b3583e2b493e77379834c74fd5a22d66d85d6540/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:836398932192dae4146c8f6f737d74baeac8b70ce14831a239bdb1ca882fc261", size = 487877 }, - { url = "https://files.pythonhosted.org/packages/ac/5b/df24cfc6424a12deb41503b64d42fbea6b8cb357ec62ca84a5a3476f654a/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:743185e7372b7bc7c389e1badcc606931a827112fbbd37f14c537320fca08620", size = 595176 }, - { url = "https://files.pythonhosted.org/packages/8f/b5/853b6757f7347de4e9b37e8cc3289283fb983cba1ab4d2d7144694871d9c/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:afaeff7696e0ad9f02cbb8f56365ff4686ab205fcf9c4c5b6fdfaaa16549dd04", size = 473577 }, - { url = "https://files.pythonhosted.org/packages/e1/f7/0a4467be0a56e80447c8529c9fce5b38eab4f513cb3d9bf82e7392a5696b/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f7eb7da0eb23aa2ba036d4f616d46906013a68caf61b7fdbe42fc8b25132e77", size = 455425 }, - { url = "https://files.pythonhosted.org/packages/8e/e0/82583485ea00137ddf69bc84a2db88bd92ab4a6e3c405e5fb878ead8d0e7/watchfiles-1.1.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:831a62658609f0e5c64178211c942ace999517f5770fe9436be4c2faeba0c0ef", size = 628826 }, - { url = "https://files.pythonhosted.org/packages/28/9a/a785356fccf9fae84c0cc90570f11702ae9571036fb25932f1242c82191c/watchfiles-1.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:f9a2ae5c91cecc9edd47e041a930490c31c3afb1f5e6d71de3dc671bfaca02bf", size = 622208 }, - { url = "https://files.pythonhosted.org/packages/c3/f4/0872229324ef69b2c3edec35e84bd57a1289e7d3fe74588048ed8947a323/watchfiles-1.1.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:d1715143123baeeaeadec0528bb7441103979a1d5f6fd0e1f915383fea7ea6d5", size = 404315 }, - { url = "https://files.pythonhosted.org/packages/7b/22/16d5331eaed1cb107b873f6ae1b69e9ced582fcf0c59a50cd84f403b1c32/watchfiles-1.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:39574d6370c4579d7f5d0ad940ce5b20db0e4117444e39b6d8f99db5676c52fd", size = 390869 }, - { url = "https://files.pythonhosted.org/packages/b2/7e/5643bfff5acb6539b18483128fdc0ef2cccc94a5b8fbda130c823e8ed636/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7365b92c2e69ee952902e8f70f3ba6360d0d596d9299d55d7d386df84b6941fb", size = 449919 }, - { url = "https://files.pythonhosted.org/packages/51/2e/c410993ba5025a9f9357c376f48976ef0e1b1aefb73b97a5ae01a5972755/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bfff9740c69c0e4ed32416f013f3c45e2ae42ccedd1167ef2d805c000b6c71a5", size = 460845 }, - { url = "https://files.pythonhosted.org/packages/8e/a4/2df3b404469122e8680f0fcd06079317e48db58a2da2950fb45020947734/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b27cf2eb1dda37b2089e3907d8ea92922b673c0c427886d4edc6b94d8dfe5db3", size = 489027 }, - { url = "https://files.pythonhosted.org/packages/ea/84/4587ba5b1f267167ee715b7f66e6382cca6938e0a4b870adad93e44747e6/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:526e86aced14a65a5b0ec50827c745597c782ff46b571dbfe46192ab9e0b3c33", size = 595615 }, - { url = "https://files.pythonhosted.org/packages/6a/0f/c6988c91d06e93cd0bb3d4a808bcf32375ca1904609835c3031799e3ecae/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04e78dd0b6352db95507fd8cb46f39d185cf8c74e4cf1e4fbad1d3df96faf510", size = 474836 }, - { url = "https://files.pythonhosted.org/packages/b4/36/ded8aebea91919485b7bbabbd14f5f359326cb5ec218cd67074d1e426d74/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c85794a4cfa094714fb9c08d4a218375b2b95b8ed1666e8677c349906246c05", size = 455099 }, - { url = "https://files.pythonhosted.org/packages/98/e0/8c9bdba88af756a2fce230dd365fab2baf927ba42cd47521ee7498fd5211/watchfiles-1.1.1-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:74d5012b7630714b66be7b7b7a78855ef7ad58e8650c73afc4c076a1f480a8d6", size = 630626 }, - { url = "https://files.pythonhosted.org/packages/2a/84/a95db05354bf2d19e438520d92a8ca475e578c647f78f53197f5a2f17aaf/watchfiles-1.1.1-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:8fbe85cb3201c7d380d3d0b90e63d520f15d6afe217165d7f98c9c649654db81", size = 622519 }, - { url = "https://files.pythonhosted.org/packages/1d/ce/d8acdc8de545de995c339be67711e474c77d643555a9bb74a9334252bd55/watchfiles-1.1.1-cp314-cp314-win32.whl", hash = "sha256:3fa0b59c92278b5a7800d3ee7733da9d096d4aabcfabb9a928918bd276ef9b9b", size = 272078 }, - { url = "https://files.pythonhosted.org/packages/c4/c9/a74487f72d0451524be827e8edec251da0cc1fcf111646a511ae752e1a3d/watchfiles-1.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:c2047d0b6cea13b3316bdbafbfa0c4228ae593d995030fda39089d36e64fc03a", size = 287664 }, - { url = "https://files.pythonhosted.org/packages/df/b8/8ac000702cdd496cdce998c6f4ee0ca1f15977bba51bdf07d872ebdfc34c/watchfiles-1.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:842178b126593addc05acf6fce960d28bc5fae7afbaa2c6c1b3a7b9460e5be02", size = 277154 }, - { url = "https://files.pythonhosted.org/packages/47/a8/e3af2184707c29f0f14b1963c0aace6529f9d1b8582d5b99f31bbf42f59e/watchfiles-1.1.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:88863fbbc1a7312972f1c511f202eb30866370ebb8493aef2812b9ff28156a21", size = 403820 }, - { url = "https://files.pythonhosted.org/packages/c0/ec/e47e307c2f4bd75f9f9e8afbe3876679b18e1bcec449beca132a1c5ffb2d/watchfiles-1.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:55c7475190662e202c08c6c0f4d9e345a29367438cf8e8037f3155e10a88d5a5", size = 390510 }, - { url = "https://files.pythonhosted.org/packages/d5/a0/ad235642118090f66e7b2f18fd5c42082418404a79205cdfca50b6309c13/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f53fa183d53a1d7a8852277c92b967ae99c2d4dcee2bfacff8868e6e30b15f7", size = 448408 }, - { url = "https://files.pythonhosted.org/packages/df/85/97fa10fd5ff3332ae17e7e40e20784e419e28521549780869f1413742e9d/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6aae418a8b323732fa89721d86f39ec8f092fc2af67f4217a2b07fd3e93c6101", size = 458968 }, - { url = "https://files.pythonhosted.org/packages/47/c2/9059c2e8966ea5ce678166617a7f75ecba6164375f3b288e50a40dc6d489/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f096076119da54a6080e8920cbdaac3dbee667eb91dcc5e5b78840b87415bd44", size = 488096 }, - { url = "https://files.pythonhosted.org/packages/94/44/d90a9ec8ac309bc26db808a13e7bfc0e4e78b6fc051078a554e132e80160/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:00485f441d183717038ed2e887a7c868154f216877653121068107b227a2f64c", size = 596040 }, - { url = "https://files.pythonhosted.org/packages/95/68/4e3479b20ca305cfc561db3ed207a8a1c745ee32bf24f2026a129d0ddb6e/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a55f3e9e493158d7bfdb60a1165035f1cf7d320914e7b7ea83fe22c6023b58fc", size = 473847 }, - { url = "https://files.pythonhosted.org/packages/4f/55/2af26693fd15165c4ff7857e38330e1b61ab8c37d15dc79118cdba115b7a/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c91ed27800188c2ae96d16e3149f199d62f86c7af5f5f4d2c61a3ed8cd3666c", size = 455072 }, - { url = "https://files.pythonhosted.org/packages/66/1d/d0d200b10c9311ec25d2273f8aad8c3ef7cc7ea11808022501811208a750/watchfiles-1.1.1-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:311ff15a0bae3714ffb603e6ba6dbfba4065ab60865d15a6ec544133bdb21099", size = 629104 }, - { url = "https://files.pythonhosted.org/packages/e3/bd/fa9bb053192491b3867ba07d2343d9f2252e00811567d30ae8d0f78136fe/watchfiles-1.1.1-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:a916a2932da8f8ab582f242c065f5c81bed3462849ca79ee357dd9551b0e9b01", size = 622112 }, - { url = "https://files.pythonhosted.org/packages/ba/4c/a888c91e2e326872fa4705095d64acd8aa2fb9c1f7b9bd0588f33850516c/watchfiles-1.1.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:17ef139237dfced9da49fb7f2232c86ca9421f666d78c264c7ffca6601d154c3", size = 409611 }, - { url = "https://files.pythonhosted.org/packages/1e/c7/5420d1943c8e3ce1a21c0a9330bcf7edafb6aa65d26b21dbb3267c9e8112/watchfiles-1.1.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:672b8adf25b1a0d35c96b5888b7b18699d27d4194bac8beeae75be4b7a3fc9b2", size = 396889 }, - { url = "https://files.pythonhosted.org/packages/0c/e5/0072cef3804ce8d3aaddbfe7788aadff6b3d3f98a286fdbee9fd74ca59a7/watchfiles-1.1.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77a13aea58bc2b90173bc69f2a90de8e282648939a00a602e1dc4ee23e26b66d", size = 451616 }, - { url = "https://files.pythonhosted.org/packages/83/4e/b87b71cbdfad81ad7e83358b3e447fedd281b880a03d64a760fe0a11fc2e/watchfiles-1.1.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b495de0bb386df6a12b18335a0285dda90260f51bdb505503c02bcd1ce27a8b", size = 458413 }, - { url = "https://files.pythonhosted.org/packages/d3/8e/e500f8b0b77be4ff753ac94dc06b33d8f0d839377fee1b78e8c8d8f031bf/watchfiles-1.1.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:db476ab59b6765134de1d4fe96a1a9c96ddf091683599be0f26147ea1b2e4b88", size = 408250 }, - { url = "https://files.pythonhosted.org/packages/bd/95/615e72cd27b85b61eec764a5ca51bd94d40b5adea5ff47567d9ebc4d275a/watchfiles-1.1.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:89eef07eee5e9d1fda06e38822ad167a044153457e6fd997f8a858ab7564a336", size = 396117 }, - { url = "https://files.pythonhosted.org/packages/c9/81/e7fe958ce8a7fb5c73cc9fb07f5aeaf755e6aa72498c57d760af760c91f8/watchfiles-1.1.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce19e06cbda693e9e7686358af9cd6f5d61312ab8b00488bc36f5aabbaf77e24", size = 450493 }, - { url = "https://files.pythonhosted.org/packages/6e/d4/ed38dd3b1767193de971e694aa544356e63353c33a85d948166b5ff58b9e/watchfiles-1.1.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e6f39af2eab0118338902798b5aa6664f46ff66bc0280de76fca67a7f262a49", size = 457546 }, +sdist = { url = "https://files.pythonhosted.org/packages/c2/c9/8869df9b2a2d6c59d79220a4db37679e74f807c559ffe5265e08b227a210/watchfiles-1.1.1.tar.gz", hash = "sha256:a173cb5c16c4f40ab19cecf48a534c409f7ea983ab8fed0741304a1c0a31b3f2", size = 94440, upload-time = "2025-10-14T15:06:21.08Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/1a/206e8cf2dd86fddf939165a57b4df61607a1e0add2785f170a3f616b7d9f/watchfiles-1.1.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:eef58232d32daf2ac67f42dea51a2c80f0d03379075d44a587051e63cc2e368c", size = 407318, upload-time = "2025-10-14T15:04:18.753Z" }, + { url = "https://files.pythonhosted.org/packages/b3/0f/abaf5262b9c496b5dad4ed3c0e799cbecb1f8ea512ecb6ddd46646a9fca3/watchfiles-1.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:03fa0f5237118a0c5e496185cafa92878568b652a2e9a9382a5151b1a0380a43", size = 394478, upload-time = "2025-10-14T15:04:20.297Z" }, + { url = "https://files.pythonhosted.org/packages/b1/04/9cc0ba88697b34b755371f5ace8d3a4d9a15719c07bdc7bd13d7d8c6a341/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8ca65483439f9c791897f7db49202301deb6e15fe9f8fe2fed555bf986d10c31", size = 449894, upload-time = "2025-10-14T15:04:21.527Z" }, + { url = "https://files.pythonhosted.org/packages/d2/9c/eda4615863cd8621e89aed4df680d8c3ec3da6a4cf1da113c17decd87c7f/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f0ab1c1af0cb38e3f598244c17919fb1a84d1629cc08355b0074b6d7f53138ac", size = 459065, upload-time = "2025-10-14T15:04:22.795Z" }, + { url = "https://files.pythonhosted.org/packages/84/13/f28b3f340157d03cbc8197629bc109d1098764abe1e60874622a0be5c112/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3bc570d6c01c206c46deb6e935a260be44f186a2f05179f52f7fcd2be086a94d", size = 488377, upload-time = "2025-10-14T15:04:24.138Z" }, + { url = "https://files.pythonhosted.org/packages/86/93/cfa597fa9389e122488f7ffdbd6db505b3b915ca7435ecd7542e855898c2/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e84087b432b6ac94778de547e08611266f1f8ffad28c0ee4c82e028b0fc5966d", size = 595837, upload-time = "2025-10-14T15:04:25.057Z" }, + { url = "https://files.pythonhosted.org/packages/57/1e/68c1ed5652b48d89fc24d6af905d88ee4f82fa8bc491e2666004e307ded1/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:620bae625f4cb18427b1bb1a2d9426dc0dd5a5ba74c7c2cdb9de405f7b129863", size = 473456, upload-time = "2025-10-14T15:04:26.497Z" }, + { url = "https://files.pythonhosted.org/packages/d5/dc/1a680b7458ffa3b14bb64878112aefc8f2e4f73c5af763cbf0bd43100658/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:544364b2b51a9b0c7000a4b4b02f90e9423d97fbbf7e06689236443ebcad81ab", size = 455614, upload-time = "2025-10-14T15:04:27.539Z" }, + { url = "https://files.pythonhosted.org/packages/61/a5/3d782a666512e01eaa6541a72ebac1d3aae191ff4a31274a66b8dd85760c/watchfiles-1.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:bbe1ef33d45bc71cf21364df962af171f96ecaeca06bd9e3d0b583efb12aec82", size = 630690, upload-time = "2025-10-14T15:04:28.495Z" }, + { url = "https://files.pythonhosted.org/packages/9b/73/bb5f38590e34687b2a9c47a244aa4dd50c56a825969c92c9c5fc7387cea1/watchfiles-1.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1a0bb430adb19ef49389e1ad368450193a90038b5b752f4ac089ec6942c4dff4", size = 622459, upload-time = "2025-10-14T15:04:29.491Z" }, + { url = "https://files.pythonhosted.org/packages/f1/ac/c9bb0ec696e07a20bd58af5399aeadaef195fb2c73d26baf55180fe4a942/watchfiles-1.1.1-cp310-cp310-win32.whl", hash = "sha256:3f6d37644155fb5beca5378feb8c1708d5783145f2a0f1c4d5a061a210254844", size = 272663, upload-time = "2025-10-14T15:04:30.435Z" }, + { url = "https://files.pythonhosted.org/packages/11/a0/a60c5a7c2ec59fa062d9a9c61d02e3b6abd94d32aac2d8344c4bdd033326/watchfiles-1.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:a36d8efe0f290835fd0f33da35042a1bb5dc0e83cbc092dcf69bce442579e88e", size = 287453, upload-time = "2025-10-14T15:04:31.53Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f8/2c5f479fb531ce2f0564eda479faecf253d886b1ab3630a39b7bf7362d46/watchfiles-1.1.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:f57b396167a2565a4e8b5e56a5a1c537571733992b226f4f1197d79e94cf0ae5", size = 406529, upload-time = "2025-10-14T15:04:32.899Z" }, + { url = "https://files.pythonhosted.org/packages/fe/cd/f515660b1f32f65df671ddf6f85bfaca621aee177712874dc30a97397977/watchfiles-1.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:421e29339983e1bebc281fab40d812742268ad057db4aee8c4d2bce0af43b741", size = 394384, upload-time = "2025-10-14T15:04:33.761Z" }, + { url = "https://files.pythonhosted.org/packages/7b/c3/28b7dc99733eab43fca2d10f55c86e03bd6ab11ca31b802abac26b23d161/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e43d39a741e972bab5d8100b5cdacf69db64e34eb19b6e9af162bccf63c5cc6", size = 448789, upload-time = "2025-10-14T15:04:34.679Z" }, + { url = "https://files.pythonhosted.org/packages/4a/24/33e71113b320030011c8e4316ccca04194bf0cbbaeee207f00cbc7d6b9f5/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f537afb3276d12814082a2e9b242bdcf416c2e8fd9f799a737990a1dbe906e5b", size = 460521, upload-time = "2025-10-14T15:04:35.963Z" }, + { url = "https://files.pythonhosted.org/packages/f4/c3/3c9a55f255aa57b91579ae9e98c88704955fa9dac3e5614fb378291155df/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b2cd9e04277e756a2e2d2543d65d1e2166d6fd4c9b183f8808634fda23f17b14", size = 488722, upload-time = "2025-10-14T15:04:37.091Z" }, + { url = "https://files.pythonhosted.org/packages/49/36/506447b73eb46c120169dc1717fe2eff07c234bb3232a7200b5f5bd816e9/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5f3f58818dc0b07f7d9aa7fe9eb1037aecb9700e63e1f6acfed13e9fef648f5d", size = 596088, upload-time = "2025-10-14T15:04:38.39Z" }, + { url = "https://files.pythonhosted.org/packages/82/ab/5f39e752a9838ec4d52e9b87c1e80f1ee3ccdbe92e183c15b6577ab9de16/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9bb9f66367023ae783551042d31b1d7fd422e8289eedd91f26754a66f44d5cff", size = 472923, upload-time = "2025-10-14T15:04:39.666Z" }, + { url = "https://files.pythonhosted.org/packages/af/b9/a419292f05e302dea372fa7e6fda5178a92998411f8581b9830d28fb9edb/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aebfd0861a83e6c3d1110b78ad54704486555246e542be3e2bb94195eabb2606", size = 456080, upload-time = "2025-10-14T15:04:40.643Z" }, + { url = "https://files.pythonhosted.org/packages/b0/c3/d5932fd62bde1a30c36e10c409dc5d54506726f08cb3e1d8d0ba5e2bc8db/watchfiles-1.1.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5fac835b4ab3c6487b5dbad78c4b3724e26bcc468e886f8ba8cc4306f68f6701", size = 629432, upload-time = "2025-10-14T15:04:41.789Z" }, + { url = "https://files.pythonhosted.org/packages/f7/77/16bddd9779fafb795f1a94319dc965209c5641db5bf1edbbccace6d1b3c0/watchfiles-1.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:399600947b170270e80134ac854e21b3ccdefa11a9529a3decc1327088180f10", size = 623046, upload-time = "2025-10-14T15:04:42.718Z" }, + { url = "https://files.pythonhosted.org/packages/46/ef/f2ecb9a0f342b4bfad13a2787155c6ee7ce792140eac63a34676a2feeef2/watchfiles-1.1.1-cp311-cp311-win32.whl", hash = "sha256:de6da501c883f58ad50db3a32ad397b09ad29865b5f26f64c24d3e3281685849", size = 271473, upload-time = "2025-10-14T15:04:43.624Z" }, + { url = "https://files.pythonhosted.org/packages/94/bc/f42d71125f19731ea435c3948cad148d31a64fccde3867e5ba4edee901f9/watchfiles-1.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:35c53bd62a0b885bf653ebf6b700d1bf05debb78ad9292cf2a942b23513dc4c4", size = 287598, upload-time = "2025-10-14T15:04:44.516Z" }, + { url = "https://files.pythonhosted.org/packages/57/c9/a30f897351f95bbbfb6abcadafbaca711ce1162f4db95fc908c98a9165f3/watchfiles-1.1.1-cp311-cp311-win_arm64.whl", hash = "sha256:57ca5281a8b5e27593cb7d82c2ac927ad88a96ed406aa446f6344e4328208e9e", size = 277210, upload-time = "2025-10-14T15:04:45.883Z" }, + { url = "https://files.pythonhosted.org/packages/74/d5/f039e7e3c639d9b1d09b07ea412a6806d38123f0508e5f9b48a87b0a76cc/watchfiles-1.1.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:8c89f9f2f740a6b7dcc753140dd5e1ab9215966f7a3530d0c0705c83b401bd7d", size = 404745, upload-time = "2025-10-14T15:04:46.731Z" }, + { url = "https://files.pythonhosted.org/packages/a5/96/a881a13aa1349827490dab2d363c8039527060cfcc2c92cc6d13d1b1049e/watchfiles-1.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bd404be08018c37350f0d6e34676bd1e2889990117a2b90070b3007f172d0610", size = 391769, upload-time = "2025-10-14T15:04:48.003Z" }, + { url = "https://files.pythonhosted.org/packages/4b/5b/d3b460364aeb8da471c1989238ea0e56bec24b6042a68046adf3d9ddb01c/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8526e8f916bb5b9a0a777c8317c23ce65de259422bba5b31325a6fa6029d33af", size = 449374, upload-time = "2025-10-14T15:04:49.179Z" }, + { url = "https://files.pythonhosted.org/packages/b9/44/5769cb62d4ed055cb17417c0a109a92f007114a4e07f30812a73a4efdb11/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2edc3553362b1c38d9f06242416a5d8e9fe235c204a4072e988ce2e5bb1f69f6", size = 459485, upload-time = "2025-10-14T15:04:50.155Z" }, + { url = "https://files.pythonhosted.org/packages/19/0c/286b6301ded2eccd4ffd0041a1b726afda999926cf720aab63adb68a1e36/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30f7da3fb3f2844259cba4720c3fc7138eb0f7b659c38f3bfa65084c7fc7abce", size = 488813, upload-time = "2025-10-14T15:04:51.059Z" }, + { url = "https://files.pythonhosted.org/packages/c7/2b/8530ed41112dd4a22f4dcfdb5ccf6a1baad1ff6eed8dc5a5f09e7e8c41c7/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8979280bdafff686ba5e4d8f97840f929a87ed9cdf133cbbd42f7766774d2aa", size = 594816, upload-time = "2025-10-14T15:04:52.031Z" }, + { url = "https://files.pythonhosted.org/packages/ce/d2/f5f9fb49489f184f18470d4f99f4e862a4b3e9ac2865688eb2099e3d837a/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dcc5c24523771db3a294c77d94771abcfcb82a0e0ee8efd910c37c59ec1b31bb", size = 475186, upload-time = "2025-10-14T15:04:53.064Z" }, + { url = "https://files.pythonhosted.org/packages/cf/68/5707da262a119fb06fbe214d82dd1fe4a6f4af32d2d14de368d0349eb52a/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db5d7ae38ff20153d542460752ff397fcf5c96090c1230803713cf3147a6803", size = 456812, upload-time = "2025-10-14T15:04:55.174Z" }, + { url = "https://files.pythonhosted.org/packages/66/ab/3cbb8756323e8f9b6f9acb9ef4ec26d42b2109bce830cc1f3468df20511d/watchfiles-1.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:28475ddbde92df1874b6c5c8aaeb24ad5be47a11f87cde5a28ef3835932e3e94", size = 630196, upload-time = "2025-10-14T15:04:56.22Z" }, + { url = "https://files.pythonhosted.org/packages/78/46/7152ec29b8335f80167928944a94955015a345440f524d2dfe63fc2f437b/watchfiles-1.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:36193ed342f5b9842edd3532729a2ad55c4160ffcfa3700e0d54be496b70dd43", size = 622657, upload-time = "2025-10-14T15:04:57.521Z" }, + { url = "https://files.pythonhosted.org/packages/0a/bf/95895e78dd75efe9a7f31733607f384b42eb5feb54bd2eb6ed57cc2e94f4/watchfiles-1.1.1-cp312-cp312-win32.whl", hash = "sha256:859e43a1951717cc8de7f4c77674a6d389b106361585951d9e69572823f311d9", size = 272042, upload-time = "2025-10-14T15:04:59.046Z" }, + { url = "https://files.pythonhosted.org/packages/87/0a/90eb755f568de2688cb220171c4191df932232c20946966c27a59c400850/watchfiles-1.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:91d4c9a823a8c987cce8fa2690923b069966dabb196dd8d137ea2cede885fde9", size = 288410, upload-time = "2025-10-14T15:05:00.081Z" }, + { url = "https://files.pythonhosted.org/packages/36/76/f322701530586922fbd6723c4f91ace21364924822a8772c549483abed13/watchfiles-1.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:a625815d4a2bdca61953dbba5a39d60164451ef34c88d751f6c368c3ea73d404", size = 278209, upload-time = "2025-10-14T15:05:01.168Z" }, + { url = "https://files.pythonhosted.org/packages/bb/f4/f750b29225fe77139f7ae5de89d4949f5a99f934c65a1f1c0b248f26f747/watchfiles-1.1.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:130e4876309e8686a5e37dba7d5e9bc77e6ed908266996ca26572437a5271e18", size = 404321, upload-time = "2025-10-14T15:05:02.063Z" }, + { url = "https://files.pythonhosted.org/packages/2b/f9/f07a295cde762644aa4c4bb0f88921d2d141af45e735b965fb2e87858328/watchfiles-1.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5f3bde70f157f84ece3765b42b4a52c6ac1a50334903c6eaf765362f6ccca88a", size = 391783, upload-time = "2025-10-14T15:05:03.052Z" }, + { url = "https://files.pythonhosted.org/packages/bc/11/fc2502457e0bea39a5c958d86d2cb69e407a4d00b85735ca724bfa6e0d1a/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14e0b1fe858430fc0251737ef3824c54027bedb8c37c38114488b8e131cf8219", size = 449279, upload-time = "2025-10-14T15:05:04.004Z" }, + { url = "https://files.pythonhosted.org/packages/e3/1f/d66bc15ea0b728df3ed96a539c777acfcad0eb78555ad9efcaa1274688f0/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f27db948078f3823a6bb3b465180db8ebecf26dd5dae6f6180bd87383b6b4428", size = 459405, upload-time = "2025-10-14T15:05:04.942Z" }, + { url = "https://files.pythonhosted.org/packages/be/90/9f4a65c0aec3ccf032703e6db02d89a157462fbb2cf20dd415128251cac0/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:059098c3a429f62fc98e8ec62b982230ef2c8df68c79e826e37b895bc359a9c0", size = 488976, upload-time = "2025-10-14T15:05:05.905Z" }, + { url = "https://files.pythonhosted.org/packages/37/57/ee347af605d867f712be7029bb94c8c071732a4b44792e3176fa3c612d39/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfb5862016acc9b869bb57284e6cb35fdf8e22fe59f7548858e2f971d045f150", size = 595506, upload-time = "2025-10-14T15:05:06.906Z" }, + { url = "https://files.pythonhosted.org/packages/a8/78/cc5ab0b86c122047f75e8fc471c67a04dee395daf847d3e59381996c8707/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:319b27255aacd9923b8a276bb14d21a5f7ff82564c744235fc5eae58d95422ae", size = 474936, upload-time = "2025-10-14T15:05:07.906Z" }, + { url = "https://files.pythonhosted.org/packages/62/da/def65b170a3815af7bd40a3e7010bf6ab53089ef1b75d05dd5385b87cf08/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c755367e51db90e75b19454b680903631d41f9e3607fbd941d296a020c2d752d", size = 456147, upload-time = "2025-10-14T15:05:09.138Z" }, + { url = "https://files.pythonhosted.org/packages/57/99/da6573ba71166e82d288d4df0839128004c67d2778d3b566c138695f5c0b/watchfiles-1.1.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c22c776292a23bfc7237a98f791b9ad3144b02116ff10d820829ce62dff46d0b", size = 630007, upload-time = "2025-10-14T15:05:10.117Z" }, + { url = "https://files.pythonhosted.org/packages/a8/51/7439c4dd39511368849eb1e53279cd3454b4a4dbace80bab88feeb83c6b5/watchfiles-1.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:3a476189be23c3686bc2f4321dd501cb329c0a0469e77b7b534ee10129ae6374", size = 622280, upload-time = "2025-10-14T15:05:11.146Z" }, + { url = "https://files.pythonhosted.org/packages/95/9c/8ed97d4bba5db6fdcdb2b298d3898f2dd5c20f6b73aee04eabe56c59677e/watchfiles-1.1.1-cp313-cp313-win32.whl", hash = "sha256:bf0a91bfb5574a2f7fc223cf95eeea79abfefa404bf1ea5e339c0c1560ae99a0", size = 272056, upload-time = "2025-10-14T15:05:12.156Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f3/c14e28429f744a260d8ceae18bf58c1d5fa56b50d006a7a9f80e1882cb0d/watchfiles-1.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:52e06553899e11e8074503c8e716d574adeeb7e68913115c4b3653c53f9bae42", size = 288162, upload-time = "2025-10-14T15:05:13.208Z" }, + { url = "https://files.pythonhosted.org/packages/dc/61/fe0e56c40d5cd29523e398d31153218718c5786b5e636d9ae8ae79453d27/watchfiles-1.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:ac3cc5759570cd02662b15fbcd9d917f7ecd47efe0d6b40474eafd246f91ea18", size = 277909, upload-time = "2025-10-14T15:05:14.49Z" }, + { url = "https://files.pythonhosted.org/packages/79/42/e0a7d749626f1e28c7108a99fb9bf524b501bbbeb9b261ceecde644d5a07/watchfiles-1.1.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:563b116874a9a7ce6f96f87cd0b94f7faf92d08d0021e837796f0a14318ef8da", size = 403389, upload-time = "2025-10-14T15:05:15.777Z" }, + { url = "https://files.pythonhosted.org/packages/15/49/08732f90ce0fbbc13913f9f215c689cfc9ced345fb1bcd8829a50007cc8d/watchfiles-1.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3ad9fe1dae4ab4212d8c91e80b832425e24f421703b5a42ef2e4a1e215aff051", size = 389964, upload-time = "2025-10-14T15:05:16.85Z" }, + { url = "https://files.pythonhosted.org/packages/27/0d/7c315d4bd5f2538910491a0393c56bf70d333d51bc5b34bee8e68e8cea19/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce70f96a46b894b36eba678f153f052967a0d06d5b5a19b336ab0dbbd029f73e", size = 448114, upload-time = "2025-10-14T15:05:17.876Z" }, + { url = "https://files.pythonhosted.org/packages/c3/24/9e096de47a4d11bc4df41e9d1e61776393eac4cb6eb11b3e23315b78b2cc/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cb467c999c2eff23a6417e58d75e5828716f42ed8289fe6b77a7e5a91036ca70", size = 460264, upload-time = "2025-10-14T15:05:18.962Z" }, + { url = "https://files.pythonhosted.org/packages/cc/0f/e8dea6375f1d3ba5fcb0b3583e2b493e77379834c74fd5a22d66d85d6540/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:836398932192dae4146c8f6f737d74baeac8b70ce14831a239bdb1ca882fc261", size = 487877, upload-time = "2025-10-14T15:05:20.094Z" }, + { url = "https://files.pythonhosted.org/packages/ac/5b/df24cfc6424a12deb41503b64d42fbea6b8cb357ec62ca84a5a3476f654a/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:743185e7372b7bc7c389e1badcc606931a827112fbbd37f14c537320fca08620", size = 595176, upload-time = "2025-10-14T15:05:21.134Z" }, + { url = "https://files.pythonhosted.org/packages/8f/b5/853b6757f7347de4e9b37e8cc3289283fb983cba1ab4d2d7144694871d9c/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:afaeff7696e0ad9f02cbb8f56365ff4686ab205fcf9c4c5b6fdfaaa16549dd04", size = 473577, upload-time = "2025-10-14T15:05:22.306Z" }, + { url = "https://files.pythonhosted.org/packages/e1/f7/0a4467be0a56e80447c8529c9fce5b38eab4f513cb3d9bf82e7392a5696b/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f7eb7da0eb23aa2ba036d4f616d46906013a68caf61b7fdbe42fc8b25132e77", size = 455425, upload-time = "2025-10-14T15:05:23.348Z" }, + { url = "https://files.pythonhosted.org/packages/8e/e0/82583485ea00137ddf69bc84a2db88bd92ab4a6e3c405e5fb878ead8d0e7/watchfiles-1.1.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:831a62658609f0e5c64178211c942ace999517f5770fe9436be4c2faeba0c0ef", size = 628826, upload-time = "2025-10-14T15:05:24.398Z" }, + { url = "https://files.pythonhosted.org/packages/28/9a/a785356fccf9fae84c0cc90570f11702ae9571036fb25932f1242c82191c/watchfiles-1.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:f9a2ae5c91cecc9edd47e041a930490c31c3afb1f5e6d71de3dc671bfaca02bf", size = 622208, upload-time = "2025-10-14T15:05:25.45Z" }, + { url = "https://files.pythonhosted.org/packages/c3/f4/0872229324ef69b2c3edec35e84bd57a1289e7d3fe74588048ed8947a323/watchfiles-1.1.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:d1715143123baeeaeadec0528bb7441103979a1d5f6fd0e1f915383fea7ea6d5", size = 404315, upload-time = "2025-10-14T15:05:26.501Z" }, + { url = "https://files.pythonhosted.org/packages/7b/22/16d5331eaed1cb107b873f6ae1b69e9ced582fcf0c59a50cd84f403b1c32/watchfiles-1.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:39574d6370c4579d7f5d0ad940ce5b20db0e4117444e39b6d8f99db5676c52fd", size = 390869, upload-time = "2025-10-14T15:05:27.649Z" }, + { url = "https://files.pythonhosted.org/packages/b2/7e/5643bfff5acb6539b18483128fdc0ef2cccc94a5b8fbda130c823e8ed636/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7365b92c2e69ee952902e8f70f3ba6360d0d596d9299d55d7d386df84b6941fb", size = 449919, upload-time = "2025-10-14T15:05:28.701Z" }, + { url = "https://files.pythonhosted.org/packages/51/2e/c410993ba5025a9f9357c376f48976ef0e1b1aefb73b97a5ae01a5972755/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bfff9740c69c0e4ed32416f013f3c45e2ae42ccedd1167ef2d805c000b6c71a5", size = 460845, upload-time = "2025-10-14T15:05:30.064Z" }, + { url = "https://files.pythonhosted.org/packages/8e/a4/2df3b404469122e8680f0fcd06079317e48db58a2da2950fb45020947734/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b27cf2eb1dda37b2089e3907d8ea92922b673c0c427886d4edc6b94d8dfe5db3", size = 489027, upload-time = "2025-10-14T15:05:31.064Z" }, + { url = "https://files.pythonhosted.org/packages/ea/84/4587ba5b1f267167ee715b7f66e6382cca6938e0a4b870adad93e44747e6/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:526e86aced14a65a5b0ec50827c745597c782ff46b571dbfe46192ab9e0b3c33", size = 595615, upload-time = "2025-10-14T15:05:32.074Z" }, + { url = "https://files.pythonhosted.org/packages/6a/0f/c6988c91d06e93cd0bb3d4a808bcf32375ca1904609835c3031799e3ecae/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04e78dd0b6352db95507fd8cb46f39d185cf8c74e4cf1e4fbad1d3df96faf510", size = 474836, upload-time = "2025-10-14T15:05:33.209Z" }, + { url = "https://files.pythonhosted.org/packages/b4/36/ded8aebea91919485b7bbabbd14f5f359326cb5ec218cd67074d1e426d74/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c85794a4cfa094714fb9c08d4a218375b2b95b8ed1666e8677c349906246c05", size = 455099, upload-time = "2025-10-14T15:05:34.189Z" }, + { url = "https://files.pythonhosted.org/packages/98/e0/8c9bdba88af756a2fce230dd365fab2baf927ba42cd47521ee7498fd5211/watchfiles-1.1.1-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:74d5012b7630714b66be7b7b7a78855ef7ad58e8650c73afc4c076a1f480a8d6", size = 630626, upload-time = "2025-10-14T15:05:35.216Z" }, + { url = "https://files.pythonhosted.org/packages/2a/84/a95db05354bf2d19e438520d92a8ca475e578c647f78f53197f5a2f17aaf/watchfiles-1.1.1-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:8fbe85cb3201c7d380d3d0b90e63d520f15d6afe217165d7f98c9c649654db81", size = 622519, upload-time = "2025-10-14T15:05:36.259Z" }, + { url = "https://files.pythonhosted.org/packages/1d/ce/d8acdc8de545de995c339be67711e474c77d643555a9bb74a9334252bd55/watchfiles-1.1.1-cp314-cp314-win32.whl", hash = "sha256:3fa0b59c92278b5a7800d3ee7733da9d096d4aabcfabb9a928918bd276ef9b9b", size = 272078, upload-time = "2025-10-14T15:05:37.63Z" }, + { url = "https://files.pythonhosted.org/packages/c4/c9/a74487f72d0451524be827e8edec251da0cc1fcf111646a511ae752e1a3d/watchfiles-1.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:c2047d0b6cea13b3316bdbafbfa0c4228ae593d995030fda39089d36e64fc03a", size = 287664, upload-time = "2025-10-14T15:05:38.95Z" }, + { url = "https://files.pythonhosted.org/packages/df/b8/8ac000702cdd496cdce998c6f4ee0ca1f15977bba51bdf07d872ebdfc34c/watchfiles-1.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:842178b126593addc05acf6fce960d28bc5fae7afbaa2c6c1b3a7b9460e5be02", size = 277154, upload-time = "2025-10-14T15:05:39.954Z" }, + { url = "https://files.pythonhosted.org/packages/47/a8/e3af2184707c29f0f14b1963c0aace6529f9d1b8582d5b99f31bbf42f59e/watchfiles-1.1.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:88863fbbc1a7312972f1c511f202eb30866370ebb8493aef2812b9ff28156a21", size = 403820, upload-time = "2025-10-14T15:05:40.932Z" }, + { url = "https://files.pythonhosted.org/packages/c0/ec/e47e307c2f4bd75f9f9e8afbe3876679b18e1bcec449beca132a1c5ffb2d/watchfiles-1.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:55c7475190662e202c08c6c0f4d9e345a29367438cf8e8037f3155e10a88d5a5", size = 390510, upload-time = "2025-10-14T15:05:41.945Z" }, + { url = "https://files.pythonhosted.org/packages/d5/a0/ad235642118090f66e7b2f18fd5c42082418404a79205cdfca50b6309c13/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f53fa183d53a1d7a8852277c92b967ae99c2d4dcee2bfacff8868e6e30b15f7", size = 448408, upload-time = "2025-10-14T15:05:43.385Z" }, + { url = "https://files.pythonhosted.org/packages/df/85/97fa10fd5ff3332ae17e7e40e20784e419e28521549780869f1413742e9d/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6aae418a8b323732fa89721d86f39ec8f092fc2af67f4217a2b07fd3e93c6101", size = 458968, upload-time = "2025-10-14T15:05:44.404Z" }, + { url = "https://files.pythonhosted.org/packages/47/c2/9059c2e8966ea5ce678166617a7f75ecba6164375f3b288e50a40dc6d489/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f096076119da54a6080e8920cbdaac3dbee667eb91dcc5e5b78840b87415bd44", size = 488096, upload-time = "2025-10-14T15:05:45.398Z" }, + { url = "https://files.pythonhosted.org/packages/94/44/d90a9ec8ac309bc26db808a13e7bfc0e4e78b6fc051078a554e132e80160/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:00485f441d183717038ed2e887a7c868154f216877653121068107b227a2f64c", size = 596040, upload-time = "2025-10-14T15:05:46.502Z" }, + { url = "https://files.pythonhosted.org/packages/95/68/4e3479b20ca305cfc561db3ed207a8a1c745ee32bf24f2026a129d0ddb6e/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a55f3e9e493158d7bfdb60a1165035f1cf7d320914e7b7ea83fe22c6023b58fc", size = 473847, upload-time = "2025-10-14T15:05:47.484Z" }, + { url = "https://files.pythonhosted.org/packages/4f/55/2af26693fd15165c4ff7857e38330e1b61ab8c37d15dc79118cdba115b7a/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c91ed27800188c2ae96d16e3149f199d62f86c7af5f5f4d2c61a3ed8cd3666c", size = 455072, upload-time = "2025-10-14T15:05:48.928Z" }, + { url = "https://files.pythonhosted.org/packages/66/1d/d0d200b10c9311ec25d2273f8aad8c3ef7cc7ea11808022501811208a750/watchfiles-1.1.1-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:311ff15a0bae3714ffb603e6ba6dbfba4065ab60865d15a6ec544133bdb21099", size = 629104, upload-time = "2025-10-14T15:05:49.908Z" }, + { url = "https://files.pythonhosted.org/packages/e3/bd/fa9bb053192491b3867ba07d2343d9f2252e00811567d30ae8d0f78136fe/watchfiles-1.1.1-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:a916a2932da8f8ab582f242c065f5c81bed3462849ca79ee357dd9551b0e9b01", size = 622112, upload-time = "2025-10-14T15:05:50.941Z" }, + { url = "https://files.pythonhosted.org/packages/ba/4c/a888c91e2e326872fa4705095d64acd8aa2fb9c1f7b9bd0588f33850516c/watchfiles-1.1.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:17ef139237dfced9da49fb7f2232c86ca9421f666d78c264c7ffca6601d154c3", size = 409611, upload-time = "2025-10-14T15:06:05.809Z" }, + { url = "https://files.pythonhosted.org/packages/1e/c7/5420d1943c8e3ce1a21c0a9330bcf7edafb6aa65d26b21dbb3267c9e8112/watchfiles-1.1.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:672b8adf25b1a0d35c96b5888b7b18699d27d4194bac8beeae75be4b7a3fc9b2", size = 396889, upload-time = "2025-10-14T15:06:07.035Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e5/0072cef3804ce8d3aaddbfe7788aadff6b3d3f98a286fdbee9fd74ca59a7/watchfiles-1.1.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77a13aea58bc2b90173bc69f2a90de8e282648939a00a602e1dc4ee23e26b66d", size = 451616, upload-time = "2025-10-14T15:06:08.072Z" }, + { url = "https://files.pythonhosted.org/packages/83/4e/b87b71cbdfad81ad7e83358b3e447fedd281b880a03d64a760fe0a11fc2e/watchfiles-1.1.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b495de0bb386df6a12b18335a0285dda90260f51bdb505503c02bcd1ce27a8b", size = 458413, upload-time = "2025-10-14T15:06:09.209Z" }, + { url = "https://files.pythonhosted.org/packages/d3/8e/e500f8b0b77be4ff753ac94dc06b33d8f0d839377fee1b78e8c8d8f031bf/watchfiles-1.1.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:db476ab59b6765134de1d4fe96a1a9c96ddf091683599be0f26147ea1b2e4b88", size = 408250, upload-time = "2025-10-14T15:06:10.264Z" }, + { url = "https://files.pythonhosted.org/packages/bd/95/615e72cd27b85b61eec764a5ca51bd94d40b5adea5ff47567d9ebc4d275a/watchfiles-1.1.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:89eef07eee5e9d1fda06e38822ad167a044153457e6fd997f8a858ab7564a336", size = 396117, upload-time = "2025-10-14T15:06:11.28Z" }, + { url = "https://files.pythonhosted.org/packages/c9/81/e7fe958ce8a7fb5c73cc9fb07f5aeaf755e6aa72498c57d760af760c91f8/watchfiles-1.1.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce19e06cbda693e9e7686358af9cd6f5d61312ab8b00488bc36f5aabbaf77e24", size = 450493, upload-time = "2025-10-14T15:06:12.321Z" }, + { url = "https://files.pythonhosted.org/packages/6e/d4/ed38dd3b1767193de971e694aa544356e63353c33a85d948166b5ff58b9e/watchfiles-1.1.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e6f39af2eab0118338902798b5aa6664f46ff66bc0280de76fca67a7f262a49", size = 457546, upload-time = "2025-10-14T15:06:13.372Z" }, ] [[package]] name = "websockets" version = "15.0.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/da/6462a9f510c0c49837bbc9345aca92d767a56c1fb2939e1579df1e1cdcf7/websockets-15.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d63efaa0cd96cf0c5fe4d581521d9fa87744540d4bc999ae6e08595a1014b45b", size = 175423 }, - { url = "https://files.pythonhosted.org/packages/1c/9f/9d11c1a4eb046a9e106483b9ff69bce7ac880443f00e5ce64261b47b07e7/websockets-15.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac60e3b188ec7574cb761b08d50fcedf9d77f1530352db4eef1707fe9dee7205", size = 173080 }, - { url = "https://files.pythonhosted.org/packages/d5/4f/b462242432d93ea45f297b6179c7333dd0402b855a912a04e7fc61c0d71f/websockets-15.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5756779642579d902eed757b21b0164cd6fe338506a8083eb58af5c372e39d9a", size = 173329 }, - { url = "https://files.pythonhosted.org/packages/6e/0c/6afa1f4644d7ed50284ac59cc70ef8abd44ccf7d45850d989ea7310538d0/websockets-15.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fdfe3e2a29e4db3659dbd5bbf04560cea53dd9610273917799f1cde46aa725e", size = 182312 }, - { url = "https://files.pythonhosted.org/packages/dd/d4/ffc8bd1350b229ca7a4db2a3e1c482cf87cea1baccd0ef3e72bc720caeec/websockets-15.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c2529b320eb9e35af0fa3016c187dffb84a3ecc572bcee7c3ce302bfeba52bf", size = 181319 }, - { url = "https://files.pythonhosted.org/packages/97/3a/5323a6bb94917af13bbb34009fac01e55c51dfde354f63692bf2533ffbc2/websockets-15.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac1e5c9054fe23226fb11e05a6e630837f074174c4c2f0fe442996112a6de4fb", size = 181631 }, - { url = "https://files.pythonhosted.org/packages/a6/cc/1aeb0f7cee59ef065724041bb7ed667b6ab1eeffe5141696cccec2687b66/websockets-15.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5df592cd503496351d6dc14f7cdad49f268d8e618f80dce0cd5a36b93c3fc08d", size = 182016 }, - { url = "https://files.pythonhosted.org/packages/79/f9/c86f8f7af208e4161a7f7e02774e9d0a81c632ae76db2ff22549e1718a51/websockets-15.0.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0a34631031a8f05657e8e90903e656959234f3a04552259458aac0b0f9ae6fd9", size = 181426 }, - { url = "https://files.pythonhosted.org/packages/c7/b9/828b0bc6753db905b91df6ae477c0b14a141090df64fb17f8a9d7e3516cf/websockets-15.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3d00075aa65772e7ce9e990cab3ff1de702aa09be3940d1dc88d5abf1ab8a09c", size = 181360 }, - { url = "https://files.pythonhosted.org/packages/89/fb/250f5533ec468ba6327055b7d98b9df056fb1ce623b8b6aaafb30b55d02e/websockets-15.0.1-cp310-cp310-win32.whl", hash = "sha256:1234d4ef35db82f5446dca8e35a7da7964d02c127b095e172e54397fb6a6c256", size = 176388 }, - { url = "https://files.pythonhosted.org/packages/1c/46/aca7082012768bb98e5608f01658ff3ac8437e563eca41cf068bd5849a5e/websockets-15.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:39c1fec2c11dc8d89bba6b2bf1556af381611a173ac2b511cf7231622058af41", size = 176830 }, - { url = "https://files.pythonhosted.org/packages/9f/32/18fcd5919c293a398db67443acd33fde142f283853076049824fc58e6f75/websockets-15.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:823c248b690b2fd9303ba00c4f66cd5e2d8c3ba4aa968b2779be9532a4dad431", size = 175423 }, - { url = "https://files.pythonhosted.org/packages/76/70/ba1ad96b07869275ef42e2ce21f07a5b0148936688c2baf7e4a1f60d5058/websockets-15.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678999709e68425ae2593acf2e3ebcbcf2e69885a5ee78f9eb80e6e371f1bf57", size = 173082 }, - { url = "https://files.pythonhosted.org/packages/86/f2/10b55821dd40eb696ce4704a87d57774696f9451108cff0d2824c97e0f97/websockets-15.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d50fd1ee42388dcfb2b3676132c78116490976f1300da28eb629272d5d93e905", size = 173330 }, - { url = "https://files.pythonhosted.org/packages/a5/90/1c37ae8b8a113d3daf1065222b6af61cc44102da95388ac0018fcb7d93d9/websockets-15.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d99e5546bf73dbad5bf3547174cd6cb8ba7273062a23808ffea025ecb1cf8562", size = 182878 }, - { url = "https://files.pythonhosted.org/packages/8e/8d/96e8e288b2a41dffafb78e8904ea7367ee4f891dafc2ab8d87e2124cb3d3/websockets-15.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66dd88c918e3287efc22409d426c8f729688d89a0c587c88971a0faa2c2f3792", size = 181883 }, - { url = "https://files.pythonhosted.org/packages/93/1f/5d6dbf551766308f6f50f8baf8e9860be6182911e8106da7a7f73785f4c4/websockets-15.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8dd8327c795b3e3f219760fa603dcae1dcc148172290a8ab15158cf85a953413", size = 182252 }, - { url = "https://files.pythonhosted.org/packages/d4/78/2d4fed9123e6620cbf1706c0de8a1632e1a28e7774d94346d7de1bba2ca3/websockets-15.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8fdc51055e6ff4adeb88d58a11042ec9a5eae317a0a53d12c062c8a8865909e8", size = 182521 }, - { url = "https://files.pythonhosted.org/packages/e7/3b/66d4c1b444dd1a9823c4a81f50231b921bab54eee2f69e70319b4e21f1ca/websockets-15.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:693f0192126df6c2327cce3baa7c06f2a117575e32ab2308f7f8216c29d9e2e3", size = 181958 }, - { url = "https://files.pythonhosted.org/packages/08/ff/e9eed2ee5fed6f76fdd6032ca5cd38c57ca9661430bb3d5fb2872dc8703c/websockets-15.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:54479983bd5fb469c38f2f5c7e3a24f9a4e70594cd68cd1fa6b9340dadaff7cf", size = 181918 }, - { url = "https://files.pythonhosted.org/packages/d8/75/994634a49b7e12532be6a42103597b71098fd25900f7437d6055ed39930a/websockets-15.0.1-cp311-cp311-win32.whl", hash = "sha256:16b6c1b3e57799b9d38427dda63edcbe4926352c47cf88588c0be4ace18dac85", size = 176388 }, - { url = "https://files.pythonhosted.org/packages/98/93/e36c73f78400a65f5e236cd376713c34182e6663f6889cd45a4a04d8f203/websockets-15.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:27ccee0071a0e75d22cb35849b1db43f2ecd3e161041ac1ee9d2352ddf72f065", size = 176828 }, - { url = "https://files.pythonhosted.org/packages/51/6b/4545a0d843594f5d0771e86463606a3988b5a09ca5123136f8a76580dd63/websockets-15.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3e90baa811a5d73f3ca0bcbf32064d663ed81318ab225ee4f427ad4e26e5aff3", size = 175437 }, - { url = "https://files.pythonhosted.org/packages/f4/71/809a0f5f6a06522af902e0f2ea2757f71ead94610010cf570ab5c98e99ed/websockets-15.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:592f1a9fe869c778694f0aa806ba0374e97648ab57936f092fd9d87f8bc03665", size = 173096 }, - { url = "https://files.pythonhosted.org/packages/3d/69/1a681dd6f02180916f116894181eab8b2e25b31e484c5d0eae637ec01f7c/websockets-15.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0701bc3cfcb9164d04a14b149fd74be7347a530ad3bbf15ab2c678a2cd3dd9a2", size = 173332 }, - { url = "https://files.pythonhosted.org/packages/a6/02/0073b3952f5bce97eafbb35757f8d0d54812b6174ed8dd952aa08429bcc3/websockets-15.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8b56bdcdb4505c8078cb6c7157d9811a85790f2f2b3632c7d1462ab5783d215", size = 183152 }, - { url = "https://files.pythonhosted.org/packages/74/45/c205c8480eafd114b428284840da0b1be9ffd0e4f87338dc95dc6ff961a1/websockets-15.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0af68c55afbd5f07986df82831c7bff04846928ea8d1fd7f30052638788bc9b5", size = 182096 }, - { url = "https://files.pythonhosted.org/packages/14/8f/aa61f528fba38578ec553c145857a181384c72b98156f858ca5c8e82d9d3/websockets-15.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64dee438fed052b52e4f98f76c5790513235efaa1ef7f3f2192c392cd7c91b65", size = 182523 }, - { url = "https://files.pythonhosted.org/packages/ec/6d/0267396610add5bc0d0d3e77f546d4cd287200804fe02323797de77dbce9/websockets-15.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d5f6b181bb38171a8ad1d6aa58a67a6aa9d4b38d0f8c5f496b9e42561dfc62fe", size = 182790 }, - { url = "https://files.pythonhosted.org/packages/02/05/c68c5adbf679cf610ae2f74a9b871ae84564462955d991178f95a1ddb7dd/websockets-15.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5d54b09eba2bada6011aea5375542a157637b91029687eb4fdb2dab11059c1b4", size = 182165 }, - { url = "https://files.pythonhosted.org/packages/29/93/bb672df7b2f5faac89761cb5fa34f5cec45a4026c383a4b5761c6cea5c16/websockets-15.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3be571a8b5afed347da347bfcf27ba12b069d9d7f42cb8c7028b5e98bbb12597", size = 182160 }, - { url = "https://files.pythonhosted.org/packages/ff/83/de1f7709376dc3ca9b7eeb4b9a07b4526b14876b6d372a4dc62312bebee0/websockets-15.0.1-cp312-cp312-win32.whl", hash = "sha256:c338ffa0520bdb12fbc527265235639fb76e7bc7faafbb93f6ba80d9c06578a9", size = 176395 }, - { url = "https://files.pythonhosted.org/packages/7d/71/abf2ebc3bbfa40f391ce1428c7168fb20582d0ff57019b69ea20fa698043/websockets-15.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcd5cf9e305d7b8338754470cf69cf81f420459dbae8a3b40cee57417f4614a7", size = 176841 }, - { url = "https://files.pythonhosted.org/packages/cb/9f/51f0cf64471a9d2b4d0fc6c534f323b664e7095640c34562f5182e5a7195/websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931", size = 175440 }, - { url = "https://files.pythonhosted.org/packages/8a/05/aa116ec9943c718905997412c5989f7ed671bc0188ee2ba89520e8765d7b/websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675", size = 173098 }, - { url = "https://files.pythonhosted.org/packages/ff/0b/33cef55ff24f2d92924923c99926dcce78e7bd922d649467f0eda8368923/websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151", size = 173329 }, - { url = "https://files.pythonhosted.org/packages/31/1d/063b25dcc01faa8fada1469bdf769de3768b7044eac9d41f734fd7b6ad6d/websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22", size = 183111 }, - { url = "https://files.pythonhosted.org/packages/93/53/9a87ee494a51bf63e4ec9241c1ccc4f7c2f45fff85d5bde2ff74fcb68b9e/websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f", size = 182054 }, - { url = "https://files.pythonhosted.org/packages/ff/b2/83a6ddf56cdcbad4e3d841fcc55d6ba7d19aeb89c50f24dd7e859ec0805f/websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8", size = 182496 }, - { url = "https://files.pythonhosted.org/packages/98/41/e7038944ed0abf34c45aa4635ba28136f06052e08fc2168520bb8b25149f/websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375", size = 182829 }, - { url = "https://files.pythonhosted.org/packages/e0/17/de15b6158680c7623c6ef0db361da965ab25d813ae54fcfeae2e5b9ef910/websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d", size = 182217 }, - { url = "https://files.pythonhosted.org/packages/33/2b/1f168cb6041853eef0362fb9554c3824367c5560cbdaad89ac40f8c2edfc/websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4", size = 182195 }, - { url = "https://files.pythonhosted.org/packages/86/eb/20b6cdf273913d0ad05a6a14aed4b9a85591c18a987a3d47f20fa13dcc47/websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa", size = 176393 }, - { url = "https://files.pythonhosted.org/packages/1b/6c/c65773d6cab416a64d191d6ee8a8b1c68a09970ea6909d16965d26bfed1e/websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561", size = 176837 }, - { url = "https://files.pythonhosted.org/packages/02/9e/d40f779fa16f74d3468357197af8d6ad07e7c5a27ea1ca74ceb38986f77a/websockets-15.0.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0c9e74d766f2818bb95f84c25be4dea09841ac0f734d1966f415e4edfc4ef1c3", size = 173109 }, - { url = "https://files.pythonhosted.org/packages/bc/cd/5b887b8585a593073fd92f7c23ecd3985cd2c3175025a91b0d69b0551372/websockets-15.0.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1009ee0c7739c08a0cd59de430d6de452a55e42d6b522de7aa15e6f67db0b8e1", size = 173343 }, - { url = "https://files.pythonhosted.org/packages/fe/ae/d34f7556890341e900a95acf4886833646306269f899d58ad62f588bf410/websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76d1f20b1c7a2fa82367e04982e708723ba0e7b8d43aa643d3dcd404d74f1475", size = 174599 }, - { url = "https://files.pythonhosted.org/packages/71/e6/5fd43993a87db364ec60fc1d608273a1a465c0caba69176dd160e197ce42/websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f29d80eb9a9263b8d109135351caf568cc3f80b9928bccde535c235de55c22d9", size = 174207 }, - { url = "https://files.pythonhosted.org/packages/2b/fb/c492d6daa5ec067c2988ac80c61359ace5c4c674c532985ac5a123436cec/websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b359ed09954d7c18bbc1680f380c7301f92c60bf924171629c5db97febb12f04", size = 174155 }, - { url = "https://files.pythonhosted.org/packages/68/a1/dcb68430b1d00b698ae7a7e0194433bce4f07ded185f0ee5fb21e2a2e91e/websockets-15.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:cad21560da69f4ce7658ca2cb83138fb4cf695a2ba3e475e0559e05991aa8122", size = 176884 }, - { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743 }, +sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016, upload-time = "2025-03-05T20:03:41.606Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/da/6462a9f510c0c49837bbc9345aca92d767a56c1fb2939e1579df1e1cdcf7/websockets-15.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d63efaa0cd96cf0c5fe4d581521d9fa87744540d4bc999ae6e08595a1014b45b", size = 175423, upload-time = "2025-03-05T20:01:35.363Z" }, + { url = "https://files.pythonhosted.org/packages/1c/9f/9d11c1a4eb046a9e106483b9ff69bce7ac880443f00e5ce64261b47b07e7/websockets-15.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac60e3b188ec7574cb761b08d50fcedf9d77f1530352db4eef1707fe9dee7205", size = 173080, upload-time = "2025-03-05T20:01:37.304Z" }, + { url = "https://files.pythonhosted.org/packages/d5/4f/b462242432d93ea45f297b6179c7333dd0402b855a912a04e7fc61c0d71f/websockets-15.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5756779642579d902eed757b21b0164cd6fe338506a8083eb58af5c372e39d9a", size = 173329, upload-time = "2025-03-05T20:01:39.668Z" }, + { url = "https://files.pythonhosted.org/packages/6e/0c/6afa1f4644d7ed50284ac59cc70ef8abd44ccf7d45850d989ea7310538d0/websockets-15.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fdfe3e2a29e4db3659dbd5bbf04560cea53dd9610273917799f1cde46aa725e", size = 182312, upload-time = "2025-03-05T20:01:41.815Z" }, + { url = "https://files.pythonhosted.org/packages/dd/d4/ffc8bd1350b229ca7a4db2a3e1c482cf87cea1baccd0ef3e72bc720caeec/websockets-15.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c2529b320eb9e35af0fa3016c187dffb84a3ecc572bcee7c3ce302bfeba52bf", size = 181319, upload-time = "2025-03-05T20:01:43.967Z" }, + { url = "https://files.pythonhosted.org/packages/97/3a/5323a6bb94917af13bbb34009fac01e55c51dfde354f63692bf2533ffbc2/websockets-15.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac1e5c9054fe23226fb11e05a6e630837f074174c4c2f0fe442996112a6de4fb", size = 181631, upload-time = "2025-03-05T20:01:46.104Z" }, + { url = "https://files.pythonhosted.org/packages/a6/cc/1aeb0f7cee59ef065724041bb7ed667b6ab1eeffe5141696cccec2687b66/websockets-15.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5df592cd503496351d6dc14f7cdad49f268d8e618f80dce0cd5a36b93c3fc08d", size = 182016, upload-time = "2025-03-05T20:01:47.603Z" }, + { url = "https://files.pythonhosted.org/packages/79/f9/c86f8f7af208e4161a7f7e02774e9d0a81c632ae76db2ff22549e1718a51/websockets-15.0.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0a34631031a8f05657e8e90903e656959234f3a04552259458aac0b0f9ae6fd9", size = 181426, upload-time = "2025-03-05T20:01:48.949Z" }, + { url = "https://files.pythonhosted.org/packages/c7/b9/828b0bc6753db905b91df6ae477c0b14a141090df64fb17f8a9d7e3516cf/websockets-15.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3d00075aa65772e7ce9e990cab3ff1de702aa09be3940d1dc88d5abf1ab8a09c", size = 181360, upload-time = "2025-03-05T20:01:50.938Z" }, + { url = "https://files.pythonhosted.org/packages/89/fb/250f5533ec468ba6327055b7d98b9df056fb1ce623b8b6aaafb30b55d02e/websockets-15.0.1-cp310-cp310-win32.whl", hash = "sha256:1234d4ef35db82f5446dca8e35a7da7964d02c127b095e172e54397fb6a6c256", size = 176388, upload-time = "2025-03-05T20:01:52.213Z" }, + { url = "https://files.pythonhosted.org/packages/1c/46/aca7082012768bb98e5608f01658ff3ac8437e563eca41cf068bd5849a5e/websockets-15.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:39c1fec2c11dc8d89bba6b2bf1556af381611a173ac2b511cf7231622058af41", size = 176830, upload-time = "2025-03-05T20:01:53.922Z" }, + { url = "https://files.pythonhosted.org/packages/9f/32/18fcd5919c293a398db67443acd33fde142f283853076049824fc58e6f75/websockets-15.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:823c248b690b2fd9303ba00c4f66cd5e2d8c3ba4aa968b2779be9532a4dad431", size = 175423, upload-time = "2025-03-05T20:01:56.276Z" }, + { url = "https://files.pythonhosted.org/packages/76/70/ba1ad96b07869275ef42e2ce21f07a5b0148936688c2baf7e4a1f60d5058/websockets-15.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678999709e68425ae2593acf2e3ebcbcf2e69885a5ee78f9eb80e6e371f1bf57", size = 173082, upload-time = "2025-03-05T20:01:57.563Z" }, + { url = "https://files.pythonhosted.org/packages/86/f2/10b55821dd40eb696ce4704a87d57774696f9451108cff0d2824c97e0f97/websockets-15.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d50fd1ee42388dcfb2b3676132c78116490976f1300da28eb629272d5d93e905", size = 173330, upload-time = "2025-03-05T20:01:59.063Z" }, + { url = "https://files.pythonhosted.org/packages/a5/90/1c37ae8b8a113d3daf1065222b6af61cc44102da95388ac0018fcb7d93d9/websockets-15.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d99e5546bf73dbad5bf3547174cd6cb8ba7273062a23808ffea025ecb1cf8562", size = 182878, upload-time = "2025-03-05T20:02:00.305Z" }, + { url = "https://files.pythonhosted.org/packages/8e/8d/96e8e288b2a41dffafb78e8904ea7367ee4f891dafc2ab8d87e2124cb3d3/websockets-15.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66dd88c918e3287efc22409d426c8f729688d89a0c587c88971a0faa2c2f3792", size = 181883, upload-time = "2025-03-05T20:02:03.148Z" }, + { url = "https://files.pythonhosted.org/packages/93/1f/5d6dbf551766308f6f50f8baf8e9860be6182911e8106da7a7f73785f4c4/websockets-15.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8dd8327c795b3e3f219760fa603dcae1dcc148172290a8ab15158cf85a953413", size = 182252, upload-time = "2025-03-05T20:02:05.29Z" }, + { url = "https://files.pythonhosted.org/packages/d4/78/2d4fed9123e6620cbf1706c0de8a1632e1a28e7774d94346d7de1bba2ca3/websockets-15.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8fdc51055e6ff4adeb88d58a11042ec9a5eae317a0a53d12c062c8a8865909e8", size = 182521, upload-time = "2025-03-05T20:02:07.458Z" }, + { url = "https://files.pythonhosted.org/packages/e7/3b/66d4c1b444dd1a9823c4a81f50231b921bab54eee2f69e70319b4e21f1ca/websockets-15.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:693f0192126df6c2327cce3baa7c06f2a117575e32ab2308f7f8216c29d9e2e3", size = 181958, upload-time = "2025-03-05T20:02:09.842Z" }, + { url = "https://files.pythonhosted.org/packages/08/ff/e9eed2ee5fed6f76fdd6032ca5cd38c57ca9661430bb3d5fb2872dc8703c/websockets-15.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:54479983bd5fb469c38f2f5c7e3a24f9a4e70594cd68cd1fa6b9340dadaff7cf", size = 181918, upload-time = "2025-03-05T20:02:11.968Z" }, + { url = "https://files.pythonhosted.org/packages/d8/75/994634a49b7e12532be6a42103597b71098fd25900f7437d6055ed39930a/websockets-15.0.1-cp311-cp311-win32.whl", hash = "sha256:16b6c1b3e57799b9d38427dda63edcbe4926352c47cf88588c0be4ace18dac85", size = 176388, upload-time = "2025-03-05T20:02:13.32Z" }, + { url = "https://files.pythonhosted.org/packages/98/93/e36c73f78400a65f5e236cd376713c34182e6663f6889cd45a4a04d8f203/websockets-15.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:27ccee0071a0e75d22cb35849b1db43f2ecd3e161041ac1ee9d2352ddf72f065", size = 176828, upload-time = "2025-03-05T20:02:14.585Z" }, + { url = "https://files.pythonhosted.org/packages/51/6b/4545a0d843594f5d0771e86463606a3988b5a09ca5123136f8a76580dd63/websockets-15.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3e90baa811a5d73f3ca0bcbf32064d663ed81318ab225ee4f427ad4e26e5aff3", size = 175437, upload-time = "2025-03-05T20:02:16.706Z" }, + { url = "https://files.pythonhosted.org/packages/f4/71/809a0f5f6a06522af902e0f2ea2757f71ead94610010cf570ab5c98e99ed/websockets-15.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:592f1a9fe869c778694f0aa806ba0374e97648ab57936f092fd9d87f8bc03665", size = 173096, upload-time = "2025-03-05T20:02:18.832Z" }, + { url = "https://files.pythonhosted.org/packages/3d/69/1a681dd6f02180916f116894181eab8b2e25b31e484c5d0eae637ec01f7c/websockets-15.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0701bc3cfcb9164d04a14b149fd74be7347a530ad3bbf15ab2c678a2cd3dd9a2", size = 173332, upload-time = "2025-03-05T20:02:20.187Z" }, + { url = "https://files.pythonhosted.org/packages/a6/02/0073b3952f5bce97eafbb35757f8d0d54812b6174ed8dd952aa08429bcc3/websockets-15.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8b56bdcdb4505c8078cb6c7157d9811a85790f2f2b3632c7d1462ab5783d215", size = 183152, upload-time = "2025-03-05T20:02:22.286Z" }, + { url = "https://files.pythonhosted.org/packages/74/45/c205c8480eafd114b428284840da0b1be9ffd0e4f87338dc95dc6ff961a1/websockets-15.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0af68c55afbd5f07986df82831c7bff04846928ea8d1fd7f30052638788bc9b5", size = 182096, upload-time = "2025-03-05T20:02:24.368Z" }, + { url = "https://files.pythonhosted.org/packages/14/8f/aa61f528fba38578ec553c145857a181384c72b98156f858ca5c8e82d9d3/websockets-15.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64dee438fed052b52e4f98f76c5790513235efaa1ef7f3f2192c392cd7c91b65", size = 182523, upload-time = "2025-03-05T20:02:25.669Z" }, + { url = "https://files.pythonhosted.org/packages/ec/6d/0267396610add5bc0d0d3e77f546d4cd287200804fe02323797de77dbce9/websockets-15.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d5f6b181bb38171a8ad1d6aa58a67a6aa9d4b38d0f8c5f496b9e42561dfc62fe", size = 182790, upload-time = "2025-03-05T20:02:26.99Z" }, + { url = "https://files.pythonhosted.org/packages/02/05/c68c5adbf679cf610ae2f74a9b871ae84564462955d991178f95a1ddb7dd/websockets-15.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5d54b09eba2bada6011aea5375542a157637b91029687eb4fdb2dab11059c1b4", size = 182165, upload-time = "2025-03-05T20:02:30.291Z" }, + { url = "https://files.pythonhosted.org/packages/29/93/bb672df7b2f5faac89761cb5fa34f5cec45a4026c383a4b5761c6cea5c16/websockets-15.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3be571a8b5afed347da347bfcf27ba12b069d9d7f42cb8c7028b5e98bbb12597", size = 182160, upload-time = "2025-03-05T20:02:31.634Z" }, + { url = "https://files.pythonhosted.org/packages/ff/83/de1f7709376dc3ca9b7eeb4b9a07b4526b14876b6d372a4dc62312bebee0/websockets-15.0.1-cp312-cp312-win32.whl", hash = "sha256:c338ffa0520bdb12fbc527265235639fb76e7bc7faafbb93f6ba80d9c06578a9", size = 176395, upload-time = "2025-03-05T20:02:33.017Z" }, + { url = "https://files.pythonhosted.org/packages/7d/71/abf2ebc3bbfa40f391ce1428c7168fb20582d0ff57019b69ea20fa698043/websockets-15.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcd5cf9e305d7b8338754470cf69cf81f420459dbae8a3b40cee57417f4614a7", size = 176841, upload-time = "2025-03-05T20:02:34.498Z" }, + { url = "https://files.pythonhosted.org/packages/cb/9f/51f0cf64471a9d2b4d0fc6c534f323b664e7095640c34562f5182e5a7195/websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931", size = 175440, upload-time = "2025-03-05T20:02:36.695Z" }, + { url = "https://files.pythonhosted.org/packages/8a/05/aa116ec9943c718905997412c5989f7ed671bc0188ee2ba89520e8765d7b/websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675", size = 173098, upload-time = "2025-03-05T20:02:37.985Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0b/33cef55ff24f2d92924923c99926dcce78e7bd922d649467f0eda8368923/websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151", size = 173329, upload-time = "2025-03-05T20:02:39.298Z" }, + { url = "https://files.pythonhosted.org/packages/31/1d/063b25dcc01faa8fada1469bdf769de3768b7044eac9d41f734fd7b6ad6d/websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22", size = 183111, upload-time = "2025-03-05T20:02:40.595Z" }, + { url = "https://files.pythonhosted.org/packages/93/53/9a87ee494a51bf63e4ec9241c1ccc4f7c2f45fff85d5bde2ff74fcb68b9e/websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f", size = 182054, upload-time = "2025-03-05T20:02:41.926Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b2/83a6ddf56cdcbad4e3d841fcc55d6ba7d19aeb89c50f24dd7e859ec0805f/websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8", size = 182496, upload-time = "2025-03-05T20:02:43.304Z" }, + { url = "https://files.pythonhosted.org/packages/98/41/e7038944ed0abf34c45aa4635ba28136f06052e08fc2168520bb8b25149f/websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375", size = 182829, upload-time = "2025-03-05T20:02:48.812Z" }, + { url = "https://files.pythonhosted.org/packages/e0/17/de15b6158680c7623c6ef0db361da965ab25d813ae54fcfeae2e5b9ef910/websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d", size = 182217, upload-time = "2025-03-05T20:02:50.14Z" }, + { url = "https://files.pythonhosted.org/packages/33/2b/1f168cb6041853eef0362fb9554c3824367c5560cbdaad89ac40f8c2edfc/websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4", size = 182195, upload-time = "2025-03-05T20:02:51.561Z" }, + { url = "https://files.pythonhosted.org/packages/86/eb/20b6cdf273913d0ad05a6a14aed4b9a85591c18a987a3d47f20fa13dcc47/websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa", size = 176393, upload-time = "2025-03-05T20:02:53.814Z" }, + { url = "https://files.pythonhosted.org/packages/1b/6c/c65773d6cab416a64d191d6ee8a8b1c68a09970ea6909d16965d26bfed1e/websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561", size = 176837, upload-time = "2025-03-05T20:02:55.237Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/d40f779fa16f74d3468357197af8d6ad07e7c5a27ea1ca74ceb38986f77a/websockets-15.0.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0c9e74d766f2818bb95f84c25be4dea09841ac0f734d1966f415e4edfc4ef1c3", size = 173109, upload-time = "2025-03-05T20:03:17.769Z" }, + { url = "https://files.pythonhosted.org/packages/bc/cd/5b887b8585a593073fd92f7c23ecd3985cd2c3175025a91b0d69b0551372/websockets-15.0.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1009ee0c7739c08a0cd59de430d6de452a55e42d6b522de7aa15e6f67db0b8e1", size = 173343, upload-time = "2025-03-05T20:03:19.094Z" }, + { url = "https://files.pythonhosted.org/packages/fe/ae/d34f7556890341e900a95acf4886833646306269f899d58ad62f588bf410/websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76d1f20b1c7a2fa82367e04982e708723ba0e7b8d43aa643d3dcd404d74f1475", size = 174599, upload-time = "2025-03-05T20:03:21.1Z" }, + { url = "https://files.pythonhosted.org/packages/71/e6/5fd43993a87db364ec60fc1d608273a1a465c0caba69176dd160e197ce42/websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f29d80eb9a9263b8d109135351caf568cc3f80b9928bccde535c235de55c22d9", size = 174207, upload-time = "2025-03-05T20:03:23.221Z" }, + { url = "https://files.pythonhosted.org/packages/2b/fb/c492d6daa5ec067c2988ac80c61359ace5c4c674c532985ac5a123436cec/websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b359ed09954d7c18bbc1680f380c7301f92c60bf924171629c5db97febb12f04", size = 174155, upload-time = "2025-03-05T20:03:25.321Z" }, + { url = "https://files.pythonhosted.org/packages/68/a1/dcb68430b1d00b698ae7a7e0194433bce4f07ded185f0ee5fb21e2a2e91e/websockets-15.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:cad21560da69f4ce7658ca2cb83138fb4cf695a2ba3e475e0559e05991aa8122", size = 176884, upload-time = "2025-03-05T20:03:27.934Z" }, + { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" }, ] [[package]] @@ -3282,101 +3284,101 @@ dependencies = [ { name = "multidict" }, { name = "propcache" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3c/fb/efaa23fa4e45537b827620f04cf8f3cd658b76642205162e072703a5b963/yarl-1.20.1.tar.gz", hash = "sha256:d017a4997ee50c91fd5466cef416231bb82177b93b029906cefc542ce14c35ac", size = 186428 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/65/7fed0d774abf47487c64be14e9223749468922817b5e8792b8a64792a1bb/yarl-1.20.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6032e6da6abd41e4acda34d75a816012717000fa6839f37124a47fcefc49bec4", size = 132910 }, - { url = "https://files.pythonhosted.org/packages/8a/7b/988f55a52da99df9e56dc733b8e4e5a6ae2090081dc2754fc8fd34e60aa0/yarl-1.20.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2c7b34d804b8cf9b214f05015c4fee2ebe7ed05cf581e7192c06555c71f4446a", size = 90644 }, - { url = "https://files.pythonhosted.org/packages/f7/de/30d98f03e95d30c7e3cc093759982d038c8833ec2451001d45ef4854edc1/yarl-1.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0c869f2651cc77465f6cd01d938d91a11d9ea5d798738c1dc077f3de0b5e5fed", size = 89322 }, - { url = "https://files.pythonhosted.org/packages/e0/7a/f2f314f5ebfe9200724b0b748de2186b927acb334cf964fd312eb86fc286/yarl-1.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62915e6688eb4d180d93840cda4110995ad50c459bf931b8b3775b37c264af1e", size = 323786 }, - { url = "https://files.pythonhosted.org/packages/15/3f/718d26f189db96d993d14b984ce91de52e76309d0fd1d4296f34039856aa/yarl-1.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:41ebd28167bc6af8abb97fec1a399f412eec5fd61a3ccbe2305a18b84fb4ca73", size = 319627 }, - { url = "https://files.pythonhosted.org/packages/a5/76/8fcfbf5fa2369157b9898962a4a7d96764b287b085b5b3d9ffae69cdefd1/yarl-1.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:21242b4288a6d56f04ea193adde174b7e347ac46ce6bc84989ff7c1b1ecea84e", size = 339149 }, - { url = "https://files.pythonhosted.org/packages/3c/95/d7fc301cc4661785967acc04f54a4a42d5124905e27db27bb578aac49b5c/yarl-1.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bea21cdae6c7eb02ba02a475f37463abfe0a01f5d7200121b03e605d6a0439f8", size = 333327 }, - { url = "https://files.pythonhosted.org/packages/65/94/e21269718349582eee81efc5c1c08ee71c816bfc1585b77d0ec3f58089eb/yarl-1.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f8a891e4a22a89f5dde7862994485e19db246b70bb288d3ce73a34422e55b23", size = 326054 }, - { url = "https://files.pythonhosted.org/packages/32/ae/8616d1f07853704523519f6131d21f092e567c5af93de7e3e94b38d7f065/yarl-1.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dd803820d44c8853a109a34e3660e5a61beae12970da479cf44aa2954019bf70", size = 315035 }, - { url = "https://files.pythonhosted.org/packages/48/aa/0ace06280861ef055855333707db5e49c6e3a08840a7ce62682259d0a6c0/yarl-1.20.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b982fa7f74c80d5c0c7b5b38f908971e513380a10fecea528091405f519b9ebb", size = 338962 }, - { url = "https://files.pythonhosted.org/packages/20/52/1e9d0e6916f45a8fb50e6844f01cb34692455f1acd548606cbda8134cd1e/yarl-1.20.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:33f29ecfe0330c570d997bcf1afd304377f2e48f61447f37e846a6058a4d33b2", size = 335399 }, - { url = "https://files.pythonhosted.org/packages/f2/65/60452df742952c630e82f394cd409de10610481d9043aa14c61bf846b7b1/yarl-1.20.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:835ab2cfc74d5eb4a6a528c57f05688099da41cf4957cf08cad38647e4a83b30", size = 338649 }, - { url = "https://files.pythonhosted.org/packages/7b/f5/6cd4ff38dcde57a70f23719a838665ee17079640c77087404c3d34da6727/yarl-1.20.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:46b5e0ccf1943a9a6e766b2c2b8c732c55b34e28be57d8daa2b3c1d1d4009309", size = 358563 }, - { url = "https://files.pythonhosted.org/packages/d1/90/c42eefd79d0d8222cb3227bdd51b640c0c1d0aa33fe4cc86c36eccba77d3/yarl-1.20.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:df47c55f7d74127d1b11251fe6397d84afdde0d53b90bedb46a23c0e534f9d24", size = 357609 }, - { url = "https://files.pythonhosted.org/packages/03/c8/cea6b232cb4617514232e0f8a718153a95b5d82b5290711b201545825532/yarl-1.20.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:76d12524d05841276b0e22573f28d5fbcb67589836772ae9244d90dd7d66aa13", size = 350224 }, - { url = "https://files.pythonhosted.org/packages/ce/a3/eaa0ab9712f1f3d01faf43cf6f1f7210ce4ea4a7e9b28b489a2261ca8db9/yarl-1.20.1-cp310-cp310-win32.whl", hash = "sha256:6c4fbf6b02d70e512d7ade4b1f998f237137f1417ab07ec06358ea04f69134f8", size = 81753 }, - { url = "https://files.pythonhosted.org/packages/8f/34/e4abde70a9256465fe31c88ed02c3f8502b7b5dead693a4f350a06413f28/yarl-1.20.1-cp310-cp310-win_amd64.whl", hash = "sha256:aef6c4d69554d44b7f9d923245f8ad9a707d971e6209d51279196d8e8fe1ae16", size = 86817 }, - { url = "https://files.pythonhosted.org/packages/b1/18/893b50efc2350e47a874c5c2d67e55a0ea5df91186b2a6f5ac52eff887cd/yarl-1.20.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:47ee6188fea634bdfaeb2cc420f5b3b17332e6225ce88149a17c413c77ff269e", size = 133833 }, - { url = "https://files.pythonhosted.org/packages/89/ed/b8773448030e6fc47fa797f099ab9eab151a43a25717f9ac043844ad5ea3/yarl-1.20.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d0f6500f69e8402d513e5eedb77a4e1818691e8f45e6b687147963514d84b44b", size = 91070 }, - { url = "https://files.pythonhosted.org/packages/e3/e3/409bd17b1e42619bf69f60e4f031ce1ccb29bd7380117a55529e76933464/yarl-1.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7a8900a42fcdaad568de58887c7b2f602962356908eedb7628eaf6021a6e435b", size = 89818 }, - { url = "https://files.pythonhosted.org/packages/f8/77/64d8431a4d77c856eb2d82aa3de2ad6741365245a29b3a9543cd598ed8c5/yarl-1.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bad6d131fda8ef508b36be3ece16d0902e80b88ea7200f030a0f6c11d9e508d4", size = 347003 }, - { url = "https://files.pythonhosted.org/packages/8d/d2/0c7e4def093dcef0bd9fa22d4d24b023788b0a33b8d0088b51aa51e21e99/yarl-1.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:df018d92fe22aaebb679a7f89fe0c0f368ec497e3dda6cb81a567610f04501f1", size = 336537 }, - { url = "https://files.pythonhosted.org/packages/f0/f3/fc514f4b2cf02cb59d10cbfe228691d25929ce8f72a38db07d3febc3f706/yarl-1.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f969afbb0a9b63c18d0feecf0db09d164b7a44a053e78a7d05f5df163e43833", size = 362358 }, - { url = "https://files.pythonhosted.org/packages/ea/6d/a313ac8d8391381ff9006ac05f1d4331cee3b1efaa833a53d12253733255/yarl-1.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:812303eb4aa98e302886ccda58d6b099e3576b1b9276161469c25803a8db277d", size = 357362 }, - { url = "https://files.pythonhosted.org/packages/00/70/8f78a95d6935a70263d46caa3dd18e1f223cf2f2ff2037baa01a22bc5b22/yarl-1.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98c4a7d166635147924aa0bf9bfe8d8abad6fffa6102de9c99ea04a1376f91e8", size = 348979 }, - { url = "https://files.pythonhosted.org/packages/cb/05/42773027968968f4f15143553970ee36ead27038d627f457cc44bbbeecf3/yarl-1.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12e768f966538e81e6e7550f9086a6236b16e26cd964cf4df35349970f3551cf", size = 337274 }, - { url = "https://files.pythonhosted.org/packages/05/be/665634aa196954156741ea591d2f946f1b78ceee8bb8f28488bf28c0dd62/yarl-1.20.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fe41919b9d899661c5c28a8b4b0acf704510b88f27f0934ac7a7bebdd8938d5e", size = 363294 }, - { url = "https://files.pythonhosted.org/packages/eb/90/73448401d36fa4e210ece5579895731f190d5119c4b66b43b52182e88cd5/yarl-1.20.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:8601bc010d1d7780592f3fc1bdc6c72e2b6466ea34569778422943e1a1f3c389", size = 358169 }, - { url = "https://files.pythonhosted.org/packages/c3/b0/fce922d46dc1eb43c811f1889f7daa6001b27a4005587e94878570300881/yarl-1.20.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:daadbdc1f2a9033a2399c42646fbd46da7992e868a5fe9513860122d7fe7a73f", size = 362776 }, - { url = "https://files.pythonhosted.org/packages/f1/0d/b172628fce039dae8977fd22caeff3eeebffd52e86060413f5673767c427/yarl-1.20.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:03aa1e041727cb438ca762628109ef1333498b122e4c76dd858d186a37cec845", size = 381341 }, - { url = "https://files.pythonhosted.org/packages/6b/9b/5b886d7671f4580209e855974fe1cecec409aa4a89ea58b8f0560dc529b1/yarl-1.20.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:642980ef5e0fa1de5fa96d905c7e00cb2c47cb468bfcac5a18c58e27dbf8d8d1", size = 379988 }, - { url = "https://files.pythonhosted.org/packages/73/be/75ef5fd0fcd8f083a5d13f78fd3f009528132a1f2a1d7c925c39fa20aa79/yarl-1.20.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:86971e2795584fe8c002356d3b97ef6c61862720eeff03db2a7c86b678d85b3e", size = 371113 }, - { url = "https://files.pythonhosted.org/packages/50/4f/62faab3b479dfdcb741fe9e3f0323e2a7d5cd1ab2edc73221d57ad4834b2/yarl-1.20.1-cp311-cp311-win32.whl", hash = "sha256:597f40615b8d25812f14562699e287f0dcc035d25eb74da72cae043bb884d773", size = 81485 }, - { url = "https://files.pythonhosted.org/packages/f0/09/d9c7942f8f05c32ec72cd5c8e041c8b29b5807328b68b4801ff2511d4d5e/yarl-1.20.1-cp311-cp311-win_amd64.whl", hash = "sha256:26ef53a9e726e61e9cd1cda6b478f17e350fb5800b4bd1cd9fe81c4d91cfeb2e", size = 86686 }, - { url = "https://files.pythonhosted.org/packages/5f/9a/cb7fad7d73c69f296eda6815e4a2c7ed53fc70c2f136479a91c8e5fbdb6d/yarl-1.20.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdcc4cd244e58593a4379fe60fdee5ac0331f8eb70320a24d591a3be197b94a9", size = 133667 }, - { url = "https://files.pythonhosted.org/packages/67/38/688577a1cb1e656e3971fb66a3492501c5a5df56d99722e57c98249e5b8a/yarl-1.20.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b29a2c385a5f5b9c7d9347e5812b6f7ab267193c62d282a540b4fc528c8a9d2a", size = 91025 }, - { url = "https://files.pythonhosted.org/packages/50/ec/72991ae51febeb11a42813fc259f0d4c8e0507f2b74b5514618d8b640365/yarl-1.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1112ae8154186dfe2de4732197f59c05a83dc814849a5ced892b708033f40dc2", size = 89709 }, - { url = "https://files.pythonhosted.org/packages/99/da/4d798025490e89426e9f976702e5f9482005c548c579bdae792a4c37769e/yarl-1.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90bbd29c4fe234233f7fa2b9b121fb63c321830e5d05b45153a2ca68f7d310ee", size = 352287 }, - { url = "https://files.pythonhosted.org/packages/1a/26/54a15c6a567aac1c61b18aa0f4b8aa2e285a52d547d1be8bf48abe2b3991/yarl-1.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:680e19c7ce3710ac4cd964e90dad99bf9b5029372ba0c7cbfcd55e54d90ea819", size = 345429 }, - { url = "https://files.pythonhosted.org/packages/d6/95/9dcf2386cb875b234353b93ec43e40219e14900e046bf6ac118f94b1e353/yarl-1.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4a979218c1fdb4246a05efc2cc23859d47c89af463a90b99b7c56094daf25a16", size = 365429 }, - { url = "https://files.pythonhosted.org/packages/91/b2/33a8750f6a4bc224242a635f5f2cff6d6ad5ba651f6edcccf721992c21a0/yarl-1.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255b468adf57b4a7b65d8aad5b5138dce6a0752c139965711bdcb81bc370e1b6", size = 363862 }, - { url = "https://files.pythonhosted.org/packages/98/28/3ab7acc5b51f4434b181b0cee8f1f4b77a65919700a355fb3617f9488874/yarl-1.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a97d67108e79cfe22e2b430d80d7571ae57d19f17cda8bb967057ca8a7bf5bfd", size = 355616 }, - { url = "https://files.pythonhosted.org/packages/36/a3/f666894aa947a371724ec7cd2e5daa78ee8a777b21509b4252dd7bd15e29/yarl-1.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8570d998db4ddbfb9a590b185a0a33dbf8aafb831d07a5257b4ec9948df9cb0a", size = 339954 }, - { url = "https://files.pythonhosted.org/packages/f1/81/5f466427e09773c04219d3450d7a1256138a010b6c9f0af2d48565e9ad13/yarl-1.20.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:97c75596019baae7c71ccf1d8cc4738bc08134060d0adfcbe5642f778d1dca38", size = 365575 }, - { url = "https://files.pythonhosted.org/packages/2e/e3/e4b0ad8403e97e6c9972dd587388940a032f030ebec196ab81a3b8e94d31/yarl-1.20.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1c48912653e63aef91ff988c5432832692ac5a1d8f0fb8a33091520b5bbe19ef", size = 365061 }, - { url = "https://files.pythonhosted.org/packages/ac/99/b8a142e79eb86c926f9f06452eb13ecb1bb5713bd01dc0038faf5452e544/yarl-1.20.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4c3ae28f3ae1563c50f3d37f064ddb1511ecc1d5584e88c6b7c63cf7702a6d5f", size = 364142 }, - { url = "https://files.pythonhosted.org/packages/34/f2/08ed34a4a506d82a1a3e5bab99ccd930a040f9b6449e9fd050320e45845c/yarl-1.20.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c5e9642f27036283550f5f57dc6156c51084b458570b9d0d96100c8bebb186a8", size = 381894 }, - { url = "https://files.pythonhosted.org/packages/92/f8/9a3fbf0968eac704f681726eff595dce9b49c8a25cd92bf83df209668285/yarl-1.20.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:2c26b0c49220d5799f7b22c6838409ee9bc58ee5c95361a4d7831f03cc225b5a", size = 383378 }, - { url = "https://files.pythonhosted.org/packages/af/85/9363f77bdfa1e4d690957cd39d192c4cacd1c58965df0470a4905253b54f/yarl-1.20.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:564ab3d517e3d01c408c67f2e5247aad4019dcf1969982aba3974b4093279004", size = 374069 }, - { url = "https://files.pythonhosted.org/packages/35/99/9918c8739ba271dcd935400cff8b32e3cd319eaf02fcd023d5dcd487a7c8/yarl-1.20.1-cp312-cp312-win32.whl", hash = "sha256:daea0d313868da1cf2fac6b2d3a25c6e3a9e879483244be38c8e6a41f1d876a5", size = 81249 }, - { url = "https://files.pythonhosted.org/packages/eb/83/5d9092950565481b413b31a23e75dd3418ff0a277d6e0abf3729d4d1ce25/yarl-1.20.1-cp312-cp312-win_amd64.whl", hash = "sha256:48ea7d7f9be0487339828a4de0360d7ce0efc06524a48e1810f945c45b813698", size = 86710 }, - { url = "https://files.pythonhosted.org/packages/8a/e1/2411b6d7f769a07687acee88a062af5833cf1966b7266f3d8dfb3d3dc7d3/yarl-1.20.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:0b5ff0fbb7c9f1b1b5ab53330acbfc5247893069e7716840c8e7d5bb7355038a", size = 131811 }, - { url = "https://files.pythonhosted.org/packages/b2/27/584394e1cb76fb771371770eccad35de400e7b434ce3142c2dd27392c968/yarl-1.20.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:14f326acd845c2b2e2eb38fb1346c94f7f3b01a4f5c788f8144f9b630bfff9a3", size = 90078 }, - { url = "https://files.pythonhosted.org/packages/bf/9a/3246ae92d4049099f52d9b0fe3486e3b500e29b7ea872d0f152966fc209d/yarl-1.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f60e4ad5db23f0b96e49c018596707c3ae89f5d0bd97f0ad3684bcbad899f1e7", size = 88748 }, - { url = "https://files.pythonhosted.org/packages/a3/25/35afe384e31115a1a801fbcf84012d7a066d89035befae7c5d4284df1e03/yarl-1.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:49bdd1b8e00ce57e68ba51916e4bb04461746e794e7c4d4bbc42ba2f18297691", size = 349595 }, - { url = "https://files.pythonhosted.org/packages/28/2d/8aca6cb2cabc8f12efcb82749b9cefecbccfc7b0384e56cd71058ccee433/yarl-1.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:66252d780b45189975abfed839616e8fd2dbacbdc262105ad7742c6ae58f3e31", size = 342616 }, - { url = "https://files.pythonhosted.org/packages/0b/e9/1312633d16b31acf0098d30440ca855e3492d66623dafb8e25b03d00c3da/yarl-1.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59174e7332f5d153d8f7452a102b103e2e74035ad085f404df2e40e663a22b28", size = 361324 }, - { url = "https://files.pythonhosted.org/packages/bc/a0/688cc99463f12f7669eec7c8acc71ef56a1521b99eab7cd3abb75af887b0/yarl-1.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e3968ec7d92a0c0f9ac34d5ecfd03869ec0cab0697c91a45db3fbbd95fe1b653", size = 359676 }, - { url = "https://files.pythonhosted.org/packages/af/44/46407d7f7a56e9a85a4c207724c9f2c545c060380718eea9088f222ba697/yarl-1.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1a4fbb50e14396ba3d375f68bfe02215d8e7bc3ec49da8341fe3157f59d2ff5", size = 352614 }, - { url = "https://files.pythonhosted.org/packages/b1/91/31163295e82b8d5485d31d9cf7754d973d41915cadce070491778d9c9825/yarl-1.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11a62c839c3a8eac2410e951301309426f368388ff2f33799052787035793b02", size = 336766 }, - { url = "https://files.pythonhosted.org/packages/b4/8e/c41a5bc482121f51c083c4c2bcd16b9e01e1cf8729e380273a952513a21f/yarl-1.20.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:041eaa14f73ff5a8986b4388ac6bb43a77f2ea09bf1913df7a35d4646db69e53", size = 364615 }, - { url = "https://files.pythonhosted.org/packages/e3/5b/61a3b054238d33d70ea06ebba7e58597891b71c699e247df35cc984ab393/yarl-1.20.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:377fae2fef158e8fd9d60b4c8751387b8d1fb121d3d0b8e9b0be07d1b41e83dc", size = 360982 }, - { url = "https://files.pythonhosted.org/packages/df/a3/6a72fb83f8d478cb201d14927bc8040af901811a88e0ff2da7842dd0ed19/yarl-1.20.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1c92f4390e407513f619d49319023664643d3339bd5e5a56a3bebe01bc67ec04", size = 369792 }, - { url = "https://files.pythonhosted.org/packages/7c/af/4cc3c36dfc7c077f8dedb561eb21f69e1e9f2456b91b593882b0b18c19dc/yarl-1.20.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d25ddcf954df1754ab0f86bb696af765c5bfaba39b74095f27eececa049ef9a4", size = 382049 }, - { url = "https://files.pythonhosted.org/packages/19/3a/e54e2c4752160115183a66dc9ee75a153f81f3ab2ba4bf79c3c53b33de34/yarl-1.20.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:909313577e9619dcff8c31a0ea2aa0a2a828341d92673015456b3ae492e7317b", size = 384774 }, - { url = "https://files.pythonhosted.org/packages/9c/20/200ae86dabfca89060ec6447649f219b4cbd94531e425e50d57e5f5ac330/yarl-1.20.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:793fd0580cb9664548c6b83c63b43c477212c0260891ddf86809e1c06c8b08f1", size = 374252 }, - { url = "https://files.pythonhosted.org/packages/83/75/11ee332f2f516b3d094e89448da73d557687f7d137d5a0f48c40ff211487/yarl-1.20.1-cp313-cp313-win32.whl", hash = "sha256:468f6e40285de5a5b3c44981ca3a319a4b208ccc07d526b20b12aeedcfa654b7", size = 81198 }, - { url = "https://files.pythonhosted.org/packages/ba/ba/39b1ecbf51620b40ab402b0fc817f0ff750f6d92712b44689c2c215be89d/yarl-1.20.1-cp313-cp313-win_amd64.whl", hash = "sha256:495b4ef2fea40596bfc0affe3837411d6aa3371abcf31aac0ccc4bdd64d4ef5c", size = 86346 }, - { url = "https://files.pythonhosted.org/packages/43/c7/669c52519dca4c95153c8ad96dd123c79f354a376346b198f438e56ffeb4/yarl-1.20.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:f60233b98423aab21d249a30eb27c389c14929f47be8430efa7dbd91493a729d", size = 138826 }, - { url = "https://files.pythonhosted.org/packages/6a/42/fc0053719b44f6ad04a75d7f05e0e9674d45ef62f2d9ad2c1163e5c05827/yarl-1.20.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6f3eff4cc3f03d650d8755c6eefc844edde99d641d0dcf4da3ab27141a5f8ddf", size = 93217 }, - { url = "https://files.pythonhosted.org/packages/4f/7f/fa59c4c27e2a076bba0d959386e26eba77eb52ea4a0aac48e3515c186b4c/yarl-1.20.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:69ff8439d8ba832d6bed88af2c2b3445977eba9a4588b787b32945871c2444e3", size = 92700 }, - { url = "https://files.pythonhosted.org/packages/2f/d4/062b2f48e7c93481e88eff97a6312dca15ea200e959f23e96d8ab898c5b8/yarl-1.20.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cf34efa60eb81dd2645a2e13e00bb98b76c35ab5061a3989c7a70f78c85006d", size = 347644 }, - { url = "https://files.pythonhosted.org/packages/89/47/78b7f40d13c8f62b499cc702fdf69e090455518ae544c00a3bf4afc9fc77/yarl-1.20.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8e0fe9364ad0fddab2688ce72cb7a8e61ea42eff3c7caeeb83874a5d479c896c", size = 323452 }, - { url = "https://files.pythonhosted.org/packages/eb/2b/490d3b2dc66f52987d4ee0d3090a147ea67732ce6b4d61e362c1846d0d32/yarl-1.20.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f64fbf81878ba914562c672024089e3401974a39767747691c65080a67b18c1", size = 346378 }, - { url = "https://files.pythonhosted.org/packages/66/ad/775da9c8a94ce925d1537f939a4f17d782efef1f973039d821cbe4bcc211/yarl-1.20.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6342d643bf9a1de97e512e45e4b9560a043347e779a173250824f8b254bd5ce", size = 353261 }, - { url = "https://files.pythonhosted.org/packages/4b/23/0ed0922b47a4f5c6eb9065d5ff1e459747226ddce5c6a4c111e728c9f701/yarl-1.20.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56dac5f452ed25eef0f6e3c6a066c6ab68971d96a9fb441791cad0efba6140d3", size = 335987 }, - { url = "https://files.pythonhosted.org/packages/3e/49/bc728a7fe7d0e9336e2b78f0958a2d6b288ba89f25a1762407a222bf53c3/yarl-1.20.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7d7f497126d65e2cad8dc5f97d34c27b19199b6414a40cb36b52f41b79014be", size = 329361 }, - { url = "https://files.pythonhosted.org/packages/93/8f/b811b9d1f617c83c907e7082a76e2b92b655400e61730cd61a1f67178393/yarl-1.20.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:67e708dfb8e78d8a19169818eeb5c7a80717562de9051bf2413aca8e3696bf16", size = 346460 }, - { url = "https://files.pythonhosted.org/packages/70/fd/af94f04f275f95da2c3b8b5e1d49e3e79f1ed8b6ceb0f1664cbd902773ff/yarl-1.20.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:595c07bc79af2494365cc96ddeb772f76272364ef7c80fb892ef9d0649586513", size = 334486 }, - { url = "https://files.pythonhosted.org/packages/84/65/04c62e82704e7dd0a9b3f61dbaa8447f8507655fd16c51da0637b39b2910/yarl-1.20.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7bdd2f80f4a7df852ab9ab49484a4dee8030023aa536df41f2d922fd57bf023f", size = 342219 }, - { url = "https://files.pythonhosted.org/packages/91/95/459ca62eb958381b342d94ab9a4b6aec1ddec1f7057c487e926f03c06d30/yarl-1.20.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c03bfebc4ae8d862f853a9757199677ab74ec25424d0ebd68a0027e9c639a390", size = 350693 }, - { url = "https://files.pythonhosted.org/packages/a6/00/d393e82dd955ad20617abc546a8f1aee40534d599ff555ea053d0ec9bf03/yarl-1.20.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:344d1103e9c1523f32a5ed704d576172d2cabed3122ea90b1d4e11fe17c66458", size = 355803 }, - { url = "https://files.pythonhosted.org/packages/9e/ed/c5fb04869b99b717985e244fd93029c7a8e8febdfcffa06093e32d7d44e7/yarl-1.20.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:88cab98aa4e13e1ade8c141daeedd300a4603b7132819c484841bb7af3edce9e", size = 341709 }, - { url = "https://files.pythonhosted.org/packages/24/fd/725b8e73ac2a50e78a4534ac43c6addf5c1c2d65380dd48a9169cc6739a9/yarl-1.20.1-cp313-cp313t-win32.whl", hash = "sha256:b121ff6a7cbd4abc28985b6028235491941b9fe8fe226e6fdc539c977ea1739d", size = 86591 }, - { url = "https://files.pythonhosted.org/packages/94/c3/b2e9f38bc3e11191981d57ea08cab2166e74ea770024a646617c9cddd9f6/yarl-1.20.1-cp313-cp313t-win_amd64.whl", hash = "sha256:541d050a355bbbc27e55d906bc91cb6fe42f96c01413dd0f4ed5a5240513874f", size = 93003 }, - { url = "https://files.pythonhosted.org/packages/b4/2d/2345fce04cfd4bee161bf1e7d9cdc702e3e16109021035dbb24db654a622/yarl-1.20.1-py3-none-any.whl", hash = "sha256:83b8eb083fe4683c6115795d9fc1cfaf2cbbefb19b3a1cb68f6527460f483a77", size = 46542 }, +sdist = { url = "https://files.pythonhosted.org/packages/3c/fb/efaa23fa4e45537b827620f04cf8f3cd658b76642205162e072703a5b963/yarl-1.20.1.tar.gz", hash = "sha256:d017a4997ee50c91fd5466cef416231bb82177b93b029906cefc542ce14c35ac", size = 186428, upload-time = "2025-06-10T00:46:09.923Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/65/7fed0d774abf47487c64be14e9223749468922817b5e8792b8a64792a1bb/yarl-1.20.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6032e6da6abd41e4acda34d75a816012717000fa6839f37124a47fcefc49bec4", size = 132910, upload-time = "2025-06-10T00:42:31.108Z" }, + { url = "https://files.pythonhosted.org/packages/8a/7b/988f55a52da99df9e56dc733b8e4e5a6ae2090081dc2754fc8fd34e60aa0/yarl-1.20.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2c7b34d804b8cf9b214f05015c4fee2ebe7ed05cf581e7192c06555c71f4446a", size = 90644, upload-time = "2025-06-10T00:42:33.851Z" }, + { url = "https://files.pythonhosted.org/packages/f7/de/30d98f03e95d30c7e3cc093759982d038c8833ec2451001d45ef4854edc1/yarl-1.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0c869f2651cc77465f6cd01d938d91a11d9ea5d798738c1dc077f3de0b5e5fed", size = 89322, upload-time = "2025-06-10T00:42:35.688Z" }, + { url = "https://files.pythonhosted.org/packages/e0/7a/f2f314f5ebfe9200724b0b748de2186b927acb334cf964fd312eb86fc286/yarl-1.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62915e6688eb4d180d93840cda4110995ad50c459bf931b8b3775b37c264af1e", size = 323786, upload-time = "2025-06-10T00:42:37.817Z" }, + { url = "https://files.pythonhosted.org/packages/15/3f/718d26f189db96d993d14b984ce91de52e76309d0fd1d4296f34039856aa/yarl-1.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:41ebd28167bc6af8abb97fec1a399f412eec5fd61a3ccbe2305a18b84fb4ca73", size = 319627, upload-time = "2025-06-10T00:42:39.937Z" }, + { url = "https://files.pythonhosted.org/packages/a5/76/8fcfbf5fa2369157b9898962a4a7d96764b287b085b5b3d9ffae69cdefd1/yarl-1.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:21242b4288a6d56f04ea193adde174b7e347ac46ce6bc84989ff7c1b1ecea84e", size = 339149, upload-time = "2025-06-10T00:42:42.627Z" }, + { url = "https://files.pythonhosted.org/packages/3c/95/d7fc301cc4661785967acc04f54a4a42d5124905e27db27bb578aac49b5c/yarl-1.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bea21cdae6c7eb02ba02a475f37463abfe0a01f5d7200121b03e605d6a0439f8", size = 333327, upload-time = "2025-06-10T00:42:44.842Z" }, + { url = "https://files.pythonhosted.org/packages/65/94/e21269718349582eee81efc5c1c08ee71c816bfc1585b77d0ec3f58089eb/yarl-1.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f8a891e4a22a89f5dde7862994485e19db246b70bb288d3ce73a34422e55b23", size = 326054, upload-time = "2025-06-10T00:42:47.149Z" }, + { url = "https://files.pythonhosted.org/packages/32/ae/8616d1f07853704523519f6131d21f092e567c5af93de7e3e94b38d7f065/yarl-1.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dd803820d44c8853a109a34e3660e5a61beae12970da479cf44aa2954019bf70", size = 315035, upload-time = "2025-06-10T00:42:48.852Z" }, + { url = "https://files.pythonhosted.org/packages/48/aa/0ace06280861ef055855333707db5e49c6e3a08840a7ce62682259d0a6c0/yarl-1.20.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b982fa7f74c80d5c0c7b5b38f908971e513380a10fecea528091405f519b9ebb", size = 338962, upload-time = "2025-06-10T00:42:51.024Z" }, + { url = "https://files.pythonhosted.org/packages/20/52/1e9d0e6916f45a8fb50e6844f01cb34692455f1acd548606cbda8134cd1e/yarl-1.20.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:33f29ecfe0330c570d997bcf1afd304377f2e48f61447f37e846a6058a4d33b2", size = 335399, upload-time = "2025-06-10T00:42:53.007Z" }, + { url = "https://files.pythonhosted.org/packages/f2/65/60452df742952c630e82f394cd409de10610481d9043aa14c61bf846b7b1/yarl-1.20.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:835ab2cfc74d5eb4a6a528c57f05688099da41cf4957cf08cad38647e4a83b30", size = 338649, upload-time = "2025-06-10T00:42:54.964Z" }, + { url = "https://files.pythonhosted.org/packages/7b/f5/6cd4ff38dcde57a70f23719a838665ee17079640c77087404c3d34da6727/yarl-1.20.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:46b5e0ccf1943a9a6e766b2c2b8c732c55b34e28be57d8daa2b3c1d1d4009309", size = 358563, upload-time = "2025-06-10T00:42:57.28Z" }, + { url = "https://files.pythonhosted.org/packages/d1/90/c42eefd79d0d8222cb3227bdd51b640c0c1d0aa33fe4cc86c36eccba77d3/yarl-1.20.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:df47c55f7d74127d1b11251fe6397d84afdde0d53b90bedb46a23c0e534f9d24", size = 357609, upload-time = "2025-06-10T00:42:59.055Z" }, + { url = "https://files.pythonhosted.org/packages/03/c8/cea6b232cb4617514232e0f8a718153a95b5d82b5290711b201545825532/yarl-1.20.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:76d12524d05841276b0e22573f28d5fbcb67589836772ae9244d90dd7d66aa13", size = 350224, upload-time = "2025-06-10T00:43:01.248Z" }, + { url = "https://files.pythonhosted.org/packages/ce/a3/eaa0ab9712f1f3d01faf43cf6f1f7210ce4ea4a7e9b28b489a2261ca8db9/yarl-1.20.1-cp310-cp310-win32.whl", hash = "sha256:6c4fbf6b02d70e512d7ade4b1f998f237137f1417ab07ec06358ea04f69134f8", size = 81753, upload-time = "2025-06-10T00:43:03.486Z" }, + { url = "https://files.pythonhosted.org/packages/8f/34/e4abde70a9256465fe31c88ed02c3f8502b7b5dead693a4f350a06413f28/yarl-1.20.1-cp310-cp310-win_amd64.whl", hash = "sha256:aef6c4d69554d44b7f9d923245f8ad9a707d971e6209d51279196d8e8fe1ae16", size = 86817, upload-time = "2025-06-10T00:43:05.231Z" }, + { url = "https://files.pythonhosted.org/packages/b1/18/893b50efc2350e47a874c5c2d67e55a0ea5df91186b2a6f5ac52eff887cd/yarl-1.20.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:47ee6188fea634bdfaeb2cc420f5b3b17332e6225ce88149a17c413c77ff269e", size = 133833, upload-time = "2025-06-10T00:43:07.393Z" }, + { url = "https://files.pythonhosted.org/packages/89/ed/b8773448030e6fc47fa797f099ab9eab151a43a25717f9ac043844ad5ea3/yarl-1.20.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d0f6500f69e8402d513e5eedb77a4e1818691e8f45e6b687147963514d84b44b", size = 91070, upload-time = "2025-06-10T00:43:09.538Z" }, + { url = "https://files.pythonhosted.org/packages/e3/e3/409bd17b1e42619bf69f60e4f031ce1ccb29bd7380117a55529e76933464/yarl-1.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7a8900a42fcdaad568de58887c7b2f602962356908eedb7628eaf6021a6e435b", size = 89818, upload-time = "2025-06-10T00:43:11.575Z" }, + { url = "https://files.pythonhosted.org/packages/f8/77/64d8431a4d77c856eb2d82aa3de2ad6741365245a29b3a9543cd598ed8c5/yarl-1.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bad6d131fda8ef508b36be3ece16d0902e80b88ea7200f030a0f6c11d9e508d4", size = 347003, upload-time = "2025-06-10T00:43:14.088Z" }, + { url = "https://files.pythonhosted.org/packages/8d/d2/0c7e4def093dcef0bd9fa22d4d24b023788b0a33b8d0088b51aa51e21e99/yarl-1.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:df018d92fe22aaebb679a7f89fe0c0f368ec497e3dda6cb81a567610f04501f1", size = 336537, upload-time = "2025-06-10T00:43:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/f0/f3/fc514f4b2cf02cb59d10cbfe228691d25929ce8f72a38db07d3febc3f706/yarl-1.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f969afbb0a9b63c18d0feecf0db09d164b7a44a053e78a7d05f5df163e43833", size = 362358, upload-time = "2025-06-10T00:43:18.704Z" }, + { url = "https://files.pythonhosted.org/packages/ea/6d/a313ac8d8391381ff9006ac05f1d4331cee3b1efaa833a53d12253733255/yarl-1.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:812303eb4aa98e302886ccda58d6b099e3576b1b9276161469c25803a8db277d", size = 357362, upload-time = "2025-06-10T00:43:20.888Z" }, + { url = "https://files.pythonhosted.org/packages/00/70/8f78a95d6935a70263d46caa3dd18e1f223cf2f2ff2037baa01a22bc5b22/yarl-1.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98c4a7d166635147924aa0bf9bfe8d8abad6fffa6102de9c99ea04a1376f91e8", size = 348979, upload-time = "2025-06-10T00:43:23.169Z" }, + { url = "https://files.pythonhosted.org/packages/cb/05/42773027968968f4f15143553970ee36ead27038d627f457cc44bbbeecf3/yarl-1.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12e768f966538e81e6e7550f9086a6236b16e26cd964cf4df35349970f3551cf", size = 337274, upload-time = "2025-06-10T00:43:27.111Z" }, + { url = "https://files.pythonhosted.org/packages/05/be/665634aa196954156741ea591d2f946f1b78ceee8bb8f28488bf28c0dd62/yarl-1.20.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fe41919b9d899661c5c28a8b4b0acf704510b88f27f0934ac7a7bebdd8938d5e", size = 363294, upload-time = "2025-06-10T00:43:28.96Z" }, + { url = "https://files.pythonhosted.org/packages/eb/90/73448401d36fa4e210ece5579895731f190d5119c4b66b43b52182e88cd5/yarl-1.20.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:8601bc010d1d7780592f3fc1bdc6c72e2b6466ea34569778422943e1a1f3c389", size = 358169, upload-time = "2025-06-10T00:43:30.701Z" }, + { url = "https://files.pythonhosted.org/packages/c3/b0/fce922d46dc1eb43c811f1889f7daa6001b27a4005587e94878570300881/yarl-1.20.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:daadbdc1f2a9033a2399c42646fbd46da7992e868a5fe9513860122d7fe7a73f", size = 362776, upload-time = "2025-06-10T00:43:32.51Z" }, + { url = "https://files.pythonhosted.org/packages/f1/0d/b172628fce039dae8977fd22caeff3eeebffd52e86060413f5673767c427/yarl-1.20.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:03aa1e041727cb438ca762628109ef1333498b122e4c76dd858d186a37cec845", size = 381341, upload-time = "2025-06-10T00:43:34.543Z" }, + { url = "https://files.pythonhosted.org/packages/6b/9b/5b886d7671f4580209e855974fe1cecec409aa4a89ea58b8f0560dc529b1/yarl-1.20.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:642980ef5e0fa1de5fa96d905c7e00cb2c47cb468bfcac5a18c58e27dbf8d8d1", size = 379988, upload-time = "2025-06-10T00:43:36.489Z" }, + { url = "https://files.pythonhosted.org/packages/73/be/75ef5fd0fcd8f083a5d13f78fd3f009528132a1f2a1d7c925c39fa20aa79/yarl-1.20.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:86971e2795584fe8c002356d3b97ef6c61862720eeff03db2a7c86b678d85b3e", size = 371113, upload-time = "2025-06-10T00:43:38.592Z" }, + { url = "https://files.pythonhosted.org/packages/50/4f/62faab3b479dfdcb741fe9e3f0323e2a7d5cd1ab2edc73221d57ad4834b2/yarl-1.20.1-cp311-cp311-win32.whl", hash = "sha256:597f40615b8d25812f14562699e287f0dcc035d25eb74da72cae043bb884d773", size = 81485, upload-time = "2025-06-10T00:43:41.038Z" }, + { url = "https://files.pythonhosted.org/packages/f0/09/d9c7942f8f05c32ec72cd5c8e041c8b29b5807328b68b4801ff2511d4d5e/yarl-1.20.1-cp311-cp311-win_amd64.whl", hash = "sha256:26ef53a9e726e61e9cd1cda6b478f17e350fb5800b4bd1cd9fe81c4d91cfeb2e", size = 86686, upload-time = "2025-06-10T00:43:42.692Z" }, + { url = "https://files.pythonhosted.org/packages/5f/9a/cb7fad7d73c69f296eda6815e4a2c7ed53fc70c2f136479a91c8e5fbdb6d/yarl-1.20.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdcc4cd244e58593a4379fe60fdee5ac0331f8eb70320a24d591a3be197b94a9", size = 133667, upload-time = "2025-06-10T00:43:44.369Z" }, + { url = "https://files.pythonhosted.org/packages/67/38/688577a1cb1e656e3971fb66a3492501c5a5df56d99722e57c98249e5b8a/yarl-1.20.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b29a2c385a5f5b9c7d9347e5812b6f7ab267193c62d282a540b4fc528c8a9d2a", size = 91025, upload-time = "2025-06-10T00:43:46.295Z" }, + { url = "https://files.pythonhosted.org/packages/50/ec/72991ae51febeb11a42813fc259f0d4c8e0507f2b74b5514618d8b640365/yarl-1.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1112ae8154186dfe2de4732197f59c05a83dc814849a5ced892b708033f40dc2", size = 89709, upload-time = "2025-06-10T00:43:48.22Z" }, + { url = "https://files.pythonhosted.org/packages/99/da/4d798025490e89426e9f976702e5f9482005c548c579bdae792a4c37769e/yarl-1.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90bbd29c4fe234233f7fa2b9b121fb63c321830e5d05b45153a2ca68f7d310ee", size = 352287, upload-time = "2025-06-10T00:43:49.924Z" }, + { url = "https://files.pythonhosted.org/packages/1a/26/54a15c6a567aac1c61b18aa0f4b8aa2e285a52d547d1be8bf48abe2b3991/yarl-1.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:680e19c7ce3710ac4cd964e90dad99bf9b5029372ba0c7cbfcd55e54d90ea819", size = 345429, upload-time = "2025-06-10T00:43:51.7Z" }, + { url = "https://files.pythonhosted.org/packages/d6/95/9dcf2386cb875b234353b93ec43e40219e14900e046bf6ac118f94b1e353/yarl-1.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4a979218c1fdb4246a05efc2cc23859d47c89af463a90b99b7c56094daf25a16", size = 365429, upload-time = "2025-06-10T00:43:53.494Z" }, + { url = "https://files.pythonhosted.org/packages/91/b2/33a8750f6a4bc224242a635f5f2cff6d6ad5ba651f6edcccf721992c21a0/yarl-1.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255b468adf57b4a7b65d8aad5b5138dce6a0752c139965711bdcb81bc370e1b6", size = 363862, upload-time = "2025-06-10T00:43:55.766Z" }, + { url = "https://files.pythonhosted.org/packages/98/28/3ab7acc5b51f4434b181b0cee8f1f4b77a65919700a355fb3617f9488874/yarl-1.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a97d67108e79cfe22e2b430d80d7571ae57d19f17cda8bb967057ca8a7bf5bfd", size = 355616, upload-time = "2025-06-10T00:43:58.056Z" }, + { url = "https://files.pythonhosted.org/packages/36/a3/f666894aa947a371724ec7cd2e5daa78ee8a777b21509b4252dd7bd15e29/yarl-1.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8570d998db4ddbfb9a590b185a0a33dbf8aafb831d07a5257b4ec9948df9cb0a", size = 339954, upload-time = "2025-06-10T00:43:59.773Z" }, + { url = "https://files.pythonhosted.org/packages/f1/81/5f466427e09773c04219d3450d7a1256138a010b6c9f0af2d48565e9ad13/yarl-1.20.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:97c75596019baae7c71ccf1d8cc4738bc08134060d0adfcbe5642f778d1dca38", size = 365575, upload-time = "2025-06-10T00:44:02.051Z" }, + { url = "https://files.pythonhosted.org/packages/2e/e3/e4b0ad8403e97e6c9972dd587388940a032f030ebec196ab81a3b8e94d31/yarl-1.20.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1c48912653e63aef91ff988c5432832692ac5a1d8f0fb8a33091520b5bbe19ef", size = 365061, upload-time = "2025-06-10T00:44:04.196Z" }, + { url = "https://files.pythonhosted.org/packages/ac/99/b8a142e79eb86c926f9f06452eb13ecb1bb5713bd01dc0038faf5452e544/yarl-1.20.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4c3ae28f3ae1563c50f3d37f064ddb1511ecc1d5584e88c6b7c63cf7702a6d5f", size = 364142, upload-time = "2025-06-10T00:44:06.527Z" }, + { url = "https://files.pythonhosted.org/packages/34/f2/08ed34a4a506d82a1a3e5bab99ccd930a040f9b6449e9fd050320e45845c/yarl-1.20.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c5e9642f27036283550f5f57dc6156c51084b458570b9d0d96100c8bebb186a8", size = 381894, upload-time = "2025-06-10T00:44:08.379Z" }, + { url = "https://files.pythonhosted.org/packages/92/f8/9a3fbf0968eac704f681726eff595dce9b49c8a25cd92bf83df209668285/yarl-1.20.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:2c26b0c49220d5799f7b22c6838409ee9bc58ee5c95361a4d7831f03cc225b5a", size = 383378, upload-time = "2025-06-10T00:44:10.51Z" }, + { url = "https://files.pythonhosted.org/packages/af/85/9363f77bdfa1e4d690957cd39d192c4cacd1c58965df0470a4905253b54f/yarl-1.20.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:564ab3d517e3d01c408c67f2e5247aad4019dcf1969982aba3974b4093279004", size = 374069, upload-time = "2025-06-10T00:44:12.834Z" }, + { url = "https://files.pythonhosted.org/packages/35/99/9918c8739ba271dcd935400cff8b32e3cd319eaf02fcd023d5dcd487a7c8/yarl-1.20.1-cp312-cp312-win32.whl", hash = "sha256:daea0d313868da1cf2fac6b2d3a25c6e3a9e879483244be38c8e6a41f1d876a5", size = 81249, upload-time = "2025-06-10T00:44:14.731Z" }, + { url = "https://files.pythonhosted.org/packages/eb/83/5d9092950565481b413b31a23e75dd3418ff0a277d6e0abf3729d4d1ce25/yarl-1.20.1-cp312-cp312-win_amd64.whl", hash = "sha256:48ea7d7f9be0487339828a4de0360d7ce0efc06524a48e1810f945c45b813698", size = 86710, upload-time = "2025-06-10T00:44:16.716Z" }, + { url = "https://files.pythonhosted.org/packages/8a/e1/2411b6d7f769a07687acee88a062af5833cf1966b7266f3d8dfb3d3dc7d3/yarl-1.20.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:0b5ff0fbb7c9f1b1b5ab53330acbfc5247893069e7716840c8e7d5bb7355038a", size = 131811, upload-time = "2025-06-10T00:44:18.933Z" }, + { url = "https://files.pythonhosted.org/packages/b2/27/584394e1cb76fb771371770eccad35de400e7b434ce3142c2dd27392c968/yarl-1.20.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:14f326acd845c2b2e2eb38fb1346c94f7f3b01a4f5c788f8144f9b630bfff9a3", size = 90078, upload-time = "2025-06-10T00:44:20.635Z" }, + { url = "https://files.pythonhosted.org/packages/bf/9a/3246ae92d4049099f52d9b0fe3486e3b500e29b7ea872d0f152966fc209d/yarl-1.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f60e4ad5db23f0b96e49c018596707c3ae89f5d0bd97f0ad3684bcbad899f1e7", size = 88748, upload-time = "2025-06-10T00:44:22.34Z" }, + { url = "https://files.pythonhosted.org/packages/a3/25/35afe384e31115a1a801fbcf84012d7a066d89035befae7c5d4284df1e03/yarl-1.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:49bdd1b8e00ce57e68ba51916e4bb04461746e794e7c4d4bbc42ba2f18297691", size = 349595, upload-time = "2025-06-10T00:44:24.314Z" }, + { url = "https://files.pythonhosted.org/packages/28/2d/8aca6cb2cabc8f12efcb82749b9cefecbccfc7b0384e56cd71058ccee433/yarl-1.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:66252d780b45189975abfed839616e8fd2dbacbdc262105ad7742c6ae58f3e31", size = 342616, upload-time = "2025-06-10T00:44:26.167Z" }, + { url = "https://files.pythonhosted.org/packages/0b/e9/1312633d16b31acf0098d30440ca855e3492d66623dafb8e25b03d00c3da/yarl-1.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59174e7332f5d153d8f7452a102b103e2e74035ad085f404df2e40e663a22b28", size = 361324, upload-time = "2025-06-10T00:44:27.915Z" }, + { url = "https://files.pythonhosted.org/packages/bc/a0/688cc99463f12f7669eec7c8acc71ef56a1521b99eab7cd3abb75af887b0/yarl-1.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e3968ec7d92a0c0f9ac34d5ecfd03869ec0cab0697c91a45db3fbbd95fe1b653", size = 359676, upload-time = "2025-06-10T00:44:30.041Z" }, + { url = "https://files.pythonhosted.org/packages/af/44/46407d7f7a56e9a85a4c207724c9f2c545c060380718eea9088f222ba697/yarl-1.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1a4fbb50e14396ba3d375f68bfe02215d8e7bc3ec49da8341fe3157f59d2ff5", size = 352614, upload-time = "2025-06-10T00:44:32.171Z" }, + { url = "https://files.pythonhosted.org/packages/b1/91/31163295e82b8d5485d31d9cf7754d973d41915cadce070491778d9c9825/yarl-1.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11a62c839c3a8eac2410e951301309426f368388ff2f33799052787035793b02", size = 336766, upload-time = "2025-06-10T00:44:34.494Z" }, + { url = "https://files.pythonhosted.org/packages/b4/8e/c41a5bc482121f51c083c4c2bcd16b9e01e1cf8729e380273a952513a21f/yarl-1.20.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:041eaa14f73ff5a8986b4388ac6bb43a77f2ea09bf1913df7a35d4646db69e53", size = 364615, upload-time = "2025-06-10T00:44:36.856Z" }, + { url = "https://files.pythonhosted.org/packages/e3/5b/61a3b054238d33d70ea06ebba7e58597891b71c699e247df35cc984ab393/yarl-1.20.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:377fae2fef158e8fd9d60b4c8751387b8d1fb121d3d0b8e9b0be07d1b41e83dc", size = 360982, upload-time = "2025-06-10T00:44:39.141Z" }, + { url = "https://files.pythonhosted.org/packages/df/a3/6a72fb83f8d478cb201d14927bc8040af901811a88e0ff2da7842dd0ed19/yarl-1.20.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1c92f4390e407513f619d49319023664643d3339bd5e5a56a3bebe01bc67ec04", size = 369792, upload-time = "2025-06-10T00:44:40.934Z" }, + { url = "https://files.pythonhosted.org/packages/7c/af/4cc3c36dfc7c077f8dedb561eb21f69e1e9f2456b91b593882b0b18c19dc/yarl-1.20.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d25ddcf954df1754ab0f86bb696af765c5bfaba39b74095f27eececa049ef9a4", size = 382049, upload-time = "2025-06-10T00:44:42.854Z" }, + { url = "https://files.pythonhosted.org/packages/19/3a/e54e2c4752160115183a66dc9ee75a153f81f3ab2ba4bf79c3c53b33de34/yarl-1.20.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:909313577e9619dcff8c31a0ea2aa0a2a828341d92673015456b3ae492e7317b", size = 384774, upload-time = "2025-06-10T00:44:45.275Z" }, + { url = "https://files.pythonhosted.org/packages/9c/20/200ae86dabfca89060ec6447649f219b4cbd94531e425e50d57e5f5ac330/yarl-1.20.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:793fd0580cb9664548c6b83c63b43c477212c0260891ddf86809e1c06c8b08f1", size = 374252, upload-time = "2025-06-10T00:44:47.31Z" }, + { url = "https://files.pythonhosted.org/packages/83/75/11ee332f2f516b3d094e89448da73d557687f7d137d5a0f48c40ff211487/yarl-1.20.1-cp313-cp313-win32.whl", hash = "sha256:468f6e40285de5a5b3c44981ca3a319a4b208ccc07d526b20b12aeedcfa654b7", size = 81198, upload-time = "2025-06-10T00:44:49.164Z" }, + { url = "https://files.pythonhosted.org/packages/ba/ba/39b1ecbf51620b40ab402b0fc817f0ff750f6d92712b44689c2c215be89d/yarl-1.20.1-cp313-cp313-win_amd64.whl", hash = "sha256:495b4ef2fea40596bfc0affe3837411d6aa3371abcf31aac0ccc4bdd64d4ef5c", size = 86346, upload-time = "2025-06-10T00:44:51.182Z" }, + { url = "https://files.pythonhosted.org/packages/43/c7/669c52519dca4c95153c8ad96dd123c79f354a376346b198f438e56ffeb4/yarl-1.20.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:f60233b98423aab21d249a30eb27c389c14929f47be8430efa7dbd91493a729d", size = 138826, upload-time = "2025-06-10T00:44:52.883Z" }, + { url = "https://files.pythonhosted.org/packages/6a/42/fc0053719b44f6ad04a75d7f05e0e9674d45ef62f2d9ad2c1163e5c05827/yarl-1.20.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6f3eff4cc3f03d650d8755c6eefc844edde99d641d0dcf4da3ab27141a5f8ddf", size = 93217, upload-time = "2025-06-10T00:44:54.658Z" }, + { url = "https://files.pythonhosted.org/packages/4f/7f/fa59c4c27e2a076bba0d959386e26eba77eb52ea4a0aac48e3515c186b4c/yarl-1.20.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:69ff8439d8ba832d6bed88af2c2b3445977eba9a4588b787b32945871c2444e3", size = 92700, upload-time = "2025-06-10T00:44:56.784Z" }, + { url = "https://files.pythonhosted.org/packages/2f/d4/062b2f48e7c93481e88eff97a6312dca15ea200e959f23e96d8ab898c5b8/yarl-1.20.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cf34efa60eb81dd2645a2e13e00bb98b76c35ab5061a3989c7a70f78c85006d", size = 347644, upload-time = "2025-06-10T00:44:59.071Z" }, + { url = "https://files.pythonhosted.org/packages/89/47/78b7f40d13c8f62b499cc702fdf69e090455518ae544c00a3bf4afc9fc77/yarl-1.20.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8e0fe9364ad0fddab2688ce72cb7a8e61ea42eff3c7caeeb83874a5d479c896c", size = 323452, upload-time = "2025-06-10T00:45:01.605Z" }, + { url = "https://files.pythonhosted.org/packages/eb/2b/490d3b2dc66f52987d4ee0d3090a147ea67732ce6b4d61e362c1846d0d32/yarl-1.20.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f64fbf81878ba914562c672024089e3401974a39767747691c65080a67b18c1", size = 346378, upload-time = "2025-06-10T00:45:03.946Z" }, + { url = "https://files.pythonhosted.org/packages/66/ad/775da9c8a94ce925d1537f939a4f17d782efef1f973039d821cbe4bcc211/yarl-1.20.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6342d643bf9a1de97e512e45e4b9560a043347e779a173250824f8b254bd5ce", size = 353261, upload-time = "2025-06-10T00:45:05.992Z" }, + { url = "https://files.pythonhosted.org/packages/4b/23/0ed0922b47a4f5c6eb9065d5ff1e459747226ddce5c6a4c111e728c9f701/yarl-1.20.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56dac5f452ed25eef0f6e3c6a066c6ab68971d96a9fb441791cad0efba6140d3", size = 335987, upload-time = "2025-06-10T00:45:08.227Z" }, + { url = "https://files.pythonhosted.org/packages/3e/49/bc728a7fe7d0e9336e2b78f0958a2d6b288ba89f25a1762407a222bf53c3/yarl-1.20.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7d7f497126d65e2cad8dc5f97d34c27b19199b6414a40cb36b52f41b79014be", size = 329361, upload-time = "2025-06-10T00:45:10.11Z" }, + { url = "https://files.pythonhosted.org/packages/93/8f/b811b9d1f617c83c907e7082a76e2b92b655400e61730cd61a1f67178393/yarl-1.20.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:67e708dfb8e78d8a19169818eeb5c7a80717562de9051bf2413aca8e3696bf16", size = 346460, upload-time = "2025-06-10T00:45:12.055Z" }, + { url = "https://files.pythonhosted.org/packages/70/fd/af94f04f275f95da2c3b8b5e1d49e3e79f1ed8b6ceb0f1664cbd902773ff/yarl-1.20.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:595c07bc79af2494365cc96ddeb772f76272364ef7c80fb892ef9d0649586513", size = 334486, upload-time = "2025-06-10T00:45:13.995Z" }, + { url = "https://files.pythonhosted.org/packages/84/65/04c62e82704e7dd0a9b3f61dbaa8447f8507655fd16c51da0637b39b2910/yarl-1.20.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7bdd2f80f4a7df852ab9ab49484a4dee8030023aa536df41f2d922fd57bf023f", size = 342219, upload-time = "2025-06-10T00:45:16.479Z" }, + { url = "https://files.pythonhosted.org/packages/91/95/459ca62eb958381b342d94ab9a4b6aec1ddec1f7057c487e926f03c06d30/yarl-1.20.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c03bfebc4ae8d862f853a9757199677ab74ec25424d0ebd68a0027e9c639a390", size = 350693, upload-time = "2025-06-10T00:45:18.399Z" }, + { url = "https://files.pythonhosted.org/packages/a6/00/d393e82dd955ad20617abc546a8f1aee40534d599ff555ea053d0ec9bf03/yarl-1.20.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:344d1103e9c1523f32a5ed704d576172d2cabed3122ea90b1d4e11fe17c66458", size = 355803, upload-time = "2025-06-10T00:45:20.677Z" }, + { url = "https://files.pythonhosted.org/packages/9e/ed/c5fb04869b99b717985e244fd93029c7a8e8febdfcffa06093e32d7d44e7/yarl-1.20.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:88cab98aa4e13e1ade8c141daeedd300a4603b7132819c484841bb7af3edce9e", size = 341709, upload-time = "2025-06-10T00:45:23.221Z" }, + { url = "https://files.pythonhosted.org/packages/24/fd/725b8e73ac2a50e78a4534ac43c6addf5c1c2d65380dd48a9169cc6739a9/yarl-1.20.1-cp313-cp313t-win32.whl", hash = "sha256:b121ff6a7cbd4abc28985b6028235491941b9fe8fe226e6fdc539c977ea1739d", size = 86591, upload-time = "2025-06-10T00:45:25.793Z" }, + { url = "https://files.pythonhosted.org/packages/94/c3/b2e9f38bc3e11191981d57ea08cab2166e74ea770024a646617c9cddd9f6/yarl-1.20.1-cp313-cp313t-win_amd64.whl", hash = "sha256:541d050a355bbbc27e55d906bc91cb6fe42f96c01413dd0f4ed5a5240513874f", size = 93003, upload-time = "2025-06-10T00:45:27.752Z" }, + { url = "https://files.pythonhosted.org/packages/b4/2d/2345fce04cfd4bee161bf1e7d9cdc702e3e16109021035dbb24db654a622/yarl-1.20.1-py3-none-any.whl", hash = "sha256:83b8eb083fe4683c6115795d9fc1cfaf2cbbefb19b3a1cb68f6527460f483a77", size = 46542, upload-time = "2025-06-10T00:46:07.521Z" }, ] [[package]] name = "zipp" version = "3.23.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547 } +sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276 }, + { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, ] From 8d642fa9995487fc600530e2841be2436b2283fa Mon Sep 17 00:00:00 2001 From: Alex Zerntev Date: Fri, 28 Nov 2025 15:39:14 +0200 Subject: [PATCH 11/26] fixes --- src/mxcp/sdk/evals/executor.py | 12 +++--- src/mxcp/server/definitions/evals/models.py | 4 +- tests/sdk/evals/test_executor.py | 42 +++++++++++++++++++++ 3 files changed, 50 insertions(+), 8 deletions(-) diff --git a/src/mxcp/sdk/evals/executor.py b/src/mxcp/sdk/evals/executor.py index d82b476a..ac900c36 100644 --- a/src/mxcp/sdk/evals/executor.py +++ b/src/mxcp/sdk/evals/executor.py @@ -10,7 +10,7 @@ import json import logging from dataclasses import dataclass, field -from typing import Any, Protocol, cast +from typing import Any, Protocol import httpx from pydantic import BaseModel, ValidationError, create_model @@ -98,11 +98,10 @@ async def execute_prompt( if llm_response.tool_calls: self._append_assistant_message(messages, llm_response) for call in llm_response.tool_calls: - validated_args = self._validate_tool_arguments(call.tool, call.arguments) - record = ToolCallRecord( - id=call.id, tool=call.tool, arguments=validated_args or {} - ) + record = ToolCallRecord(id=call.id, tool=call.tool, arguments={}) try: + validated_args = self._validate_tool_arguments(call.tool, call.arguments) + record.arguments = validated_args result = await self.tool_executor.execute_tool( call.tool, validated_args, user_context ) @@ -378,7 +377,7 @@ async def _call_claude( "model": self.model_config.name, "system": system_override or self.system_prompt, "messages": messages, - "max_output_tokens": 4096, + "max_tokens": 4096, } if use_tools and self._anthropic_tools: @@ -422,6 +421,7 @@ async def _call_openai(self, messages: list[dict[str, Any]], use_tools: bool) -> payload["tools"] = self._openai_tools payload["tool_choice"] = "auto" + # Allow callers to set provider-specific extras via options payload.update(self.model_config.options or {}) async with httpx.AsyncClient() as client: diff --git a/src/mxcp/server/definitions/evals/models.py b/src/mxcp/server/definitions/evals/models.py index 0c822df2..f988a9b0 100644 --- a/src/mxcp/server/definitions/evals/models.py +++ b/src/mxcp/server/definitions/evals/models.py @@ -1,7 +1,7 @@ from __future__ import annotations from contextlib import suppress -from typing import Any, Literal +from typing import Any from pydantic import BaseModel, ConfigDict, field_validator, model_validator @@ -50,7 +50,7 @@ class EvalSuiteModel(EvalBaseModel): mxcp: int = 1 suite: str description: str | None = None - model: Literal["claude-4-opus", "claude-4-sonnet", "gpt-4o", "gpt-4.1"] | None = None + model: str | None = None tests: list[EvalTestModel] @field_validator("suite") diff --git a/tests/sdk/evals/test_executor.py b/tests/sdk/evals/test_executor.py index a6368830..279e3817 100644 --- a/tests/sdk/evals/test_executor.py +++ b/tests/sdk/evals/test_executor.py @@ -152,6 +152,48 @@ async def fake_post(*args: Any, **kwargs: Any): # noqa: ANN401 assert "400" in message assert "bad request details" in message + @pytest.mark.asyncio + async def test_claude_payload_uses_max_tokens(self, monkeypatch) -> None: + """Ensure Claude requests use the correct max_tokens field.""" + captured: dict[str, Any] = {} + + async def fake_post( + self, url: str, *args: Any, **kwargs: Any + ) -> httpx.Response: # noqa: ANN401 + captured["json"] = kwargs.get("json") + return httpx.Response( + status_code=200, + json={"content": [{"type": "text", "text": "ok"}]}, + request=httpx.Request("POST", url), + ) + + monkeypatch.setattr(httpx.AsyncClient, "post", fake_post) + + await self.executor._call_claude([{"role": "user", "content": "hi"}], use_tools=False, system_override=None) # type: ignore[attr-defined] + + payload = captured["json"] + assert "max_tokens" in payload + assert "max_output_tokens" not in payload + + @pytest.mark.asyncio + async def test_tool_argument_validation_error_is_captured(self, monkeypatch) -> None: + """Validation errors should be recorded, not crash the agent loop.""" + first = LLMResponse( + content="", + tool_calls=[ + LLMToolCall(id="1", tool="get_weather", arguments={}) + ], # missing required arg + ) + second = LLMResponse(content="done", tool_calls=[]) + self.executor._call_llm = AsyncMock(side_effect=[first, second]) # type: ignore[assignment] + + result = await self.executor.execute_prompt("Weather?") + + assert result.answer == "done" + assert len(result.tool_calls) == 1 + assert result.tool_calls[0].error # validation error captured + assert self.tool_executor.calls == [] # execute_tool not called + def test_parse_grade_response_code_fence(self) -> None: """Parses grading JSON even when wrapped in code fences.""" fenced = """```json From 784e0eb6b8b4cb4f810dad6c2701f046b42a968e Mon Sep 17 00:00:00 2001 From: Alex Zerntev Date: Fri, 28 Nov 2025 19:16:05 +0200 Subject: [PATCH 12/26] added pydantic --- docs/guides/configuration.md | 19 +- docs/guides/quality.md | 10 +- docs/reference/cli.md | 2 +- pyproject.toml | 1 + src/mxcp/sdk/evals/__init__.py | 8 +- src/mxcp/sdk/evals/_types.py | 46 +- src/mxcp/sdk/evals/executor.py | 486 ++++++---------------- src/mxcp/server/core/config/models.py | 2 +- src/mxcp/server/services/evals/service.py | 66 +-- tests/sdk/evals/test_executor.py | 293 ++++++------- uv.lock | 248 ++++++++++- 11 files changed, 538 insertions(+), 643 deletions(-) diff --git a/docs/guides/configuration.md b/docs/guides/configuration.md index 1640a818..733a4c5c 100644 --- a/docs/guides/configuration.md +++ b/docs/guides/configuration.md @@ -609,19 +609,14 @@ Add model configuration to your user config file (`~/.mxcp/config.yml`): ```yaml models: - default: "claude-4-sonnet" # Default model to use for evals + default: "claude-3-5-sonnet-20240620" # Default model to use for evals (update to a valid ID) models: - claude-4-opus: - type: "claude" + claude-3-5-sonnet-20240620: + type: "anthropic" api_key: "${ANTHROPIC_API_KEY}" # Environment variable containing API key - timeout: 60 # Request timeout in seconds + timeout: 30 # Anthropic Messages model ID; ensure your account has access max_retries: 3 # Number of retries on failure - claude-4-sonnet: - type: "claude" - api_key: "${ANTHROPIC_API_KEY}" - timeout: 30 - gpt-4o: type: "openai" api_key: "${OPENAI_API_KEY}" @@ -638,7 +633,7 @@ models: - **default**: The model to use when not specified in eval suite or CLI - **models**: Dictionary of model configurations - - **type**: Either "claude" or "openai" +- **type**: Either "anthropic" or "openai" - **api_key**: API key (you can use environment variables references) - **base_url**: Custom API endpoint (optional, for OpenAI-compatible services) - **timeout**: Request timeout in seconds @@ -657,8 +652,8 @@ models: timeout: 45 options: reasoning: "fast" - claude-4-sonnet: - type: "claude" + claude-3-5-sonnet-20240620: + type: "anthropic" api_key: "${ANTHROPIC_API_KEY}" timeout: 30 options: diff --git a/docs/guides/quality.md b/docs/guides/quality.md index 0cf4608d..c1ff9b53 100644 --- a/docs/guides/quality.md +++ b/docs/guides/quality.md @@ -805,7 +805,7 @@ Create eval files with the suffix `-evals.yml` or `.evals.yml`: mxcp: 1 suite: customer_analysis description: "Test LLM's ability to analyze customer data" -model: claude-3-opus # Optional: specify model for this suite +model: claude-3-5-sonnet-20240620 # Optional: specify model for this suite (ensure valid ID) tests: - name: churn_risk_assessment @@ -924,10 +924,10 @@ Add models to your user config (`~/.mxcp/config.yml`) so evals know which provid ```yaml models: - default: "claude-4-sonnet" + default: "claude-3-5-sonnet-20240620" models: - claude-4-sonnet: - type: "claude" + claude-3-5-sonnet-20240620: + type: "anthropic" api_key: "${ANTHROPIC_API_KEY}" timeout: 30 gpt-4o: @@ -969,7 +969,7 @@ models: default: claude-3-opus models: claude-3-opus: - type: claude + type: anthropic api_key: ${ANTHROPIC_API_KEY} timeout: 60 max_retries: 3 diff --git a/docs/reference/cli.md b/docs/reference/cli.md index 0b23368c..cf67984a 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -340,7 +340,7 @@ The evals command executes evaluation tests that verify LLM behavior. Unlike reg 1. Send prompts to an LLM 2. Verify the LLM calls appropriate tools -3. Check that responses contain expected information (including `expected_answer` grading) +3. Check that responses contain expected information 4. Ensure safety by verifying destructive operations aren't called inappropriately Eval files should have the suffix `-evals.yml` or `.evals.yml` and are automatically discovered in your repository. diff --git a/pyproject.toml b/pyproject.toml index 16b9b2df..e60ede07 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,6 +41,7 @@ dependencies = [ "fastapi>=0.110.0", # FastAPI for admin API "uvicorn[standard]>=0.27.0", # ASGI server for admin API "psutil>=5.9.0", # System metrics for admin API + "pydantic-ai-slim[anthropic,openai]>=1.25.0", ] [project.scripts] diff --git a/src/mxcp/sdk/evals/__init__.py b/src/mxcp/sdk/evals/__init__.py index 9f8a91b0..9045c433 100644 --- a/src/mxcp/sdk/evals/__init__.py +++ b/src/mxcp/sdk/evals/__init__.py @@ -9,15 +9,13 @@ - Tool definition types for describing available tools to the LLM """ -from ._types import ClaudeConfig, ModelConfigType, OpenAIConfig, ParameterDefinition, ToolDefinition -from .executor import LLMExecutor, ToolExecutor +from ._types import ParameterDefinition, ToolDefinition +from .executor import LLMExecutor, ProviderConfig, ToolExecutor __all__ = [ "LLMExecutor", "ToolExecutor", "ToolDefinition", "ParameterDefinition", - "ModelConfigType", - "ClaudeConfig", - "OpenAIConfig", + "ProviderConfig", ] diff --git a/src/mxcp/sdk/evals/_types.py b/src/mxcp/sdk/evals/_types.py index b842055e..a24e9975 100644 --- a/src/mxcp/sdk/evals/_types.py +++ b/src/mxcp/sdk/evals/_types.py @@ -1,57 +1,15 @@ """Types for MXCP SDK Evals module. -This module contains type definitions for LLM models, tool definitions, -and other data structures used in the evaluation framework. +This module contains type definitions for tool definitions and +other data structures used in the evaluation framework. """ -from abc import ABC, abstractmethod from dataclasses import dataclass, field from typing import Any from mxcp.sdk.validator import TypeSchema -# LLM Model configuration types -@dataclass -class ModelConfig(ABC): - """Base class for LLM model configurations.""" - - name: str - api_key: str - options: dict[str, Any] = field(default_factory=dict) - - @abstractmethod - def get_type(self) -> str: - """Get the type identifier for this model.""" - pass - - -@dataclass -class ClaudeConfig(ModelConfig): - """Configuration for Claude models.""" - - base_url: str = "https://api.anthropic.com" - timeout: int = 30 - - def get_type(self) -> str: - return "claude" - - -@dataclass -class OpenAIConfig(ModelConfig): - """Configuration for OpenAI models.""" - - base_url: str = "https://api.openai.com/v1" - timeout: int = 30 - - def get_type(self) -> str: - return "openai" - - -# Union type for all supported model configurations -ModelConfigType = ClaudeConfig | OpenAIConfig - - @dataclass class ParameterDefinition: """Definition of a tool parameter.""" diff --git a/src/mxcp/sdk/evals/executor.py b/src/mxcp/sdk/evals/executor.py index ac900c36..04f74caf 100644 --- a/src/mxcp/sdk/evals/executor.py +++ b/src/mxcp/sdk/evals/executor.py @@ -1,23 +1,21 @@ -"""Agent-style LLM executor for MXCP evals. - -This implementation builds pydantic-based tool schemas from MXCP tool -definitions and drives a lightweight agent loop that lets the model call -tools, execute them, and return a final answer plus tool history. -""" +"""Agent-style LLM executor for MXCP evals.""" from __future__ import annotations -import json import logging +import os +from collections.abc import Callable from dataclasses import dataclass, field from typing import Any, Protocol -import httpx -from pydantic import BaseModel, ValidationError, create_model +from pydantic import BaseModel, Field, create_model +from pydantic_ai import Agent, ModelSettings, RunContext +from pydantic_ai.tools import Tool +from pydantic_ai.tools import ToolDefinition as AgentToolDefinition from mxcp.sdk.auth import UserContext -from ._types import ModelConfigType, ToolDefinition +from ._types import ToolDefinition logger = logging.getLogger(__name__) @@ -30,13 +28,6 @@ async def execute_tool( ) -> Any: ... -@dataclass -class LLMToolCall: - id: str | None - tool: str - arguments: dict[str, Any] - - @dataclass class ToolCallRecord: id: str | None @@ -46,40 +37,52 @@ class ToolCallRecord: error: str | None = None -@dataclass -class LLMResponse: - content: str - tool_calls: list[LLMToolCall] - raw_message: dict[str, Any] | None = None - - @dataclass class AgentResult: answer: str tool_calls: list[ToolCallRecord] = field(default_factory=list) +class ProviderConfig(BaseModel): + api_key: str | None = None + base_url: str | None = None + timeout: int | None = None + model_config = {"extra": "forbid"} + + +class GradeResult(BaseModel): + result: str = Field(default="unknown") + comment: str = Field(default="") + reasoning: str = Field(default="") + + class LLMExecutor: """Pydantic-based agent loop with tool support.""" def __init__( self, - model_config: ModelConfigType, + model_name: str, + model_type: str, + model_settings: ModelSettings, available_tools: list[ToolDefinition], tool_executor: ToolExecutor, + provider_config: ProviderConfig | None = None, ): - self.model_config = model_config self.available_tools = available_tools self.tool_executor = tool_executor - self.model_type = model_config.get_type() + self.model_name = model_name + self.model_type = model_type + self.provider_config = provider_config or ProviderConfig() + self._agent_cls: Callable[..., Any] = Agent + self._model_settings = model_settings + self._apply_provider_env() self._tool_models = self._build_tool_models(available_tools) - self._openai_tools, self._anthropic_tools = self._build_tool_schemas(available_tools) self.system_prompt = self._build_system_prompt(available_tools) logger.info( "LLM executor initialized with model %s (%s) and %d tools", - model_config.name, + self.model_name, self.model_type, len(available_tools), ) @@ -87,38 +90,58 @@ def __init__( async def execute_prompt( self, prompt: str, user_context: UserContext | None = None, max_turns: int = 10 ) -> AgentResult: - """Run the agent loop for a prompt.""" - messages = self._initial_messages(prompt) + """Run the agent loop for a prompt using pydantic-ai Agent.""" history: list[ToolCallRecord] = [] - for _ in range(max_turns): - llm_response = await self._call_llm(messages, use_tools=True) - - # If the model wants to call tools, execute them and continue. - if llm_response.tool_calls: - self._append_assistant_message(messages, llm_response) - for call in llm_response.tool_calls: - record = ToolCallRecord(id=call.id, tool=call.tool, arguments={}) - try: - validated_args = self._validate_tool_arguments(call.tool, call.arguments) - record.arguments = validated_args - result = await self.tool_executor.execute_tool( - call.tool, validated_args, user_context - ) - record.result = result - except Exception as exc: # noqa: BLE001 - record.error = str(exc) + def _make_tool(tool_def: ToolDefinition) -> Tool: + args_model = self._tool_models.get(tool_def.name) + schema = ( + args_model.model_json_schema() + if args_model + else {"type": "object", "properties": {}, "required": []} + ) + async def _fn(**kwargs: Any) -> Any: + record = ToolCallRecord(id=None, tool=tool_def.name, arguments=kwargs) + try: + validated = ( + args_model.model_validate(kwargs).model_dump() if args_model else kwargs + ) + record.arguments = validated + result = await self.tool_executor.execute_tool( + tool_def.name, validated, user_context + ) + record.result = result + return result + except Exception as exc: # noqa: BLE001 + record.error = str(exc) + return {"error": str(exc)} + finally: history.append(record) - messages.append(self._tool_result_message(record)) - continue - # No tool calls: final answer. - self._append_assistant_message(messages, llm_response) - return AgentResult(answer=llm_response.content, tool_calls=history) + async def _prepare( + _ctx: RunContext[Any], _tool_def: AgentToolDefinition + ) -> AgentToolDefinition: + return AgentToolDefinition( + name=tool_def.name, + description=tool_def.description, + parameters_json_schema=schema, + strict=True, + ) + + tool = Tool(_fn, name=tool_def.name, description=tool_def.description, prepare=_prepare) + tool._mxcp_callable = _fn # type: ignore[attr-defined] + return tool + + agent_tools = [_make_tool(t) for t in self.available_tools] + model_string = f"{self.model_type}:{self.model_name}" + agent = self._agent_cls( + model=model_string, instructions=self.system_prompt, tools=agent_tools + ) - logger.warning("Max agent turns reached without a final answer") - return AgentResult(answer=llm_response.content, tool_calls=history) + agent_run = await agent.run(prompt, deps=user_context, model_settings=self._model_settings) + answer = getattr(agent_run, "output", "") + return AgentResult(answer=str(answer), tool_calls=history) async def evaluate_expected_answer(self, answer: str, expected_answer: str) -> dict[str, str]: """Ask the model to grade an answer against an expected value.""" @@ -139,11 +162,13 @@ async def evaluate_expected_answer(self, answer: str, expected_answer: str) -> d '{"result":"correct|wrong|partially correct","comment":"short","reasoning":"short"}' ) - messages = self._initial_messages(grader_prompt, system_override=grader_system) - llm_response = await self._call_llm( - messages, use_tools=False, system_override=grader_system + model_string = f"{self.model_type}:{self.model_name}" + agent = self._agent_cls( + model=model_string, instructions=grader_system, tools=(), output_type=GradeResult ) - return self._parse_grade_response(llm_response.content) + run = await agent.run(grader_prompt, model_settings=self._model_settings) + out: GradeResult = getattr(run, "output", GradeResult()) + return out.model_dump() def _build_system_prompt(self, tools: list[ToolDefinition]) -> str: if not tools: @@ -158,34 +183,6 @@ def _build_system_prompt(self, tools: list[ToolDefinition]) -> str: "When no tool fits, answer directly and concisely." ) - def _initial_messages( - self, prompt: str, system_override: str | None = None - ) -> list[dict[str, Any]]: - system_prompt = system_override or self.system_prompt - if self.model_type == "openai": - return [ - {"role": "system", "content": system_prompt}, - {"role": "user", "content": prompt}, - ] - - # Anthropic/Claude style keeps system separate; we include it in the API call. - return [{"role": "user", "content": [{"type": "text", "text": prompt}]}] - - def _append_assistant_message( - self, messages: list[dict[str, Any]], response: LLMResponse - ) -> None: - if self.model_type == "openai": - messages.append( - response.raw_message or {"role": "assistant", "content": response.content} - ) - return - - # Claude/Anthropic - messages.append( - response.raw_message - or {"role": "assistant", "content": [{"type": "text", "text": response.content}]} - ) - def _build_tool_models(self, tools: list[ToolDefinition]) -> dict[str, type[BaseModel]]: models: dict[str, type[BaseModel]] = {} @@ -194,294 +191,47 @@ def _build_tool_models(self, tools: list[ToolDefinition]) -> dict[str, type[Base for param in tool.parameters: py_type = self._map_param_type(param.type) default = param.default if param.default is not None else None - if param.required and default is None: - fields[param.name] = (py_type, ...) - else: - fields[param.name] = (py_type, default) + field_info = Field( + default if default is not None or not param.required else ..., + description=param.description or None, + ) + fields[param.name] = (py_type, field_info) models[tool.name] = create_model(f"{tool.name}_Args", **fields) return models - def _build_tool_schemas( - self, tools: list[ToolDefinition] - ) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]: - openai_tools: list[dict[str, Any]] = [] - anthropic_tools: list[dict[str, Any]] = [] - - for tool in tools: - schema = self._tool_json_schema(tool) - openai_tools.append( - { - "type": "function", - "function": { - "name": tool.name, - "description": tool.description, - "parameters": schema, - }, - } - ) - anthropic_tools.append( - { - "name": tool.name, - "description": tool.description, - "input_schema": schema, - } - ) - - return openai_tools, anthropic_tools - - def _tool_json_schema(self, tool: ToolDefinition) -> dict[str, Any]: - properties: dict[str, Any] = {} - required: list[str] = [] - - for param in tool.parameters: - schema: dict[str, Any] = {"type": self._map_type_string(param.type)} - if param.description: - schema["description"] = param.description - if param.default is not None: - schema["default"] = param.default - - properties[param.name] = schema - if param.required: - required.append(param.name) - - return {"type": "object", "properties": properties, "required": required} - def _map_param_type(self, param_type: str) -> Any: - mapping: dict[str, Any] = { - "string": str, - "integer": int, - "number": float, - "boolean": bool, - "object": dict[str, Any], - "array": list[Any], - } - return mapping.get(param_type.lower(), Any) - - def _map_type_string(self, param_type: str) -> str: - mapping = { - "string": "string", - "integer": "integer", - "number": "number", - "boolean": "boolean", - "object": "object", - "array": "array", + """Map simple tool parameter types to Python/Pydantic types.""" + key = param_type.lower() + mapping: dict[tuple[str, ...], Any] = { + ("string", "str", "text"): str, + ("integer", "int"): int, + ("number", "float", "double"): float, + ("boolean", "bool"): bool, + ("object", "map", "dict"): dict[str, Any], + ("array", "list"): list[Any], } - return mapping.get(param_type.lower(), "string") - - def _validate_tool_arguments(self, tool_name: str, arguments: dict[str, Any]) -> dict[str, Any]: - model = self._tool_models.get(tool_name) - if not model: - return arguments - - try: - return model.model_validate(arguments).model_dump() - except ValidationError as exc: - raise ValueError(f"Invalid arguments for tool '{tool_name}': {exc}") from exc - - def _tool_result_message(self, record: ToolCallRecord) -> dict[str, Any]: - payload = record.result - if record.error: - payload = {"error": record.error} - - content = self._serialize_result(payload) - - if self.model_type == "openai": - return { - "role": "tool", - "tool_call_id": record.id or record.tool, - "name": record.tool, - "content": content, - } - - return { - "role": "user", - "content": [ - { - "type": "tool_result", - "tool_use_id": record.id or record.tool, - "content": content, - } - ], - } - - def _serialize_result(self, result: Any) -> str: - if isinstance(result, str): - return result - if result is None: - return "null" - if isinstance(result, int | float | bool): - return json.dumps(result) - try: - return json.dumps(result) - except TypeError: - return str(result) - - def _extract_openai_response(self, data: dict[str, Any]) -> LLMResponse: - message = data["choices"][0]["message"] - content = message.get("content") or "" - tool_calls = [] - - for call in message.get("tool_calls", []) or []: - args_raw = call.get("function", {}).get("arguments", "{}") - try: - args = json.loads(args_raw) - except json.JSONDecodeError: - args = {} - tool_calls.append( - LLMToolCall(id=call.get("id"), tool=call["function"]["name"], arguments=args) - ) - - return LLMResponse(content=content, tool_calls=tool_calls, raw_message=message) - - def _extract_anthropic_response(self, data: dict[str, Any]) -> LLMResponse: - content_blocks = data.get("content", []) - tool_calls = [] - text_parts = [] - - for block in content_blocks: - if block.get("type") == "tool_use": - tool_calls.append( - LLMToolCall( - id=block.get("id"), - tool=block.get("name"), - arguments=block.get("input") or {}, - ) - ) - elif block.get("type") == "text": - text_parts.append(block.get("text") or "") + for aliases, py_type in mapping.items(): + if key in aliases: + return py_type - return LLMResponse( - content="\n".join(text_parts).strip(), - tool_calls=tool_calls, - raw_message={"role": "assistant", "content": content_blocks}, - ) + logger.debug("Unknown tool parameter type '%s'; defaulting to Any", param_type) + return Any - async def _call_llm( - self, - messages: list[dict[str, Any]], - use_tools: bool = True, - system_override: str | None = None, - ) -> LLMResponse: - if self.model_type == "claude": - return await self._call_claude(messages, use_tools, system_override) + def _apply_provider_env(self) -> None: + """Populate provider env vars if missing, using provided config.""" + env_overrides: dict[str, str] = {} if self.model_type == "openai": - return await self._call_openai(messages, use_tools) - raise ValueError(f"Unknown model type: {self.model_type}") - - async def _call_claude( - self, messages: list[dict[str, Any]], use_tools: bool, system_override: str | None - ) -> LLMResponse: - payload: dict[str, Any] = { - "model": self.model_config.name, - "system": system_override or self.system_prompt, - "messages": messages, - "max_tokens": 4096, - } - - if use_tools and self._anthropic_tools: - payload["tools"] = self._anthropic_tools - - payload.update(self.model_config.options or {}) - - async with httpx.AsyncClient() as client: - response = await client.post( - f"{self.model_config.base_url}/v1/messages", - headers={ - "x-api-key": self.model_config.api_key, - "anthropic-version": "2023-06-01", - "content-type": "application/json", - }, - json=payload, - timeout=self.model_config.timeout, - ) - - try: - response.raise_for_status() - except httpx.HTTPStatusError as exc: - body = (exc.response.text or "").strip() - snippet = body[:500] if body else "No response body" - raise ValueError( - f"Claude API call failed ({exc.response.status_code} {exc.response.reason_phrase}): " - f"{snippet}" - ) from exc - - data = response.json() - logger.debug("Claude response: %s", data) - return self._extract_anthropic_response(data) - - async def _call_openai(self, messages: list[dict[str, Any]], use_tools: bool) -> LLMResponse: - payload: dict[str, Any] = { - "model": self.model_config.name, - "messages": messages, - } - - if use_tools and self._openai_tools: - payload["tools"] = self._openai_tools - payload["tool_choice"] = "auto" - - # Allow callers to set provider-specific extras via options - payload.update(self.model_config.options or {}) - - async with httpx.AsyncClient() as client: - response = await client.post( - f"{self.model_config.base_url}/chat/completions", - headers={ - "Authorization": f"Bearer {self.model_config.api_key}", - "Content-Type": "application/json", - }, - json=payload, - timeout=self.model_config.timeout, - ) - - try: - response.raise_for_status() - except httpx.HTTPStatusError as exc: - body = (exc.response.text or "").strip() - snippet = body[:500] if body else "No response body" - raise ValueError( - f"OpenAI API call failed ({exc.response.status_code} {exc.response.reason_phrase}): " - f"{snippet}" - ) from exc - - data = response.json() - logger.debug("OpenAI response: %s", data) - return self._extract_openai_response(data) - - def _parse_grade_response(self, content: str) -> dict[str, str]: - def _parse(obj: str) -> dict[str, str] | None: - try: - data = json.loads(obj) - except json.JSONDecodeError: - return None - if not isinstance(data, dict): - return None - result = str(data.get("result", "unknown")).lower() - comment = str(data.get("comment", "") or "").strip() - reasoning = str(data.get("reasoning", "") or "").strip() - return {"result": result, "comment": comment, "reasoning": reasoning} - - # Try direct parse - parsed = _parse(content) - if parsed: - return parsed - - # Strip markdown code fences - trimmed = content.strip() - if trimmed.startswith("```") and "```" in trimmed[3:]: - inner = trimmed.strip("`") - parsed = _parse(inner) - if parsed: - return parsed - - # Extract first JSON object from the text - start = trimmed.find("{") - end = trimmed.rfind("}") - if start != -1 and end != -1 and end > start: - parsed = _parse(trimmed[start : end + 1]) - if parsed: - return parsed - - logger.debug("Failed to parse grade JSON: %s", content) - return {"result": "unknown", "comment": trimmed[:200], "reasoning": ""} + if self.provider_config.api_key: + env_overrides["OPENAI_API_KEY"] = self.provider_config.api_key + if self.provider_config.base_url: + env_overrides["OPENAI_BASE_URL"] = self.provider_config.base_url + elif self.model_type == "anthropic": + if self.provider_config.api_key: + env_overrides["ANTHROPIC_API_KEY"] = self.provider_config.api_key + if self.provider_config.base_url: + env_overrides["ANTHROPIC_BASE_URL"] = self.provider_config.base_url + + for key, value in env_overrides.items(): + os.environ.setdefault(key, value) diff --git a/src/mxcp/server/core/config/models.py b/src/mxcp/server/core/config/models.py index c2a3439a..e50a84dd 100644 --- a/src/mxcp/server/core/config/models.py +++ b/src/mxcp/server/core/config/models.py @@ -392,7 +392,7 @@ def _apply_defaults(self) -> UserAuthConfigModel: class UserModelConfigModel(BaseModel): model_config = ConfigDict(extra="forbid", frozen=True) - type: Literal["claude", "openai"] + type: Literal["anthropic", "openai"] api_key: str | None = None base_url: str | None = None timeout: int | None = None diff --git a/src/mxcp/server/services/evals/service.py b/src/mxcp/server/services/evals/service.py index f87917d6..f37047e1 100644 --- a/src/mxcp/server/services/evals/service.py +++ b/src/mxcp/server/services/evals/service.py @@ -3,15 +3,10 @@ from pathlib import Path from typing import Any +from pydantic_ai import ModelSettings + from mxcp.sdk.auth import UserContext -from mxcp.sdk.evals import ( - ClaudeConfig, - LLMExecutor, - ModelConfigType, - OpenAIConfig, - ParameterDefinition, - ToolDefinition, -) +from mxcp.sdk.evals import LLMExecutor, ParameterDefinition, ProviderConfig, ToolDefinition from mxcp.sdk.validator import TypeSchema from mxcp.server.core.config.models import SiteConfigModel, UserConfigModel from mxcp.server.core.config.site_config import find_repo_root @@ -24,15 +19,17 @@ logger = logging.getLogger(__name__) -def _create_model_config(model: str, user_config: UserConfigModel) -> ModelConfigType: - """Create a model configuration from user config. +def _create_model_config( + model: str, user_config: UserConfigModel +) -> tuple[str, str, dict[str, Any], ProviderConfig]: + """Create a model configuration tuple from user config. Args: model: Model name to use user_config: User configuration containing model settings Returns: - Configured model object + Tuple of (model_name, model_type, options, api_key, base_url, timeout) Raises: ValueError: If model is not configured or has invalid type @@ -47,26 +44,25 @@ def _create_model_config(model: str, user_config: UserConfigModel) -> ModelConfi model_type = model_config.type api_key = model_config.api_key - options = model_config.options or {} + options = dict(model_config.options or {}) if not api_key: raise ValueError(f"No API key configured for model '{model}'") - if model_type == "claude": - base_url = model_config.base_url or "https://api.anthropic.com" - timeout = model_config.timeout or 30 - return ClaudeConfig( - name=model, api_key=api_key, base_url=base_url, timeout=timeout, options=options - ) - elif model_type == "openai": - base_url = model_config.base_url or "https://api.openai.com/v1" - timeout = model_config.timeout or 30 - return OpenAIConfig( - name=model, api_key=api_key, base_url=base_url, timeout=timeout, options=options - ) - else: + if model_type not in {"anthropic", "openai"}: raise ValueError(f"Unknown model type: {model_type}") + base_url = model_config.base_url + timeout = model_config.timeout + + # Ensure timeout also flows through options if present + if timeout and "timeout" not in options: + options["timeout"] = timeout + + provider_config = ProviderConfig(api_key=api_key, base_url=base_url, timeout=timeout) + + return model, model_type, options, provider_config + def _load_endpoints(site_config: SiteConfigModel) -> list[tuple[EndpointDefinitionModel, Path]]: """Load all available endpoints. @@ -227,7 +223,16 @@ async def run_eval_suite( } # Create model configuration - model_config = _create_model_config(model, user_config) + model_name, model_type, model_options, provider_config = _create_model_config( + model, user_config + ) + allowed_keys = set(ModelSettings.__annotations__.keys()) + unknown_keys = set(model_options.keys()) - allowed_keys + if unknown_keys: + raise ValueError( + f"Invalid model options keys: {sorted(unknown_keys)}. Allowed: {sorted(allowed_keys)}" + ) + model_settings = ModelSettings(**model_options) # type: ignore[typeddict-item] # Load endpoints endpoints = _load_endpoints(site_config) @@ -249,7 +254,14 @@ async def run_eval_suite( try: # Create LLM executor with model config, tool definitions, and tool executor - executor = LLMExecutor(model_config, tool_definitions, tool_executor) + executor = LLMExecutor( + model_name, + model_type, + model_settings, + tool_definitions, + tool_executor, + provider_config=provider_config, + ) # Run each test tests = [] diff --git a/tests/sdk/evals/test_executor.py b/tests/sdk/evals/test_executor.py index 279e3817..61cdaa10 100644 --- a/tests/sdk/evals/test_executor.py +++ b/tests/sdk/evals/test_executor.py @@ -1,19 +1,44 @@ -"""Tests for the agent-style LLM executor.""" - +import asyncio from typing import Any -from unittest.mock import AsyncMock import pytest from mxcp.sdk.auth import UserContext -from mxcp.sdk.evals import ClaudeConfig, ParameterDefinition, ToolDefinition -from mxcp.sdk.evals.executor import AgentResult, LLMExecutor, LLMResponse, LLMToolCall -import httpx +from mxcp.sdk.evals import ParameterDefinition, ToolDefinition +from mxcp.sdk.evals.executor import AgentResult, GradeResult, LLMExecutor, ProviderConfig + + +class FakeRun: + def __init__(self, output: Any) -> None: + self.output = output + + +class FakeAgent: + def __init__( + self, *, tools: list[Any], output: Any, tool_args: dict[str, dict[str, Any]] + ) -> None: + self.tools = tools + self.output = output + self.tool_args = tool_args + + async def run( + self, _prompt: str, deps: Any | None = None, model_settings: Any | None = None + ) -> FakeRun: + for tool in self.tools: + fn = getattr(tool, "_mxcp_callable", None) + tool_name = getattr(tool, "name", None) or getattr( + getattr(tool, "tool_def", None), "name", None + ) + args = self.tool_args.get(tool_name or "", {}) + if fn: + if asyncio.iscoroutinefunction(fn): + await fn(**args) + else: + fn(**args) + return FakeRun(self.output) class MockToolExecutor: - """Mock tool executor for testing.""" - def __init__(self, responses: dict[str, Any] | None = None): self.responses = responses or {} self.calls: list[dict[str, Any]] = [] @@ -24,181 +49,95 @@ async def execute_tool( self.calls.append( {"tool_name": tool_name, "arguments": arguments, "user_context": user_context} ) - if tool_name in self.responses: value = self.responses[tool_name] if isinstance(value, Exception): raise value return value - return {"echo": arguments} -class TestLLMExecutor: - def setup_method(self) -> None: - self.model_config = ClaudeConfig(name="claude-test", api_key="key") - self.tools = [ - ToolDefinition( - name="get_weather", - description="Weather lookup", - parameters=[ParameterDefinition(name="location", type="string", required=True)], - ) - ] - self.tool_executor = MockToolExecutor({"get_weather": {"temperature": 20}}) - self.executor = LLMExecutor(self.model_config, self.tools, self.tool_executor) - - @pytest.mark.asyncio - async def test_execute_prompt_no_tools(self) -> None: - """Returns final answer when no tool calls are present.""" - self.executor._call_llm = AsyncMock( # type: ignore[assignment] - return_value=LLMResponse(content="Hello!", tool_calls=[]) - ) - - result = await self.executor.execute_prompt("Hi") - - assert isinstance(result, AgentResult) - assert result.answer == "Hello!" - assert result.tool_calls == [] - assert self.tool_executor.calls == [] - - @pytest.mark.asyncio - async def test_execute_prompt_with_tool_call(self) -> None: - """Executes tool calls and returns final answer.""" - first = LLMResponse( - content="", - tool_calls=[LLMToolCall(id="1", tool="get_weather", arguments={"location": "Paris"})], - ) - second = LLMResponse(content="Sunny", tool_calls=[]) - self.executor._call_llm = AsyncMock(side_effect=[first, second]) # type: ignore[assignment] - - user_ctx = UserContext(provider="test", user_id="u1", username="user") - - result = await self.executor.execute_prompt("Weather?", user_context=user_ctx) - - assert result.answer == "Sunny" - assert len(result.tool_calls) == 1 - call = result.tool_calls[0] - assert call.tool == "get_weather" - assert call.arguments["location"] == "Paris" - assert call.result == {"temperature": 20} - assert call.error is None - assert self.tool_executor.calls[0]["user_context"] == user_ctx - - @pytest.mark.asyncio - async def test_execute_prompt_tool_error(self) -> None: - """Records tool errors without failing the loop.""" - self.tool_executor.responses["get_weather"] = ValueError("boom") - first = LLMResponse( - content="", - tool_calls=[LLMToolCall(id="t1", tool="get_weather", arguments={"location": "Rome"})], - ) - second = LLMResponse(content="Could not fetch", tool_calls=[]) - self.executor._call_llm = AsyncMock(side_effect=[first, second]) # type: ignore[assignment] - - result = await self.executor.execute_prompt("Weather?") - - assert result.answer == "Could not fetch" - assert len(result.tool_calls) == 1 - assert result.tool_calls[0].error == "boom" - - @pytest.mark.asyncio - async def test_execute_prompt_max_turns(self) -> None: - """Stops after max turns when the model keeps calling tools.""" - - async def _loop_response(*_: Any, **__: Any) -> LLMResponse: - return LLMResponse( - content="", - tool_calls=[ - LLMToolCall(id=None, tool="get_weather", arguments={"location": "Paris"}) - ], - ) - - self.executor._call_llm = AsyncMock(side_effect=_loop_response) # type: ignore[assignment] - - result = await self.executor.execute_prompt("Weather?") - - assert len(result.tool_calls) == 10 - assert result.answer == "" - - def test_parse_grade_response(self) -> None: - """Parses grading JSON with fallbacks.""" - parsed = self.executor._parse_grade_response( # type: ignore[attr-defined] - '{"result":"correct","comment":"ok","reasoning":"short"}' - ) - assert parsed["result"] == "correct" - assert parsed["comment"] == "ok" - - fallback = self.executor._parse_grade_response("not json") # type: ignore[attr-defined] - assert fallback["result"] == "unknown" - - @pytest.mark.asyncio - async def test_openai_http_error_includes_body(self, monkeypatch) -> None: - """HTTP errors should include status and body for easier debugging.""" - - async def fake_post(*args: Any, **kwargs: Any): # noqa: ANN401 - return httpx.Response( - status_code=400, - request=httpx.Request("POST", "https://api.openai.com/v1/chat/completions"), - text="bad request details", - ) - - monkeypatch.setattr(httpx.AsyncClient, "post", fake_post) - - with pytest.raises(ValueError) as excinfo: - await self.executor._call_openai([], use_tools=False) # type: ignore[attr-defined] - - message = str(excinfo.value) - assert "OpenAI API call failed" in message - assert "400" in message - assert "bad request details" in message - - @pytest.mark.asyncio - async def test_claude_payload_uses_max_tokens(self, monkeypatch) -> None: - """Ensure Claude requests use the correct max_tokens field.""" - captured: dict[str, Any] = {} - - async def fake_post( - self, url: str, *args: Any, **kwargs: Any - ) -> httpx.Response: # noqa: ANN401 - captured["json"] = kwargs.get("json") - return httpx.Response( - status_code=200, - json={"content": [{"type": "text", "text": "ok"}]}, - request=httpx.Request("POST", url), - ) - - monkeypatch.setattr(httpx.AsyncClient, "post", fake_post) - - await self.executor._call_claude([{"role": "user", "content": "hi"}], use_tools=False, system_override=None) # type: ignore[attr-defined] - - payload = captured["json"] - assert "max_tokens" in payload - assert "max_output_tokens" not in payload - - @pytest.mark.asyncio - async def test_tool_argument_validation_error_is_captured(self, monkeypatch) -> None: - """Validation errors should be recorded, not crash the agent loop.""" - first = LLMResponse( - content="", - tool_calls=[ - LLMToolCall(id="1", tool="get_weather", arguments={}) - ], # missing required arg +def make_executor() -> LLMExecutor: + tools = [ + ToolDefinition( + name="get_weather", + description="Weather lookup", + parameters=[ParameterDefinition(name="location", type="string", required=True)], ) - second = LLMResponse(content="done", tool_calls=[]) - self.executor._call_llm = AsyncMock(side_effect=[first, second]) # type: ignore[assignment] - - result = await self.executor.execute_prompt("Weather?") - - assert result.answer == "done" - assert len(result.tool_calls) == 1 - assert result.tool_calls[0].error # validation error captured - assert self.tool_executor.calls == [] # execute_tool not called - - def test_parse_grade_response_code_fence(self) -> None: - """Parses grading JSON even when wrapped in code fences.""" - fenced = """```json - {"result":"partially correct","comment":"ok","reasoning":"short"} - ```""" - parsed = self.executor._parse_grade_response(fenced) # type: ignore[attr-defined] - assert parsed["result"] == "partially correct" - assert parsed["comment"] == "ok" + ] + tool_executor = MockToolExecutor({"get_weather": {"temperature": 20}}) + executor = LLMExecutor( + "claude-test", + "anthropic", + {}, + tools, + tool_executor, + provider_config=ProviderConfig(api_key="key", base_url="https://api.anthropic.com"), + ) + return executor + + +def test_execute_prompt_with_tool_call() -> None: + executor = make_executor() + user_ctx = UserContext(provider="test", user_id="u1", username="user") + executor._agent_cls = lambda **kwargs: FakeAgent( + tools=kwargs["tools"], + output="Sunny", + tool_args={"get_weather": {"location": "Paris"}}, + ) + + result = asyncio.run(executor.execute_prompt("Weather?", user_context=user_ctx)) + + assert isinstance(result, AgentResult) + assert result.answer == "Sunny" + assert len(result.tool_calls) == 1 + call = result.tool_calls[0] + assert call.tool == "get_weather" + assert call.arguments["location"] == "Paris" + assert call.result == {"temperature": 20} + assert call.error is None + + +def test_execute_prompt_tool_error() -> None: + executor = make_executor() + executor.tool_executor.responses["get_weather"] = ValueError("boom") # type: ignore[attr-defined] + executor._agent_cls = lambda **kwargs: FakeAgent( + tools=kwargs["tools"], + output="Error", + tool_args={"get_weather": {"location": "Rome"}}, + ) + + result = asyncio.run(executor.execute_prompt("Weather?")) + + assert result.tool_calls and result.tool_calls[0].error == "boom" + + +def test_tool_argument_validation_error_is_captured() -> None: + executor = make_executor() + executor._agent_cls = lambda **kwargs: FakeAgent( + tools=kwargs["tools"], + output="done", + tool_args={"get_weather": {}}, # missing required arg + ) + + result = asyncio.run(executor.execute_prompt("Weather?")) + + assert result.tool_calls + assert result.tool_calls[0].error + + +def test_expected_answer_grading() -> None: + executor = make_executor() + executor._agent_cls = lambda **kwargs: FakeAgent( + tools=kwargs.get("tools", []), + output=GradeResult(result="correct", comment="ok", reasoning="match"), + tool_args={}, + ) + + result = asyncio.run(executor.evaluate_expected_answer("hello", "hello")) + assert result["result"] == "correct" + assert result["comment"] + + +def test_temporary_env_sets_and_restores() -> None: + pytest.skip("Environment injection removed; no temporary env to test.") diff --git a/uv.lock b/uv.lock index e296f405..d745f7a4 100644 --- a/uv.lock +++ b/uv.lock @@ -164,6 +164,25 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, ] +[[package]] +name = "anthropic" +version = "0.75.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "distro" }, + { name = "docstring-parser" }, + { name = "httpx" }, + { name = "jiter" }, + { name = "pydantic" }, + { name = "sniffio" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/04/1f/08e95f4b7e2d35205ae5dcbb4ae97e7d477fc521c275c02609e2931ece2d/anthropic-0.75.0.tar.gz", hash = "sha256:e8607422f4ab616db2ea5baacc215dd5f028da99ce2f022e33c7c535b29f3dfb", size = 439565, upload-time = "2025-11-24T20:41:45.28Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/1c/1cd02b7ae64302a6e06724bf80a96401d5313708651d277b1458504a1730/anthropic-0.75.0-py3-none-any.whl", hash = "sha256:ea8317271b6c15d80225a9f3c670152746e88805a7a61e14d4a374577164965b", size = 388164, upload-time = "2025-11-24T20:41:43.587Z" }, +] + [[package]] name = "anyio" version = "4.9.0" @@ -703,6 +722,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" }, ] +[[package]] +name = "docstring-parser" +version = "0.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/9d/c3b43da9515bd270df0f80548d9944e389870713cc1fe2b8fb35fe2bcefd/docstring_parser-0.17.0.tar.gz", hash = "sha256:583de4a309722b3315439bb31d64ba3eebada841f2e2cee23b99df001434c912", size = 27442, upload-time = "2025-07-21T07:35:01.868Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/55/e2/2537ebcff11c1ee1ff17d8d0b6f4db75873e3b0fb32c2d4a2ee31ecb310a/docstring_parser-0.17.0-py3-none-any.whl", hash = "sha256:cf2569abd23dce8099b300f9b4fa8191e9582dda731fd533daf54c4551658708", size = 36896, upload-time = "2025-07-21T07:35:00.684Z" }, +] + [[package]] name = "docutils" version = "0.21.2" @@ -744,6 +772,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/30/79/4f544d73fcc0513b71296cb3ebb28a227d22e80dec27204977039b9fa875/duckdb-1.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:280fd663dacdd12bb3c3bf41f3e5b2e5b95e00b88120afabb8b8befa5f335c6f", size = 12336460, upload-time = "2025-10-07T10:37:12.154Z" }, ] +[[package]] +name = "eval-type-backport" +version = "0.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/51/23/079e39571d6dd8d90d7a369ecb55ad766efb6bae4e77389629e14458c280/eval_type_backport-0.3.0.tar.gz", hash = "sha256:1638210401e184ff17f877e9a2fa076b60b5838790f4532a21761cc2be67aea1", size = 9272, upload-time = "2025-11-13T20:56:50.845Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/19/d8/2a1c638d9e0aa7e269269a1a1bf423ddd94267f1a01bbe3ad03432b67dd4/eval_type_backport-0.3.0-py3-none-any.whl", hash = "sha256:975a10a0fe333c8b6260d7fdb637698c9a16c3a9e3b6eb943fee6a6f67a37fe8", size = 6061, upload-time = "2025-11-13T20:56:49.499Z" }, +] + [[package]] name = "exceptiongroup" version = "1.3.0" @@ -865,6 +902,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ee/45/b82e3c16be2182bff01179db177fe144d58b5dc787a7d4492c6ed8b9317f/frozenlist-1.7.0-py3-none-any.whl", hash = "sha256:9a5af342e34f7e97caf8c995864c7a396418ae2859cc6fdf1b1073020d516a7e", size = 13106, upload-time = "2025-06-09T23:02:34.204Z" }, ] +[[package]] +name = "genai-prices" +version = "0.0.47" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "eval-type-backport", marker = "python_full_version < '3.11'" }, + { name = "httpx" }, + { name = "pydantic" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b8/47/f25fb84fa40142699dc54ca294628d600625eb3d90fead103a606b4e999a/genai_prices-0.0.47.tar.gz", hash = "sha256:3b8c514f0ce5818b3944a371861586ed9bfe10d02598e62c350b5bd2916d03c2", size = 54501, upload-time = "2025-11-25T18:38:17.695Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4e/84/d50c52d0eeadb9dbf7f2f86da9b6257e162b7c6a791f5b1009bae912c103/genai_prices-0.0.47-py3-none-any.whl", hash = "sha256:735e45950d2299276f2c00cd18075b77a124cd24ee58243f236ee29af3210594", size = 57000, upload-time = "2025-11-25T18:38:16.464Z" }, +] + [[package]] name = "googleapis-common-protos" version = "1.70.0" @@ -877,6 +928,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/86/f1/62a193f0227cf15a920390abe675f386dec35f7ae3ffe6da582d3ade42c7/googleapis_common_protos-1.70.0-py3-none-any.whl", hash = "sha256:b8bfcca8c25a2bb253e0e0b0adaf8c00773e5e6af6fd92397576680b807e0fd8", size = 294530, upload-time = "2025-04-14T10:17:01.271Z" }, ] +[[package]] +name = "griffe" +version = "1.15.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0d/0c/3a471b6e31951dce2360477420d0a8d1e00dea6cf33b70f3e8c3ab6e28e1/griffe-1.15.0.tar.gz", hash = "sha256:7726e3afd6f298fbc3696e67958803e7ac843c1cfe59734b6251a40cdbfb5eea", size = 424112, upload-time = "2025-11-10T15:03:15.52Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9c/83/3b1d03d36f224edded98e9affd0467630fc09d766c0e56fb1498cbb04a9b/griffe-1.15.0-py3-none-any.whl", hash = "sha256:6f6762661949411031f5fcda9593f586e6ce8340f0ba88921a0f2ef7a81eb9a3", size = 150705, upload-time = "2025-11-10T15:03:13.549Z" }, +] + [[package]] name = "h11" version = "0.16.0" @@ -959,11 +1022,11 @@ wheels = [ [[package]] name = "httpx-sse" -version = "0.4.1" +version = "0.4.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6e/fa/66bd985dd0b7c109a3bcb89272ee0bfb7e2b4d06309ad7b38ff866734b2a/httpx_sse-0.4.1.tar.gz", hash = "sha256:8f44d34414bc7b21bf3602713005c5df4917884f76072479b21f68befa4ea26e", size = 12998, upload-time = "2025-06-24T13:21:05.71Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/60/8f4281fa9bbf3c8034fd54c0e7412e66edbab6bc74c4996bd616f8d0406e/httpx-sse-0.4.0.tar.gz", hash = "sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721", size = 12624, upload-time = "2023-12-22T08:01:21.083Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/25/0a/6269e3473b09aed2dab8aa1a600c70f31f00ae1349bee30658f7e358a159/httpx_sse-0.4.1-py3-none-any.whl", hash = "sha256:cba42174344c3a5b06f255ce65b350880f962d99ead85e776f23c6618a377a37", size = 8054, upload-time = "2025-06-24T13:21:04.772Z" }, + { url = "https://files.pythonhosted.org/packages/e1/9b/a181f281f65d776426002f330c31849b86b31fc9d848db62e16f03ff739f/httpx_sse-0.4.0-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f", size = 7819, upload-time = "2023-12-22T08:01:19.89Z" }, ] [[package]] @@ -1089,6 +1152,103 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, ] +[[package]] +name = "jiter" +version = "0.12.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/45/9d/e0660989c1370e25848bb4c52d061c71837239738ad937e83edca174c273/jiter-0.12.0.tar.gz", hash = "sha256:64dfcd7d5c168b38d3f9f8bba7fc639edb3418abcc74f22fdbe6b8938293f30b", size = 168294, upload-time = "2025-11-09T20:49:23.302Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/91/13cb9505f7be74a933f37da3af22e029f6ba64f5669416cb8b2774bc9682/jiter-0.12.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:e7acbaba9703d5de82a2c98ae6a0f59ab9770ab5af5fa35e43a303aee962cf65", size = 316652, upload-time = "2025-11-09T20:46:41.021Z" }, + { url = "https://files.pythonhosted.org/packages/4e/76/4e9185e5d9bb4e482cf6dec6410d5f78dfeb374cfcecbbe9888d07c52daa/jiter-0.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:364f1a7294c91281260364222f535bc427f56d4de1d8ffd718162d21fbbd602e", size = 319829, upload-time = "2025-11-09T20:46:43.281Z" }, + { url = "https://files.pythonhosted.org/packages/86/af/727de50995d3a153138139f259baae2379d8cb0522c0c00419957bc478a6/jiter-0.12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85ee4d25805d4fb23f0a5167a962ef8e002dbfb29c0989378488e32cf2744b62", size = 350568, upload-time = "2025-11-09T20:46:45.075Z" }, + { url = "https://files.pythonhosted.org/packages/6a/c1/d6e9f4b7a3d5ac63bcbdfddeb50b2dcfbdc512c86cffc008584fdc350233/jiter-0.12.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:796f466b7942107eb889c08433b6e31b9a7ed31daceaecf8af1be26fb26c0ca8", size = 369052, upload-time = "2025-11-09T20:46:46.818Z" }, + { url = "https://files.pythonhosted.org/packages/eb/be/00824cd530f30ed73fa8a4f9f3890a705519e31ccb9e929f1e22062e7c76/jiter-0.12.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:35506cb71f47dba416694e67af996bbdefb8e3608f1f78799c2e1f9058b01ceb", size = 481585, upload-time = "2025-11-09T20:46:48.319Z" }, + { url = "https://files.pythonhosted.org/packages/74/b6/2ad7990dff9504d4b5052eef64aa9574bd03d722dc7edced97aad0d47be7/jiter-0.12.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:726c764a90c9218ec9e4f99a33d6bf5ec169163f2ca0fc21b654e88c2abc0abc", size = 380541, upload-time = "2025-11-09T20:46:49.643Z" }, + { url = "https://files.pythonhosted.org/packages/b5/c7/f3c26ecbc1adbf1db0d6bba99192143d8fe8504729d9594542ecc4445784/jiter-0.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa47810c5565274810b726b0dc86d18dce5fd17b190ebdc3890851d7b2a0e74", size = 364423, upload-time = "2025-11-09T20:46:51.731Z" }, + { url = "https://files.pythonhosted.org/packages/18/51/eac547bf3a2d7f7e556927278e14c56a0604b8cddae75815d5739f65f81d/jiter-0.12.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f8ec0259d3f26c62aed4d73b198c53e316ae11f0f69c8fbe6682c6dcfa0fcce2", size = 389958, upload-time = "2025-11-09T20:46:53.432Z" }, + { url = "https://files.pythonhosted.org/packages/2c/1f/9ca592e67175f2db156cff035e0d817d6004e293ee0c1d73692d38fcb596/jiter-0.12.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:79307d74ea83465b0152fa23e5e297149506435535282f979f18b9033c0bb025", size = 522084, upload-time = "2025-11-09T20:46:54.848Z" }, + { url = "https://files.pythonhosted.org/packages/83/ff/597d9cdc3028f28224f53e1a9d063628e28b7a5601433e3196edda578cdd/jiter-0.12.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cf6e6dd18927121fec86739f1a8906944703941d000f0639f3eb6281cc601dca", size = 513054, upload-time = "2025-11-09T20:46:56.487Z" }, + { url = "https://files.pythonhosted.org/packages/24/6d/1970bce1351bd02e3afcc5f49e4f7ef3dabd7fb688f42be7e8091a5b809a/jiter-0.12.0-cp310-cp310-win32.whl", hash = "sha256:b6ae2aec8217327d872cbfb2c1694489057b9433afce447955763e6ab015b4c4", size = 206368, upload-time = "2025-11-09T20:46:58.638Z" }, + { url = "https://files.pythonhosted.org/packages/e3/6b/eb1eb505b2d86709b59ec06681a2b14a94d0941db091f044b9f0e16badc0/jiter-0.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:c7f49ce90a71e44f7e1aa9e7ec415b9686bbc6a5961e57eab511015e6759bc11", size = 204847, upload-time = "2025-11-09T20:47:00.295Z" }, + { url = "https://files.pythonhosted.org/packages/32/f9/eaca4633486b527ebe7e681c431f529b63fe2709e7c5242fc0f43f77ce63/jiter-0.12.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d8f8a7e317190b2c2d60eb2e8aa835270b008139562d70fe732e1c0020ec53c9", size = 316435, upload-time = "2025-11-09T20:47:02.087Z" }, + { url = "https://files.pythonhosted.org/packages/10/c1/40c9f7c22f5e6ff715f28113ebaba27ab85f9af2660ad6e1dd6425d14c19/jiter-0.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2218228a077e784c6c8f1a8e5d6b8cb1dea62ce25811c356364848554b2056cd", size = 320548, upload-time = "2025-11-09T20:47:03.409Z" }, + { url = "https://files.pythonhosted.org/packages/6b/1b/efbb68fe87e7711b00d2cfd1f26bb4bfc25a10539aefeaa7727329ffb9cb/jiter-0.12.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9354ccaa2982bf2188fd5f57f79f800ef622ec67beb8329903abf6b10da7d423", size = 351915, upload-time = "2025-11-09T20:47:05.171Z" }, + { url = "https://files.pythonhosted.org/packages/15/2d/c06e659888c128ad1e838123d0638f0efad90cc30860cb5f74dd3f2fc0b3/jiter-0.12.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8f2607185ea89b4af9a604d4c7ec40e45d3ad03ee66998b031134bc510232bb7", size = 368966, upload-time = "2025-11-09T20:47:06.508Z" }, + { url = "https://files.pythonhosted.org/packages/6b/20/058db4ae5fb07cf6a4ab2e9b9294416f606d8e467fb74c2184b2a1eeacba/jiter-0.12.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3a585a5e42d25f2e71db5f10b171f5e5ea641d3aa44f7df745aa965606111cc2", size = 482047, upload-time = "2025-11-09T20:47:08.382Z" }, + { url = "https://files.pythonhosted.org/packages/49/bb/dc2b1c122275e1de2eb12905015d61e8316b2f888bdaac34221c301495d6/jiter-0.12.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd9e21d34edff5a663c631f850edcb786719c960ce887a5661e9c828a53a95d9", size = 380835, upload-time = "2025-11-09T20:47:09.81Z" }, + { url = "https://files.pythonhosted.org/packages/23/7d/38f9cd337575349de16da575ee57ddb2d5a64d425c9367f5ef9e4612e32e/jiter-0.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a612534770470686cd5431478dc5a1b660eceb410abade6b1b74e320ca98de6", size = 364587, upload-time = "2025-11-09T20:47:11.529Z" }, + { url = "https://files.pythonhosted.org/packages/f0/a3/b13e8e61e70f0bb06085099c4e2462647f53cc2ca97614f7fedcaa2bb9f3/jiter-0.12.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3985aea37d40a908f887b34d05111e0aae822943796ebf8338877fee2ab67725", size = 390492, upload-time = "2025-11-09T20:47:12.993Z" }, + { url = "https://files.pythonhosted.org/packages/07/71/e0d11422ed027e21422f7bc1883c61deba2d9752b720538430c1deadfbca/jiter-0.12.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b1207af186495f48f72529f8d86671903c8c10127cac6381b11dddc4aaa52df6", size = 522046, upload-time = "2025-11-09T20:47:14.6Z" }, + { url = "https://files.pythonhosted.org/packages/9f/59/b968a9aa7102a8375dbbdfbd2aeebe563c7e5dddf0f47c9ef1588a97e224/jiter-0.12.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ef2fb241de583934c9915a33120ecc06d94aa3381a134570f59eed784e87001e", size = 513392, upload-time = "2025-11-09T20:47:16.011Z" }, + { url = "https://files.pythonhosted.org/packages/ca/e4/7df62002499080dbd61b505c5cb351aa09e9959d176cac2aa8da6f93b13b/jiter-0.12.0-cp311-cp311-win32.whl", hash = "sha256:453b6035672fecce8007465896a25b28a6b59cfe8fbc974b2563a92f5a92a67c", size = 206096, upload-time = "2025-11-09T20:47:17.344Z" }, + { url = "https://files.pythonhosted.org/packages/bb/60/1032b30ae0572196b0de0e87dce3b6c26a1eff71aad5fe43dee3082d32e0/jiter-0.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:ca264b9603973c2ad9435c71a8ec8b49f8f715ab5ba421c85a51cde9887e421f", size = 204899, upload-time = "2025-11-09T20:47:19.365Z" }, + { url = "https://files.pythonhosted.org/packages/49/d5/c145e526fccdb834063fb45c071df78b0cc426bbaf6de38b0781f45d956f/jiter-0.12.0-cp311-cp311-win_arm64.whl", hash = "sha256:cb00ef392e7d684f2754598c02c409f376ddcef857aae796d559e6cacc2d78a5", size = 188070, upload-time = "2025-11-09T20:47:20.75Z" }, + { url = "https://files.pythonhosted.org/packages/92/c9/5b9f7b4983f1b542c64e84165075335e8a236fa9e2ea03a0c79780062be8/jiter-0.12.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:305e061fa82f4680607a775b2e8e0bcb071cd2205ac38e6ef48c8dd5ebe1cf37", size = 314449, upload-time = "2025-11-09T20:47:22.999Z" }, + { url = "https://files.pythonhosted.org/packages/98/6e/e8efa0e78de00db0aee82c0cf9e8b3f2027efd7f8a71f859d8f4be8e98ef/jiter-0.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5c1860627048e302a528333c9307c818c547f214d8659b0705d2195e1a94b274", size = 319855, upload-time = "2025-11-09T20:47:24.779Z" }, + { url = "https://files.pythonhosted.org/packages/20/26/894cd88e60b5d58af53bec5c6759d1292bd0b37a8b5f60f07abf7a63ae5f/jiter-0.12.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df37577a4f8408f7e0ec3205d2a8f87672af8f17008358063a4d6425b6081ce3", size = 350171, upload-time = "2025-11-09T20:47:26.469Z" }, + { url = "https://files.pythonhosted.org/packages/f5/27/a7b818b9979ac31b3763d25f3653ec3a954044d5e9f5d87f2f247d679fd1/jiter-0.12.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:75fdd787356c1c13a4f40b43c2156276ef7a71eb487d98472476476d803fb2cf", size = 365590, upload-time = "2025-11-09T20:47:27.918Z" }, + { url = "https://files.pythonhosted.org/packages/ba/7e/e46195801a97673a83746170b17984aa8ac4a455746354516d02ca5541b4/jiter-0.12.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1eb5db8d9c65b112aacf14fcd0faae9913d07a8afea5ed06ccdd12b724e966a1", size = 479462, upload-time = "2025-11-09T20:47:29.654Z" }, + { url = "https://files.pythonhosted.org/packages/ca/75/f833bfb009ab4bd11b1c9406d333e3b4357709ed0570bb48c7c06d78c7dd/jiter-0.12.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:73c568cc27c473f82480abc15d1301adf333a7ea4f2e813d6a2c7d8b6ba8d0df", size = 378983, upload-time = "2025-11-09T20:47:31.026Z" }, + { url = "https://files.pythonhosted.org/packages/71/b3/7a69d77943cc837d30165643db753471aff5df39692d598da880a6e51c24/jiter-0.12.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4321e8a3d868919bcb1abb1db550d41f2b5b326f72df29e53b2df8b006eb9403", size = 361328, upload-time = "2025-11-09T20:47:33.286Z" }, + { url = "https://files.pythonhosted.org/packages/b0/ac/a78f90caf48d65ba70d8c6efc6f23150bc39dc3389d65bbec2a95c7bc628/jiter-0.12.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0a51bad79f8cc9cac2b4b705039f814049142e0050f30d91695a2d9a6611f126", size = 386740, upload-time = "2025-11-09T20:47:34.703Z" }, + { url = "https://files.pythonhosted.org/packages/39/b6/5d31c2cc8e1b6a6bcf3c5721e4ca0a3633d1ab4754b09bc7084f6c4f5327/jiter-0.12.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:2a67b678f6a5f1dd6c36d642d7db83e456bc8b104788262aaefc11a22339f5a9", size = 520875, upload-time = "2025-11-09T20:47:36.058Z" }, + { url = "https://files.pythonhosted.org/packages/30/b5/4df540fae4e9f68c54b8dab004bd8c943a752f0b00efd6e7d64aa3850339/jiter-0.12.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efe1a211fe1fd14762adea941e3cfd6c611a136e28da6c39272dbb7a1bbe6a86", size = 511457, upload-time = "2025-11-09T20:47:37.932Z" }, + { url = "https://files.pythonhosted.org/packages/07/65/86b74010e450a1a77b2c1aabb91d4a91dd3cd5afce99f34d75fd1ac64b19/jiter-0.12.0-cp312-cp312-win32.whl", hash = "sha256:d779d97c834b4278276ec703dc3fc1735fca50af63eb7262f05bdb4e62203d44", size = 204546, upload-time = "2025-11-09T20:47:40.47Z" }, + { url = "https://files.pythonhosted.org/packages/1c/c7/6659f537f9562d963488e3e55573498a442503ced01f7e169e96a6110383/jiter-0.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:e8269062060212b373316fe69236096aaf4c49022d267c6736eebd66bbbc60bb", size = 205196, upload-time = "2025-11-09T20:47:41.794Z" }, + { url = "https://files.pythonhosted.org/packages/21/f4/935304f5169edadfec7f9c01eacbce4c90bb9a82035ac1de1f3bd2d40be6/jiter-0.12.0-cp312-cp312-win_arm64.whl", hash = "sha256:06cb970936c65de926d648af0ed3d21857f026b1cf5525cb2947aa5e01e05789", size = 186100, upload-time = "2025-11-09T20:47:43.007Z" }, + { url = "https://files.pythonhosted.org/packages/3d/a6/97209693b177716e22576ee1161674d1d58029eb178e01866a0422b69224/jiter-0.12.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:6cc49d5130a14b732e0612bc76ae8db3b49898732223ef8b7599aa8d9810683e", size = 313658, upload-time = "2025-11-09T20:47:44.424Z" }, + { url = "https://files.pythonhosted.org/packages/06/4d/125c5c1537c7d8ee73ad3d530a442d6c619714b95027143f1b61c0b4dfe0/jiter-0.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:37f27a32ce36364d2fa4f7fdc507279db604d27d239ea2e044c8f148410defe1", size = 318605, upload-time = "2025-11-09T20:47:45.973Z" }, + { url = "https://files.pythonhosted.org/packages/99/bf/a840b89847885064c41a5f52de6e312e91fa84a520848ee56c97e4fa0205/jiter-0.12.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbc0944aa3d4b4773e348cda635252824a78f4ba44328e042ef1ff3f6080d1cf", size = 349803, upload-time = "2025-11-09T20:47:47.535Z" }, + { url = "https://files.pythonhosted.org/packages/8a/88/e63441c28e0db50e305ae23e19c1d8fae012d78ed55365da392c1f34b09c/jiter-0.12.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:da25c62d4ee1ffbacb97fac6dfe4dcd6759ebdc9015991e92a6eae5816287f44", size = 365120, upload-time = "2025-11-09T20:47:49.284Z" }, + { url = "https://files.pythonhosted.org/packages/0a/7c/49b02714af4343970eb8aca63396bc1c82fa01197dbb1e9b0d274b550d4e/jiter-0.12.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:048485c654b838140b007390b8182ba9774621103bd4d77c9c3f6f117474ba45", size = 479918, upload-time = "2025-11-09T20:47:50.807Z" }, + { url = "https://files.pythonhosted.org/packages/69/ba/0a809817fdd5a1db80490b9150645f3aae16afad166960bcd562be194f3b/jiter-0.12.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:635e737fbb7315bef0037c19b88b799143d2d7d3507e61a76751025226b3ac87", size = 379008, upload-time = "2025-11-09T20:47:52.211Z" }, + { url = "https://files.pythonhosted.org/packages/5f/c3/c9fc0232e736c8877d9e6d83d6eeb0ba4e90c6c073835cc2e8f73fdeef51/jiter-0.12.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e017c417b1ebda911bd13b1e40612704b1f5420e30695112efdbed8a4b389ed", size = 361785, upload-time = "2025-11-09T20:47:53.512Z" }, + { url = "https://files.pythonhosted.org/packages/96/61/61f69b7e442e97ca6cd53086ddc1cf59fb830549bc72c0a293713a60c525/jiter-0.12.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:89b0bfb8b2bf2351fba36bb211ef8bfceba73ef58e7f0c68fb67b5a2795ca2f9", size = 386108, upload-time = "2025-11-09T20:47:54.893Z" }, + { url = "https://files.pythonhosted.org/packages/e9/2e/76bb3332f28550c8f1eba3bf6e5efe211efda0ddbbaf24976bc7078d42a5/jiter-0.12.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:f5aa5427a629a824a543672778c9ce0c5e556550d1569bb6ea28a85015287626", size = 519937, upload-time = "2025-11-09T20:47:56.253Z" }, + { url = "https://files.pythonhosted.org/packages/84/d6/fa96efa87dc8bff2094fb947f51f66368fa56d8d4fc9e77b25d7fbb23375/jiter-0.12.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ed53b3d6acbcb0fd0b90f20c7cb3b24c357fe82a3518934d4edfa8c6898e498c", size = 510853, upload-time = "2025-11-09T20:47:58.32Z" }, + { url = "https://files.pythonhosted.org/packages/8a/28/93f67fdb4d5904a708119a6ab58a8f1ec226ff10a94a282e0215402a8462/jiter-0.12.0-cp313-cp313-win32.whl", hash = "sha256:4747de73d6b8c78f2e253a2787930f4fffc68da7fa319739f57437f95963c4de", size = 204699, upload-time = "2025-11-09T20:47:59.686Z" }, + { url = "https://files.pythonhosted.org/packages/c4/1f/30b0eb087045a0abe2a5c9c0c0c8da110875a1d3be83afd4a9a4e548be3c/jiter-0.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:e25012eb0c456fcc13354255d0338cd5397cce26c77b2832b3c4e2e255ea5d9a", size = 204258, upload-time = "2025-11-09T20:48:01.01Z" }, + { url = "https://files.pythonhosted.org/packages/2c/f4/2b4daf99b96bce6fc47971890b14b2a36aef88d7beb9f057fafa032c6141/jiter-0.12.0-cp313-cp313-win_arm64.whl", hash = "sha256:c97b92c54fe6110138c872add030a1f99aea2401ddcdaa21edf74705a646dd60", size = 185503, upload-time = "2025-11-09T20:48:02.35Z" }, + { url = "https://files.pythonhosted.org/packages/39/ca/67bb15a7061d6fe20b9b2a2fd783e296a1e0f93468252c093481a2f00efa/jiter-0.12.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:53839b35a38f56b8be26a7851a48b89bc47e5d88e900929df10ed93b95fea3d6", size = 317965, upload-time = "2025-11-09T20:48:03.783Z" }, + { url = "https://files.pythonhosted.org/packages/18/af/1788031cd22e29c3b14bc6ca80b16a39a0b10e611367ffd480c06a259831/jiter-0.12.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94f669548e55c91ab47fef8bddd9c954dab1938644e715ea49d7e117015110a4", size = 345831, upload-time = "2025-11-09T20:48:05.55Z" }, + { url = "https://files.pythonhosted.org/packages/05/17/710bf8472d1dff0d3caf4ced6031060091c1320f84ee7d5dcbed1f352417/jiter-0.12.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:351d54f2b09a41600ffea43d081522d792e81dcfb915f6d2d242744c1cc48beb", size = 361272, upload-time = "2025-11-09T20:48:06.951Z" }, + { url = "https://files.pythonhosted.org/packages/fb/f1/1dcc4618b59761fef92d10bcbb0b038b5160be653b003651566a185f1a5c/jiter-0.12.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2a5e90604620f94bf62264e7c2c038704d38217b7465b863896c6d7c902b06c7", size = 204604, upload-time = "2025-11-09T20:48:08.328Z" }, + { url = "https://files.pythonhosted.org/packages/d9/32/63cb1d9f1c5c6632a783c0052cde9ef7ba82688f7065e2f0d5f10a7e3edb/jiter-0.12.0-cp313-cp313t-win_arm64.whl", hash = "sha256:88ef757017e78d2860f96250f9393b7b577b06a956ad102c29c8237554380db3", size = 185628, upload-time = "2025-11-09T20:48:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/a8/99/45c9f0dbe4a1416b2b9a8a6d1236459540f43d7fb8883cff769a8db0612d/jiter-0.12.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:c46d927acd09c67a9fb1416df45c5a04c27e83aae969267e98fba35b74e99525", size = 312478, upload-time = "2025-11-09T20:48:10.898Z" }, + { url = "https://files.pythonhosted.org/packages/4c/a7/54ae75613ba9e0f55fcb0bc5d1f807823b5167cc944e9333ff322e9f07dd/jiter-0.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:774ff60b27a84a85b27b88cd5583899c59940bcc126caca97eb2a9df6aa00c49", size = 318706, upload-time = "2025-11-09T20:48:12.266Z" }, + { url = "https://files.pythonhosted.org/packages/59/31/2aa241ad2c10774baf6c37f8b8e1f39c07db358f1329f4eb40eba179c2a2/jiter-0.12.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5433fab222fb072237df3f637d01b81f040a07dcac1cb4a5c75c7aa9ed0bef1", size = 351894, upload-time = "2025-11-09T20:48:13.673Z" }, + { url = "https://files.pythonhosted.org/packages/54/4f/0f2759522719133a9042781b18cc94e335b6d290f5e2d3e6899d6af933e3/jiter-0.12.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f8c593c6e71c07866ec6bfb790e202a833eeec885022296aff6b9e0b92d6a70e", size = 365714, upload-time = "2025-11-09T20:48:15.083Z" }, + { url = "https://files.pythonhosted.org/packages/dc/6f/806b895f476582c62a2f52c453151edd8a0fde5411b0497baaa41018e878/jiter-0.12.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:90d32894d4c6877a87ae00c6b915b609406819dce8bc0d4e962e4de2784e567e", size = 478989, upload-time = "2025-11-09T20:48:16.706Z" }, + { url = "https://files.pythonhosted.org/packages/86/6c/012d894dc6e1033acd8db2b8346add33e413ec1c7c002598915278a37f79/jiter-0.12.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:798e46eed9eb10c3adbbacbd3bdb5ecd4cf7064e453d00dbef08802dae6937ff", size = 378615, upload-time = "2025-11-09T20:48:18.614Z" }, + { url = "https://files.pythonhosted.org/packages/87/30/d718d599f6700163e28e2c71c0bbaf6dace692e7df2592fd793ac9276717/jiter-0.12.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3f1368f0a6719ea80013a4eb90ba72e75d7ea67cfc7846db2ca504f3df0169a", size = 364745, upload-time = "2025-11-09T20:48:20.117Z" }, + { url = "https://files.pythonhosted.org/packages/8f/85/315b45ce4b6ddc7d7fceca24068543b02bdc8782942f4ee49d652e2cc89f/jiter-0.12.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:65f04a9d0b4406f7e51279710b27484af411896246200e461d80d3ba0caa901a", size = 386502, upload-time = "2025-11-09T20:48:21.543Z" }, + { url = "https://files.pythonhosted.org/packages/74/0b/ce0434fb40c5b24b368fe81b17074d2840748b4952256bab451b72290a49/jiter-0.12.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:fd990541982a24281d12b67a335e44f117e4c6cbad3c3b75c7dea68bf4ce3a67", size = 519845, upload-time = "2025-11-09T20:48:22.964Z" }, + { url = "https://files.pythonhosted.org/packages/e8/a3/7a7a4488ba052767846b9c916d208b3ed114e3eb670ee984e4c565b9cf0d/jiter-0.12.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:b111b0e9152fa7df870ecaebb0bd30240d9f7fff1f2003bcb4ed0f519941820b", size = 510701, upload-time = "2025-11-09T20:48:24.483Z" }, + { url = "https://files.pythonhosted.org/packages/c3/16/052ffbf9d0467b70af24e30f91e0579e13ded0c17bb4a8eb2aed3cb60131/jiter-0.12.0-cp314-cp314-win32.whl", hash = "sha256:a78befb9cc0a45b5a5a0d537b06f8544c2ebb60d19d02c41ff15da28a9e22d42", size = 205029, upload-time = "2025-11-09T20:48:25.749Z" }, + { url = "https://files.pythonhosted.org/packages/e4/18/3cf1f3f0ccc789f76b9a754bdb7a6977e5d1d671ee97a9e14f7eb728d80e/jiter-0.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:e1fe01c082f6aafbe5c8faf0ff074f38dfb911d53f07ec333ca03f8f6226debf", size = 204960, upload-time = "2025-11-09T20:48:27.415Z" }, + { url = "https://files.pythonhosted.org/packages/02/68/736821e52ecfdeeb0f024b8ab01b5a229f6b9293bbdb444c27efade50b0f/jiter-0.12.0-cp314-cp314-win_arm64.whl", hash = "sha256:d72f3b5a432a4c546ea4bedc84cce0c3404874f1d1676260b9c7f048a9855451", size = 185529, upload-time = "2025-11-09T20:48:29.125Z" }, + { url = "https://files.pythonhosted.org/packages/30/61/12ed8ee7a643cce29ac97c2281f9ce3956eb76b037e88d290f4ed0d41480/jiter-0.12.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e6ded41aeba3603f9728ed2b6196e4df875348ab97b28fc8afff115ed42ba7a7", size = 318974, upload-time = "2025-11-09T20:48:30.87Z" }, + { url = "https://files.pythonhosted.org/packages/2d/c6/f3041ede6d0ed5e0e79ff0de4c8f14f401bbf196f2ef3971cdbe5fd08d1d/jiter-0.12.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a947920902420a6ada6ad51892082521978e9dd44a802663b001436e4b771684", size = 345932, upload-time = "2025-11-09T20:48:32.658Z" }, + { url = "https://files.pythonhosted.org/packages/d5/5d/4d94835889edd01ad0e2dbfc05f7bdfaed46292e7b504a6ac7839aa00edb/jiter-0.12.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:add5e227e0554d3a52cf390a7635edaffdf4f8fce4fdbcef3cc2055bb396a30c", size = 367243, upload-time = "2025-11-09T20:48:34.093Z" }, + { url = "https://files.pythonhosted.org/packages/fd/76/0051b0ac2816253a99d27baf3dda198663aff882fa6ea7deeb94046da24e/jiter-0.12.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f9b1cda8fcb736250d7e8711d4580ebf004a46771432be0ae4796944b5dfa5d", size = 479315, upload-time = "2025-11-09T20:48:35.507Z" }, + { url = "https://files.pythonhosted.org/packages/70/ae/83f793acd68e5cb24e483f44f482a1a15601848b9b6f199dacb970098f77/jiter-0.12.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:deeb12a2223fe0135c7ff1356a143d57f95bbf1f4a66584f1fc74df21d86b993", size = 380714, upload-time = "2025-11-09T20:48:40.014Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/4808a88338ad2c228b1126b93fcd8ba145e919e886fe910d578230dabe3b/jiter-0.12.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c596cc0f4cb574877550ce4ecd51f8037469146addd676d7c1a30ebe6391923f", size = 365168, upload-time = "2025-11-09T20:48:41.462Z" }, + { url = "https://files.pythonhosted.org/packages/0c/d4/04619a9e8095b42aef436b5aeb4c0282b4ff1b27d1db1508df9f5dc82750/jiter-0.12.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5ab4c823b216a4aeab3fdbf579c5843165756bd9ad87cc6b1c65919c4715f783", size = 387893, upload-time = "2025-11-09T20:48:42.921Z" }, + { url = "https://files.pythonhosted.org/packages/17/ea/d3c7e62e4546fdc39197fa4a4315a563a89b95b6d54c0d25373842a59cbe/jiter-0.12.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:e427eee51149edf962203ff8db75a7514ab89be5cb623fb9cea1f20b54f1107b", size = 520828, upload-time = "2025-11-09T20:48:44.278Z" }, + { url = "https://files.pythonhosted.org/packages/cc/0b/c6d3562a03fd767e31cb119d9041ea7958c3c80cb3d753eafb19b3b18349/jiter-0.12.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:edb868841f84c111255ba5e80339d386d937ec1fdce419518ce1bd9370fac5b6", size = 511009, upload-time = "2025-11-09T20:48:45.726Z" }, + { url = "https://files.pythonhosted.org/packages/aa/51/2cb4468b3448a8385ebcd15059d325c9ce67df4e2758d133ab9442b19834/jiter-0.12.0-cp314-cp314t-win32.whl", hash = "sha256:8bbcfe2791dfdb7c5e48baf646d37a6a3dcb5a97a032017741dea9f817dca183", size = 205110, upload-time = "2025-11-09T20:48:47.033Z" }, + { url = "https://files.pythonhosted.org/packages/b2/c5/ae5ec83dec9c2d1af805fd5fe8f74ebded9c8670c5210ec7820ce0dbeb1e/jiter-0.12.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2fa940963bf02e1d8226027ef461e36af472dea85d36054ff835aeed944dd873", size = 205223, upload-time = "2025-11-09T20:48:49.076Z" }, + { url = "https://files.pythonhosted.org/packages/97/9a/3c5391907277f0e55195550cf3fa8e293ae9ee0c00fb402fec1e38c0c82f/jiter-0.12.0-cp314-cp314t-win_arm64.whl", hash = "sha256:506c9708dd29b27288f9f8f1140c3cb0e3d8ddb045956d7757b1fa0e0f39a473", size = 185564, upload-time = "2025-11-09T20:48:50.376Z" }, + { url = "https://files.pythonhosted.org/packages/fe/54/5339ef1ecaa881c6948669956567a64d2670941925f245c434f494ffb0e5/jiter-0.12.0-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:4739a4657179ebf08f85914ce50332495811004cc1747852e8b2041ed2aab9b8", size = 311144, upload-time = "2025-11-09T20:49:10.503Z" }, + { url = "https://files.pythonhosted.org/packages/27/74/3446c652bffbd5e81ab354e388b1b5fc1d20daac34ee0ed11ff096b1b01a/jiter-0.12.0-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:41da8def934bf7bec16cb24bd33c0ca62126d2d45d81d17b864bd5ad721393c3", size = 305877, upload-time = "2025-11-09T20:49:12.269Z" }, + { url = "https://files.pythonhosted.org/packages/a1/f4/ed76ef9043450f57aac2d4fbeb27175aa0eb9c38f833be6ef6379b3b9a86/jiter-0.12.0-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c44ee814f499c082e69872d426b624987dbc5943ab06e9bbaa4f81989fdb79e", size = 340419, upload-time = "2025-11-09T20:49:13.803Z" }, + { url = "https://files.pythonhosted.org/packages/21/01/857d4608f5edb0664aa791a3d45702e1a5bcfff9934da74035e7b9803846/jiter-0.12.0-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd2097de91cf03eaa27b3cbdb969addf83f0179c6afc41bbc4513705e013c65d", size = 347212, upload-time = "2025-11-09T20:49:15.643Z" }, + { url = "https://files.pythonhosted.org/packages/cb/f5/12efb8ada5f5c9edc1d4555fe383c1fb2eac05ac5859258a72d61981d999/jiter-0.12.0-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:e8547883d7b96ef2e5fe22b88f8a4c8725a56e7f4abafff20fd5272d634c7ecb", size = 309974, upload-time = "2025-11-09T20:49:17.187Z" }, + { url = "https://files.pythonhosted.org/packages/85/15/d6eb3b770f6a0d332675141ab3962fd4a7c270ede3515d9f3583e1d28276/jiter-0.12.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:89163163c0934854a668ed783a2546a0617f71706a2551a4a0666d91ab365d6b", size = 304233, upload-time = "2025-11-09T20:49:18.734Z" }, + { url = "https://files.pythonhosted.org/packages/8c/3e/e7e06743294eea2cf02ced6aa0ff2ad237367394e37a0e2b4a1108c67a36/jiter-0.12.0-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d96b264ab7d34bbb2312dedc47ce07cd53f06835eacbc16dde3761f47c3a9e7f", size = 338537, upload-time = "2025-11-09T20:49:20.317Z" }, + { url = "https://files.pythonhosted.org/packages/2f/9c/6753e6522b8d0ef07d3a3d239426669e984fb0eba15a315cdbc1253904e4/jiter-0.12.0-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c24e864cb30ab82311c6425655b0cdab0a98c5d973b065c66a3f020740c2324c", size = 346110, upload-time = "2025-11-09T20:49:21.817Z" }, +] + [[package]] name = "jsonschema" version = "4.24.0" @@ -1152,6 +1312,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a1/30/9ec597c962c5249ebd5c580386e4b5f2884cd943af42634291ee3b406415/leather-0.4.0-py2.py3-none-any.whl", hash = "sha256:18290bc93749ae39039af5e31e871fcfad74d26c4c3ea28ea4f681f4571b3a2b", size = 30256, upload-time = "2024-02-23T22:03:34.75Z" }, ] +[[package]] +name = "logfire-api" +version = "4.15.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/e6/3895c0ebf9f6a7acee04a816a569ca871c3d3048fdbd6b2a041f980abc54/logfire_api-4.15.1.tar.gz", hash = "sha256:3fbafc5593f4a16a038a3d23c67a7a7ee9da8be9e3b148fa73069d32e1ed4e8e", size = 57614, upload-time = "2025-11-20T15:52:17.019Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f3/2b/851e78a60b85e8e8e8c6ebb9928f8e883df0340a93e34960ed9f0a41fa82/logfire_api-4.15.1-py3-none-any.whl", hash = "sha256:a88b5c4b6e4acbf6f35a3e992a63f271cf2797aefd21e1cfc93d52b21ade65f6", size = 95031, upload-time = "2025-11-20T15:52:14.433Z" }, +] + [[package]] name = "makefun" version = "1.16.0" @@ -1461,6 +1630,7 @@ dependencies = [ { name = "pandas" }, { name = "posthog" }, { name = "psutil" }, + { name = "pydantic-ai-slim", extra = ["anthropic", "openai"] }, { name = "pyyaml" }, { name = "starlette" }, { name = "uvicorn", extra = ["standard"] }, @@ -1550,6 +1720,7 @@ requires-dist = [ { name = "pandas-stubs", marker = "extra == 'dev'" }, { name = "posthog", specifier = ">=3.0.0" }, { name = "psutil", specifier = ">=5.9.0" }, + { name = "pydantic-ai-slim", extras = ["anthropic", "openai"], specifier = ">=1.25.0" }, { name = "pytest", marker = "extra == 'all'" }, { name = "pytest", marker = "extra == 'dev'" }, { name = "pytest-asyncio", marker = "extra == 'all'" }, @@ -1844,6 +2015,25 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c7/1d/91becb8fa0e417c172a5721c06dc403ad2abbbc766e9a8bdeff46bdea6ba/onepassword_sdk-0.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:4a77fa3fdbad03738faf2703387256f53c2c86329bcd9f19ee5725a2075db77b", size = 5506878, upload-time = "2025-06-11T17:24:33.339Z" }, ] +[[package]] +name = "openai" +version = "2.8.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "distro" }, + { name = "httpx" }, + { name = "jiter" }, + { name = "pydantic" }, + { name = "sniffio" }, + { name = "tqdm" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d5/e4/42591e356f1d53c568418dc7e30dcda7be31dd5a4d570bca22acb0525862/openai-2.8.1.tar.gz", hash = "sha256:cb1b79eef6e809f6da326a7ef6038719e35aa944c42d081807bfa1be8060f15f", size = 602490, upload-time = "2025-11-17T22:39:59.549Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/55/4f/dbc0c124c40cb390508a82770fb9f6e3ed162560181a85089191a851c59a/openai-2.8.1-py3-none-any.whl", hash = "sha256:c6c3b5a04994734386e8dad3c00a393f56d3b68a27cd2e8acae91a59e4122463", size = 1022688, upload-time = "2025-11-17T22:39:57.675Z" }, +] + [[package]] name = "opentelemetry-api" version = "1.36.0" @@ -2272,6 +2462,32 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6a/c0/ec2b1c8712ca690e5d61979dee872603e92b8a32f94cc1b72d53beab008a/pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b", size = 444782, upload-time = "2025-06-14T08:33:14.905Z" }, ] +[[package]] +name = "pydantic-ai-slim" +version = "1.25.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "genai-prices" }, + { name = "griffe" }, + { name = "httpx" }, + { name = "opentelemetry-api" }, + { name = "pydantic" }, + { name = "pydantic-graph" }, + { name = "typing-inspection" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/b0/7e3de325bf45d7fbf798ec7c74894f18a6fb4bebb8f250936dd26015d4cf/pydantic_ai_slim-1.25.0-py3-none-any.whl", hash = "sha256:87fd01472939862ffba92dc7f93ae2cb47d6a417c0278846dd24ea7f5164f9a8", size = 420416, upload-time = "2025-11-28T05:04:33.012Z" }, +] + +[package.optional-dependencies] +anthropic = [ + { name = "anthropic" }, +] +openai = [ + { name = "openai" }, +] + [[package]] name = "pydantic-core" version = "2.33.2" @@ -2359,6 +2575,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/32/56/8a7ca5d2cd2cda1d245d34b1c9a942920a718082ae8e54e5f3e5a58b7add/pydantic_core-2.33.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:329467cecfb529c925cf2bbd4d60d2c509bc2fb52a20c1045bf09bb70971a9c1", size = 2066757, upload-time = "2025-04-23T18:33:30.645Z" }, ] +[[package]] +name = "pydantic-graph" +version = "1.25.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "httpx" }, + { name = "logfire-api" }, + { name = "pydantic" }, + { name = "typing-inspection" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/3e/c6f5d0a1a22e8ad968c7fb9ea443a1310f7878a6d0a7682526ee210684c5/pydantic_graph-1.25.0-py3-none-any.whl", hash = "sha256:30f0890729cae49f6967297815d4e226557001c650ffe1500fe7ea517561bc2b", size = 72262, upload-time = "2025-11-28T05:04:36.83Z" }, +] + [[package]] name = "pydantic-settings" version = "2.10.1" @@ -2946,6 +3176,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" }, ] +[[package]] +name = "tqdm" +version = "4.67.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737, upload-time = "2024-11-24T20:12:22.481Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload-time = "2024-11-24T20:12:19.698Z" }, +] + [[package]] name = "twine" version = "6.1.0" From 709dc6aa9581a3c72099d618449d8e06ed161385 Mon Sep 17 00:00:00 2001 From: Alex Zerntev Date: Sat, 29 Nov 2025 09:47:51 +0200 Subject: [PATCH 13/26] fixed the bug --- src/mxcp/sdk/evals/executor.py | 22 +++++++++++++++++++--- tests/sdk/evals/test_executor.py | 30 +++++++++++++++++++++++++++++- 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/src/mxcp/sdk/evals/executor.py b/src/mxcp/sdk/evals/executor.py index 04f74caf..950fb00c 100644 --- a/src/mxcp/sdk/evals/executor.py +++ b/src/mxcp/sdk/evals/executor.py @@ -102,6 +102,16 @@ def _make_tool(tool_def: ToolDefinition) -> Tool: ) async def _fn(**kwargs: Any) -> Any: + if max_turns is not None and len(history) >= max_turns: + record = ToolCallRecord( + id=None, + tool=tool_def.name, + arguments=kwargs, + error=f"Maximum tool calls exceeded ({max_turns})", + ) + history.append(record) + raise RuntimeError(record.error) + record = ToolCallRecord(id=None, tool=tool_def.name, arguments=kwargs) try: validated = ( @@ -139,9 +149,15 @@ async def _prepare( model=model_string, instructions=self.system_prompt, tools=agent_tools ) - agent_run = await agent.run(prompt, deps=user_context, model_settings=self._model_settings) - answer = getattr(agent_run, "output", "") - return AgentResult(answer=str(answer), tool_calls=history) + try: + agent_run = await agent.run( + prompt, deps=user_context, model_settings=self._model_settings + ) + answer = getattr(agent_run, "output", "") + return AgentResult(answer=str(answer), tool_calls=history) + except RuntimeError as exc: + logger.error("LLM execution aborted: %s", exc) + return AgentResult(answer="", tool_calls=history) async def evaluate_expected_answer(self, answer: str, expected_answer: str) -> dict[str, str]: """Ask the model to grade an answer against an expected value.""" diff --git a/tests/sdk/evals/test_executor.py b/tests/sdk/evals/test_executor.py index 61cdaa10..5dabe6f4 100644 --- a/tests/sdk/evals/test_executor.py +++ b/tests/sdk/evals/test_executor.py @@ -2,6 +2,7 @@ from typing import Any import pytest +from pydantic_ai import ModelSettings from mxcp.sdk.auth import UserContext from mxcp.sdk.evals import ParameterDefinition, ToolDefinition @@ -69,7 +70,7 @@ def make_executor() -> LLMExecutor: executor = LLMExecutor( "claude-test", "anthropic", - {}, + ModelSettings(), tools, tool_executor, provider_config=ProviderConfig(api_key="key", base_url="https://api.anthropic.com"), @@ -141,3 +142,30 @@ def test_expected_answer_grading() -> None: def test_temporary_env_sets_and_restores() -> None: pytest.skip("Environment injection removed; no temporary env to test.") + + +def test_max_turns_limits_tool_calls() -> None: + class MultiCallAgent: + def __init__(self, tools: list[Any]) -> None: + self.tools = tools + + async def run( + self, _prompt: str, deps: Any | None = None, model_settings: Any | None = None + ) -> FakeRun: + for _ in range(2): + for tool in self.tools: + fn = getattr(tool, "_mxcp_callable", None) + if fn: + if asyncio.iscoroutinefunction(fn): + await fn() + else: + fn() + return FakeRun("done") + + executor = make_executor() + executor._agent_cls = lambda **kwargs: MultiCallAgent(kwargs["tools"]) + + result = asyncio.run(executor.execute_prompt("Weather?", max_turns=1)) + + assert len(result.tool_calls) == 2 + assert result.tool_calls[-1].error From 48ec3f91a23ebed67966632109490cf325b042da Mon Sep 17 00:00:00 2001 From: Alex Zerntev Date: Sat, 29 Nov 2025 11:37:39 +0200 Subject: [PATCH 14/26] added expected_anser model --- docs/guides/configuration.md | 13 ++ docs/guides/quality.md | 26 ++++ src/mxcp/server/definitions/evals/models.py | 1 + src/mxcp/server/interfaces/cli/evals.py | 26 ++++ src/mxcp/server/services/evals/service.py | 134 ++++++++++++++++++-- tests/server/test_evals_service.py | 30 +++++ 6 files changed, 219 insertions(+), 11 deletions(-) create mode 100644 tests/server/test_evals_service.py diff --git a/docs/guides/configuration.md b/docs/guides/configuration.md index 733a4c5c..cab214be 100644 --- a/docs/guides/configuration.md +++ b/docs/guides/configuration.md @@ -658,6 +658,19 @@ models: timeout: 30 options: thinking: false + +# Using OpenAI Responses API with reasoning +# Set api: responses to route through the Responses endpoint (e.g., for reasoning) +models: + default: "gpt-5" + models: + gpt-5: + type: "openai" + api_key: "${OPENAI_API_KEY}" + options: + api: "responses" # Choices: responses (for OpenAI Responses API), chat (default) + reasoning: + effort: "medium" # Passed via extra_body to the provider ``` For more information on using evals, see the [LLM Evaluation section](quality.md#llm-evaluation-evals) in the Quality & Testing Guide. diff --git a/docs/guides/quality.md b/docs/guides/quality.md index c1ff9b53..f35010e9 100644 --- a/docs/guides/quality.md +++ b/docs/guides/quality.md @@ -937,6 +937,32 @@ models: timeout: 45 options: reasoning: "fast" # forwarded to the provider as-is + +# Example: use a faster model just for grading expected answers +mxcp: 1 +suite: faq_checks +model: gpt-4o # primary model used to answer +expected_answer_model: gpt-4o-mini # model used only for grading expected answers +tests: + - name: expected_answer_grading + prompt: "What are your support hours?" + assertions: + expected_answer: "Our support team is available Monday to Friday, 9am-5pm local time." + # expected_answer_model is useful when: + # - Your main model is slow/expensive, but grading can use a lighter model + # - You want deterministic, faster grading for many evals + +# OpenAI Responses API example (reasoning) +models: + default: "gpt-5" + models: + gpt-5: + type: "openai" + api_key: "${OPENAI_API_KEY}" + options: + api: "responses" # Choices: responses (Responses API) or chat (default) + reasoning: + effort: "medium" ``` ### Running Evals diff --git a/src/mxcp/server/definitions/evals/models.py b/src/mxcp/server/definitions/evals/models.py index f988a9b0..a50d6b46 100644 --- a/src/mxcp/server/definitions/evals/models.py +++ b/src/mxcp/server/definitions/evals/models.py @@ -51,6 +51,7 @@ class EvalSuiteModel(EvalBaseModel): suite: str description: str | None = None model: str | None = None + expected_answer_model: str | None = None tests: list[EvalTestModel] @field_validator("suite") diff --git a/src/mxcp/server/interfaces/cli/evals.py b/src/mxcp/server/interfaces/cli/evals.py index 2508f344..b9d9f99a 100644 --- a/src/mxcp/server/interfaces/cli/evals.py +++ b/src/mxcp/server/interfaces/cli/evals.py @@ -343,6 +343,28 @@ async def _evals_impl( # Run evals start_time = time.time() + + # Stateful progress renderer that overwrites the same line. + _lines: dict[str, str] = {} + _order: list[str] = [] + _lines_printed = 0 + + def _render_progress() -> None: + nonlocal _lines_printed + # Move cursor up to rewrite previous lines + if _lines_printed: + click.echo(f"\033[{_lines_printed}A\r", nl=False) + for key in _order: + line = _lines.get(key, "") + click.echo("\033[2K\r" + line) + _lines_printed = len(_order) + + def _progress(key: str, msg: str) -> None: + if key not in _order: + _order.append(key) + _lines[key] = msg + _render_progress() + if suite_name: results = await run_eval_suite( suite_name, @@ -351,6 +373,7 @@ async def _evals_impl( profile, cli_user_context=cli_user_context, override_model=model, + progress_callback=_progress, ) else: results = await run_all_evals( @@ -359,6 +382,7 @@ async def _evals_impl( profile, cli_user_context=cli_user_context, override_model=model, + progress_callback=_progress, ) elapsed_time = time.time() - start_time results["elapsed_time"] = elapsed_time @@ -366,6 +390,8 @@ async def _evals_impl( if json_output: output_result(results, json_output, debug) else: + if _lines_printed: + click.echo() click.echo(format_eval_results(results, debug)) # Exit with error code if any tests failed diff --git a/src/mxcp/server/services/evals/service.py b/src/mxcp/server/services/evals/service.py index f37047e1..bbf8529b 100644 --- a/src/mxcp/server/services/evals/service.py +++ b/src/mxcp/server/services/evals/service.py @@ -1,8 +1,10 @@ import logging import time +from collections.abc import Callable from pathlib import Path from typing import Any +import click from pydantic_ai import ModelSettings from mxcp.sdk.auth import UserContext @@ -45,6 +47,7 @@ def _create_model_config( model_type = model_config.type api_key = model_config.api_key options = dict(model_config.options or {}) + api_mode = options.get("api") or options.get("endpoint") if not api_key: raise ValueError(f"No API key configured for model '{model}'") @@ -52,6 +55,10 @@ def _create_model_config( if model_type not in {"anthropic", "openai"}: raise ValueError(f"Unknown model type: {model_type}") + effective_model_type = ( + "openai-responses" if model_type == "openai" and api_mode == "responses" else model_type + ) + base_url = model_config.base_url timeout = model_config.timeout @@ -61,7 +68,41 @@ def _create_model_config( provider_config = ProviderConfig(api_key=api_key, base_url=base_url, timeout=timeout) - return model, model_type, options, provider_config + return model, effective_model_type, options, provider_config + + +def _build_model_settings( + model_name: str, model_type: str, model_options: dict[str, Any], allowed_keys: set[str] +) -> ModelSettings: + model_opts = dict(model_options) + api_mode = model_opts.pop("api", None) or model_opts.pop("endpoint", None) + + recognized_options = {k: v for k, v in model_opts.items() if k in allowed_keys} + extras: dict[str, Any] = {k: v for k, v in model_opts.items() if k not in allowed_keys} + + if model_type == "openai": + if api_mode == "responses": + # Responses API supports additional parameters such as reasoning. + if extras: + recognized_options["extra_body"] = extras + else: + # Filter out Responses-only keys to avoid 400s on chat completions. + filtered = { + k: v + for k, v in extras.items() + if k not in {"reasoning", "effort", "response_format"} + } + if len(filtered) != len(extras): + logger.warning( + "Dropping response-specific options for model '%s' using chat completions: %s", + model_name, + sorted(set(extras) - set(filtered)), + ) + extras = filtered + if extras and "extra_body" not in recognized_options: + recognized_options["extra_body"] = extras + + return ModelSettings(**recognized_options) # type: ignore[typeddict-item,no-any-return] def _load_endpoints(site_config: SiteConfigModel) -> list[tuple[EndpointDefinitionModel, Path]]: @@ -189,6 +230,8 @@ async def run_eval_suite( profile: str | None, cli_user_context: UserContext | None = None, override_model: str | None = None, + progress_callback: Callable[[str, str], None] | None = None, + expected_answer_model: str | None = None, ) -> dict[str, Any]: """Run a specific eval suite by name. @@ -221,18 +264,16 @@ async def run_eval_suite( "error": "No model specified. Set 'model' in eval suite or configure a default model.", "suite": suite_name, } + grading_model = expected_answer_model or getattr(eval_suite, "expected_answer_model", None) # Create model configuration model_name, model_type, model_options, provider_config = _create_model_config( model, user_config ) allowed_keys = set(ModelSettings.__annotations__.keys()) - unknown_keys = set(model_options.keys()) - allowed_keys - if unknown_keys: - raise ValueError( - f"Invalid model options keys: {sorted(unknown_keys)}. Allowed: {sorted(allowed_keys)}" - ) - model_settings = ModelSettings(**model_options) # type: ignore[typeddict-item] + model_opts = dict(model_options) + + model_settings = _build_model_settings(model_name, model_type, model_opts, allowed_keys) # Load endpoints endpoints = _load_endpoints(site_config) @@ -246,12 +287,41 @@ async def run_eval_suite( # Create tool executor that bridges LLM calls to endpoint execution tool_executor = EndpointToolExecutor(engine, endpoints) + grading_executor: LLMExecutor | None = None + + if grading_model: + grade_model_name, grade_model_type, grade_opts, grade_provider = _create_model_config( + grading_model, user_config + ) + grade_settings = _build_model_settings( + grade_model_name, grade_model_type, dict(grade_opts), allowed_keys + ) + grading_executor = LLMExecutor( + grade_model_name, + grade_model_type, + grade_settings, + [], # no tools needed for grading + tool_executor, + provider_config=grade_provider, + ) logger.info(f"Running eval suite: {suite_name} from {file_path}") logger.info(f"Suite description: {eval_suite.description or 'No description'}") logger.info(f"Model: {model}") logger.info(f"Number of tests: {len(eval_suite.tests)}") + total_tests = len(eval_suite.tests) + if progress_callback: + progress_callback( + f"suite:{suite_name}", + "🧪 " + + click.style( + f"Running suite '{suite_name}' with {total_tests} test" + f"{'' if total_tests == 1 else 's'} using model '{model_name}'...", + fg="yellow", + ), + ) + try: # Create LLM executor with model config, tool definitions, and tool executor executor = LLMExecutor( @@ -265,8 +335,17 @@ async def run_eval_suite( # Run each test tests = [] - for test in eval_suite.tests: + for idx, test in enumerate(eval_suite.tests, start=1): test_start = time.time() + if progress_callback: + progress_callback( + f"test:{suite_name}:{idx}", + "⏳ " + + click.style( + f"[{suite_name}] {idx}/{total_tests} • {test.name}...", + fg="cyan", + ), + ) # Determine user context for this test test_user_context = cli_user_context @@ -347,7 +426,8 @@ async def run_eval_suite( failures.append(f"Forbidden text '{forbidden_text}' found in response") if assertions.expected_answer: - evaluation = await executor.evaluate_expected_answer( + grader = grading_executor or executor + evaluation = await grader.evaluate_expected_answer( response, assertions.expected_answer ) grade = (evaluation.get("result") or "").lower() @@ -366,11 +446,12 @@ async def run_eval_suite( test_time = time.time() - test_start + passed = len(failures) == 0 tests.append( { "name": test.name, "description": test.description, - "passed": len(failures) == 0, + "passed": passed, "failures": failures, "time": test_time, "details": { @@ -390,6 +471,17 @@ async def run_eval_suite( }, } ) + if progress_callback: + icon = click.style("✓", fg="green") if passed else click.style("✗", fg="red") + progress_callback( + f"test:{suite_name}:{idx}", + icon + + " " + + click.style( + f"[{suite_name}] {idx}/{total_tests} • {test.name} ({test_time:.2f}s)", + fg="green" if passed else "red", + ), + ) except Exception as e: test_time = time.time() - test_start @@ -402,6 +494,15 @@ async def run_eval_suite( "time": test_time, } ) + if progress_callback: + progress_callback( + f"test:{suite_name}:{idx}", + "✗ " + + click.style( + f"[{suite_name}] {idx}/{total_tests} • {test.name} errored: {e} ({test_time:.2f}s)", + fg="red", + ), + ) finally: # Clean up runtime environment @@ -424,6 +525,8 @@ async def run_all_evals( profile: str | None, cli_user_context: UserContext | None = None, override_model: str | None = None, + progress_callback: Callable[[str, str], None] | None = None, + expected_answer_model: str | None = None, ) -> dict[str, Any]: """Run all eval suites found in the repository. @@ -461,8 +564,17 @@ async def run_all_evals( continue suite_name = eval_suite.suite or "unnamed" # Run the suite + if progress_callback: + progress_callback(f"suite:{suite_name}", f"🧪 Running eval suite: {suite_name}") result = await run_eval_suite( - suite_name, user_config, site_config, profile, cli_user_context, override_model + suite_name, + user_config, + site_config, + profile, + cli_user_context, + override_model, + progress_callback=progress_callback, + expected_answer_model=expected_answer_model, ) # Get relative path diff --git a/tests/server/test_evals_service.py b/tests/server/test_evals_service.py new file mode 100644 index 00000000..1122a4cf --- /dev/null +++ b/tests/server/test_evals_service.py @@ -0,0 +1,30 @@ +from pydantic_ai import ModelSettings + +from mxcp.server.services.evals.service import _build_model_settings + + +def test_model_settings_chat_drops_response_only_keys() -> None: + allowed = set(ModelSettings.__annotations__.keys()) + settings = _build_model_settings( + "gpt-4o", + "openai", + {"reasoning": {"effort": "medium"}, "timeout": 30}, + allowed, + ) + + extra_body = getattr(settings, "extra_body", None) + assert extra_body is None or "reasoning" not in extra_body + assert getattr(settings, "timeout", None) == 30 + + +def test_model_settings_responses_keeps_extras() -> None: + allowed = set(ModelSettings.__annotations__.keys()) + settings = _build_model_settings( + "gpt-5", + "openai", + {"api": "responses", "reasoning": {"effort": "medium"}}, + allowed, + ) + + extra_body = getattr(settings, "extra_body", None) + assert extra_body and "reasoning" in extra_body From 4e6bd425f32011c4fc8b1a2b29be70caa0bdd0f7 Mon Sep 17 00:00:00 2001 From: Alex Zerntev Date: Sat, 29 Nov 2025 12:27:11 +0200 Subject: [PATCH 15/26] improvements --- docs/guides/configuration.md | 5 ++- docs/guides/quality.md | 4 +- src/mxcp/sdk/evals/executor.py | 7 ++- src/mxcp/server/services/evals/service.py | 55 +++++++++++++---------- tests/sdk/evals/test_executor.py | 30 +++++++++++++ tests/server/test_evals_service.py | 23 ++++++++-- 6 files changed, 94 insertions(+), 30 deletions(-) diff --git a/docs/guides/configuration.md b/docs/guides/configuration.md index cab214be..2fa99046 100644 --- a/docs/guides/configuration.md +++ b/docs/guides/configuration.md @@ -669,7 +669,10 @@ models: api_key: "${OPENAI_API_KEY}" options: api: "responses" # Choices: responses (for OpenAI Responses API), chat (default) - reasoning: + # Provider-specific fields must be prefixed: + # - body: goes into the request body + # - header: goes into request headers + body:reasoning: effort: "medium" # Passed via extra_body to the provider ``` diff --git a/docs/guides/quality.md b/docs/guides/quality.md index f35010e9..d83ce363 100644 --- a/docs/guides/quality.md +++ b/docs/guides/quality.md @@ -961,7 +961,9 @@ models: api_key: "${OPENAI_API_KEY}" options: api: "responses" # Choices: responses (Responses API) or chat (default) - reasoning: + # Provider-specific fields must use prefixes: + # body: for request body, header: for headers + body:reasoning: effort: "medium" ``` diff --git a/src/mxcp/sdk/evals/executor.py b/src/mxcp/sdk/evals/executor.py index 950fb00c..6e3eac8d 100644 --- a/src/mxcp/sdk/evals/executor.py +++ b/src/mxcp/sdk/evals/executor.py @@ -237,13 +237,16 @@ def _map_param_type(self, param_type: str) -> Any: def _apply_provider_env(self) -> None: """Populate provider env vars if missing, using provided config.""" + model_type = self.model_type or "" + is_openai = model_type.startswith("openai") + is_anthropic = model_type.startswith("anthropic") env_overrides: dict[str, str] = {} - if self.model_type == "openai": + if is_openai: if self.provider_config.api_key: env_overrides["OPENAI_API_KEY"] = self.provider_config.api_key if self.provider_config.base_url: env_overrides["OPENAI_BASE_URL"] = self.provider_config.base_url - elif self.model_type == "anthropic": + elif is_anthropic: if self.provider_config.api_key: env_overrides["ANTHROPIC_API_KEY"] = self.provider_config.api_key if self.provider_config.base_url: diff --git a/src/mxcp/server/services/evals/service.py b/src/mxcp/server/services/evals/service.py index bbf8529b..8ed44bd0 100644 --- a/src/mxcp/server/services/evals/service.py +++ b/src/mxcp/server/services/evals/service.py @@ -75,32 +75,41 @@ def _build_model_settings( model_name: str, model_type: str, model_options: dict[str, Any], allowed_keys: set[str] ) -> ModelSettings: model_opts = dict(model_options) - api_mode = model_opts.pop("api", None) or model_opts.pop("endpoint", None) + model_opts.pop("api", None) + model_opts.pop("endpoint", None) recognized_options = {k: v for k, v in model_opts.items() if k in allowed_keys} - extras: dict[str, Any] = {k: v for k, v in model_opts.items() if k not in allowed_keys} - - if model_type == "openai": - if api_mode == "responses": - # Responses API supports additional parameters such as reasoning. - if extras: - recognized_options["extra_body"] = extras + body_extras: dict[str, Any] = dict(recognized_options.get("extra_body") or {}) + header_extras: dict[str, str] = dict(recognized_options.get("extra_headers") or {}) + ignored: list[str] = [] + + for key, value in model_opts.items(): + if key in allowed_keys: + continue + if key.startswith("body:"): + body_extras[key.split(":", 1)[1]] = value + elif key.startswith("header:"): + header_value: str + if isinstance(value, list): + header_value = ",".join(str(v) for v in value) + else: + header_value = str(value) + header_extras[key.split(":", 1)[1]] = header_value else: - # Filter out Responses-only keys to avoid 400s on chat completions. - filtered = { - k: v - for k, v in extras.items() - if k not in {"reasoning", "effort", "response_format"} - } - if len(filtered) != len(extras): - logger.warning( - "Dropping response-specific options for model '%s' using chat completions: %s", - model_name, - sorted(set(extras) - set(filtered)), - ) - extras = filtered - if extras and "extra_body" not in recognized_options: - recognized_options["extra_body"] = extras + ignored.append(key) + + if ignored: + logger.warning( + "Ignoring unprefixed model options for model '%s': %s. " + "Use 'body:' or 'header:' prefixes.", + model_name, + sorted(ignored), + ) + + if body_extras: + recognized_options["extra_body"] = body_extras + if header_extras: + recognized_options["extra_headers"] = header_extras return ModelSettings(**recognized_options) # type: ignore[typeddict-item,no-any-return] diff --git a/tests/sdk/evals/test_executor.py b/tests/sdk/evals/test_executor.py index 5dabe6f4..d6f07bb3 100644 --- a/tests/sdk/evals/test_executor.py +++ b/tests/sdk/evals/test_executor.py @@ -1,4 +1,5 @@ import asyncio +import os from typing import Any import pytest @@ -169,3 +170,32 @@ async def run( assert len(result.tool_calls) == 2 assert result.tool_calls[-1].error + + +def test_apply_provider_env_for_openai_responses() -> None: + prev_key = os.environ.get("OPENAI_API_KEY") + prev_url = os.environ.get("OPENAI_BASE_URL") + try: + os.environ.pop("OPENAI_API_KEY", None) + os.environ.pop("OPENAI_BASE_URL", None) + + LLMExecutor( + "gpt-5", + "openai-responses", + ModelSettings(), + [], + MockToolExecutor(), + provider_config=ProviderConfig(api_key="key", base_url="https://api.openai.com"), + ) + + assert os.environ.get("OPENAI_API_KEY") == "key" + assert os.environ.get("OPENAI_BASE_URL") == "https://api.openai.com" + finally: + if prev_key is None: + os.environ.pop("OPENAI_API_KEY", None) + else: + os.environ["OPENAI_API_KEY"] = prev_key + if prev_url is None: + os.environ.pop("OPENAI_BASE_URL", None) + else: + os.environ["OPENAI_BASE_URL"] = prev_url diff --git a/tests/server/test_evals_service.py b/tests/server/test_evals_service.py index 1122a4cf..ae5c4afa 100644 --- a/tests/server/test_evals_service.py +++ b/tests/server/test_evals_service.py @@ -8,12 +8,12 @@ def test_model_settings_chat_drops_response_only_keys() -> None: settings = _build_model_settings( "gpt-4o", "openai", - {"reasoning": {"effort": "medium"}, "timeout": 30}, + {"body:reasoning": {"effort": "medium"}, "timeout": 30}, allowed, ) extra_body = getattr(settings, "extra_body", None) - assert extra_body is None or "reasoning" not in extra_body + assert extra_body and "reasoning" in extra_body assert getattr(settings, "timeout", None) == 30 @@ -22,9 +22,26 @@ def test_model_settings_responses_keeps_extras() -> None: settings = _build_model_settings( "gpt-5", "openai", - {"api": "responses", "reasoning": {"effort": "medium"}}, + {"api": "responses", "body:reasoning": {"effort": "medium"}}, allowed, ) extra_body = getattr(settings, "extra_body", None) assert extra_body and "reasoning" in extra_body + + +def test_model_settings_anthropic_output_config_and_betas() -> None: + allowed = set(ModelSettings.__annotations__.keys()) + settings = _build_model_settings( + "claude", + "anthropic", + { + "body:output_config": {"effort": "medium"}, + "header:anthropic-beta": ["effort-2025-11-24"], + }, + allowed, + ) + + assert getattr(settings, "extra_body", None) == {"effort": "medium"} + headers = getattr(settings, "extra_headers", None) + assert headers and headers.get("anthropic-beta") == "effort-2025-11-24" From 7dbbdfd01c77c866910c532a82c75e9042465e57 Mon Sep 17 00:00:00 2001 From: Alex Zerntev Date: Sat, 29 Nov 2025 12:41:06 +0200 Subject: [PATCH 16/26] fix --- src/mxcp/sdk/evals/executor.py | 19 ++++++++++++++----- tests/server/test_evals_service.py | 3 ++- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/mxcp/sdk/evals/executor.py b/src/mxcp/sdk/evals/executor.py index 6e3eac8d..0e03af67 100644 --- a/src/mxcp/sdk/evals/executor.py +++ b/src/mxcp/sdk/evals/executor.py @@ -78,6 +78,15 @@ def __init__( self._apply_provider_env() self._tool_models = self._build_tool_models(available_tools) + # Cache tool schemas once to avoid recomputing per prompt + self._tool_schemas: dict[str, dict[str, Any]] = { + name: ( + model.model_json_schema() + if model + else {"type": "object", "properties": {}, "required": []} + ) + for name, model in self._tool_models.items() + } self.system_prompt = self._build_system_prompt(available_tools) logger.info( @@ -95,11 +104,11 @@ async def execute_prompt( def _make_tool(tool_def: ToolDefinition) -> Tool: args_model = self._tool_models.get(tool_def.name) - schema = ( - args_model.model_json_schema() - if args_model - else {"type": "object", "properties": {}, "required": []} - ) + schema = self._tool_schemas.get(tool_def.name) or { + "type": "object", + "properties": {}, + "required": [], + } async def _fn(**kwargs: Any) -> Any: if max_turns is not None and len(history) >= max_turns: diff --git a/tests/server/test_evals_service.py b/tests/server/test_evals_service.py index ae5c4afa..99e41b72 100644 --- a/tests/server/test_evals_service.py +++ b/tests/server/test_evals_service.py @@ -42,6 +42,7 @@ def test_model_settings_anthropic_output_config_and_betas() -> None: allowed, ) - assert getattr(settings, "extra_body", None) == {"effort": "medium"} + extra_body = getattr(settings, "extra_body", None) + assert extra_body and extra_body.get("output_config") == {"effort": "medium"} headers = getattr(settings, "extra_headers", None) assert headers and headers.get("anthropic-beta") == "effort-2025-11-24" From 712eea722e2599d609b2cf94fb795e12522432d0 Mon Sep 17 00:00:00 2001 From: Alex Zerntev Date: Sat, 29 Nov 2025 12:59:35 +0200 Subject: [PATCH 17/26] clean up --- src/mxcp/sdk/evals/executor.py | 31 ++++++++++++----------- src/mxcp/server/executor/runners/tool.py | 16 ++++++++---- src/mxcp/server/interfaces/cli/evals.py | 9 +++++-- src/mxcp/server/services/evals/service.py | 15 +++++------ 4 files changed, 41 insertions(+), 30 deletions(-) diff --git a/src/mxcp/sdk/evals/executor.py b/src/mxcp/sdk/evals/executor.py index 0e03af67..5b290413 100644 --- a/src/mxcp/sdk/evals/executor.py +++ b/src/mxcp/sdk/evals/executor.py @@ -78,15 +78,8 @@ def __init__( self._apply_provider_env() self._tool_models = self._build_tool_models(available_tools) - # Cache tool schemas once to avoid recomputing per prompt - self._tool_schemas: dict[str, dict[str, Any]] = { - name: ( - model.model_json_schema() - if model - else {"type": "object", "properties": {}, "required": []} - ) - for name, model in self._tool_models.items() - } + self._tool_schemas: dict[str, dict[str, Any]] | None = None + self._agent_tools: list[Tool] | None = None self.system_prompt = self._build_system_prompt(available_tools) logger.info( @@ -104,11 +97,15 @@ async def execute_prompt( def _make_tool(tool_def: ToolDefinition) -> Tool: args_model = self._tool_models.get(tool_def.name) - schema = self._tool_schemas.get(tool_def.name) or { - "type": "object", - "properties": {}, - "required": [], - } + schema = self._tool_schemas.get(tool_def.name) if self._tool_schemas else None + if schema is None: + schema = ( + args_model.model_json_schema() + if args_model + else {"type": "object", "properties": {}, "required": []} + ) + if self._tool_schemas is not None: + self._tool_schemas[tool_def.name] = schema async def _fn(**kwargs: Any) -> Any: if max_turns is not None and len(history) >= max_turns: @@ -152,7 +149,11 @@ async def _prepare( tool._mxcp_callable = _fn # type: ignore[attr-defined] return tool - agent_tools = [_make_tool(t) for t in self.available_tools] + if self._agent_tools is None: + # initialize schema cache on first build + self._tool_schemas = {} + self._agent_tools = [_make_tool(t) for t in self.available_tools] + agent_tools = self._agent_tools model_string = f"{self.model_type}:{self.model_name}" agent = self._agent_cls( model=model_string, instructions=self.system_prompt, tools=agent_tools diff --git a/src/mxcp/server/executor/runners/tool.py b/src/mxcp/server/executor/runners/tool.py index 17d22e2a..625ca2be 100644 --- a/src/mxcp/server/executor/runners/tool.py +++ b/src/mxcp/server/executor/runners/tool.py @@ -6,6 +6,7 @@ """ import logging +from dataclasses import dataclass from pathlib import Path from typing import Any @@ -18,6 +19,12 @@ logger = logging.getLogger(__name__) +@dataclass(frozen=True) +class EndpointWithPath: + definition: EndpointDefinitionModel + path: Path + + class EndpointToolExecutor: """Tool executor that executes tools via SDK ExecutionEngine and endpoints. @@ -43,9 +50,7 @@ class EndpointToolExecutor: >>> llm_executor = LLMExecutor(model_config, tool_definitions, tool_executor) """ - def __init__( - self, engine: ExecutionEngine, endpoints: list[tuple[EndpointDefinitionModel, Path]] - ): + def __init__(self, engine: ExecutionEngine, endpoints: list[EndpointWithPath]): """Initialize the endpoint tool executor. Args: @@ -53,11 +58,12 @@ def __init__( endpoints: List of endpoint definitions """ self.engine = engine - self.endpoints = [ep for ep, _ in endpoints] + self.endpoints = [entry.definition for entry in endpoints] # Create lookup map for faster tool resolution self._tool_map: dict[str, tuple[EndpointDefinitionModel, Path]] = {} - for endpoint_def, path in endpoints: + for entry in endpoints: + endpoint_def, path = entry.definition, entry.path if endpoint_def.tool: self._tool_map[endpoint_def.tool.name] = (endpoint_def, path) elif endpoint_def.resource: diff --git a/src/mxcp/server/interfaces/cli/evals.py b/src/mxcp/server/interfaces/cli/evals.py index b9d9f99a..01ee1876 100644 --- a/src/mxcp/server/interfaces/cli/evals.py +++ b/src/mxcp/server/interfaces/cli/evals.py @@ -344,14 +344,16 @@ async def _evals_impl( # Run evals start_time = time.time() - # Stateful progress renderer that overwrites the same line. + # Stateful progress renderer that overwrites the same line (TTY only). _lines: dict[str, str] = {} _order: list[str] = [] _lines_printed = 0 + _is_tty = click.get_text_stream("stdout").isatty() def _render_progress() -> None: nonlocal _lines_printed - # Move cursor up to rewrite previous lines + if not _is_tty: + return if _lines_printed: click.echo(f"\033[{_lines_printed}A\r", nl=False) for key in _order: @@ -360,6 +362,9 @@ def _render_progress() -> None: _lines_printed = len(_order) def _progress(key: str, msg: str) -> None: + if not _is_tty: + click.echo(msg) + return if key not in _order: _order.append(key) _lines[key] = msg diff --git a/src/mxcp/server/services/evals/service.py b/src/mxcp/server/services/evals/service.py index 8ed44bd0..8bb696f8 100644 --- a/src/mxcp/server/services/evals/service.py +++ b/src/mxcp/server/services/evals/service.py @@ -1,7 +1,6 @@ import logging import time from collections.abc import Callable -from pathlib import Path from typing import Any import click @@ -13,10 +12,9 @@ from mxcp.server.core.config.models import SiteConfigModel, UserConfigModel from mxcp.server.core.config.site_config import find_repo_root from mxcp.server.definitions.endpoints.loader import EndpointLoader -from mxcp.server.definitions.endpoints.models import EndpointDefinitionModel from mxcp.server.definitions.evals.loader import discover_eval_files, load_eval_suite from mxcp.server.executor.engine import create_runtime_environment -from mxcp.server.executor.runners.tool import EndpointToolExecutor +from mxcp.server.executor.runners.tool import EndpointToolExecutor, EndpointWithPath logger = logging.getLogger(__name__) @@ -114,7 +112,7 @@ def _build_model_settings( return ModelSettings(**recognized_options) # type: ignore[typeddict-item,no-any-return] -def _load_endpoints(site_config: SiteConfigModel) -> list[tuple[EndpointDefinitionModel, Path]]: +def _load_endpoints(site_config: SiteConfigModel) -> list[EndpointWithPath]: """Load all available endpoints. Args: @@ -124,19 +122,19 @@ def _load_endpoints(site_config: SiteConfigModel) -> list[tuple[EndpointDefiniti List of (endpoint definition, file path) """ loader = EndpointLoader(site_config) - endpoints: list[tuple[EndpointDefinitionModel, Path]] = [] + endpoints: list[EndpointWithPath] = [] discovered = loader.discover_endpoints() for path, endpoint_def, error in discovered: if error is None and endpoint_def and (endpoint_def.tool or endpoint_def.resource): # Only include endpoints that have a tool or resource definition - endpoints.append((endpoint_def, path)) + endpoints.append(EndpointWithPath(endpoint_def, path)) return endpoints def _convert_endpoints_to_tool_definitions( - endpoints: list[tuple[EndpointDefinitionModel, Path]], + endpoints: list[EndpointWithPath], ) -> list[ToolDefinition]: """Convert endpoint definitions to ToolDefinition objects for the LLM. @@ -148,7 +146,8 @@ def _convert_endpoints_to_tool_definitions( """ tool_definitions = [] - for endpoint_def, _endpoint_path in endpoints: + for entry in endpoints: + endpoint_def = entry.definition if endpoint_def.tool: tool = endpoint_def.tool From 68c6f832c9918968c9e025b8b0b96c9dc938982f Mon Sep 17 00:00:00 2001 From: Alex Zerntev Date: Sat, 29 Nov 2025 16:15:44 +0200 Subject: [PATCH 18/26] fixes --- src/mxcp/sdk/evals/executor.py | 76 ++++++++---- .../server/definitions/endpoints/utils.py | 31 +++-- src/mxcp/server/executor/runners/tool.py | 89 +++----------- src/mxcp/server/interfaces/cli/evals.py | 20 ++- src/mxcp/server/services/evals/service.py | 38 ++++-- tests/sdk/evals/test_executor.py | 74 +++++++++++- tests/server/test_evals_service.py | 30 ++++- tests/server/test_evals_tool_executor.py | 114 +++++++++--------- 8 files changed, 287 insertions(+), 185 deletions(-) diff --git a/src/mxcp/sdk/evals/executor.py b/src/mxcp/sdk/evals/executor.py index 5b290413..b2ea6aa8 100644 --- a/src/mxcp/sdk/evals/executor.py +++ b/src/mxcp/sdk/evals/executor.py @@ -2,9 +2,11 @@ from __future__ import annotations +import asyncio import logging import os from collections.abc import Callable +from contextlib import asynccontextmanager from dataclasses import dataclass, field from typing import Any, Protocol @@ -57,6 +59,7 @@ class GradeResult(BaseModel): class LLMExecutor: + _env_lock: asyncio.Lock = asyncio.Lock() """Pydantic-based agent loop with tool support.""" def __init__( @@ -75,12 +78,10 @@ def __init__( self.provider_config = provider_config or ProviderConfig() self._agent_cls: Callable[..., Any] = Agent self._model_settings = model_settings - - self._apply_provider_env() self._tool_models = self._build_tool_models(available_tools) - self._tool_schemas: dict[str, dict[str, Any]] | None = None - self._agent_tools: list[Tool] | None = None + self._tool_schemas: dict[str, dict[str, Any]] = {} self.system_prompt = self._build_system_prompt(available_tools) + self._ensure_responses_env_defaults() logger.info( "LLM executor initialized with model %s (%s) and %d tools", @@ -88,6 +89,7 @@ def __init__( self.model_type, len(available_tools), ) + self._env_lock: asyncio.Lock = getattr(self, "_env_lock", asyncio.Lock()) async def execute_prompt( self, prompt: str, user_context: UserContext | None = None, max_turns: int = 10 @@ -97,15 +99,14 @@ async def execute_prompt( def _make_tool(tool_def: ToolDefinition) -> Tool: args_model = self._tool_models.get(tool_def.name) - schema = self._tool_schemas.get(tool_def.name) if self._tool_schemas else None + schema = self._tool_schemas.get(tool_def.name) if schema is None: schema = ( args_model.model_json_schema() if args_model else {"type": "object", "properties": {}, "required": []} ) - if self._tool_schemas is not None: - self._tool_schemas[tool_def.name] = schema + self._tool_schemas[tool_def.name] = schema async def _fn(**kwargs: Any) -> Any: if max_turns is not None and len(history) >= max_turns: @@ -149,20 +150,17 @@ async def _prepare( tool._mxcp_callable = _fn # type: ignore[attr-defined] return tool - if self._agent_tools is None: - # initialize schema cache on first build - self._tool_schemas = {} - self._agent_tools = [_make_tool(t) for t in self.available_tools] - agent_tools = self._agent_tools + agent_tools = [_make_tool(t) for t in self.available_tools] model_string = f"{self.model_type}:{self.model_name}" agent = self._agent_cls( model=model_string, instructions=self.system_prompt, tools=agent_tools ) try: - agent_run = await agent.run( - prompt, deps=user_context, model_settings=self._model_settings - ) + async with self._temporary_provider_env(): + agent_run = await agent.run( + prompt, deps=user_context, model_settings=self._model_settings + ) answer = getattr(agent_run, "output", "") return AgentResult(answer=str(answer), tool_calls=history) except RuntimeError as exc: @@ -192,7 +190,8 @@ async def evaluate_expected_answer(self, answer: str, expected_answer: str) -> d agent = self._agent_cls( model=model_string, instructions=grader_system, tools=(), output_type=GradeResult ) - run = await agent.run(grader_prompt, model_settings=self._model_settings) + async with self._temporary_provider_env(): + run = await agent.run(grader_prompt, model_settings=self._model_settings) out: GradeResult = getattr(run, "output", GradeResult()) return out.model_dump() @@ -245,22 +244,47 @@ def _map_param_type(self, param_type: str) -> Any: logger.debug("Unknown tool parameter type '%s'; defaulting to Any", param_type) return Any - def _apply_provider_env(self) -> None: - """Populate provider env vars if missing, using provided config.""" + def _provider_env_overrides(self) -> dict[str, str]: + overrides: dict[str, str] = {} model_type = self.model_type or "" is_openai = model_type.startswith("openai") is_anthropic = model_type.startswith("anthropic") - env_overrides: dict[str, str] = {} if is_openai: if self.provider_config.api_key: - env_overrides["OPENAI_API_KEY"] = self.provider_config.api_key + overrides["OPENAI_API_KEY"] = self.provider_config.api_key if self.provider_config.base_url: - env_overrides["OPENAI_BASE_URL"] = self.provider_config.base_url + overrides["OPENAI_BASE_URL"] = self.provider_config.base_url elif is_anthropic: if self.provider_config.api_key: - env_overrides["ANTHROPIC_API_KEY"] = self.provider_config.api_key + overrides["ANTHROPIC_API_KEY"] = self.provider_config.api_key if self.provider_config.base_url: - env_overrides["ANTHROPIC_BASE_URL"] = self.provider_config.base_url - - for key, value in env_overrides.items(): - os.environ.setdefault(key, value) + overrides["ANTHROPIC_BASE_URL"] = self.provider_config.base_url + return overrides + + def _ensure_responses_env_defaults(self) -> None: + if self.model_type != "openai-responses": + return + overrides = self._provider_env_overrides() + for key, value in overrides.items(): + if key not in os.environ: + os.environ[key] = value + + @asynccontextmanager + async def _temporary_provider_env(self) -> Any: + overrides = self._provider_env_overrides() + if not overrides: + yield + return + previous: dict[str, str | None] = {} + async with self._env_lock: + try: + for key, value in overrides.items(): + previous[key] = os.environ.get(key) + os.environ[key] = value + yield + finally: + for key, original in previous.items(): + if original is None: + os.environ.pop(key, None) + else: + os.environ[key] = original diff --git a/src/mxcp/server/definitions/endpoints/utils.py b/src/mxcp/server/definitions/endpoints/utils.py index c9ca9cbf..1fe858c7 100644 --- a/src/mxcp/server/definitions/endpoints/utils.py +++ b/src/mxcp/server/definitions/endpoints/utils.py @@ -58,11 +58,7 @@ def get_endpoint_source_code( return source.code if source.file is not None: - source_path = Path(source.file) - if source_path.is_absolute(): - full_path = repo_root / source_path.relative_to("/") - else: - full_path = endpoint_file_path.parent / source_path + full_path = resolve_file_path(source.file, endpoint_file_path, repo_root) return full_path.read_text() raise ValueError("No source code found in endpoint definition") @@ -124,9 +120,24 @@ def resolve_file_path(file_path: str, endpoint_file_path: Path, repo_root: Path) """ source_path = Path(file_path) if source_path.is_absolute(): - return repo_root / source_path.relative_to("/") + return source_path + + repo_candidate = (repo_root / source_path).resolve(strict=False) + + endpoint_path = endpoint_file_path + if not endpoint_path.is_absolute(): + endpoint_path = (repo_root / endpoint_path).resolve(strict=False) else: - return endpoint_file_path.parent / source_path + endpoint_path = endpoint_path.resolve(strict=False) + endpoint_candidate = (endpoint_path.parent / source_path).resolve(strict=False) + + if repo_candidate.exists(): + return repo_candidate + if endpoint_candidate.exists(): + return endpoint_candidate + + # Default to repo-relative path to keep behavior predictable even if file is missing + return repo_candidate def get_endpoint_name_or_uri( @@ -193,14 +204,14 @@ def prepare_source_for_execution( if not tool_def: raise ValueError("No tool definition found") source = tool_def.source - language = tool_def.language + language = source.language or tool_def.language function_name = tool_def.name elif endpoint_type == "resource": resource_def = endpoint_definition.resource if not resource_def: raise ValueError("No resource definition found") source = resource_def.source - language = resource_def.language + language = source.language or resource_def.language else: raise ValueError("Prompts don't have source code") @@ -236,4 +247,4 @@ def prepare_source_for_execution( endpoint_definition, endpoint_type, endpoint_file_path, repo_root ) return (language, source_code) - raise ValueError("No source code or file specified in endpoint definition") + raise ValueError("No source found for endpoint") diff --git a/src/mxcp/server/executor/runners/tool.py b/src/mxcp/server/executor/runners/tool.py index 625ca2be..7574b949 100644 --- a/src/mxcp/server/executor/runners/tool.py +++ b/src/mxcp/server/executor/runners/tool.py @@ -14,7 +14,7 @@ from mxcp.sdk.executor import ExecutionContext, ExecutionEngine from mxcp.server.core.config.site_config import find_repo_root from mxcp.server.definitions.endpoints.models import EndpointDefinitionModel -from mxcp.server.definitions.endpoints.utils import detect_language_from_source, extract_source_info +from mxcp.server.definitions.endpoints.utils import prepare_source_for_execution logger = logging.getLogger(__name__) @@ -99,15 +99,28 @@ async def execute_tool( context = ExecutionContext(user_context=user_context) # Determine the source code and language - source_info, source_path = self._get_source_code(endpoint_def, endpoint_path, tool_name) - language = self._get_language(endpoint_def, tool_name, source_path) + if endpoint_def.tool: + endpoint_type = "tool" + elif endpoint_def.resource: + endpoint_type = "resource" + else: + raise ValueError(f"Endpoint '{tool_name}' has no tool or resource definition") + + repo_root = find_repo_root() + language, source_payload = prepare_source_for_execution( + endpoint_def, + endpoint_type, + endpoint_path, + repo_root, + include_function_name=True, + ) logger.debug(f"Executing tool '{tool_name}' with language '{language}'") try: # Execute using the SDK engine result = await self.engine.execute( - language=language, source_code=source_info, params=arguments, context=context + language=language, source_code=source_payload, params=arguments, context=context ) logger.debug(f"Tool '{tool_name}' executed successfully") @@ -116,71 +129,3 @@ async def execute_tool( except Exception as e: logger.error(f"Tool '{tool_name}' execution failed: {e}") raise - - def _get_source_code( - self, endpoint_def: EndpointDefinitionModel, endpoint_path: Path, tool_name: str - ) -> tuple[str, str | None]: - """Extract source code from endpoint definition, loading files when needed.""" - # Get the tool or resource definition - source = None - if endpoint_def.tool: - source = endpoint_def.tool.source - elif endpoint_def.resource: - source = endpoint_def.resource.source - - if not source: - raise ValueError(f"No source found for endpoint '{tool_name}'") - - source_type, source_value = extract_source_info(source) - if source_type == "file": - relative_path = Path(source_value) - candidates = [] - # Resolve endpoint path against repo root (or CWD fallback) to handle relative paths - try: - base_root = find_repo_root() - except FileNotFoundError: - base_root = Path.cwd() - - endpoint_path_abs = ( - endpoint_path if endpoint_path.is_absolute() else (base_root / endpoint_path) - ).resolve() - - if not relative_path.is_absolute(): - candidates.append((endpoint_path_abs.parent / relative_path).resolve()) - candidates.append((base_root / relative_path).resolve()) - else: - candidates.append(relative_path.resolve()) - - source_path = next((c for c in candidates if c.exists()), None) - - if not source_path: - # Report first candidate for clarity - candidate_msg = candidates[0] if candidates else relative_path - raise ValueError( - f"Source file not found for endpoint '{tool_name}': {candidate_msg}" - ) - - try: - return source_path.read_text(), str(source_path) - except Exception as exc: # noqa: BLE001 - raise ValueError( - f"Failed to read source file for endpoint '{tool_name}': {exc}" - ) from exc - - return source_value, None - - def _get_language( - self, endpoint_def: EndpointDefinitionModel, tool_name: str, source_path: str | None - ) -> str: - """Determine the programming language for the endpoint.""" - # Get the tool or resource definition - source = None - if endpoint_def.tool: - source = endpoint_def.tool.source - elif endpoint_def.resource: - source = endpoint_def.resource.source - - if not source: - raise ValueError(f"No source found for endpoint '{tool_name}'") - - return detect_language_from_source(source, source_path) diff --git a/src/mxcp/server/interfaces/cli/evals.py b/src/mxcp/server/interfaces/cli/evals.py index 01ee1876..e44cbe4b 100644 --- a/src/mxcp/server/interfaces/cli/evals.py +++ b/src/mxcp/server/interfaces/cli/evals.py @@ -65,7 +65,14 @@ def format_eval_results(results: dict[str, Any], debug: bool = False) -> str: failures = test.get("failures", []) for failure in failures: - output.append(f" {click.style('💡', fg='yellow')} {failure}") + lines = failure.splitlines() + if not lines: + continue + indent = " " * 4 + continuation_indent = indent + " " * 3 + output.append(f"{indent}{click.style('💡', fg='yellow')} {lines[0]}") + for line in lines[1:]: + output.append(f"{continuation_indent}{line}") if debug and "details" in test: output.append(f" {click.style('Debug info:', fg='yellow')}") @@ -168,12 +175,19 @@ def format_eval_results(results: dict[str, Any], debug: bool = False) -> str: output.append( f" {click.style('✗', fg='red')} {test['name']} {click.style(f'({test_time:.2f}s)', fg='bright_black')}" ) - if test.get("error"): + if test.get("error") and debug: output.append( f" {click.style('Error:', fg='red')} {test['error']}" ) for failure in test.get("failures", []): - output.append(f" {click.style('💡', fg='yellow')} {failure}") + lines = failure.splitlines() + if not lines: + continue + indent = " " * 6 + continuation_indent = indent + " " * 3 + output.append(f"{indent}{click.style('💡', fg='yellow')} {lines[0]}") + for line in lines[1:]: + output.append(f"{continuation_indent}{line}") # Show passed suites passed = [s for s in suites if s.get("status") == "passed"] diff --git a/src/mxcp/server/services/evals/service.py b/src/mxcp/server/services/evals/service.py index 8bb696f8..dedde99b 100644 --- a/src/mxcp/server/services/evals/service.py +++ b/src/mxcp/server/services/evals/service.py @@ -231,6 +231,24 @@ def _compact_text(*parts: str, max_length: int | None = 240) -> str: return text +def _format_expected_answer_failure( + response: str, + expected: str, + grade: str | None, + comment: str | None, + reasoning: str | None, +) -> str: + """Build a multi-line failure detail block for expected-answer grading.""" + lines = [ + f"LLM Answer: {response}", + f"Expected: {expected}", + f"Grade: {grade or 'unknown'}", + f"Comment: {comment or 'n/a'}", + f"Reasoning: {reasoning or 'n/a'}", + ] + return "\n".join(lines) + + async def run_eval_suite( suite_name: str, user_config: UserConfigModel, @@ -348,7 +366,7 @@ async def run_eval_suite( if progress_callback: progress_callback( f"test:{suite_name}:{idx}", - "⏳ " + " ⏳ " + click.style( f"[{suite_name}] {idx}/{total_tests} • {test.name}...", fg="cyan", @@ -441,13 +459,12 @@ async def run_eval_suite( grade = (evaluation.get("result") or "").lower() comment = evaluation.get("comment") or "Model answer did not match expected" reasoning = evaluation.get("reasoning") or "" - detail = _compact_text( - f"LLM answer: {response}", - f"Expected: {assertions.expected_answer}", - f"Grade: {grade or 'unknown'}", - f"Comment: {comment}", - f"Reasoning: {reasoning or 'n/a'}", - max_length=None, # show the full detail in output + detail = _format_expected_answer_failure( + response, + assertions.expected_answer, + grade or "unknown", + comment, + reasoning, ) if grade != "correct": failures.append(detail) @@ -483,7 +500,8 @@ async def run_eval_suite( icon = click.style("✓", fg="green") if passed else click.style("✗", fg="red") progress_callback( f"test:{suite_name}:{idx}", - icon + " " + + icon + " " + click.style( f"[{suite_name}] {idx}/{total_tests} • {test.name} ({test_time:.2f}s)", @@ -505,7 +523,7 @@ async def run_eval_suite( if progress_callback: progress_callback( f"test:{suite_name}:{idx}", - "✗ " + " ✗ " + click.style( f"[{suite_name}] {idx}/{total_tests} • {test.name} errored: {e} ({test_time:.2f}s)", fg="red", diff --git a/tests/sdk/evals/test_executor.py b/tests/sdk/evals/test_executor.py index d6f07bb3..251977b3 100644 --- a/tests/sdk/evals/test_executor.py +++ b/tests/sdk/evals/test_executor.py @@ -100,6 +100,27 @@ def test_execute_prompt_with_tool_call() -> None: assert call.error is None +def test_execute_prompt_tool_calls_do_not_leak_between_runs() -> None: + executor = make_executor() + + # First run + executor._agent_cls = lambda **kwargs: FakeAgent( + tools=kwargs["tools"], output="ok", tool_args={"get_weather": {"location": "Paris"}} + ) + first = asyncio.run(executor.execute_prompt("Weather?")) + assert len(first.tool_calls) == 1 + assert first.tool_calls[0].arguments["location"] == "Paris" + + # Second run should still invoke tools and capture history independently + executor._agent_cls = lambda **kwargs: FakeAgent( + tools=kwargs["tools"], output="ok", tool_args={"get_weather": {"location": "Rome"}} + ) + second = asyncio.run(executor.execute_prompt("Weather?")) + + assert len(second.tool_calls) == 1 + assert second.tool_calls[0].arguments["location"] == "Rome" + + def test_execute_prompt_tool_error() -> None: executor = make_executor() executor.tool_executor.responses["get_weather"] = ValueError("boom") # type: ignore[attr-defined] @@ -142,7 +163,58 @@ def test_expected_answer_grading() -> None: def test_temporary_env_sets_and_restores() -> None: - pytest.skip("Environment injection removed; no temporary env to test.") + prev_openai_key = os.environ.get("OPENAI_API_KEY") + prev_openai_url = os.environ.get("OPENAI_BASE_URL") + prev_anthropic_key = os.environ.get("ANTHROPIC_API_KEY") + prev_anthropic_url = os.environ.get("ANTHROPIC_BASE_URL") + + os.environ["OPENAI_API_KEY"] = "original" + os.environ["OPENAI_BASE_URL"] = "orig-url" + + try: + executor = make_executor() + + class EnvAgent(FakeAgent): + async def run( # type: ignore[override] + self, _prompt: str, deps: Any | None = None, model_settings: Any | None = None + ) -> FakeRun: + # Capture environment while the agent runs + captured["during"] = ( + os.environ.get("ANTHROPIC_API_KEY"), + os.environ.get("ANTHROPIC_BASE_URL"), + ) + return await super().run(_prompt, deps=deps, model_settings=model_settings) + + captured: dict[str, tuple[str | None, str | None]] = {} + executor._agent_cls = lambda **kwargs: EnvAgent( + tools=kwargs["tools"], output="done", tool_args={} + ) + + asyncio.run(executor.execute_prompt("Weather?")) + + assert captured["during"] == ("key", "https://api.anthropic.com") + # Ensure globals restored + assert os.environ["OPENAI_API_KEY"] == "original" + assert os.environ["OPENAI_BASE_URL"] == "orig-url" + assert os.environ.get("ANTHROPIC_API_KEY") == prev_anthropic_key + assert os.environ.get("ANTHROPIC_BASE_URL") == prev_anthropic_url + finally: + if prev_anthropic_key is None: + os.environ.pop("ANTHROPIC_API_KEY", None) + else: + os.environ["ANTHROPIC_API_KEY"] = prev_anthropic_key + if prev_anthropic_url is None: + os.environ.pop("ANTHROPIC_BASE_URL", None) + else: + os.environ["ANTHROPIC_BASE_URL"] = prev_anthropic_url + if prev_openai_key is None: + os.environ.pop("OPENAI_API_KEY", None) + else: + os.environ["OPENAI_API_KEY"] = prev_openai_key + if prev_openai_url is None: + os.environ.pop("OPENAI_BASE_URL", None) + else: + os.environ["OPENAI_BASE_URL"] = prev_openai_url def test_max_turns_limits_tool_calls() -> None: diff --git a/tests/server/test_evals_service.py b/tests/server/test_evals_service.py index 99e41b72..bda8bf3e 100644 --- a/tests/server/test_evals_service.py +++ b/tests/server/test_evals_service.py @@ -1,6 +1,6 @@ from pydantic_ai import ModelSettings -from mxcp.server.services.evals.service import _build_model_settings +from mxcp.server.services.evals.service import _build_model_settings, _format_expected_answer_failure def test_model_settings_chat_drops_response_only_keys() -> None: @@ -12,9 +12,9 @@ def test_model_settings_chat_drops_response_only_keys() -> None: allowed, ) - extra_body = getattr(settings, "extra_body", None) + extra_body = settings.get("extra_body") assert extra_body and "reasoning" in extra_body - assert getattr(settings, "timeout", None) == 30 + assert settings.get("timeout") == 30 def test_model_settings_responses_keeps_extras() -> None: @@ -26,7 +26,7 @@ def test_model_settings_responses_keeps_extras() -> None: allowed, ) - extra_body = getattr(settings, "extra_body", None) + extra_body = settings.get("extra_body") assert extra_body and "reasoning" in extra_body @@ -42,7 +42,25 @@ def test_model_settings_anthropic_output_config_and_betas() -> None: allowed, ) - extra_body = getattr(settings, "extra_body", None) + extra_body = settings.get("extra_body") assert extra_body and extra_body.get("output_config") == {"effort": "medium"} - headers = getattr(settings, "extra_headers", None) + headers = settings.get("extra_headers") assert headers and headers.get("anthropic-beta") == "effort-2025-11-24" + + +def test_expected_answer_failure_formatting_is_multiline() -> None: + detail = _format_expected_answer_failure( + "Answer", + "Expected", + "wrong", + "bad", + "missed value", + ) + lines = detail.splitlines() + assert lines == [ + "LLM Answer: Answer", + "Expected: Expected", + "Grade: wrong", + "Comment: bad", + "Reasoning: missed value", + ] diff --git a/tests/server/test_evals_tool_executor.py b/tests/server/test_evals_tool_executor.py index 27e2e661..2af80af5 100644 --- a/tests/server/test_evals_tool_executor.py +++ b/tests/server/test_evals_tool_executor.py @@ -1,6 +1,6 @@ """Tests for EndpointToolExecutor integration.""" -import os +from pathlib import Path from typing import Any import pytest @@ -8,8 +8,7 @@ from mxcp.sdk.auth import UserContext from mxcp.sdk.executor import ExecutionContext from mxcp.server.definitions.endpoints.models import EndpointDefinitionModel, SourceDefinitionModel -from mxcp.server.executor.runners.tool import EndpointToolExecutor -from pathlib import Path +from mxcp.server.executor.runners.tool import EndpointToolExecutor, EndpointWithPath class MockExecutionEngine: @@ -49,9 +48,13 @@ def setup_method(self): "weather.py": {"temperature": 22, "condition": "sunny"}, } ) + self._monkeypatch = pytest.MonkeyPatch() + self._monkeypatch.setattr( + "mxcp.server.executor.runners.tool.find_repo_root", lambda: Path.cwd() + ) - self.endpoints: list[tuple[EndpointDefinitionModel, Path]] = [ - ( + self.endpoints = [ + EndpointWithPath( EndpointDefinitionModel.model_validate( { "mxcp": 1, @@ -65,7 +68,7 @@ def setup_method(self): ), Path("endpoints/get_date.yml"), ), - ( + EndpointWithPath( EndpointDefinitionModel.model_validate( { "mxcp": 1, @@ -79,7 +82,7 @@ def setup_method(self): ), Path("endpoints/calculate.yml"), ), - ( + EndpointWithPath( EndpointDefinitionModel.model_validate( { "mxcp": 1, @@ -93,7 +96,7 @@ def setup_method(self): ), Path("endpoints/get_weather.yml"), ), - ( + EndpointWithPath( EndpointDefinitionModel.model_validate( { "mxcp": 1, @@ -111,6 +114,9 @@ def setup_method(self): self.executor = EndpointToolExecutor(self.engine, self.endpoints) + def teardown_method(self): + self._monkeypatch.undo() + def test_initialization(self): """Test EndpointToolExecutor initialization.""" assert self.executor.engine == self.engine @@ -223,55 +229,13 @@ async def test_execute_tool_no_source(self): "source", SourceDefinitionModel.model_construct(code=None, file=None), ) - endpoints_no_source: list[tuple[EndpointDefinitionModel, Path]] = [ - (endpoint, Path("endpoints/broken.yml")) - ] - test_endpoints: list[tuple[EndpointDefinitionModel, Path]] = [ - ( - EndpointDefinitionModel.model_validate( - { - "mxcp": 1, - "tool": {"name": "python_file_tool", "source": {"file": "script.py"}}, - } - ), - Path("endpoints/python.yml"), - ), - ( - EndpointDefinitionModel.model_validate( - {"mxcp": 1, "tool": {"name": "sql_file_tool", "source": {"file": "query.sql"}}} - ), - Path("endpoints/sql.yml"), - ), - ( - EndpointDefinitionModel.model_validate( - { - "mxcp": 1, - "tool": { - "name": "explicit_override_tool", - "source": {"file": "script.py", "language": "sql"}, - }, - } - ), - Path("endpoints/override.yml"), - ), - ( - EndpointDefinitionModel.model_validate( - { - "mxcp": 1, - "tool": {"name": "default_sql_tool", "source": {"code": "some code"}}, - } - ), - Path("endpoints/default.yml"), - ), - ] + endpoints_no_source = [EndpointWithPath(endpoint, Path("endpoints/broken.yml"))] + executor = EndpointToolExecutor(self.engine, endpoints_no_source) - test_executor = EndpointToolExecutor(self.engine, test_endpoints) + with pytest.raises(ValueError) as exc_info: + await executor.execute_tool("broken_tool", {}) - # Verify the tools were properly registered - assert "python_file_tool" in test_executor._tool_map - assert "sql_file_tool" in test_executor._tool_map - assert "explicit_override_tool" in test_executor._tool_map - assert "default_sql_tool" in test_executor._tool_map + assert "No source found for endpoint" in str(exc_info.value) @pytest.mark.asyncio async def test_execute_tool_loads_file_content(self, tmp_path, monkeypatch): @@ -285,7 +249,7 @@ async def test_execute_tool_loads_file_content(self, tmp_path, monkeypatch): (tmp_path / "mxcp-site.yml").write_text("mxcp: 1\nproject: test\nprofile: default\n") monkeypatch.chdir(tmp_path) - endpoint = ( + endpoint = EndpointWithPath( EndpointDefinitionModel.model_validate( {"mxcp": 1, "tool": {"name": "hello_tool", "source": {"file": "sql/hello.sql"}}} ), @@ -310,7 +274,7 @@ async def test_execute_tool_loads_relative_parent_path(self, tmp_path, monkeypat sql_file.write_text("select 2 as val;") # endpoint references ../shared-sql/hi.sql relative to repo root - endpoint = ( + endpoint = EndpointWithPath( EndpointDefinitionModel.model_validate( {"mxcp": 1, "tool": {"name": "hi_tool", "source": {"file": "../shared-sql/hi.sql"}}} ), @@ -324,3 +288,39 @@ async def test_execute_tool_loads_relative_parent_path(self, tmp_path, monkeypat result = await executor.execute_tool("hi_tool", {}) assert result == {"val": 2} + + @pytest.mark.asyncio + async def test_python_file_executes_by_path(self, tmp_path, monkeypatch): + """Python sources should be passed as file paths to the engine.""" + (tmp_path / "mxcp-site.yml").write_text("mxcp: 1\nproject: test\nprofile: default\n") + py_dir = tmp_path / "python" + py_dir.mkdir() + script = py_dir / "hello.py" + script.write_text("def python_tool():\n" " return {'message': 'hi'}\n") + + endpoint = EndpointWithPath( + EndpointDefinitionModel.model_validate( + { + "mxcp": 1, + "tool": { + "name": "python_tool", + "source": {"file": "python/hello.py", "language": "python"}, + }, + } + ), + Path("endpoints/python.yml"), + ) + + monkeypatch.chdir(tmp_path) + engine = MockExecutionEngine() + executor = EndpointToolExecutor(engine, [endpoint]) + + result = await executor.execute_tool("python_tool", {}) + + assert result == "Mock result for " + engine.calls[0]["source_code"] + assert engine.calls[0]["language"] == "python" + source_code = engine.calls[0]["source_code"] + file_part, sep, function_name = source_code.partition(":") + assert sep == ":" + assert function_name == "python_tool" + assert Path(file_part).resolve() == script.resolve() From 204dd894817c6736a653874e6c6268555e55b1a6 Mon Sep 17 00:00:00 2001 From: Alex Zerntev Date: Sat, 29 Nov 2025 16:15:51 +0200 Subject: [PATCH 19/26] improvements --- tests/server/test_evals_service.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/server/test_evals_service.py b/tests/server/test_evals_service.py index bda8bf3e..24b39fc3 100644 --- a/tests/server/test_evals_service.py +++ b/tests/server/test_evals_service.py @@ -1,6 +1,9 @@ from pydantic_ai import ModelSettings -from mxcp.server.services.evals.service import _build_model_settings, _format_expected_answer_failure +from mxcp.server.services.evals.service import ( + _build_model_settings, + _format_expected_answer_failure, +) def test_model_settings_chat_drops_response_only_keys() -> None: From 495e290d09401ce555572ce486c1f2868242fc62 Mon Sep 17 00:00:00 2001 From: Alex Zerntev Date: Mon, 1 Dec 2025 15:39:47 +0200 Subject: [PATCH 20/26] fix --- src/mxcp/sdk/evals/executor.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/mxcp/sdk/evals/executor.py b/src/mxcp/sdk/evals/executor.py index b2ea6aa8..b8d46f02 100644 --- a/src/mxcp/sdk/evals/executor.py +++ b/src/mxcp/sdk/evals/executor.py @@ -215,11 +215,18 @@ def _build_tool_models(self, tools: list[ToolDefinition]) -> dict[str, type[Base fields: dict[str, Any] = {} for param in tool.parameters: py_type = self._map_param_type(param.type) - default = param.default if param.default is not None else None - field_info = Field( - default if default is not None or not param.required else ..., - description=param.description or None, - ) + field_info_kwargs: dict[str, Any] = {} + if param.description: + field_info_kwargs["description"] = param.description + + if param.default is not None: + default_value: Any = param.default + elif param.required: + default_value = ... + else: + default_value = None + + field_info = Field(default_value, **field_info_kwargs) fields[param.name] = (py_type, field_info) models[tool.name] = create_model(f"{tool.name}_Args", **fields) From 13ef7434c38d80ab2dc4bcdeb83bfc93e9ffca72 Mon Sep 17 00:00:00 2001 From: Alex Zerntev Date: Tue, 2 Dec 2025 20:39:04 +0200 Subject: [PATCH 21/26] fixes --- docs/guides/quality.md | 23 ++ src/mxcp/sdk/evals/_types.py | 1 + src/mxcp/sdk/evals/executor.py | 297 +++++++++++++------ src/mxcp/sdk/executor/plugins/python.py | 8 +- src/mxcp/server/definitions/evals/models.py | 1 + src/mxcp/server/executor/runners/tool.py | 2 +- src/mxcp/server/interfaces/cli/evals.py | 26 ++ src/mxcp/server/services/evals/service.py | 150 ++++++++-- tests/sdk/evals/test_executor.py | 313 ++++++++++++++------ tests/server/test_evals_service.py | 56 ++++ 10 files changed, 657 insertions(+), 220 deletions(-) diff --git a/docs/guides/quality.md b/docs/guides/quality.md index d83ce363..d52a8c46 100644 --- a/docs/guides/quality.md +++ b/docs/guides/quality.md @@ -918,6 +918,29 @@ tests: - "Friday" ``` +### Customizing the System Prompt + +Each eval suite can override the default LLM instructions to better match your domain or desired behavior. Add a `system_prompt` field at the suite level—if it is omitted, MXCP falls back to the built-in prompt that encourages concise, tool-aware answers. + +```yaml +mxcp: 1 +suite: relationship_navigation +description: "Ensure the assistant navigates relationships carefully" +model: gpt-4o +system_prompt: | + You are a Vertec specialist. Always explain which tool you used. + If a tool fails, read the error carefully before trying again. + +tests: + - name: compare_owners + prompt: "Are the owners of Project A and Project B the same?" + assertions: + must_call: + - tool: sql_search_objects + args: + object_type: "Project" +``` + ### Model Configuration Example Add models to your user config (`~/.mxcp/config.yml`) so evals know which providers to call: diff --git a/src/mxcp/sdk/evals/_types.py b/src/mxcp/sdk/evals/_types.py index a24e9975..49203971 100644 --- a/src/mxcp/sdk/evals/_types.py +++ b/src/mxcp/sdk/evals/_types.py @@ -19,6 +19,7 @@ class ParameterDefinition: description: str = "" default: Any | None = None required: bool = True + schema: dict[str, Any] | None = None @dataclass diff --git a/src/mxcp/sdk/evals/executor.py b/src/mxcp/sdk/evals/executor.py index b8d46f02..ba38627d 100644 --- a/src/mxcp/sdk/evals/executor.py +++ b/src/mxcp/sdk/evals/executor.py @@ -2,16 +2,18 @@ from __future__ import annotations -import asyncio import logging -import os from collections.abc import Callable -from contextlib import asynccontextmanager from dataclasses import dataclass, field from typing import Any, Protocol from pydantic import BaseModel, Field, create_model from pydantic_ai import Agent, ModelSettings, RunContext +from pydantic_ai.exceptions import ModelRetry, UnexpectedModelBehavior, UsageLimitExceeded +from pydantic_ai.models.anthropic import AnthropicModel +from pydantic_ai.models.openai import OpenAIChatModel, OpenAIResponsesModel +from pydantic_ai.providers.anthropic import AnthropicProvider +from pydantic_ai.providers.openai import OpenAIProvider from pydantic_ai.tools import Tool from pydantic_ai.tools import ToolDefinition as AgentToolDefinition @@ -19,6 +21,9 @@ from ._types import ToolDefinition +# Agent/tool retry configuration +DEFAULT_AGENT_RETRIES = 20 + logger = logging.getLogger(__name__) @@ -36,13 +41,14 @@ class ToolCallRecord: tool: str arguments: dict[str, Any] result: Any | None = None - error: str | None = None + error: Any | None = None @dataclass class AgentResult: answer: str tool_calls: list[ToolCallRecord] = field(default_factory=list) + error: str | None = None # Execution error if agent failed to produce an answer class ProviderConfig(BaseModel): @@ -59,7 +65,6 @@ class GradeResult(BaseModel): class LLMExecutor: - _env_lock: asyncio.Lock = asyncio.Lock() """Pydantic-based agent loop with tool support.""" def __init__( @@ -70,6 +75,8 @@ def __init__( available_tools: list[ToolDefinition], tool_executor: ToolExecutor, provider_config: ProviderConfig | None = None, + system_prompt: str | None = None, + agent_retries: int = DEFAULT_AGENT_RETRIES, ): self.available_tools = available_tools self.tool_executor = tool_executor @@ -80,8 +87,9 @@ def __init__( self._model_settings = model_settings self._tool_models = self._build_tool_models(available_tools) self._tool_schemas: dict[str, dict[str, Any]] = {} - self.system_prompt = self._build_system_prompt(available_tools) - self._ensure_responses_env_defaults() + self.system_prompt = system_prompt or self._build_system_prompt(available_tools) + self._agent_retries = max(1, agent_retries) + self._model_reference = self._build_model_reference() logger.info( "LLM executor initialized with model %s (%s) and %d tools", @@ -89,10 +97,9 @@ def __init__( self.model_type, len(available_tools), ) - self._env_lock: asyncio.Lock = getattr(self, "_env_lock", asyncio.Lock()) async def execute_prompt( - self, prompt: str, user_context: UserContext | None = None, max_turns: int = 10 + self, prompt: str, user_context: UserContext | None = None, max_turns: int = 20 ) -> AgentResult: """Run the agent loop for a prompt using pydantic-ai Agent.""" history: list[ToolCallRecord] = [] @@ -110,14 +117,13 @@ def _make_tool(tool_def: ToolDefinition) -> Tool: async def _fn(**kwargs: Any) -> Any: if max_turns is not None and len(history) >= max_turns: - record = ToolCallRecord( - id=None, - tool=tool_def.name, - arguments=kwargs, - error=f"Maximum tool calls exceeded ({max_turns})", + error_msg = f"Maximum tool calls exceeded ({max_turns})" + history.append( + ToolCallRecord( + id=None, tool=tool_def.name, arguments=kwargs, error=error_msg + ) ) - history.append(record) - raise RuntimeError(record.error) + raise RuntimeError(error_msg) record = ToolCallRecord(id=None, tool=tool_def.name, arguments=kwargs) try: @@ -130,9 +136,15 @@ async def _fn(**kwargs: Any) -> Any: ) record.result = result return result + except ModelRetry as exc: + error_response = self._build_tool_error_response(tool_def.name, exc.message) + record.error = error_response + raise except Exception as exc: # noqa: BLE001 - record.error = str(exc) - return {"error": str(exc)} + error_response = self._build_tool_error_response(tool_def.name, str(exc)) + record.error = error_response + retry_message = self._format_tool_retry_message(error_response) + raise ModelRetry(retry_message) from exc finally: history.append(record) @@ -146,35 +158,96 @@ async def _prepare( strict=True, ) - tool = Tool(_fn, name=tool_def.name, description=tool_def.description, prepare=_prepare) + tool = Tool( + _fn, + name=tool_def.name, + description=tool_def.description, + prepare=_prepare, + ) tool._mxcp_callable = _fn # type: ignore[attr-defined] return tool agent_tools = [_make_tool(t) for t in self.available_tools] - model_string = f"{self.model_type}:{self.model_name}" agent = self._agent_cls( - model=model_string, instructions=self.system_prompt, tools=agent_tools + model=self._model_reference, + instructions=self.system_prompt, + tools=agent_tools, + retries=self._agent_retries, ) try: - async with self._temporary_provider_env(): - agent_run = await agent.run( - prompt, deps=user_context, model_settings=self._model_settings - ) + agent_run = await agent.run( + prompt, deps=user_context, model_settings=self._model_settings + ) + answer = getattr(agent_run, "output", "") + + # Log detailed info about the result + logger.debug( + "Agent completed: answer_length=%d, tool_calls=%d, raw_output_type=%s", + len(str(answer)) if answer else 0, + len(history), + type(answer).__name__, + ) + + if not answer: + logger.warning( + "Agent returned empty output after %d tool calls. " + "Check conversation history above for details.", + len(history), + ) return AgentResult(answer=str(answer), tool_calls=history) + + except UnexpectedModelBehavior as exc: + error_msg = f"Agent exhausted retries ({self._agent_retries}): {exc}" + logger.error( + "Agent failed after exhausting retries (retries=%d, tool_calls=%d): %s", + self._agent_retries, + len(history), + exc, + ) + if logger.isEnabledFor(logging.DEBUG): + logger.debug("Tool call history on failure: %s", [tc.tool for tc in history]) + return AgentResult(answer="", tool_calls=history, error=error_msg) + except UsageLimitExceeded as exc: + error_msg = f"Usage limit exceeded: {exc}" + logger.error("Agent hit usage limit after %d tool calls: %s", len(history), exc) + return AgentResult(answer="", tool_calls=history, error=error_msg) except RuntimeError as exc: - logger.error("LLM execution aborted: %s", exc) - return AgentResult(answer="", tool_calls=history) + error_msg = f"Execution aborted: {exc}" + logger.error("LLM execution aborted after %d tool calls: %s", len(history), exc) + return AgentResult(answer="", tool_calls=history, error=error_msg) + except Exception as exc: + error_msg = f"Unexpected error ({type(exc).__name__}): {exc}" + logger.error( + "Unexpected error during agent execution after %d tool calls: %s: %s", + len(history), + type(exc).__name__, + exc, + ) + return AgentResult(answer="", tool_calls=history, error=error_msg) async def evaluate_expected_answer(self, answer: str, expected_answer: str) -> dict[str, str]: """Ask the model to grade an answer against an expected value.""" + logger.debug( + "Grading answer:\n Candidate: %s\n Expected: %s", + answer[:200] + "..." if len(answer) > 200 else answer, + expected_answer[:200] + "..." if len(expected_answer) > 200 else expected_answer, + ) + grader_system = ( - "You grade semantic equivalence between a candidate answer and an expected answer. " - "Focus on meaning, not wording or punctuation. Treat rephrasings, casing, and " - "minor formatting differences as correct if the meaning matches. Use 'partially correct' " - "only when the meaning overlaps but is incomplete or slightly off. " - "Return concise JSON with keys result (correct|wrong|partially correct), comment, and reasoning." + "You are a strict but fair judge of semantic coverage. Follow these rules:\n" + "1. Break the expected answer into individual facts (names, values, relationships, etc.).\n" + "2. For each fact, look for the same meaning anywhere in the candidate answer " + "(synonyms, paraphrases, or richer phrasing all count).\n" + "3. Extra information in the candidate answer MUST NOT reduce the score as long as every expected fact " + "is still stated correctly.\n" + "4. Return 'correct' only when all expected facts are present (even if the candidate says more).\n" + "5. Return 'partially correct' only when some expected facts appear but at least one fact is missing " + "or slightly inaccurate.\n" + "6. Return 'wrong' when the expected information is missing, contradicted, or the candidate claims the " + "information is unavailable.\n" + "Respond with concise JSON containing result (correct|wrong|partially correct), comment, and reasoning." ) grader_prompt = ( "Compare the candidate answer to the expected answer (semantic match, not exact string).\n" @@ -188,12 +261,29 @@ async def evaluate_expected_answer(self, answer: str, expected_answer: str) -> d model_string = f"{self.model_type}:{self.model_name}" agent = self._agent_cls( - model=model_string, instructions=grader_system, tools=(), output_type=GradeResult + model=model_string, + instructions=grader_system, + tools=(), + output_type=GradeResult, + retries=self._agent_retries, ) - async with self._temporary_provider_env(): + + try: run = await agent.run(grader_prompt, model_settings=self._model_settings) - out: GradeResult = getattr(run, "output", GradeResult()) - return out.model_dump() + out: GradeResult = getattr(run, "output", GradeResult()) + result = out.model_dump() + + logger.debug( + "Grading result: %s (comment: %s, reasoning: %s)", + result.get("result", "unknown"), + result.get("comment", ""), + result.get("reasoning", ""), + ) + + return result + except Exception as exc: + logger.error("Grading failed with error: %s: %s", type(exc).__name__, exc) + return {"result": "unknown", "comment": f"Grading error: {exc}", "reasoning": ""} def _build_system_prompt(self, tools: list[ToolDefinition]) -> str: if not tools: @@ -201,23 +291,35 @@ def _build_system_prompt(self, tools: list[ToolDefinition]) -> str: tool_names = ", ".join(tool.name for tool in tools) return ( - "You are an MXCP agent. Use the provided tools when they help answer the user. " - "Call tools only with JSON arguments that match their schema. " - "Tools available: " - f"{tool_names}. " - "When no tool fits, answer directly and concisely." + "You are an AI assistant that uses tools to answer questions accurately. " + f"Available tools: {tool_names}.\n\n" + "IMPORTANT GUIDELINES:\n" + "1. If a tool call fails, READ THE ERROR MESSAGE CAREFULLY. " + "It often contains hints about what went wrong and how to fix it.\n" + "2. If you don't know the correct parameters (like field names or schema), " + "look for tools that can help you discover this information first.\n" + "3. Be persistent: try different approaches if one doesn't work.\n" + "4. YOU MUST ALWAYS PROVIDE A FINAL ANSWER. Even if tools fail, " + "provide the best answer you can with the information available, " + "or explain what information you were unable to retrieve." ) def _build_tool_models(self, tools: list[ToolDefinition]) -> dict[str, type[BaseModel]]: models: dict[str, type[BaseModel]] = {} - for tool in tools: fields: dict[str, Any] = {} for param in tool.parameters: py_type = self._map_param_type(param.type) - field_info_kwargs: dict[str, Any] = {} + field_kwargs: dict[str, Any] = {} if param.description: - field_info_kwargs["description"] = param.description + field_kwargs["description"] = param.description + + if getattr(param, "schema", None): + schema_extra_raw: dict[str, Any] = param.schema or {} + schema_extra = dict(schema_extra_raw) + schema_extra.pop("type", None) + if schema_extra: + field_kwargs["json_schema_extra"] = schema_extra if param.default is not None: default_value: Any = param.default @@ -226,13 +328,70 @@ def _build_tool_models(self, tools: list[ToolDefinition]) -> dict[str, type[Base else: default_value = None - field_info = Field(default_value, **field_info_kwargs) + field_info = Field(default_value, **field_kwargs) fields[param.name] = (py_type, field_info) models[tool.name] = create_model(f"{tool.name}_Args", **fields) - return models + def _build_tool_error_response(self, tool_name: str, error_message: str) -> dict[str, Any]: + """Build a structured error response that guides the model to recover.""" + return { + "status": "error", + "tool": tool_name, + "error": error_message, + "suggestion": ( + "This tool call failed. Read the error message carefully - it often " + "contains hints about what went wrong. Consider: (1) calling this tool " + "with corrected arguments, (2) using a different tool to discover the " + "correct parameters first, or (3) trying a different approach." + ), + } + + def _format_tool_retry_message(self, error_response: dict[str, Any]) -> str: + """Convert a structured error response into a message for ModelRetry.""" + tool = error_response.get("tool", "unknown") + error_text = error_response.get("error", "Unknown error") + suggestion = error_response.get("suggestion") + base = f"Tool '{tool}' failed with error: {error_text}" + if suggestion: + return f"{base}. {suggestion}" + return base + + def _build_model_reference(self) -> Any: + """Instantiate a model object for providers that support direct configuration.""" + model_type = (self.model_type or "").lower() + provider_kwargs = self._provider_kwargs() + + try: + if model_type in {"openai", "openai-chat"}: + return OpenAIChatModel(self.model_name, provider=OpenAIProvider(**provider_kwargs)) + if model_type == "openai-responses": + return OpenAIResponsesModel( + self.model_name, provider=OpenAIProvider(**provider_kwargs) + ) + if model_type.startswith("anthropic"): + return AnthropicModel( + self.model_name, provider=AnthropicProvider(**provider_kwargs) + ) + except Exception as exc: # noqa: BLE001 + logger.warning( + "Failed to build custom provider for model '%s' (%s): %s. Falling back to string reference.", + self.model_name, + self.model_type, + exc, + ) + + return f"{self.model_type}:{self.model_name}" + + def _provider_kwargs(self) -> dict[str, Any]: + kwargs: dict[str, Any] = {} + if self.provider_config.base_url: + kwargs["base_url"] = self.provider_config.base_url + if self.provider_config.api_key: + kwargs["api_key"] = self.provider_config.api_key + return kwargs + def _map_param_type(self, param_type: str) -> Any: """Map simple tool parameter types to Python/Pydantic types.""" key = param_type.lower() @@ -247,51 +406,5 @@ def _map_param_type(self, param_type: str) -> Any: for aliases, py_type in mapping.items(): if key in aliases: return py_type - logger.debug("Unknown tool parameter type '%s'; defaulting to Any", param_type) return Any - - def _provider_env_overrides(self) -> dict[str, str]: - overrides: dict[str, str] = {} - model_type = self.model_type or "" - is_openai = model_type.startswith("openai") - is_anthropic = model_type.startswith("anthropic") - if is_openai: - if self.provider_config.api_key: - overrides["OPENAI_API_KEY"] = self.provider_config.api_key - if self.provider_config.base_url: - overrides["OPENAI_BASE_URL"] = self.provider_config.base_url - elif is_anthropic: - if self.provider_config.api_key: - overrides["ANTHROPIC_API_KEY"] = self.provider_config.api_key - if self.provider_config.base_url: - overrides["ANTHROPIC_BASE_URL"] = self.provider_config.base_url - return overrides - - def _ensure_responses_env_defaults(self) -> None: - if self.model_type != "openai-responses": - return - overrides = self._provider_env_overrides() - for key, value in overrides.items(): - if key not in os.environ: - os.environ[key] = value - - @asynccontextmanager - async def _temporary_provider_env(self) -> Any: - overrides = self._provider_env_overrides() - if not overrides: - yield - return - previous: dict[str, str | None] = {} - async with self._env_lock: - try: - for key, value in overrides.items(): - previous[key] = os.environ.get(key) - os.environ[key] = value - yield - finally: - for key, original in previous.items(): - if original is None: - os.environ.pop(key, None) - else: - os.environ[key] = original diff --git a/src/mxcp/sdk/executor/plugins/python.py b/src/mxcp/sdk/executor/plugins/python.py index d699142a..792bd950 100644 --- a/src/mxcp/sdk/executor/plugins/python.py +++ b/src/mxcp/sdk/executor/plugins/python.py @@ -410,7 +410,7 @@ async def execute( return result except (ImportError, SyntaxError) as e: # These are executor-level errors that should be wrapped - logger.error(f"Python execution failed: {e}") + logger.debug(f"Python execution failed: {e}") # Record error metrics record_counter( "mxcp.python.executions_total", @@ -495,7 +495,7 @@ async def _execute_from_file( return await self._execute_function(func, params, context) except Exception as e: - logger.error(f"Failed to execute file {file_path}: {e}") + logger.debug(f"Failed to execute file {file_path}: {e}") raise async def _execute_inline( @@ -565,7 +565,7 @@ async def _execute_inline( raise ValueError("No suitable function found in inline code") except Exception as e: - logger.error(f"Failed to execute inline code: {e}") + logger.debug(f"Failed to execute inline code: {e}") raise async def _execute_function( @@ -605,5 +605,5 @@ def sync_function_wrapper() -> Any: return result except Exception as e: - logger.error(f"Function execution failed: {e}") + logger.debug(f"Function execution failed: {e}") raise diff --git a/src/mxcp/server/definitions/evals/models.py b/src/mxcp/server/definitions/evals/models.py index a50d6b46..cbed8dff 100644 --- a/src/mxcp/server/definitions/evals/models.py +++ b/src/mxcp/server/definitions/evals/models.py @@ -52,6 +52,7 @@ class EvalSuiteModel(EvalBaseModel): description: str | None = None model: str | None = None expected_answer_model: str | None = None + system_prompt: str | None = None tests: list[EvalTestModel] @field_validator("suite") diff --git a/src/mxcp/server/executor/runners/tool.py b/src/mxcp/server/executor/runners/tool.py index 7574b949..ed68d033 100644 --- a/src/mxcp/server/executor/runners/tool.py +++ b/src/mxcp/server/executor/runners/tool.py @@ -127,5 +127,5 @@ async def execute_tool( return result except Exception as e: - logger.error(f"Tool '{tool_name}' execution failed: {e}") + logger.debug(f"Tool '{tool_name}' execution failed: {e}") raise diff --git a/src/mxcp/server/interfaces/cli/evals.py b/src/mxcp/server/interfaces/cli/evals.py index e44cbe4b..7a3ccff6 100644 --- a/src/mxcp/server/interfaces/cli/evals.py +++ b/src/mxcp/server/interfaces/cli/evals.py @@ -1,5 +1,6 @@ import asyncio import json +import logging import time from pathlib import Path from typing import Any @@ -18,6 +19,30 @@ ) from mxcp.server.services.evals import run_all_evals, run_eval_suite +_NOISY_EVAL_LOGGERS = ( + "openai", + "openai._base_client", + "openai._client", + "openai._streaming", + "httpx", + "httpcore", + "httpcore.connection", + "httpcore.connectionpool", + "urllib3", + "urllib3.connectionpool", +) + + +def _suppress_noisy_eval_logs(debug: bool) -> None: + """Clamp overly chatty third-party loggers unless debug is explicitly enabled.""" + if debug: + return + + for name in _NOISY_EVAL_LOGGERS: + noisy_logger = logging.getLogger(name) + noisy_logger.setLevel(logging.WARNING) + noisy_logger.propagate = True + def format_eval_results(results: dict[str, Any], debug: bool = False) -> str: """Format eval results for human-readable output""" @@ -286,6 +311,7 @@ def evals( user_config=user_config, debug=debug, ) + _suppress_noisy_eval_logs(debug) # Run async implementation asyncio.run( _evals_impl( diff --git a/src/mxcp/server/services/evals/service.py b/src/mxcp/server/services/evals/service.py index dedde99b..e520550f 100644 --- a/src/mxcp/server/services/evals/service.py +++ b/src/mxcp/server/services/evals/service.py @@ -12,6 +12,7 @@ from mxcp.server.core.config.models import SiteConfigModel, UserConfigModel from mxcp.server.core.config.site_config import find_repo_root from mxcp.server.definitions.endpoints.loader import EndpointLoader +from mxcp.server.definitions.endpoints.models import ParamDefinitionModel, TypeDefinitionModel from mxcp.server.definitions.evals.loader import discover_eval_files, load_eval_suite from mxcp.server.executor.engine import create_runtime_environment from mxcp.server.executor.runners.tool import EndpointToolExecutor, EndpointWithPath @@ -109,9 +110,83 @@ def _build_model_settings( if header_extras: recognized_options["extra_headers"] = header_extras + if "max_tokens" not in recognized_options: + recognized_options["max_tokens"] = 10_000 + return ModelSettings(**recognized_options) # type: ignore[typeddict-item,no-any-return] +def _type_definition_to_schema(type_definition: TypeDefinitionModel) -> dict[str, Any]: + schema: dict[str, Any] = {"type": type_definition.type} + + if type_definition.description: + schema["description"] = type_definition.description + if type_definition.default is not None: + schema["default"] = type_definition.default + if type_definition.enum: + schema["enum"] = list(type_definition.enum) + if type_definition.examples: + schema["examples"] = list(type_definition.examples) + + if type_definition.type == "string": + if type_definition.format: + schema["format"] = type_definition.format + if type_definition.minLength is not None: + schema["minLength"] = type_definition.minLength + if type_definition.maxLength is not None: + schema["maxLength"] = type_definition.maxLength + if type_definition.pattern: + schema["pattern"] = type_definition.pattern + elif type_definition.type in {"number", "integer"}: + if type_definition.minimum is not None: + schema["minimum"] = type_definition.minimum + if type_definition.maximum is not None: + schema["maximum"] = type_definition.maximum + if type_definition.exclusiveMinimum is not None: + schema["exclusiveMinimum"] = type_definition.exclusiveMinimum + if type_definition.exclusiveMaximum is not None: + schema["exclusiveMaximum"] = type_definition.exclusiveMaximum + if type_definition.multipleOf is not None: + schema["multipleOf"] = type_definition.multipleOf + elif type_definition.type == "array": + if type_definition.items is not None: + schema["items"] = _type_definition_to_schema(type_definition.items) + else: + schema["items"] = {"type": "string"} + if type_definition.minItems is not None: + schema["minItems"] = type_definition.minItems + if type_definition.maxItems is not None: + schema["maxItems"] = type_definition.maxItems + if type_definition.uniqueItems is not None: + schema["uniqueItems"] = type_definition.uniqueItems + elif type_definition.type == "object": + if type_definition.properties: + schema["properties"] = { + key: _type_definition_to_schema(value) + for key, value in type_definition.properties.items() + } + if type_definition.required: + schema["required"] = list(type_definition.required) + if type_definition.additionalProperties is not None: + schema["additionalProperties"] = type_definition.additionalProperties + + return schema + + +def _parameter_definition_from_model(param: ParamDefinitionModel) -> ParameterDefinition: + has_default = "default" in param.model_fields_set + schema = _type_definition_to_schema(param) + schema.pop("name", None) + return ParameterDefinition( + name=param.name, + type=param.type, + description=param.description or "", + default=param.default if has_default else None, + required=not has_default, + schema=schema or None, + ) + + def _load_endpoints(site_config: SiteConfigModel) -> list[EndpointWithPath]: """Load all available endpoints. @@ -151,18 +226,9 @@ def _convert_endpoints_to_tool_definitions( if endpoint_def.tool: tool = endpoint_def.tool - tool_parameters = [] - for param in tool.parameters or []: - has_default = "default" in param.model_fields_set - tool_parameters.append( - ParameterDefinition( - name=param.name, - type=param.type, - description=param.description or "", - default=param.default if has_default else None, - required=not has_default, - ) - ) + tool_parameters = [ + _parameter_definition_from_model(param) for param in (tool.parameters or []) + ] return_type = None if tool.return_: @@ -189,18 +255,9 @@ def _convert_endpoints_to_tool_definitions( elif endpoint_def.resource: resource = endpoint_def.resource - resource_parameters = [] - for param in resource.parameters or []: - has_default = "default" in param.model_fields_set - resource_parameters.append( - ParameterDefinition( - name=param.name, - type=param.type, - description=param.description or "", - default=param.default if has_default else None, - required=not has_default, - ) - ) + resource_parameters = [ + _parameter_definition_from_model(param) for param in (resource.parameters or []) + ] return_type = None if resource.return_: @@ -357,6 +414,7 @@ async def run_eval_suite( tool_definitions, tool_executor, provider_config=provider_config, + system_prompt=eval_suite.system_prompt, ) # Run each test @@ -395,19 +453,24 @@ async def run_eval_suite( response = agent_result.answer tool_calls = agent_result.tool_calls + execution_error = agent_result.error # Evaluate assertions failures: list[str] = [] assertions = test.assertions evaluation: dict[str, Any] | None = None - # Surface tool execution errors directly + # If the agent failed to execute, report it clearly + if execution_error: + failures.append(f"Agent execution failed: {execution_error}") + for call in tool_calls: if call.error: - failures.append( - _compact_text( - f"Tool '{call.tool}' failed: {call.error}", max_length=None - ) + logger.debug( + "Tool '%s' failed during test '%s': %s", + call.tool, + test.name, + call.error, ) # Check must_call assertions @@ -452,6 +515,16 @@ async def run_eval_suite( failures.append(f"Forbidden text '{forbidden_text}' found in response") if assertions.expected_answer: + logger.debug( + "Evaluating expected_answer assertion for test '%s': response_length=%d, expected='%s'", + test.name, + len(response), + ( + assertions.expected_answer[:100] + "..." + if len(assertions.expected_answer) > 100 + else assertions.expected_answer + ), + ) grader = grading_executor or executor evaluation = await grader.evaluate_expected_answer( response, assertions.expected_answer @@ -459,6 +532,14 @@ async def run_eval_suite( grade = (evaluation.get("result") or "").lower() comment = evaluation.get("comment") or "Model answer did not match expected" reasoning = evaluation.get("reasoning") or "" + + logger.debug( + "Expected answer evaluation for '%s': grade=%s, response='%s'", + test.name, + grade, + response[:150] + "..." if len(response) > 150 else response, + ) + detail = _format_expected_answer_failure( response, assertions.expected_answer, @@ -467,6 +548,12 @@ async def run_eval_suite( reasoning, ) if grade != "correct": + logger.info( + "Test '%s' failed expected_answer check: grade=%s, comment=%s", + test.name, + grade, + comment, + ) failures.append(detail) test_time = time.time() - test_start @@ -481,6 +568,7 @@ async def run_eval_suite( "time": test_time, "details": { "response": response, + "execution_error": execution_error, "tool_calls": [ { "id": call.id, @@ -589,9 +677,7 @@ async def run_all_evals( if eval_suite is None: continue suite_name = eval_suite.suite or "unnamed" - # Run the suite - if progress_callback: - progress_callback(f"suite:{suite_name}", f"🧪 Running eval suite: {suite_name}") + # Run the suite (progress_callback is passed through to run_eval_suite) result = await run_eval_suite( suite_name, user_config, diff --git a/tests/sdk/evals/test_executor.py b/tests/sdk/evals/test_executor.py index 251977b3..7a1431a7 100644 --- a/tests/sdk/evals/test_executor.py +++ b/tests/sdk/evals/test_executor.py @@ -1,9 +1,9 @@ import asyncio -import os from typing import Any import pytest from pydantic_ai import ModelSettings +from pydantic_ai.exceptions import ModelRetry from mxcp.sdk.auth import UserContext from mxcp.sdk.evals import ParameterDefinition, ToolDefinition @@ -34,9 +34,15 @@ async def run( args = self.tool_args.get(tool_name or "", {}) if fn: if asyncio.iscoroutinefunction(fn): - await fn(**args) + try: + await fn(**args) + except ModelRetry: + continue else: - fn(**args) + try: + fn(**args) + except ModelRetry: + continue return FakeRun(self.output) @@ -59,26 +65,62 @@ async def execute_tool( return {"echo": arguments} -def make_executor() -> LLMExecutor: - tools = [ +def make_executor( + tools: list[ToolDefinition] | None = None, + responses: dict[str, Any] | None = None, + system_prompt: str | None = None, + agent_retries: int = 3, +) -> LLMExecutor: + default_tools = [ ToolDefinition( name="get_weather", description="Weather lookup", parameters=[ParameterDefinition(name="location", type="string", required=True)], ) ] - tool_executor = MockToolExecutor({"get_weather": {"temperature": 20}}) + tool_defs = tools or default_tools + default_responses = {"get_weather": {"temperature": 20}} if tools is None else {} + tool_executor = MockToolExecutor(responses or default_responses) executor = LLMExecutor( "claude-test", "anthropic", ModelSettings(), - tools, + tool_defs, tool_executor, provider_config=ProviderConfig(api_key="key", base_url="https://api.anthropic.com"), + system_prompt=system_prompt, + agent_retries=agent_retries, ) return executor +def test_executor_uses_custom_system_prompt() -> None: + custom_prompt = "You are a specialized assistant." + executor = make_executor(system_prompt=custom_prompt) + + assert executor.system_prompt == custom_prompt + + +def test_executor_passes_agent_retries_to_agent() -> None: + observed: list[int | None] = [] + + executor = make_executor(agent_retries=5) + + def agent_factory(**kwargs: Any) -> FakeAgent: + observed.append(kwargs.get("retries")) + return FakeAgent( + tools=kwargs["tools"], + output="ok", + tool_args={"get_weather": {"location": "Paris"}}, + ) + + executor._agent_cls = agent_factory + + asyncio.run(executor.execute_prompt("Weather?")) + + assert observed == [5] + + def test_execute_prompt_with_tool_call() -> None: executor = make_executor() user_ctx = UserContext(provider="test", user_id="u1", username="user") @@ -132,7 +174,13 @@ def test_execute_prompt_tool_error() -> None: result = asyncio.run(executor.execute_prompt("Weather?")) - assert result.tool_calls and result.tool_calls[0].error == "boom" + assert result.tool_calls + error = result.tool_calls[0].error + # Error is now a dict with status, tool, error, and suggestion + assert isinstance(error, dict) + assert error["status"] == "error" + assert error["tool"] == "get_weather" + assert "boom" in error["error"] def test_tool_argument_validation_error_is_captured() -> None: @@ -146,7 +194,89 @@ def test_tool_argument_validation_error_is_captured() -> None: result = asyncio.run(executor.execute_prompt("Weather?")) assert result.tool_calls - assert result.tool_calls[0].error + error = result.tool_calls[0].error + # Error is now a dict with status, tool, error, and suggestion + assert isinstance(error, dict) + assert error["status"] == "error" + assert "Field required" in error["error"] + + +def test_tool_model_retry_reinvokes_tool() -> None: + class FlakyToolExecutor: + def __init__(self) -> None: + self.calls = 0 + + async def execute_tool( + self, tool_name: str, arguments: dict[str, Any], user_context: UserContext | None = None + ) -> Any: + self.calls += 1 + if self.calls == 1: + raise ValueError("temporary issue") + return {"status": "ok"} + + class RetryingAgent(FakeAgent): + """Agent that retries tool calls when ModelRetry is raised.""" + + def __init__( + self, + *, + tools: list[Any], + output: Any, + tool_args: dict[str, dict[str, Any]], + max_retries: int = 1, + ) -> None: + super().__init__(tools=tools, output=output, tool_args=tool_args) + self.max_retries = max_retries + + async def run( # type: ignore[override] + self, _prompt: str, deps: Any | None = None, model_settings: Any | None = None + ) -> FakeRun: + for tool in self.tools: + fn = getattr(tool, "_mxcp_callable", None) + tool_name = getattr(tool, "name", None) or getattr( + getattr(tool, "tool_def", None), "name", None + ) + args = self.tool_args.get(tool_name or "", {}) + if not fn: + continue + + attempt = 0 + while attempt < self.max_retries: + try: + if asyncio.iscoroutinefunction(fn): + await fn(**args) + else: + fn(**args) + break + except ModelRetry: + attempt += 1 + if attempt >= self.max_retries: + raise + continue + return FakeRun(self.output) + + executor = make_executor() + flaky_executor = FlakyToolExecutor() + executor.tool_executor = flaky_executor # type: ignore[assignment] + executor._agent_cls = lambda **kwargs: RetryingAgent( + tools=kwargs["tools"], + output="ok", + tool_args={"get_weather": {"location": "Paris"}}, + max_retries=kwargs.get("retries", 1), + ) + + result = asyncio.run(executor.execute_prompt("Weather?")) + + # Tool was called twice: first raised ModelRetry, second succeeded + assert flaky_executor.calls == 2 + assert len(result.tool_calls) == 2 + # First call should have error recorded as dict + first_error = result.tool_calls[0].error + assert isinstance(first_error, dict) + assert first_error["status"] == "error" + assert "temporary issue" in first_error["error"] + # Second call should succeed + assert result.tool_calls[1].result == {"status": "ok"} def test_expected_answer_grading() -> None: @@ -162,61 +292,6 @@ def test_expected_answer_grading() -> None: assert result["comment"] -def test_temporary_env_sets_and_restores() -> None: - prev_openai_key = os.environ.get("OPENAI_API_KEY") - prev_openai_url = os.environ.get("OPENAI_BASE_URL") - prev_anthropic_key = os.environ.get("ANTHROPIC_API_KEY") - prev_anthropic_url = os.environ.get("ANTHROPIC_BASE_URL") - - os.environ["OPENAI_API_KEY"] = "original" - os.environ["OPENAI_BASE_URL"] = "orig-url" - - try: - executor = make_executor() - - class EnvAgent(FakeAgent): - async def run( # type: ignore[override] - self, _prompt: str, deps: Any | None = None, model_settings: Any | None = None - ) -> FakeRun: - # Capture environment while the agent runs - captured["during"] = ( - os.environ.get("ANTHROPIC_API_KEY"), - os.environ.get("ANTHROPIC_BASE_URL"), - ) - return await super().run(_prompt, deps=deps, model_settings=model_settings) - - captured: dict[str, tuple[str | None, str | None]] = {} - executor._agent_cls = lambda **kwargs: EnvAgent( - tools=kwargs["tools"], output="done", tool_args={} - ) - - asyncio.run(executor.execute_prompt("Weather?")) - - assert captured["during"] == ("key", "https://api.anthropic.com") - # Ensure globals restored - assert os.environ["OPENAI_API_KEY"] == "original" - assert os.environ["OPENAI_BASE_URL"] == "orig-url" - assert os.environ.get("ANTHROPIC_API_KEY") == prev_anthropic_key - assert os.environ.get("ANTHROPIC_BASE_URL") == prev_anthropic_url - finally: - if prev_anthropic_key is None: - os.environ.pop("ANTHROPIC_API_KEY", None) - else: - os.environ["ANTHROPIC_API_KEY"] = prev_anthropic_key - if prev_anthropic_url is None: - os.environ.pop("ANTHROPIC_BASE_URL", None) - else: - os.environ["ANTHROPIC_BASE_URL"] = prev_anthropic_url - if prev_openai_key is None: - os.environ.pop("OPENAI_API_KEY", None) - else: - os.environ["OPENAI_API_KEY"] = prev_openai_key - if prev_openai_url is None: - os.environ.pop("OPENAI_BASE_URL", None) - else: - os.environ["OPENAI_BASE_URL"] = prev_openai_url - - def test_max_turns_limits_tool_calls() -> None: class MultiCallAgent: def __init__(self, tools: list[Any]) -> None: @@ -230,9 +305,15 @@ async def run( fn = getattr(tool, "_mxcp_callable", None) if fn: if asyncio.iscoroutinefunction(fn): - await fn() + try: + await fn() + except ModelRetry: + continue else: - fn() + try: + fn() + except ModelRetry: + continue return FakeRun("done") executor = make_executor() @@ -244,30 +325,80 @@ async def run( assert result.tool_calls[-1].error -def test_apply_provider_env_for_openai_responses() -> None: - prev_key = os.environ.get("OPENAI_API_KEY") - prev_url = os.environ.get("OPENAI_BASE_URL") - try: - os.environ.pop("OPENAI_API_KEY", None) - os.environ.pop("OPENAI_BASE_URL", None) - - LLMExecutor( - "gpt-5", - "openai-responses", - ModelSettings(), - [], - MockToolExecutor(), - provider_config=ProviderConfig(api_key="key", base_url="https://api.openai.com"), +def test_tool_model_schema_preserves_array_items_type() -> None: + predicates_param = ParameterDefinition( + name="predicates", + type="array", + description="Filters", + required=True, + schema={ + "type": "array", + "description": "Filters", + "items": {"type": "string", "description": "SQL predicate"}, + }, + ) + members_param = ParameterDefinition( + name="members", + type="array", + description="Projection list", + required=True, + schema={"type": "array", "items": {"type": "string"}}, + ) + tools = [ + ToolDefinition( + name="sql_search", + description="Search objects", + parameters=[predicates_param, members_param], + ) + ] + executor = make_executor(tools=tools) + + schema = executor._tool_models["sql_search"].model_json_schema() + props = schema["properties"] + + assert props["predicates"]["type"] == "array" + assert props["predicates"]["items"]["type"] == "string" + assert props["predicates"]["items"]["description"] == "SQL predicate" + assert props["members"]["items"]["type"] == "string" + assert "predicates" in schema["required"] + assert "members" in schema["required"] + + +def test_tool_model_schema_supports_optional_object_params() -> None: + context_param = ParameterDefinition( + name="context", + type="object", + description="Optional filters", + required=False, + default={}, + schema={ + "type": "object", + "properties": { + "limit": {"type": "integer", "minimum": 1, "maximum": 100}, + "sort": {"type": "string"}, + }, + "required": ["limit"], + "additionalProperties": False, + }, + ) + tools = [ + ToolDefinition( + name="fetch_objects", + description="Fetch objects", + parameters=[ + ParameterDefinition(name="object_type", type="string", required=True), + context_param, + ], ) + ] + executor = make_executor(tools=tools) + + schema = executor._tool_models["fetch_objects"].model_json_schema() + props = schema["properties"] - assert os.environ.get("OPENAI_API_KEY") == "key" - assert os.environ.get("OPENAI_BASE_URL") == "https://api.openai.com" - finally: - if prev_key is None: - os.environ.pop("OPENAI_API_KEY", None) - else: - os.environ["OPENAI_API_KEY"] = prev_key - if prev_url is None: - os.environ.pop("OPENAI_BASE_URL", None) - else: - os.environ["OPENAI_BASE_URL"] = prev_url + assert "context" in props + assert props["context"]["type"] == "object" + assert props["context"]["properties"]["limit"]["minimum"] == 1 + assert props["context"]["properties"]["limit"]["maximum"] == 100 + assert props["context"]["required"] == ["limit"] + assert "context" not in schema.get("required", []) diff --git a/tests/server/test_evals_service.py b/tests/server/test_evals_service.py index 24b39fc3..4224971b 100644 --- a/tests/server/test_evals_service.py +++ b/tests/server/test_evals_service.py @@ -1,8 +1,11 @@ from pydantic_ai import ModelSettings +from mxcp.server.definitions.endpoints.models import ParamDefinitionModel, TypeDefinitionModel from mxcp.server.services.evals.service import ( _build_model_settings, _format_expected_answer_failure, + _parameter_definition_from_model, + _type_definition_to_schema, ) @@ -18,6 +21,7 @@ def test_model_settings_chat_drops_response_only_keys() -> None: extra_body = settings.get("extra_body") assert extra_body and "reasoning" in extra_body assert settings.get("timeout") == 30 + assert settings.get("max_tokens") == 10_000 def test_model_settings_responses_keeps_extras() -> None: @@ -31,6 +35,7 @@ def test_model_settings_responses_keeps_extras() -> None: extra_body = settings.get("extra_body") assert extra_body and "reasoning" in extra_body + assert settings.get("max_tokens") == 10_000 def test_model_settings_anthropic_output_config_and_betas() -> None: @@ -49,6 +54,19 @@ def test_model_settings_anthropic_output_config_and_betas() -> None: assert extra_body and extra_body.get("output_config") == {"effort": "medium"} headers = settings.get("extra_headers") assert headers and headers.get("anthropic-beta") == "effort-2025-11-24" + assert settings.get("max_tokens") == 10_000 + + +def test_model_settings_respects_user_max_tokens_override() -> None: + allowed = set(ModelSettings.__annotations__.keys()) + settings = _build_model_settings( + "gpt-4o", + "openai", + {"max_tokens": 2048}, + allowed, + ) + + assert settings.get("max_tokens") == 2048 def test_expected_answer_failure_formatting_is_multiline() -> None: @@ -67,3 +85,41 @@ def test_expected_answer_failure_formatting_is_multiline() -> None: "Comment: bad", "Reasoning: missed value", ] + + +def test_parameter_definition_from_model_includes_array_items_schema() -> None: + param = ParamDefinitionModel( + name="predicates", + type="array", + description="Filters", + items=TypeDefinitionModel(type="string", description="SQL predicate"), + ) + + definition = _parameter_definition_from_model(param) + + assert definition.required is True + assert definition.default is None + assert definition.schema == { + "type": "array", + "description": "Filters", + "items": {"type": "string", "description": "SQL predicate"}, + } + + +def test_parameter_definition_from_model_marks_optional_when_default_present() -> None: + param = ParamDefinitionModel( + name="limit", + type="integer", + description="Result limit", + default=25, + minimum=1, + maximum=100, + ) + + definition = _parameter_definition_from_model(param) + + assert definition.required is False + assert definition.default == 25 + assert definition.schema["type"] == "integer" + assert definition.schema["minimum"] == 1 + assert definition.schema["maximum"] == 100 From a86f8721aa53473f561b2aa31d96901dd75d3ecc Mon Sep 17 00:00:00 2001 From: Alex Zerntev Date: Tue, 2 Dec 2025 20:47:30 +0200 Subject: [PATCH 22/26] fixes --- src/mxcp/sdk/evals/executor.py | 5 ++--- src/mxcp/server/interfaces/cli/evals.py | 17 +++++++++++++---- tests/sdk/evals/test_executor.py | 19 +++++++++++++++++++ 3 files changed, 34 insertions(+), 7 deletions(-) diff --git a/src/mxcp/sdk/evals/executor.py b/src/mxcp/sdk/evals/executor.py index ba38627d..44efdad9 100644 --- a/src/mxcp/sdk/evals/executor.py +++ b/src/mxcp/sdk/evals/executor.py @@ -22,7 +22,7 @@ from ._types import ToolDefinition # Agent/tool retry configuration -DEFAULT_AGENT_RETRIES = 20 +DEFAULT_AGENT_RETRIES = 30 logger = logging.getLogger(__name__) @@ -259,9 +259,8 @@ async def evaluate_expected_answer(self, answer: str, expected_answer: str) -> d '{"result":"correct|wrong|partially correct","comment":"short","reasoning":"short"}' ) - model_string = f"{self.model_type}:{self.model_name}" agent = self._agent_cls( - model=model_string, + model=self._model_reference, instructions=grader_system, tools=(), output_type=GradeResult, diff --git a/src/mxcp/server/interfaces/cli/evals.py b/src/mxcp/server/interfaces/cli/evals.py index 7a3ccff6..f7efbf44 100644 --- a/src/mxcp/server/interfaces/cli/evals.py +++ b/src/mxcp/server/interfaces/cli/evals.py @@ -1,6 +1,7 @@ import asyncio import json import logging +import sys import time from pathlib import Path from typing import Any @@ -388,18 +389,26 @@ async def _evals_impl( _lines: dict[str, str] = {} _order: list[str] = [] _lines_printed = 0 + _last_rendered: dict[str, str] = {} _is_tty = click.get_text_stream("stdout").isatty() def _render_progress() -> None: - nonlocal _lines_printed + nonlocal _lines_printed, _last_rendered if not _is_tty: return if _lines_printed: - click.echo(f"\033[{_lines_printed}A\r", nl=False) - for key in _order: + sys.stdout.write(f"\033[{_lines_printed}F") + total = len(_order) + for idx, key in enumerate(_order): line = _lines.get(key, "") - click.echo("\033[2K\r" + line) + previous = _last_rendered.get(key) + if line != previous: + sys.stdout.write("\033[2K\r" + line) + if idx < total - 1: + sys.stdout.write("\033[E") + sys.stdout.flush() _lines_printed = len(_order) + _last_rendered = dict(_lines) def _progress(key: str, msg: str) -> None: if not _is_tty: diff --git a/tests/sdk/evals/test_executor.py b/tests/sdk/evals/test_executor.py index 7a1431a7..53c2ab20 100644 --- a/tests/sdk/evals/test_executor.py +++ b/tests/sdk/evals/test_executor.py @@ -292,6 +292,25 @@ def test_expected_answer_grading() -> None: assert result["comment"] +def test_expected_answer_uses_model_reference() -> None: + executor = make_executor() + observed: list[Any] = [] + + def agent_factory(**kwargs: Any) -> FakeAgent: + observed.append(kwargs.get("model")) + return FakeAgent( + tools=kwargs.get("tools", []), + output=GradeResult(result="correct", comment="ok", reasoning="match"), + tool_args={}, + ) + + executor._agent_cls = agent_factory + + result = asyncio.run(executor.evaluate_expected_answer("value", "value")) + assert result["result"] == "correct" + assert observed == [executor._model_reference] + + def test_max_turns_limits_tool_calls() -> None: class MultiCallAgent: def __init__(self, tools: list[Any]) -> None: From e964dc2a29aa766db8e2ff1c335120ea85f075e6 Mon Sep 17 00:00:00 2001 From: Alex Zerntev Date: Tue, 2 Dec 2025 21:09:43 +0200 Subject: [PATCH 23/26] fixes --- src/mxcp/sdk/evals/executor.py | 21 +++++----- src/mxcp/server/interfaces/cli/evals.py | 54 ++++++++++++++++--------- 2 files changed, 44 insertions(+), 31 deletions(-) diff --git a/src/mxcp/sdk/evals/executor.py b/src/mxcp/sdk/evals/executor.py index e5ac87ca..60d4fae0 100644 --- a/src/mxcp/sdk/evals/executor.py +++ b/src/mxcp/sdk/evals/executor.py @@ -239,18 +239,15 @@ async def evaluate_expected_answer(self, answer: str, expected_answer: str) -> d ) grader_system = ( - "You are a strict but fair judge of semantic coverage. Follow these rules:\n" - "1. Break the expected answer into individual facts (names, values, relationships, etc.).\n" - "2. For each fact, look for the same meaning anywhere in the candidate answer " - "(synonyms, paraphrases, or richer phrasing all count).\n" - "3. Extra information in the candidate answer MUST NOT reduce the score as long as every expected fact " - "is still stated correctly.\n" - "4. Return 'correct' only when all expected facts are present (even if the candidate says more).\n" - "5. Return 'partially correct' only when some expected facts appear but at least one fact is missing " - "or slightly inaccurate.\n" - "6. Return 'wrong' when the expected information is missing, contradicted, or the candidate claims the " - "information is unavailable.\n" - "Respond with concise JSON containing result (correct|wrong|partially correct), comment, and reasoning." + "You check if the candidate answer CONTAINS the expected information.\n\n" + "GRADING RULES:\n" + "- 'correct': The expected fact(s) appear in the candidate answer. " + "Extra details, context, or longer explanations are FINE and do not affect the grade.\n" + "- 'partially correct': Only use when the expected answer has MULTIPLE facts and some are missing.\n" + "- 'wrong': The expected information is absent, contradicted, or the candidate says it's unavailable.\n\n" + "IMPORTANT: If the expected answer is a single value (e.g., a name, status, role) and that exact value " + "appears anywhere in the candidate answer, grade it as 'correct' regardless of surrounding text.\n\n" + 'Return JSON: {"result": "correct|wrong|partially correct", "comment": "...", "reasoning": "..."}' ) grader_prompt = ( "Compare the candidate answer to the expected answer (semantic match, not exact string).\n" diff --git a/src/mxcp/server/interfaces/cli/evals.py b/src/mxcp/server/interfaces/cli/evals.py index 5ef17222..f3ccccf5 100644 --- a/src/mxcp/server/interfaces/cli/evals.py +++ b/src/mxcp/server/interfaces/cli/evals.py @@ -385,35 +385,52 @@ async def _evals_impl( _lines: dict[str, str] = {} _order: list[str] = [] _lines_printed = 0 - _last_rendered: dict[str, str] = {} _is_tty = click.get_text_stream("stdout").isatty() + def _clear_all_progress() -> None: + """Clear all progress lines and reset cursor to start.""" + nonlocal _lines_printed + if not _is_tty or _lines_printed == 0: + return + # Move up and clear each line + for _ in range(_lines_printed): + sys.stdout.write("\033[F\033[2K") + sys.stdout.flush() + _lines_printed = 0 + def _render_progress() -> None: - nonlocal _lines_printed, _last_rendered - if not _is_tty: + """Render all in-progress items.""" + nonlocal _lines_printed + if not _is_tty or not _order: return - if _lines_printed: - sys.stdout.write(f"\033[{_lines_printed}F") - total = len(_order) - for idx, key in enumerate(_order): + for key in _order: line = _lines.get(key, "") - previous = _last_rendered.get(key) - if line != previous: - sys.stdout.write("\033[2K\r" + line) - if idx < total - 1: - sys.stdout.write("\033[E") + sys.stdout.write(line + "\n") sys.stdout.flush() _lines_printed = len(_order) - _last_rendered = dict(_lines) def _progress(key: str, msg: str) -> None: + nonlocal _order, _lines + is_final = msg.lstrip().startswith("✓") or msg.lstrip().startswith("✗") + if not _is_tty: click.echo(msg) return - if key not in _order: - _order.append(key) - _lines[key] = msg - _render_progress() + + if is_final: + # Clear progress, print final result, re-render remaining + _clear_all_progress() + _order = [k for k in _order if k != key] + _lines.pop(key, None) + click.echo(msg) + _render_progress() + else: + # Update or add progress line + _clear_all_progress() + if key not in _order: + _order.append(key) + _lines[key] = msg + _render_progress() if suite_name: results = await run_eval_suite( @@ -440,8 +457,7 @@ def _progress(key: str, msg: str) -> None: if json_output: output_result(results, json_output, debug) else: - if _lines_printed: - click.echo() + _clear_all_progress() click.echo(format_eval_results(results, debug)) # Exit with error code if any tests failed From 6fe93416d8ab517e5a4790fdb39998332dcd99e4 Mon Sep 17 00:00:00 2001 From: Alex Zerntev Date: Wed, 3 Dec 2025 09:56:11 +0200 Subject: [PATCH 24/26] fix --- src/mxcp/server/interfaces/cli/evals.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/mxcp/server/interfaces/cli/evals.py b/src/mxcp/server/interfaces/cli/evals.py index f3ccccf5..371d7236 100644 --- a/src/mxcp/server/interfaces/cli/evals.py +++ b/src/mxcp/server/interfaces/cli/evals.py @@ -1,5 +1,6 @@ import json import logging +import re import sys import time from pathlib import Path @@ -33,6 +34,8 @@ "urllib3.connectionpool", ) +_ANSI_ESCAPE = re.compile(r"\x1B\[[0-?]*[ -/]*[@-~]") + def _suppress_noisy_eval_logs(debug: bool) -> None: """Clamp overly chatty third-party loggers unless debug is explicitly enabled.""" @@ -411,7 +414,8 @@ def _render_progress() -> None: def _progress(key: str, msg: str) -> None: nonlocal _order, _lines - is_final = msg.lstrip().startswith("✓") or msg.lstrip().startswith("✗") + clean = _ANSI_ESCAPE.sub("", msg).lstrip() + is_final = clean.startswith("✓") or clean.startswith("✗") if not _is_tty: click.echo(msg) From 72c2bfa5f598a8e5cbe1c708a613b4179b4b2e4f Mon Sep 17 00:00:00 2001 From: Alex Zerntev Date: Wed, 3 Dec 2025 11:01:26 +0200 Subject: [PATCH 25/26] fixes --- src/mxcp/sdk/evals/_types.py | 6 +- src/mxcp/sdk/evals/executor.py | 32 ++- src/mxcp/server/interfaces/cli/evals.py | 121 ++++++----- src/mxcp/server/services/evals/service.py | 234 +++++++++++----------- tests/sdk/evals/test_executor.py | 134 ++++++++++++- 5 files changed, 334 insertions(+), 193 deletions(-) diff --git a/src/mxcp/sdk/evals/_types.py b/src/mxcp/sdk/evals/_types.py index 594c546e..60bab68e 100644 --- a/src/mxcp/sdk/evals/_types.py +++ b/src/mxcp/sdk/evals/_types.py @@ -9,6 +9,9 @@ from mxcp.sdk.validator import TypeSchemaModel +# Type alias for JSON Schema representation +JsonSchema = dict[str, Any] + @dataclass class ParameterDefinition: @@ -19,7 +22,8 @@ class ParameterDefinition: description: str = "" default: Any | None = None required: bool = True - schema: dict[str, Any] | None = None + schema: JsonSchema | None = None + """Optional JSON Schema for complex parameter validation.""" @dataclass diff --git a/src/mxcp/sdk/evals/executor.py b/src/mxcp/sdk/evals/executor.py index 60d4fae0..8de4e02e 100644 --- a/src/mxcp/sdk/evals/executor.py +++ b/src/mxcp/sdk/evals/executor.py @@ -24,6 +24,9 @@ # Agent/tool retry configuration DEFAULT_AGENT_RETRIES = 30 +# Type alias for model references (either a model object or a string identifier) +ModelReference = OpenAIChatModel | OpenAIResponsesModel | AnthropicModel | str + logger = logging.getLogger(__name__) @@ -106,6 +109,8 @@ async def execute_prompt( ) -> AgentResult: """Run the agent loop for a prompt using pydantic-ai Agent.""" history: list[ToolCallRecord] = [] + # Local callable mapping for this execution (passed to agent factory for testing) + tool_callables: dict[str, Callable[..., Any]] = {} def _make_tool(tool_def: ToolDefinition) -> Tool: args_model = self._tool_models.get(tool_def.name) @@ -167,16 +172,23 @@ async def _prepare( description=tool_def.description, prepare=_prepare, ) - tool._mxcp_callable = _fn # type: ignore[attr-defined] + tool_callables[tool_def.name] = _fn return tool agent_tools = [_make_tool(t) for t in self.available_tools] - agent = self._agent_cls( - model=self._model_reference, - instructions=self.system_prompt, - tools=agent_tools, - retries=self._agent_retries, - ) + + # Build agent kwargs - only pass _tool_callables for test agents (not real pydantic-ai Agent) + agent_kwargs: dict[str, Any] = { + "model": self._model_reference, + "instructions": self.system_prompt, + "tools": agent_tools, + "retries": self._agent_retries, + } + if self._agent_cls is not Agent: + # Test agent factory - pass tool callables for invocation + agent_kwargs["_tool_callables"] = tool_callables + + agent = self._agent_cls(**agent_kwargs) try: agent_run = await agent.run( @@ -357,7 +369,7 @@ def _format_tool_retry_message(self, error_response: dict[str, Any]) -> str: return f"{base}. {suggestion}" return base - def _build_model_reference(self) -> Any: + def _build_model_reference(self) -> ModelReference: """Instantiate a model object for providers that support direct configuration.""" model_type = (self.model_type or "").lower() provider_kwargs = self._provider_kwargs() @@ -389,6 +401,8 @@ def _provider_kwargs(self) -> dict[str, Any]: kwargs["base_url"] = self.provider_config.base_url if self.provider_config.api_key: kwargs["api_key"] = self.provider_config.api_key + if self.provider_config.timeout: + kwargs["timeout"] = self.provider_config.timeout return kwargs def _map_param_type(self, param_type: str) -> Any: @@ -405,5 +419,5 @@ def _map_param_type(self, param_type: str) -> Any: for aliases, py_type in mapping.items(): if key in aliases: return py_type - logger.debug("Unknown tool parameter type '%s'; defaulting to Any", param_type) + logger.warning("Unknown tool parameter type '%s'; defaulting to Any", param_type) return Any diff --git a/src/mxcp/server/interfaces/cli/evals.py b/src/mxcp/server/interfaces/cli/evals.py index 371d7236..1ad0dad4 100644 --- a/src/mxcp/server/interfaces/cli/evals.py +++ b/src/mxcp/server/interfaces/cli/evals.py @@ -36,6 +36,67 @@ _ANSI_ESCAPE = re.compile(r"\x1B\[[0-?]*[ -/]*[@-~]") +# ANSI escape sequences for terminal control +_ANSI_MOVE_UP_CLEAR = "\033[F\033[2K" + +# Characters that indicate a final (completed) progress message +_FINAL_INDICATORS = ("✓", "✗") + + +class ProgressRenderer: + """Stateful progress renderer that overwrites lines in TTY mode.""" + + def __init__(self, is_tty: bool = True) -> None: + self._lines: dict[str, str] = {} + self._order: list[str] = [] + self._lines_printed: int = 0 + self._is_tty = is_tty + + def clear_all(self) -> None: + """Clear all progress lines and reset cursor to start.""" + if not self._is_tty or self._lines_printed == 0: + return + for _ in range(self._lines_printed): + sys.stdout.write(_ANSI_MOVE_UP_CLEAR) + sys.stdout.flush() + self._lines_printed = 0 + + def _render(self) -> None: + """Render all in-progress items.""" + if not self._is_tty or not self._order: + return + for key in self._order: + line = self._lines.get(key, "") + sys.stdout.write(line + "\n") + sys.stdout.flush() + self._lines_printed = len(self._order) + + def _is_final_message(self, msg: str) -> bool: + """Check if message indicates completion (success or failure).""" + clean = _ANSI_ESCAPE.sub("", msg).lstrip() + return any(clean.startswith(indicator) for indicator in _FINAL_INDICATORS) + + def update(self, key: str, msg: str) -> None: + """Update progress for a key. Final messages are printed permanently.""" + if not self._is_tty: + click.echo(msg) + return + + if self._is_final_message(msg): + # Clear progress, print final result, re-render remaining + self.clear_all() + self._order = [k for k in self._order if k != key] + self._lines.pop(key, None) + click.echo(msg) + self._render() + else: + # Update or add progress line + self.clear_all() + if key not in self._order: + self._order.append(key) + self._lines[key] = msg + self._render() + def _suppress_noisy_eval_logs(debug: bool) -> None: """Clamp overly chatty third-party loggers unless debug is explicitly enabled.""" @@ -384,57 +445,9 @@ async def _evals_impl( # Run evals start_time = time.time() - # Stateful progress renderer that overwrites the same line (TTY only). - _lines: dict[str, str] = {} - _order: list[str] = [] - _lines_printed = 0 - _is_tty = click.get_text_stream("stdout").isatty() - - def _clear_all_progress() -> None: - """Clear all progress lines and reset cursor to start.""" - nonlocal _lines_printed - if not _is_tty or _lines_printed == 0: - return - # Move up and clear each line - for _ in range(_lines_printed): - sys.stdout.write("\033[F\033[2K") - sys.stdout.flush() - _lines_printed = 0 - - def _render_progress() -> None: - """Render all in-progress items.""" - nonlocal _lines_printed - if not _is_tty or not _order: - return - for key in _order: - line = _lines.get(key, "") - sys.stdout.write(line + "\n") - sys.stdout.flush() - _lines_printed = len(_order) - - def _progress(key: str, msg: str) -> None: - nonlocal _order, _lines - clean = _ANSI_ESCAPE.sub("", msg).lstrip() - is_final = clean.startswith("✓") or clean.startswith("✗") - - if not _is_tty: - click.echo(msg) - return - - if is_final: - # Clear progress, print final result, re-render remaining - _clear_all_progress() - _order = [k for k in _order if k != key] - _lines.pop(key, None) - click.echo(msg) - _render_progress() - else: - # Update or add progress line - _clear_all_progress() - if key not in _order: - _order.append(key) - _lines[key] = msg - _render_progress() + # Create progress renderer for TTY-aware output + is_tty = click.get_text_stream("stdout").isatty() + progress = ProgressRenderer(is_tty=is_tty) if suite_name: results = await run_eval_suite( @@ -444,7 +457,7 @@ def _progress(key: str, msg: str) -> None: profile, cli_user_context=cli_user_context, override_model=model, - progress_callback=_progress, + progress_callback=progress.update, ) else: results = await run_all_evals( @@ -453,7 +466,7 @@ def _progress(key: str, msg: str) -> None: profile, cli_user_context=cli_user_context, override_model=model, - progress_callback=_progress, + progress_callback=progress.update, ) elapsed_time = time.time() - start_time results["elapsed_time"] = elapsed_time @@ -461,7 +474,7 @@ def _progress(key: str, msg: str) -> None: if json_output: output_result(results, json_output, debug) else: - _clear_all_progress() + progress.clear_all() click.echo(format_eval_results(results, debug)) # Exit with error code if any tests failed diff --git a/src/mxcp/server/services/evals/service.py b/src/mxcp/server/services/evals/service.py index 924ed3ee..0d5b2468 100644 --- a/src/mxcp/server/services/evals/service.py +++ b/src/mxcp/server/services/evals/service.py @@ -30,7 +30,7 @@ def _create_model_config( user_config: User configuration containing model settings Returns: - Tuple of (model_name, model_type, options, api_key, base_url, timeout) + Tuple of (model_name, model_type, options, provider_config) Raises: ValueError: If model is not configured or has invalid type @@ -279,15 +279,6 @@ def _convert_endpoints_to_tool_definitions( return tool_definitions -def _compact_text(*parts: str, max_length: int | None = 240) -> str: - """Join parts, collapse whitespace, and optionally truncate for display.""" - text = " ".join(p.strip() for p in parts if p).strip() - text = " ".join(text.split()) - if max_length and len(text) > max_length: - return text[: max_length - 3] + "..." - return text - - def _format_expected_answer_failure( response: str, expected: str, @@ -306,6 +297,114 @@ def _format_expected_answer_failure( return "\n".join(lines) +async def _evaluate_test_assertions( + test: Any, + response: str, + tool_calls: list[Any], + execution_error: str | None, + grader: LLMExecutor, +) -> tuple[list[str], dict[str, Any] | None]: + """Evaluate all assertions for a test. + + Returns: + Tuple of (failures list, expected_answer_evaluation dict or None) + """ + failures: list[str] = [] + evaluation: dict[str, Any] | None = None + assertions = test.assertions + + # If the agent failed to execute, report it clearly + if execution_error: + failures.append(f"Agent execution failed: {execution_error}") + + for call in tool_calls: + if call.error: + logger.debug( + "Tool '%s' failed during test '%s': %s", + call.tool, + test.name, + call.error, + ) + + # Check must_call assertions + if assertions.must_call: + for expected_call in assertions.must_call: + expected_tool = expected_call.tool + expected_args = expected_call.args or {} + + found = False + for call in tool_calls: + if call.tool == expected_tool: + actual_args = call.arguments or {} + if all(actual_args.get(k) == v for k, v in expected_args.items()): + found = True + break + + if not found: + failures.append( + f"Expected call to '{expected_tool}' with args {expected_args} not found" + ) + + # Check must_not_call assertions + if assertions.must_not_call: + for forbidden_tool in assertions.must_not_call: + if any(call.tool == forbidden_tool for call in tool_calls): + failures.append(f"Tool '{forbidden_tool}' was called but should not have been") + + # Check answer_contains assertions + if assertions.answer_contains: + for expected_text in assertions.answer_contains: + if expected_text.lower() not in response.lower(): + failures.append(f"Expected text '{expected_text}' not found in response") + + # Check answer_not_contains assertions + if assertions.answer_not_contains: + for forbidden_text in assertions.answer_not_contains: + if forbidden_text.lower() in response.lower(): + failures.append(f"Forbidden text '{forbidden_text}' found in response") + + if assertions.expected_answer: + logger.debug( + "Evaluating expected_answer assertion for test '%s': response_length=%d, expected='%s'", + test.name, + len(response), + ( + assertions.expected_answer[:100] + "..." + if len(assertions.expected_answer) > 100 + else assertions.expected_answer + ), + ) + evaluation = await grader.evaluate_expected_answer(response, assertions.expected_answer) + grade = (evaluation.get("result") or "").lower() + comment = evaluation.get("comment") or "Model answer did not match expected" + reasoning = evaluation.get("reasoning") or "" + + logger.debug( + "Expected answer evaluation for '%s': grade=%s, response='%s'", + test.name, + grade, + response[:150] + "..." if len(response) > 150 else response, + ) + + detail = _format_expected_answer_failure( + response, + assertions.expected_answer, + grade or "unknown", + comment, + reasoning, + ) + if grade != "correct": + logger.info( + "Test '%s' failed expected_answer check: grade=%s, comment=%s", + test.name, + grade, + comment, + ) + failures.append(detail) + + return failures, evaluation + + async def run_eval_suite( suite_name: str, user_config: UserConfigModel, @@ -394,16 +493,6 @@ async def run_eval_suite( logger.info(f"Number of tests: {len(eval_suite.tests)}") total_tests = len(eval_suite.tests) - if progress_callback: - progress_callback( - f"suite:{suite_name}", - "🧪 " - + click.style( - f"Running suite '{suite_name}' with {total_tests} test" - f"{'' if total_tests == 1 else 's'} using model '{model_name}'...", - fg="yellow", - ), - ) try: # Create LLM executor with model config, tool definitions, and tool executor @@ -456,105 +545,10 @@ async def run_eval_suite( execution_error = agent_result.error # Evaluate assertions - failures: list[str] = [] - assertions = test.assertions - evaluation: dict[str, Any] | None = None - - # If the agent failed to execute, report it clearly - if execution_error: - failures.append(f"Agent execution failed: {execution_error}") - - for call in tool_calls: - if call.error: - logger.debug( - "Tool '%s' failed during test '%s': %s", - call.tool, - test.name, - call.error, - ) - - # Check must_call assertions - if assertions.must_call: - for expected_call in assertions.must_call: - expected_tool = expected_call.tool - expected_args = expected_call.args or {} - - found = False - for call in tool_calls: - if call.tool == expected_tool: - actual_args = call.arguments or {} - if all(actual_args.get(k) == v for k, v in expected_args.items()): - found = True - break - - if not found: - failures.append( - f"Expected call to '{expected_tool}' with args {expected_args} not found" - ) - - # Check must_not_call assertions - if assertions.must_not_call: - for forbidden_tool in assertions.must_not_call: - if any(call.tool == forbidden_tool for call in tool_calls): - failures.append( - f"Tool '{forbidden_tool}' was called but should not have been" - ) - - # Check answer_contains assertions - if assertions.answer_contains: - for expected_text in assertions.answer_contains: - if expected_text.lower() not in response.lower(): - failures.append( - f"Expected text '{expected_text}' not found in response" - ) - - # Check answer_not_contains assertions - if assertions.answer_not_contains: - for forbidden_text in assertions.answer_not_contains: - if forbidden_text.lower() in response.lower(): - failures.append(f"Forbidden text '{forbidden_text}' found in response") - - if assertions.expected_answer: - logger.debug( - "Evaluating expected_answer assertion for test '%s': response_length=%d, expected='%s'", - test.name, - len(response), - ( - assertions.expected_answer[:100] + "..." - if len(assertions.expected_answer) > 100 - else assertions.expected_answer - ), - ) - grader = grading_executor or executor - evaluation = await grader.evaluate_expected_answer( - response, assertions.expected_answer - ) - grade = (evaluation.get("result") or "").lower() - comment = evaluation.get("comment") or "Model answer did not match expected" - reasoning = evaluation.get("reasoning") or "" - - logger.debug( - "Expected answer evaluation for '%s': grade=%s, response='%s'", - test.name, - grade, - response[:150] + "..." if len(response) > 150 else response, - ) - - detail = _format_expected_answer_failure( - response, - assertions.expected_answer, - grade or "unknown", - comment, - reasoning, - ) - if grade != "correct": - logger.info( - "Test '%s' failed expected_answer check: grade=%s, comment=%s", - test.name, - grade, - comment, - ) - failures.append(detail) + grader = grading_executor or executor + failures, evaluation = await _evaluate_test_assertions( + test, response, tool_calls, execution_error, grader + ) test_time = time.time() - test_start @@ -579,7 +573,7 @@ async def run_eval_suite( } for call in tool_calls ], - "expected_answer": assertions.expected_answer, + "expected_answer": test.assertions.expected_answer, "expected_answer_evaluation": evaluation, }, } diff --git a/tests/sdk/evals/test_executor.py b/tests/sdk/evals/test_executor.py index 3b7e1604..2805187c 100644 --- a/tests/sdk/evals/test_executor.py +++ b/tests/sdk/evals/test_executor.py @@ -17,20 +17,27 @@ def __init__(self, output: Any) -> None: class FakeAgent: def __init__( - self, *, tools: list[Any], output: Any, tool_args: dict[str, dict[str, Any]] + self, + *, + tools: list[Any], + output: Any, + tool_args: dict[str, dict[str, Any]], + tool_callables: dict[str, Any] | None = None, ) -> None: self.tools = tools self.output = output self.tool_args = tool_args + self.tool_callables = tool_callables or {} async def run( self, _prompt: str, deps: Any | None = None, model_settings: Any | None = None ) -> FakeRun: for tool in self.tools: - fn = getattr(tool, "_mxcp_callable", None) tool_name = getattr(tool, "name", None) or getattr( getattr(tool, "tool_def", None), "name", None ) + # Look up callable from provided map + fn = self.tool_callables.get(tool_name or "") args = self.tool_args.get(tool_name or "", {}) if fn: if asyncio.iscoroutinefunction(fn): @@ -115,6 +122,7 @@ def agent_factory(**kwargs: Any) -> FakeAgent: tools=kwargs["tools"], output="ok", tool_args={"get_weather": {"location": "Paris"}}, + tool_callables=kwargs.get("_tool_callables", {}), ) executor._agent_cls = agent_factory @@ -131,6 +139,7 @@ def test_execute_prompt_with_tool_call() -> None: tools=kwargs["tools"], output="Sunny", tool_args={"get_weather": {"location": "Paris"}}, + tool_callables=kwargs.get("_tool_callables", {}), ) result = asyncio.run(executor.execute_prompt("Weather?", user_context=user_ctx)) @@ -150,7 +159,10 @@ def test_execute_prompt_tool_calls_do_not_leak_between_runs() -> None: # First run executor._agent_cls = lambda **kwargs: FakeAgent( - tools=kwargs["tools"], output="ok", tool_args={"get_weather": {"location": "Paris"}} + tools=kwargs["tools"], + output="ok", + tool_args={"get_weather": {"location": "Paris"}}, + tool_callables=kwargs.get("_tool_callables", {}), ) first = asyncio.run(executor.execute_prompt("Weather?")) assert len(first.tool_calls) == 1 @@ -158,7 +170,10 @@ def test_execute_prompt_tool_calls_do_not_leak_between_runs() -> None: # Second run should still invoke tools and capture history independently executor._agent_cls = lambda **kwargs: FakeAgent( - tools=kwargs["tools"], output="ok", tool_args={"get_weather": {"location": "Rome"}} + tools=kwargs["tools"], + output="ok", + tool_args={"get_weather": {"location": "Rome"}}, + tool_callables=kwargs.get("_tool_callables", {}), ) second = asyncio.run(executor.execute_prompt("Weather?")) @@ -173,6 +188,7 @@ def test_execute_prompt_tool_error() -> None: tools=kwargs["tools"], output="Error", tool_args={"get_weather": {"location": "Rome"}}, + tool_callables=kwargs.get("_tool_callables", {}), ) result = asyncio.run(executor.execute_prompt("Weather?")) @@ -192,6 +208,7 @@ def test_tool_argument_validation_error_is_captured() -> None: tools=kwargs["tools"], output="done", tool_args={"get_weather": {}}, # missing required arg + tool_callables=kwargs.get("_tool_callables", {}), ) result = asyncio.run(executor.execute_prompt("Weather?")) @@ -229,19 +246,22 @@ def __init__( tools: list[Any], output: Any, tool_args: dict[str, dict[str, Any]], + tool_callables: dict[str, Any] | None = None, max_retries: int = 1, ) -> None: - super().__init__(tools=tools, output=output, tool_args=tool_args) + super().__init__( + tools=tools, output=output, tool_args=tool_args, tool_callables=tool_callables + ) self.max_retries = max_retries async def run( # type: ignore[override] self, _prompt: str, deps: Any | None = None, model_settings: Any | None = None ) -> FakeRun: for tool in self.tools: - fn = getattr(tool, "_mxcp_callable", None) tool_name = getattr(tool, "name", None) or getattr( getattr(tool, "tool_def", None), "name", None ) + fn = self.tool_callables.get(tool_name or "") args = self.tool_args.get(tool_name or "", {}) if not fn: continue @@ -268,6 +288,7 @@ async def run( # type: ignore[override] tools=kwargs["tools"], output="ok", tool_args={"get_weather": {"location": "Paris"}}, + tool_callables=kwargs.get("_tool_callables", {}), max_retries=kwargs.get("retries", 1), ) @@ -291,6 +312,7 @@ def test_expected_answer_grading() -> None: tools=kwargs.get("tools", []), output=GradeResult(result="correct", comment="ok", reasoning="match"), tool_args={}, + tool_callables=kwargs.get("_tool_callables", {}), ) result = asyncio.run(executor.evaluate_expected_answer("hello", "hello")) @@ -308,6 +330,7 @@ def agent_factory(**kwargs: Any) -> FakeAgent: tools=kwargs.get("tools", []), output=GradeResult(result="correct", comment="ok", reasoning="match"), tool_args={}, + tool_callables=kwargs.get("_tool_callables", {}), ) executor._agent_cls = agent_factory @@ -319,15 +342,17 @@ def agent_factory(**kwargs: Any) -> FakeAgent: def test_max_turns_limits_tool_calls() -> None: class MultiCallAgent: - def __init__(self, tools: list[Any]) -> None: + def __init__(self, tools: list[Any], tool_callables: dict[str, Any]) -> None: self.tools = tools + self.tool_callables = tool_callables async def run( self, _prompt: str, deps: Any | None = None, model_settings: Any | None = None ) -> FakeRun: for _ in range(2): for tool in self.tools: - fn = getattr(tool, "_mxcp_callable", None) + tool_name = getattr(tool, "name", None) + fn = self.tool_callables.get(tool_name or "") if fn: if asyncio.iscoroutinefunction(fn): try: @@ -342,7 +367,9 @@ async def run( return FakeRun("done") executor = make_executor() - executor._agent_cls = lambda **kwargs: MultiCallAgent(kwargs["tools"]) + executor._agent_cls = lambda **kwargs: MultiCallAgent( + kwargs["tools"], kwargs.get("_tool_callables", {}) + ) result = asyncio.run(executor.execute_prompt("Weather?", max_turns=1)) @@ -427,3 +454,92 @@ def test_tool_model_schema_supports_optional_object_params() -> None: assert props["context"]["properties"]["limit"]["maximum"] == 100 assert props["context"]["required"] == ["limit"] assert "context" not in schema.get("required", []) + + +def test_executor_with_empty_tool_list() -> None: + """Test executor handles empty tool list gracefully.""" + # Create executor directly, bypassing make_executor which adds default tools + tool_executor = MockToolExecutor() + executor = LLMExecutor( + "claude-test", + "anthropic", + ModelSettings(), + [], # Empty tool list + tool_executor, + provider_config=ProviderConfig(api_key="key", base_url="https://api.anthropic.com"), + ) + + assert executor.available_tools == [] + assert executor._tool_models == {} + # System prompt should indicate no tools + assert ( + "no tools" in executor.system_prompt.lower() + or "answer directly" in executor.system_prompt.lower() + ) + + +def test_unknown_parameter_type_defaults_to_any() -> None: + """Test that unknown parameter types fall back to Any with a warning.""" + tools = [ + ToolDefinition( + name="custom_tool", + description="Tool with unknown type", + parameters=[ParameterDefinition(name="param", type="unknown_type", required=True)], + ) + ] + # This should not raise an error + executor = make_executor(tools=tools) + + # The tool model should be created + assert "custom_tool" in executor._tool_models + + +def test_execute_prompt_handles_empty_output() -> None: + """Test that empty agent output is handled gracefully.""" + executor = make_executor() + + executor._agent_cls = lambda **kwargs: FakeAgent( + tools=kwargs["tools"], + output="", # Empty output + tool_args={}, + tool_callables=kwargs.get("_tool_callables", {}), + ) + + result = asyncio.run(executor.execute_prompt("Test prompt")) + + assert isinstance(result, AgentResult) + assert result.answer == "" + assert result.error is None + + +def test_provider_config_defaults() -> None: + """Test ProviderConfig uses defaults correctly.""" + from mxcp.sdk.evals.executor import ProviderConfig + + config = ProviderConfig() + assert config.api_key is None + assert config.base_url is None + assert config.timeout is None + + +def test_provider_config_with_values() -> None: + """Test ProviderConfig accepts and stores values.""" + from mxcp.sdk.evals.executor import ProviderConfig + + config = ProviderConfig( + api_key="test-key", + base_url="https://api.example.com", + timeout=30, + ) + assert config.api_key == "test-key" + assert config.base_url == "https://api.example.com" + assert config.timeout == 30 + + +def test_agent_retries_clamped_to_minimum() -> None: + """Test that agent_retries is clamped to at least 1.""" + executor = make_executor(agent_retries=0) + assert executor._agent_retries == 1 + + executor = make_executor(agent_retries=-5) + assert executor._agent_retries == 1 From 3aec2be4ce8c9d864bc3bfa6d0f9c3d829579817 Mon Sep 17 00:00:00 2001 From: Alex Zerntev Date: Wed, 3 Dec 2025 13:41:15 +0200 Subject: [PATCH 26/26] fix --- src/mxcp/server/definitions/endpoints/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mxcp/server/definitions/endpoints/utils.py b/src/mxcp/server/definitions/endpoints/utils.py index 1fe858c7..ac2410c2 100644 --- a/src/mxcp/server/definitions/endpoints/utils.py +++ b/src/mxcp/server/definitions/endpoints/utils.py @@ -204,14 +204,14 @@ def prepare_source_for_execution( if not tool_def: raise ValueError("No tool definition found") source = tool_def.source - language = source.language or tool_def.language + language = (source.language if source else None) or tool_def.language function_name = tool_def.name elif endpoint_type == "resource": resource_def = endpoint_definition.resource if not resource_def: raise ValueError("No resource definition found") source = resource_def.source - language = source.language or resource_def.language + language = (source.language if source else None) or resource_def.language else: raise ValueError("Prompts don't have source code")