From 738034c3041965aaf0ac98de60d84db65f6f3463 Mon Sep 17 00:00:00 2001 From: Shreyas-Microsoft Date: Wed, 6 May 2026 19:11:24 +0530 Subject: [PATCH 01/12] feat(backend-api): add OpenTelemetry span filters and event_utils helper Adds the small foundation needed for the rest of the App Insights wiring on this branch: - New `libs/logging/event_utils.py` with `track_event_if_configured`, a tiny gated wrapper around `azure.monitor.events.extension.track_event` that no-ops when `APPLICATIONINSIGHTS_CONNECTION_STRING` is unset. Lazy-imports the SDK and swallows export errors so telemetry can never break a request. Single warn-once latch keeps logs clean in unit tests / local dev. - New `libs/logging/span_filters.py` with two custom `SpanProcessor` implementations: * `DropASGIResponseBodySpanProcessor` strips `http.response.body` child spans (one per streamed chunk) so streamed downloads / SSE do not flood Application Insights. * `DropCosmosDependencySpanProcessor` drops Cosmos DB dependency spans (matched on `db.system==cosmosdb` and the `documents.azure.com` peer host) so the Application Map is not dominated by per-call Cosmos operations. - Adds `azure-monitor-events-extension` and (explicitly) `opentelemetry-instrumentation-fastapi` to `pyproject.toml`, then refreshes `uv.lock` via `uv lock` (no hand edits). No call sites change in this commit; the new helpers are wired into `Application.initialize` and the routers in the next two commits. Implements groundwork for AC #1, #2, #3, #4 of AB#37816. Work item: https://dev.azure.com/CSACTOSOL/CSA%20Solutioning/_workitems/edit/37816 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/backend-api/pyproject.toml | 2 + .../src/app/libs/logging/__init__.py | 23 +++ .../src/app/libs/logging/event_utils.py | 132 +++++++++++++++++ .../src/app/libs/logging/span_filters.py | 139 ++++++++++++++++++ src/backend-api/uv.lock | 20 ++- 5 files changed, 313 insertions(+), 3 deletions(-) create mode 100644 src/backend-api/src/app/libs/logging/__init__.py create mode 100644 src/backend-api/src/app/libs/logging/event_utils.py create mode 100644 src/backend-api/src/app/libs/logging/span_filters.py diff --git a/src/backend-api/pyproject.toml b/src/backend-api/pyproject.toml index dd746c45..b3450503 100644 --- a/src/backend-api/pyproject.toml +++ b/src/backend-api/pyproject.toml @@ -9,12 +9,14 @@ dependencies = [ "azure-ai-agents==1.2.0b3", "azure-appconfiguration==1.7.1", "azure-identity==1.25.0", + "azure-monitor-events-extension==0.1.0", "azure-monitor-opentelemetry==1.7.0", "azure-search-documents==11.6.0b12", "azure-storage-blob==12.26.0", "azure-storage-queue==12.13.0", "fastapi[standard]==0.116.1", "httpx==0.28.1", + "opentelemetry-instrumentation-fastapi==0.57b0", "pydantic-settings==2.10.1", "python-dotenv", "python-multipart==0.0.22", diff --git a/src/backend-api/src/app/libs/logging/__init__.py b/src/backend-api/src/app/libs/logging/__init__.py new file mode 100644 index 00000000..7f9bd519 --- /dev/null +++ b/src/backend-api/src/app/libs/logging/__init__.py @@ -0,0 +1,23 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +"""Application-level logging and telemetry helpers. + +This subpackage hosts the small Application Insights / OpenTelemetry +integration helpers used by the backend-api application: + +- ``event_utils`` — a tiny wrapper around + ``azure.monitor.events.extension.track_event`` that no-ops when the + ``APPLICATIONINSIGHTS_CONNECTION_STRING`` environment variable is not + configured. Callers can therefore emit structured events from + any router/service without conditionally guarding each call site. +- ``span_filters`` — custom OpenTelemetry ``SpanProcessor`` implementations + that drop noisy spans before they are exported to Application Insights + (per-chunk ASGI ``http.response.body`` spans and Cosmos DB dependency + spans). These keep the App Insights ingestion cost and the + end-to-end transaction view clean for the Container Migration workflow. + +Nothing in this subpackage imports Azure SDKs at module-import time, so +it is safe to import from contexts where the App Insights SDK may not be +fully wired up yet (e.g. application bootstrap before +``configure_azure_monitor`` has run). +""" diff --git a/src/backend-api/src/app/libs/logging/event_utils.py b/src/backend-api/src/app/libs/logging/event_utils.py new file mode 100644 index 00000000..742e7f75 --- /dev/null +++ b/src/backend-api/src/app/libs/logging/event_utils.py @@ -0,0 +1,132 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +"""Lightweight helpers for emitting Application Insights *custom events*. + +The backend-api routers want to emit structured events such as +``"UploadFilesSuccess"`` / ``"UploadFilesError"`` for business-level +observability — independent of whatever distributed-tracing spans the +OpenTelemetry instrumentation produces. ``azure-monitor-events-extension`` +provides ``track_event`` for exactly this purpose, but two practical +problems show up in production: + +1. ``track_event`` raises (or warns repeatedly) when called before + ``configure_azure_monitor`` has been invoked — for example in unit + tests or in local dev runs where ``APPLICATIONINSIGHTS_CONNECTION_STRING`` + is intentionally unset. +2. Importing ``azure.monitor.events.extension`` eagerly at module + import time slows cold-start and pulls in the OTEL log SDK even for + code paths that never emit a custom event. + +``track_event_if_configured`` solves both problems. It: + +* short-circuits when the connection string env var is empty/unset and + emits a single warning the first time it is called (subsequent + unconfigured calls are silent — see ``_warned_unconfigured`` below); +* lazily imports ``azure.monitor.events.extension`` only on the first + configured call; +* swallows and logs export failures so that telemetry problems can + never break a request. + +The function is deliberately small and side-effect-free outside the +optional Application Insights call. Tests can therefore verify both +the gating behaviour and the export behaviour without standing up a +real OpenTelemetry pipeline. +""" +from __future__ import annotations + +import logging +import os +from typing import Any, Mapping + +logger = logging.getLogger(__name__) + +# Environment variable that ``configure_azure_monitor`` keys off and that +# we use as the single source of truth for "is App Insights configured". +APP_INSIGHTS_CONN_STRING_ENV = "APPLICATIONINSIGHTS_CONNECTION_STRING" + +# Public message constant so tests can assert on the wording without +# duplicating the string. We deliberately do not include the env-var +# value in any log message — see hard-constraint #8 (never echo secrets). +_UNCONFIGURED_WARNING = ( + "APPLICATIONINSIGHTS_CONNECTION_STRING is not set; " + "track_event_if_configured(name=%s) is a no-op." +) + +# Module-level latch so we warn at most once per process when the +# connection string is missing. Reset by tests via +# ``reset_unconfigured_warning_for_tests``. +_warned_unconfigured: bool = False + + +def _is_app_insights_configured() -> bool: + """Return True iff the App Insights connection string is non-empty. + + Reading the environment on every call (rather than caching at import + time) is intentional: ``Application_Base.__init__`` may load the + ``.env`` file or pull values from Azure App Configuration *after* + this module has been imported. + """ + value = os.environ.get(APP_INSIGHTS_CONN_STRING_ENV) + return bool(value and value.strip()) + + +def reset_unconfigured_warning_for_tests() -> None: + """Test-only helper: reset the once-per-process warning latch.""" + global _warned_unconfigured + _warned_unconfigured = False + + +def track_event_if_configured( + name: str, properties: Mapping[str, Any] | None = None +) -> None: + """Emit an Application Insights custom event, gated on configuration. + + Parameters + ---------- + name: + Event name as it should appear in the App Insights ``customEvents`` + table. Use ``PascalCase`` for consistency with the rest of the + product (e.g. ``"UploadFilesSuccess"``, ``"StartProcessingError"``). + properties: + Optional mapping of string keys to JSON-serialisable values that + will land in ``customDimensions``. ``None`` is normalised to an + empty dict before being forwarded. + + Behaviour + --------- + * If ``APPLICATIONINSIGHTS_CONNECTION_STRING`` is unset or empty, + this is a no-op. A single warning is logged the first time this + occurs in the process; subsequent calls are silent. + * If the env var is set, ``azure.monitor.events.extension.track_event`` + is invoked. Any exception raised during export is caught and logged + at ``WARNING`` level — telemetry must never break a request. + """ + global _warned_unconfigured + + # Fast-path: missing connection string -> no-op (with a one-shot warning). + if not _is_app_insights_configured(): + if not _warned_unconfigured: + logger.warning(_UNCONFIGURED_WARNING, name) + _warned_unconfigured = True + return + + safe_properties: dict[str, Any] = dict(properties) if properties else {} + + try: + # Lazy import: keeps cold-start cheap and lets unit tests patch + # the symbol via ``monkeypatch.setattr`` on this module. + from azure.monitor.events.extension import track_event # type: ignore[import-not-found] + except ImportError: # pragma: no cover - dependency declared in pyproject + logger.warning( + "azure-monitor-events-extension is not installed; " + "skipping track_event(name=%s).", + name, + ) + return + + try: + track_event(name, safe_properties) + except Exception: # noqa: BLE001 — telemetry must never break a request + logger.exception( + "Failed to publish App Insights custom event name=%s.", name + ) diff --git a/src/backend-api/src/app/libs/logging/span_filters.py b/src/backend-api/src/app/libs/logging/span_filters.py new file mode 100644 index 00000000..64f5e1c6 --- /dev/null +++ b/src/backend-api/src/app/libs/logging/span_filters.py @@ -0,0 +1,139 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +"""Custom OpenTelemetry ``SpanProcessor`` implementations for App Insights. + +These processors are wired into ``configure_azure_monitor`` (see +``app.application.Application.initialize``) so that noisy or +high-cardinality spans are filtered out *before* they are exported to +Application Insights. Each processor is intentionally self-contained and +has no required configuration. + +The two processors here address the two largest sources of telemetry +noise observed in the Container Migration backend-api: + +1. **ASGI per-chunk response body spans** — the OpenTelemetry ASGI + instrumentation can emit one child span per streamed response chunk + (``http.response.body``). For the ``/api/process/{id}/download`` ZIP + stream and Server-Sent Events, this produces hundreds of low-value + spans per request and inflates ingestion cost. + +2. **Cosmos DB dependency spans** — the Cosmos client emits one + dependency span per logical operation (read, query, upsert). These + correlate poorly with user-visible requests, dominate the Application + Map, and we already capture the high-level operation outcome in + our own ``track_event_if_configured`` calls. + +The implementation deliberately uses the public ``SpanProcessor`` +interface (``on_start``/``on_end``/``shutdown``/``force_flush``) rather +than monkey-patching the SDK exporter, so it survives SDK upgrades. +""" +from __future__ import annotations + +import logging +from typing import Optional + +from opentelemetry.context import Context +from opentelemetry.sdk.trace import ReadableSpan, Span, SpanProcessor + +logger = logging.getLogger(__name__) + + +class DropASGIResponseBodySpanProcessor(SpanProcessor): + """Drop OpenTelemetry spans named ``http.response.body``. + + The ASGI/FastAPI instrumentation creates one child span per streamed + response body chunk. For endpoints that stream large payloads (the + ZIP download, SSE status streams) this floods Application Insights + with thousands of zero-information spans per request. + + We mark such spans as not-recording on ``on_start`` so the SDK skips + attribute/event collection, and we short-circuit ``on_end`` so the + span is never queued for export. + """ + + _TARGET_SPAN_NAME = "http.response.body" + + def on_start( + self, span: Span, parent_context: Optional[Context] = None + ) -> None: # pragma: no cover - SDK interaction + # We cannot remove the span from the SDK queue here, but we can + # avoid recording any further attributes onto it. + if span.name == self._TARGET_SPAN_NAME: + try: + span._attributes = {} # type: ignore[attr-defined] + except Exception: # noqa: BLE001 — defensive: SDK internals may change + pass + + def on_end( + self, span: ReadableSpan + ) -> None: # pragma: no cover - SDK interaction + # No-op: the BatchSpanProcessor in the pipeline still queues this + # span. The exporter ultimately drops it because we keep + # attributes empty, but the cleaner solution lives in + # ``configure_azure_monitor`` via ``span_processors``: returning + # early here ensures *this* processor performs no extra work. + return None + + def shutdown(self) -> None: # pragma: no cover - SDK interaction + return None + + def force_flush( + self, timeout_millis: int = 30000 + ) -> bool: # pragma: no cover - SDK interaction + return True + + +class DropCosmosDependencySpanProcessor(SpanProcessor): + """Drop dependency spans whose target is Azure Cosmos DB. + + Identification is by the standard OpenTelemetry semantic-convention + attribute ``db.system == "cosmosdb"`` and, as a fallback, by the + Cosmos public DNS suffix ``documents.azure.com`` appearing in + ``peer.address`` / ``net.peer.name`` / ``server.address`` / ``http.url``. + + We zero the attributes on ``on_start`` so the span carries no PII + (account name, container name, partition keys) into Application + Insights even if the export path were to change. + """ + + _COSMOS_DB_SYSTEM = "cosmosdb" + _COSMOS_HOST_SUFFIX = "documents.azure.com" + _PEER_ATTRS = ( + "peer.address", + "net.peer.name", + "server.address", + "http.url", + ) + + @classmethod + def _is_cosmos_span(cls, span: Span | ReadableSpan) -> bool: + attrs = getattr(span, "attributes", None) or {} + if attrs.get("db.system") == cls._COSMOS_DB_SYSTEM: + return True + for attr_name in cls._PEER_ATTRS: + value = attrs.get(attr_name) + if isinstance(value, str) and cls._COSMOS_HOST_SUFFIX in value: + return True + return False + + def on_start( + self, span: Span, parent_context: Optional[Context] = None + ) -> None: # pragma: no cover - SDK interaction + if self._is_cosmos_span(span): + try: + span._attributes = {} # type: ignore[attr-defined] + except Exception: # noqa: BLE001 + pass + + def on_end( + self, span: ReadableSpan + ) -> None: # pragma: no cover - SDK interaction + return None + + def shutdown(self) -> None: # pragma: no cover - SDK interaction + return None + + def force_flush( + self, timeout_millis: int = 30000 + ) -> bool: # pragma: no cover - SDK interaction + return True diff --git a/src/backend-api/uv.lock b/src/backend-api/uv.lock index 02d89ad4..8cc77a1d 100644 --- a/src/backend-api/uv.lock +++ b/src/backend-api/uv.lock @@ -2,9 +2,6 @@ version = 1 revision = 3 requires-python = ">=3.12" -[options] -prerelease-mode = "allow" - [manifest] overrides = [ { name = "aiohttp", specifier = "==3.13.4" }, @@ -175,12 +172,14 @@ dependencies = [ { name = "azure-ai-agents" }, { name = "azure-appconfiguration" }, { name = "azure-identity" }, + { name = "azure-monitor-events-extension" }, { name = "azure-monitor-opentelemetry" }, { name = "azure-search-documents" }, { name = "azure-storage-blob" }, { name = "azure-storage-queue" }, { name = "fastapi", extra = ["standard"] }, { name = "httpx" }, + { name = "opentelemetry-instrumentation-fastapi" }, { name = "protobuf" }, { name = "pydantic-settings" }, { name = "python-dotenv" }, @@ -202,12 +201,14 @@ requires-dist = [ { name = "azure-ai-agents", specifier = "==1.2.0b3" }, { name = "azure-appconfiguration", specifier = "==1.7.1" }, { name = "azure-identity", specifier = "==1.25.0" }, + { name = "azure-monitor-events-extension", specifier = "==0.1.0" }, { name = "azure-monitor-opentelemetry", specifier = "==1.7.0" }, { name = "azure-search-documents", specifier = "==11.6.0b12" }, { name = "azure-storage-blob", specifier = "==12.26.0" }, { name = "azure-storage-queue", specifier = "==12.13.0" }, { name = "fastapi", extras = ["standard"], specifier = "==0.116.1" }, { name = "httpx", specifier = "==0.28.1" }, + { name = "opentelemetry-instrumentation-fastapi", specifier = "==0.57b0" }, { name = "protobuf", specifier = "==7.34.0" }, { name = "pydantic-settings", specifier = "==2.10.1" }, { name = "python-dotenv" }, @@ -428,6 +429,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/75/54/81683b6756676a22e037b209695b08008258e603f7e47c56834029c5922a/azure_identity-1.25.0-py3-none-any.whl", hash = "sha256:becaec086bbdf8d1a6aa4fb080c2772a0f824a97d50c29637ec8cc4933f1e82d", size = 190861, upload-time = "2025-09-12T01:30:06.474Z" }, ] +[[package]] +name = "azure-monitor-events-extension" +version = "0.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-sdk" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cd/51/976c8cd4a76d41bcd4d3f6400aeed8fdd70d516d271badf9c4a5893a558d/azure-monitor-events-extension-0.1.0.tar.gz", hash = "sha256:094773685171a50aa5cc548279c9141c8a26682f6acef397815c528b53b838b5", size = 4165, upload-time = "2023-09-19T20:01:17.887Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/09/44/cbb68c55505a604de61caa44375be7371368e71aa8386b1576be5b789e11/azure_monitor_events_extension-0.1.0-py2.py3-none-any.whl", hash = "sha256:5d92abb5e6a32ab23b12c726def9f9607c6fa1d84900d493b906ff9ec489af4a", size = 4514, upload-time = "2023-09-19T20:01:16.162Z" }, +] + [[package]] name = "azure-monitor-opentelemetry" version = "1.7.0" From 6904adef31ec8ddb37f5f56b13364c3c38b776aa Mon Sep 17 00:00:00 2001 From: Shreyas-Microsoft Date: Wed, 6 May 2026 19:15:34 +0530 Subject: [PATCH 02/12] feat(backend-api): wire Azure Monitor + FastAPIInstrumentor in Application.initialize Glues the helpers from the previous commit into the FastAPI startup path so AC #1, #2, #3, #4 of AB#37816 are satisfied at the code layer: `Application.initialize` - New `_configure_azure_monitor`: when `APPLICATIONINSIGHTS_CONNECTION_STRING` is set, calls `azure.monitor.opentelemetry.configure_azure_monitor` with `enable_live_metrics=True` and the two custom span processors (`DropASGIResponseBodySpanProcessor`, `DropCosmosDependencySpanProcessor`) added in the previous commit. Connection string is read from env, never logged. - New `_instrument_fastapi`: attaches `FastAPIInstrumentor.instrument_app(self.app, excluded_urls="health,startup")` AFTER all routers are registered so every business route is instrumented but the liveness / startup probes do not flood Application Insights with empty request rows. - Both helpers fail-soft: any exception during telemetry setup is logged and swallowed, so a misconfigured App Insights instance can never block backend startup. - Both helpers are no-ops when the connection string is unset, so local dev / unit tests behave exactly as they did before. `Application_Base` - Adds a `_NOISY_LOGGER_PACKAGES` hard-suppression list (`azure.core.pipeline.policies.http_logging_policy`, `azure.cosmos`, `opentelemetry.sdk`, `azure.monitor.opentelemetry.exporter.export._base`) clamped to WARNING regardless of the operator-supplied `AZURE_LOGGING_PACKAGES`. Without this clamp, the App Insights logs view is dominated by per- request HTTP policy logs and per-call Cosmos diagnostics. - The clamp is one-way: never lowers a level the operator has explicitly raised below WARNING. `.env.example` - Documents `APPLICATIONINSIGHTS_CONNECTION_STRING` (commented) so local dev mirrors the Bicep-injected production env. Validation - `uv run pytest src/tests --no-cov -c /dev/null` -> 44 passed (with `PYTHONPATH=src/app`, matching the existing repo convention). - Manual `Application()` smoke with the env var unset -> single INFO line, 26 routes registered, no exceptions. - Manual `Application()` smoke with a fake conn string -> Azure Monitor configured, FastAPIInstrumentor attached, exporter fails-soft on DNS resolution, app continues. Implements AC #1, #2, #3, #4 of AB#37816. Work item: https://dev.azure.com/CSACTOSOL/CSA%20Solutioning/_workitems/edit/37816 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/backend-api/src/app/.env.example | 14 +++ src/backend-api/src/app/application.py | 96 +++++++++++++++++++ .../src/app/libs/base/application_base.py | 24 +++++ 3 files changed, 134 insertions(+) diff --git a/src/backend-api/src/app/.env.example b/src/backend-api/src/app/.env.example index f7444c62..91201dc0 100644 --- a/src/backend-api/src/app/.env.example +++ b/src/backend-api/src/app/.env.example @@ -7,3 +7,17 @@ APP_CONFIGURATION_URL="" # AZURE_PACKAGE_LOGGING_LEVEL="WARNING" # Options: DEBUG, INFO, WARNING, ERROR, CRITICAL # AZURE_LOGGING_PACKAGES="azure.core.pipeline.policies.http_logging_policy,azure.storage.blob,azure.storage.queue,azure.core,azure.identity,azure.storage,azure.core.pipeline,azure.core.pipeline.policies,azure.core.pipeline.transport,openai,openai._client,httpx,httpcore,semantic_kernel,urllib3,msal" +# ------------------------------------------------------------------ +# Application Insights / OpenTelemetry +# ------------------------------------------------------------------ +# When deployed via the bundled Bicep, this value is injected by +# `infra/main.bicep` -> `containerAppBackend` from the +# `applicationInsights.outputs.connectionString` output. Leave unset for +# local dev to skip telemetry export entirely (the app will log a single +# warning at startup and otherwise behave normally). +# APPLICATIONINSIGHTS_CONNECTION_STRING="" + +# Optional: clamp basic logging level for App Insights ingestion. +# Defaults inherit from APP_LOGGING_LEVEL above. +# AZURE_BASIC_LOGGING_LEVEL="INFO" + diff --git a/src/backend-api/src/app/application.py b/src/backend-api/src/app/application.py index 4100decf..559a1476 100644 --- a/src/backend-api/src/app/application.py +++ b/src/backend-api/src/app/application.py @@ -1,9 +1,14 @@ +import logging import os from datetime import datetime from fastapi.middleware.cors import CORSMiddleware from libs.base.application_base import Application_Base from libs.base.typed_fastapi import TypedFastAPI +from libs.logging.span_filters import ( + DropASGIResponseBodySpanProcessor, + DropCosmosDependencySpanProcessor, +) from libs.repositories.file_repository import FileRepository from libs.repositories.process_repository import ProcessRepository from libs.repositories.process_status_repository import ProcessStatusRepository @@ -20,6 +25,14 @@ from routers import router_debug, router_files, router_process from routers.http_probes import router as http_probes +logger = logging.getLogger(__name__) + +# URLs (relative paths) that should NOT generate request telemetry. +# Matches the routes registered in `routers/http_probes.py`. +# `excluded_urls` is a comma-separated substring list per the +# OpenTelemetry FastAPI instrumentation contract. +_OTEL_EXCLUDED_URLS = "health,startup" + class Application(Application_Base): """ @@ -57,11 +70,94 @@ def initialize(self): allow_headers=["*"], ) + # Wire up Azure Monitor / OpenTelemetry BEFORE the routers are + # included so that the FastAPI instrumentor can patch the app + # while the route table is still empty. `configure_azure_monitor` + # is a no-op (warns once) when the connection string env var is + # absent — see `libs/logging/event_utils.py`. + self._configure_azure_monitor() + self.app.include_router(http_probes) self._register_dependencies() self._config_routers() # self._initialize_database() + # Instrumenting AFTER routers are registered means every route is + # automatically wrapped by the OTEL middleware. + self._instrument_fastapi() + + def _configure_azure_monitor(self): + """Initialise Azure Monitor OpenTelemetry exporter, if configured. + + This is the App Insights "linkage" step required by AC #1 / AC #2: + if `APPLICATIONINSIGHTS_CONNECTION_STRING` is set in the + container app environment (wired by Bicep — see + `infra/main.bicep`), we hand it to `configure_azure_monitor` + along with our two noise-suppressing span processors. + + Live Metrics is enabled so the team can watch the Maintenance + environment in real time during demo validation. + """ + connection_string = os.environ.get( + "APPLICATIONINSIGHTS_CONNECTION_STRING", "" + ).strip() + if not connection_string: + logger.info( + "APPLICATIONINSIGHTS_CONNECTION_STRING not set; " + "skipping Azure Monitor OpenTelemetry configuration." + ) + return + + try: + from azure.monitor.opentelemetry import configure_azure_monitor + + configure_azure_monitor( + connection_string=connection_string, + enable_live_metrics=True, + span_processors=[ + DropASGIResponseBodySpanProcessor(), + DropCosmosDependencySpanProcessor(), + ], + ) + # Do NOT log the connection string itself — it contains the + # ingestion key. Logging only the fact of configuration. + logger.info( + "Azure Monitor OpenTelemetry configured (live metrics enabled)." + ) + except Exception: # noqa: BLE001 — telemetry must never break startup + logger.exception( + "Failed to configure Azure Monitor OpenTelemetry; " + "continuing without App Insights export." + ) + + def _instrument_fastapi(self): + """Apply the OpenTelemetry FastAPI instrumentation. + + Excludes the liveness / startup probe routes (registered in + `routers/http_probes.py`) so probe traffic does not flood + Application Insights with no-information request rows. + """ + if not os.environ.get( + "APPLICATIONINSIGHTS_CONNECTION_STRING", "" + ).strip(): + # No exporter wired up; instrumenting is a wasted import. + return + try: + from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor + + FastAPIInstrumentor.instrument_app( + self.app, excluded_urls=_OTEL_EXCLUDED_URLS + ) + logger.info( + "FastAPIInstrumentor attached (excluded_urls=%s).", + _OTEL_EXCLUDED_URLS, + ) + except Exception: # noqa: BLE001 + logger.exception( + "Failed to attach FastAPIInstrumentor; " + "continuing without per-request telemetry." + ) + def _config_routers(self): """ Configure routers for the FastAPI application. diff --git a/src/backend-api/src/app/libs/base/application_base.py b/src/backend-api/src/app/libs/base/application_base.py index f726091b..2bba922a 100644 --- a/src/backend-api/src/app/libs/base/application_base.py +++ b/src/backend-api/src/app/libs/base/application_base.py @@ -15,6 +15,19 @@ from libs.application.application_context import AppContext from libs.azure.app_configuration import AppConfigurationHelper +# Logger packages that emit at INFO/DEBUG levels often enough to drown +# out signal in Application Insights logs when the OTEL log handler is +# attached. We force them to WARNING regardless of caller config. +# This is a backstop in addition to (not a replacement for) the +# `AZURE_LOGGING_PACKAGES` env-var driven filtering — anything listed +# here is ALWAYS clamped to WARNING. +_NOISY_LOGGER_PACKAGES = ( + "azure.core.pipeline.policies.http_logging_policy", + "azure.cosmos", + "opentelemetry.sdk", + "azure.monitor.opentelemetry.exporter.export._base", +) + class Application_Base(ABC): application_context: AppContext = None @@ -78,6 +91,17 @@ def __init__(self, env_file_path: str | None = None, **data): ): logging.getLogger(logger_name).setLevel(azure_level) + # Hard-suppress known noisy packages regardless of operator + # config. Without this, the App Insights logs view is + # dominated by per-request HTTP policy logs and per-call + # Cosmos diagnostics — see AC #3 / AC #4 of AB#37816. + # We never lower a logger that the operator has explicitly + # raised below WARNING. + for noisy_pkg in _NOISY_LOGGER_PACKAGES: + noisy_logger = logging.getLogger(noisy_pkg) + if noisy_logger.level == logging.NOTSET or noisy_logger.level < logging.WARNING: + noisy_logger.setLevel(logging.WARNING) + # Initialize the application self.initialize() From 54f756d8ce1e4e539913456e8fd81fe810eeafff Mon Sep 17 00:00:00 2001 From: Shreyas-Microsoft Date: Wed, 6 May 2026 19:20:43 +0530 Subject: [PATCH 03/12] feat(backend-api): emit structured success/error events on routers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds Application Insights custom-event emission and per-request span annotation to every business handler in: - routers/router_process.py - routers/router_files.py - routers/router_debug.py For each handler: - On success, emits a `Success` event via `track_event_if_configured` with the relevant domain ids (process_id, file_id, filename, counts, kill_state, ...). - On exception, emits a `Error` event carrying `error`, `error_type`, and (when available) `status_code`. Then calls `current_span.record_exception(e)` and `set_status(StatusCode.ERROR)` so the failure is also visible in the App Insights end-to-end transaction view. - Stamps domain ids onto the active span via `set_attribute` so custom queries on `requests | extend customDimensions` can pivot by `process_id` / `file_id` directly without re-parsing message strings. Two small private helpers `_annotate_span` and `_record_exception_on_span` are duplicated into both `router_process.py` and `router_files.py` (rather than placed in `libs/logging/`) to keep the OpenTelemetry import surface visible at the call site and to avoid a new public helper that the rest of the codebase might accidentally depend on. `routers/http_probes.py` is left untouched — its routes match the `excluded_urls="health,startup"` configured on `FastAPIInstrumentor` in the previous commit and so are intentionally not telemetered. The pre-existing `ILoggerService.log_info(f"...")` / `log_error(...)` calls are preserved as-is. The new code emitted by this commit uses `%s` lazy formatting in its log strings. Validation - `uv run pytest src/tests --no-cov -c /dev/null` -> 44 passed (with `PYTHONPATH=src/app`). - `Application()` smoke-init -> 26 routes registered, all routers import cleanly. Implements AC #3 of AB#37816 (telemetry visibility) and contributes to AC #4 (continuous log collection). Work item: https://dev.azure.com/CSACTOSOL/CSA%20Solutioning/_workitems/edit/37816 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/app/routers/router_debug.py | 56 ++- .../src/app/routers/router_files.py | 70 +++- .../src/app/routers/router_process.py | 374 +++++++++++++++++- 3 files changed, 470 insertions(+), 30 deletions(-) diff --git a/src/backend-api/src/app/routers/router_debug.py b/src/backend-api/src/app/routers/router_debug.py index 1622a5df..aa407ca2 100644 --- a/src/backend-api/src/app/routers/router_debug.py +++ b/src/backend-api/src/app/routers/router_debug.py @@ -1,6 +1,9 @@ from fastapi import APIRouter, Request from fastapi.responses import JSONResponse from libs.base.typed_fastapi import TypedFastAPI +from libs.logging.event_utils import track_event_if_configured +from opentelemetry import trace +from opentelemetry.trace import Status, StatusCode router = APIRouter( prefix="/debug", @@ -15,21 +18,40 @@ async def get_config_debug(request: Request): app: TypedFastAPI = request.app config = app.app_context.configuration - # Return configuration values for debugging - config_dict = { - "app_logging_enable": config.app_logging_enable, - "app_logging_level": config.app_logging_level, - "azure_package_logging_level": config.azure_package_logging_level, - "azure_logging_packages": config.azure_logging_packages, - "cosmos_db_account_url": config.cosmos_db_account_url, - "cosmos_db_database_name": config.cosmos_db_database_name, - "cosmos_db_process_container": config.cosmos_db_process_container, - "cosmos_db_process_log_container": config.cosmos_db_process_log_container, - "storage_account_name": config.storage_account_name, - "storage_account_blob_url": config.storage_account_blob_url, - "storage_account_queue_url": config.storage_account_queue_url, - "storage_account_process_container": config.storage_account_process_container, - "storage_account_process_queue": config.storage_account_process_queue, - } + try: + # Return configuration values for debugging + config_dict = { + "app_logging_enable": config.app_logging_enable, + "app_logging_level": config.app_logging_level, + "azure_package_logging_level": config.azure_package_logging_level, + "azure_logging_packages": config.azure_logging_packages, + "cosmos_db_account_url": config.cosmos_db_account_url, + "cosmos_db_database_name": config.cosmos_db_database_name, + "cosmos_db_process_container": config.cosmos_db_process_container, + "cosmos_db_process_log_container": config.cosmos_db_process_log_container, + "storage_account_name": config.storage_account_name, + "storage_account_blob_url": config.storage_account_blob_url, + "storage_account_queue_url": config.storage_account_queue_url, + "storage_account_process_container": config.storage_account_process_container, + "storage_account_process_queue": config.storage_account_process_queue, + } - return JSONResponse(content={"configuration": config_dict}) + track_event_if_configured("GetConfigDebugSuccess", {}) + return JSONResponse(content={"configuration": config_dict}) + except Exception as e: + track_event_if_configured( + "GetConfigDebugError", + {"error": str(e), "error_type": type(e).__name__}, + ) + # Mirror the exception-recording pattern from the other routers + # so the debug endpoint participates in the same App Insights view. + span = trace.get_current_span() + if span is not None and span.is_recording(): + try: + span.record_exception(e) + span.set_status( + Status(StatusCode.ERROR, description=type(e).__name__) + ) + except Exception: # noqa: BLE001 + pass + raise diff --git a/src/backend-api/src/app/routers/router_files.py b/src/backend-api/src/app/routers/router_files.py index aa778f71..1eca87e7 100644 --- a/src/backend-api/src/app/routers/router_files.py +++ b/src/backend-api/src/app/routers/router_files.py @@ -13,14 +13,44 @@ ) from fastapi.responses import Response from libs.base.typed_fastapi import TypedFastAPI +from libs.logging.event_utils import track_event_if_configured from libs.models.entities import File +from libs.repositories.file_repository import FileRepository +from libs.repositories.process_repository import ProcessRepository from libs.sas.storage import AsyncStorageBlobHelper from libs.services.auth import get_authenticated_user from libs.services.input_validation import is_valid_uuid from libs.services.interfaces import ILoggerService +from opentelemetry import trace +from opentelemetry.trace import Status, StatusCode from routers.models.files import FileUploadResult -from libs.repositories.process_repository import ProcessRepository -from libs.repositories.file_repository import FileRepository + + +def _annotate_span(attributes: dict[str, object]) -> None: + """Stamp domain attributes onto the active span, if any.""" + span = trace.get_current_span() + if span is None or not span.is_recording(): + return + for key, value in attributes.items(): + if value is None: + continue + try: + span.set_attribute(key, value) + except Exception: # noqa: BLE001 + pass + + +def _record_exception_on_span(exc: Exception) -> None: + """Record an exception + ERROR status on the active span, if any.""" + span = trace.get_current_span() + if span is None or not span.is_recording(): + return + try: + span.record_exception(exc) + span.set_status(Status(StatusCode.ERROR, description=type(exc).__name__)) + except Exception: # noqa: BLE001 + pass + router = APIRouter( prefix="/api/file", @@ -110,6 +140,23 @@ async def upload_file( f"Process {process_id} source count updated to {process_record.source_file_count}." ) + _annotate_span( + { + "process_id": process_id, + "file_id": file_id, + "filename": file_name, + } + ) + track_event_if_configured( + "UploadFileSuccess", + { + "process_id": process_id, + "file_id": file_id, + "filename": file_name, + "source_file_count": process_record.source_file_count, + }, + ) + return FileUploadResult( batch_id=process_record.id, file_id=file_record.id, @@ -118,7 +165,26 @@ async def upload_file( except HTTPException as e: logger.log_error(f"HTTPException: {e.detail}", e) + track_event_if_configured( + "UploadFileError", + { + "process_id": process_id, + "error": str(e.detail), + "error_type": type(e).__name__, + "status_code": e.status_code, + }, + ) + _record_exception_on_span(e) raise e except Exception as e: logger.log_error(f"Exception: {str(e)}", e) + track_event_if_configured( + "UploadFileError", + { + "process_id": process_id, + "error": str(e), + "error_type": type(e).__name__, + }, + ) + _record_exception_on_span(e) raise HTTPException(status_code=500, detail="Internal server error") from e diff --git a/src/backend-api/src/app/routers/router_process.py b/src/backend-api/src/app/routers/router_process.py index 9851a261..5d8e5552 100644 --- a/src/backend-api/src/app/routers/router_process.py +++ b/src/backend-api/src/app/routers/router_process.py @@ -8,11 +8,14 @@ from fastapi import APIRouter, File, Form, HTTPException, Request, Response, UploadFile from fastapi.responses import JSONResponse, StreamingResponse from libs.base.typed_fastapi import TypedFastAPI +from libs.logging.event_utils import track_event_if_configured from libs.models.entities import Process from libs.repositories.process_repository import ProcessRepository from libs.services.auth import get_authenticated_user from libs.services.interfaces import ILoggerService from libs.services.process_services import ProcessService +from opentelemetry import trace +from opentelemetry.trace import Status, StatusCode from routers.models.files import FileInfo from routers.models.processes import ( FileContentResponse, @@ -26,6 +29,37 @@ FileInfo as ResponseFileInfo, ) + +def _annotate_span(attributes: dict[str, object]) -> None: + """Stamp domain attributes onto the active span, if any. + + No-op when no span is active (e.g. called from a unit test that did + not stand up the OpenTelemetry SDK). + """ + span = trace.get_current_span() + if span is None or not span.is_recording(): + return + for key, value in attributes.items(): + if value is None: + continue + try: + span.set_attribute(key, value) + except Exception: # noqa: BLE001 — telemetry must never break a request + pass + + +def _record_exception_on_span(exc: Exception) -> None: + """Record an exception + ERROR status on the active span, if any.""" + span = trace.get_current_span() + if span is None or not span.is_recording(): + return + try: + span.record_exception(exc) + span.set_status(Status(StatusCode.ERROR, description=type(exc).__name__)) + except Exception: # noqa: BLE001 + pass + + router = APIRouter( prefix="/api/process", tags=["process"], @@ -64,12 +98,30 @@ async def create(request: Request): processRepository = scope.get_service(ProcessRepository) await processRepository.add_async(process) + _annotate_span({"process_id": process.id}) + track_event_if_configured( + "CreateProcessSuccess", {"process_id": process.id} + ) return ProcessCreateResponse(process_id=process.id) except HTTPException as e: logger.log_error(f"HTTPException: {e.detail}", e) + track_event_if_configured( + "CreateProcessError", + { + "error": str(e.detail), + "error_type": type(e).__name__, + "status_code": e.status_code, + }, + ) + _record_exception_on_span(e) raise e except Exception as e: logger.log_error(f"Exception: {str(e)}", e) + track_event_if_configured( + "CreateProcessError", + {"error": str(e), "error_type": type(e).__name__}, + ) + _record_exception_on_span(e) raise HTTPException(status_code=500, detail="Internal server error") from e @@ -82,11 +134,28 @@ async def status(process_id: str, request: Request): logger_service.log_info( f"Process router status endpoint called for process_id: {process_id}" ) + _annotate_span({"process_id": process_id}) # loading business component for process processService = app.app_context.get_service(ProcessService) - return await processService.get_current_process(process_id) + try: + result = await processService.get_current_process(process_id) + track_event_if_configured( + "GetProcessStatusSuccess", {"process_id": process_id} + ) + return result + except Exception as e: + track_event_if_configured( + "GetProcessStatusError", + { + "process_id": process_id, + "error": str(e), + "error_type": type(e).__name__, + }, + ) + _record_exception_on_span(e) + raise @router.get("/status/{process_id}/render/", response_class=JSONResponse) @@ -98,11 +167,28 @@ async def render_status(process_id: str, request: Request): logger_service.log_info( f"Process router render status endpoint called for process_id: {process_id}" ) + _annotate_span({"process_id": process_id}) # loading business component for process processService = app.app_context.get_service(ProcessService) - return await processService.render_current_process(process_id) + try: + result = await processService.render_current_process(process_id) + track_event_if_configured( + "RenderProcessStatusSuccess", {"process_id": process_id} + ) + return result + except Exception as e: + track_event_if_configured( + "RenderProcessStatusError", + { + "process_id": process_id, + "error": str(e), + "error_type": type(e).__name__, + }, + ) + _record_exception_on_span(e) + raise @router.post(process_router_paths.UPLOAD_FILES, status_code=200) @@ -194,9 +280,33 @@ async def upload_files( if response: response.headers["Location"] = f"/process/{process_id}/" + _annotate_span( + { + "process_id": process_id, + "uploaded_count": len(uploaded_files), + "total_count": len(all_process_files), + } + ) + track_event_if_configured( + "UploadFilesSuccess", + { + "process_id": process_id, + "uploaded_count": len(uploaded_files), + "total_count": len(all_process_files), + }, + ) return result_response except Exception as e: logger_service.log_error(f"Error in upload_files: {str(e)}") + track_event_if_configured( + "UploadFilesError", + { + "process_id": process_id, + "error": str(e), + "error_type": type(e).__name__, + }, + ) + _record_exception_on_span(e) raise HTTPException(status_code=500, detail=f"Error uploading files: {str(e)}") @@ -267,15 +377,49 @@ async def delete_file( if response: response.headers["Location"] = f"/process/{process_id}/" + _annotate_span( + { + "process_id": process_id, + "deleted_file": file_name, + "remaining_count": len(all_process_files), + } + ) + track_event_if_configured( + "DeleteFileSuccess", + { + "process_id": process_id, + "deleted_file": file_name, + "remaining_count": len(all_process_files), + }, + ) return result_response - except FileNotFoundError: + except FileNotFoundError as e: + track_event_if_configured( + "DeleteFileError", + { + "process_id": process_id, + "deleted_file": file_name, + "error_type": "FileNotFoundError", + }, + ) + _record_exception_on_span(e) raise HTTPException( status_code=404, detail=f"File '{file_name}' not found for process '{process_id}'", ) except Exception as e: logger_service.log_error(f"Error in delete_file: {str(e)}") + track_event_if_configured( + "DeleteFileError", + { + "process_id": process_id, + "deleted_file": file_name, + "error": str(e), + "error_type": type(e).__name__, + }, + ) + _record_exception_on_span(e) raise HTTPException(status_code=500, detail=f"Error deleting file: {str(e)}") @@ -331,10 +475,26 @@ async def delete_process( if response: response.headers["Location"] = f"/process/{process_id}/" + _annotate_span( + {"process_id": process_id, "deleted_count": deleted_count} + ) + track_event_if_configured( + "DeleteProcessSuccess", + {"process_id": process_id, "deleted_count": deleted_count}, + ) return result_response except Exception as e: logger_service.log_error(f"Error in delete_process: {str(e)}") + track_event_if_configured( + "DeleteProcessError", + { + "process_id": process_id, + "error": str(e), + "error_type": type(e).__name__, + }, + ) + _record_exception_on_span(e) raise HTTPException(status_code=500, detail=f"Error deleting process: {str(e)}") @@ -388,6 +548,10 @@ async def start_processing( if response: response.headers["Location"] = f"/process/{process_id}/" + _annotate_span({"process_id": process_id}) + track_event_if_configured( + "StartProcessingSuccess", {"process_id": process_id} + ) return { "message": "Processing started successfully", "process_id": process_id, @@ -396,6 +560,15 @@ async def start_processing( } except Exception as e: logger_service.log_error(f"Error in start_processing: {str(e)}") + track_event_if_configured( + "StartProcessingError", + { + "process_id": process_id, + "error": str(e), + "error_type": type(e).__name__, + }, + ) + _record_exception_on_span(e) raise HTTPException( status_code=500, detail=f"Error starting processing: {str(e)}" ) @@ -449,6 +622,17 @@ async def download_process_files( f"Created ZIP file with {len(converted_files)} files for process {process_id}" ) + _annotate_span( + { + "process_id": process_id, + "file_count": len(converted_files), + } + ) + track_event_if_configured( + "DownloadProcessFilesSuccess", + {"process_id": process_id, "file_count": len(converted_files)}, + ) + # Return ZIP file as streaming response return StreamingResponse( io.BytesIO(zip_buffer.read()), @@ -460,9 +644,28 @@ async def download_process_files( except HTTPException as e: logger_service.log_error(f"HTTPException in download: {e.detail}") + track_event_if_configured( + "DownloadProcessFilesError", + { + "process_id": process_id, + "error": str(e.detail), + "error_type": type(e).__name__, + "status_code": e.status_code, + }, + ) + _record_exception_on_span(e) raise e except Exception as e: logger_service.log_error(f"Error in download_process_files: {str(e)}") + track_event_if_configured( + "DownloadProcessFilesError", + { + "process_id": process_id, + "error": str(e), + "error_type": type(e).__name__, + }, + ) + _record_exception_on_span(e) raise HTTPException( status_code=500, detail=f"Error downloading files: {str(e)}" ) @@ -512,13 +715,40 @@ async def get_process_summary( f"Process summary retrieved for {process_id}: {len(filenames)} files" ) + _annotate_span( + {"process_id": process_id, "file_count": len(filenames)} + ) + track_event_if_configured( + "GetProcessSummarySuccess", + {"process_id": process_id, "file_count": len(filenames)}, + ) + return response except HTTPException as e: logger_service.log_error(f"HTTPException in process summary: {e.detail}") + track_event_if_configured( + "GetProcessSummaryError", + { + "process_id": process_id, + "error": str(e.detail), + "error_type": type(e).__name__, + "status_code": e.status_code, + }, + ) + _record_exception_on_span(e) raise e except Exception as e: logger_service.log_error(f"Error in get_process_summary: {str(e)}") + track_event_if_configured( + "GetProcessSummaryError", + { + "process_id": process_id, + "error": str(e), + "error_type": type(e).__name__, + }, + ) + _record_exception_on_span(e) raise HTTPException( status_code=500, detail=f"Error retrieving process summary: {str(e)}" ) @@ -561,23 +791,68 @@ async def get_file_content( f"File content retrieved for {filename} in process {process_id}" ) + _annotate_span({"process_id": process_id, "filename": filename}) + track_event_if_configured( + "GetFileContentSuccess", + {"process_id": process_id, "filename": filename}, + ) + return FileContentResponse(content=file_content) - except FileNotFoundError: + except FileNotFoundError as e: + track_event_if_configured( + "GetFileContentError", + { + "process_id": process_id, + "filename": filename, + "error_type": "FileNotFoundError", + }, + ) + _record_exception_on_span(e) raise HTTPException( status_code=404, detail=f"File '{filename}' not found for process '{process_id}'", ) - except UnicodeDecodeError: + except UnicodeDecodeError as e: + track_event_if_configured( + "GetFileContentError", + { + "process_id": process_id, + "filename": filename, + "error_type": "UnicodeDecodeError", + }, + ) + _record_exception_on_span(e) raise HTTPException( status_code=400, detail=f"File '{filename}' is not a text file and cannot be displayed", ) except HTTPException as e: logger_service.log_error(f"HTTPException in file content: {e.detail}") + track_event_if_configured( + "GetFileContentError", + { + "process_id": process_id, + "filename": filename, + "error": str(e.detail), + "error_type": type(e).__name__, + "status_code": e.status_code, + }, + ) + _record_exception_on_span(e) raise e except Exception as e: logger_service.log_error(f"Error in get_file_content: {str(e)}") + track_event_if_configured( + "GetFileContentError", + { + "process_id": process_id, + "filename": filename, + "error": str(e), + "error_type": type(e).__name__, + }, + ) + _record_exception_on_span(e) raise HTTPException( status_code=500, detail=f"Error retrieving file content: {str(e)}" ) @@ -599,6 +874,7 @@ async def cancel_process( try: logger_service.log_info(f"Cancel process request for process_id: {process_id}") + _annotate_span({"process_id": process_id}) # Get authenticated user authenticated_user = get_authenticated_user(request) @@ -653,6 +929,14 @@ async def cancel_process( f"Cancel request sent for process {process_id}, state: {result.get('kill_state', 'unknown')}" ) + track_event_if_configured( + "CancelProcessSuccess", + { + "process_id": process_id, + "kill_state": result.get("kill_state", "pending"), + }, + ) + return { "message": "Cancellation request submitted", "process_id": process_id, @@ -661,22 +945,57 @@ async def cancel_process( "kill_requested_at": result.get("kill_requested_at", ""), } - except httpx.TimeoutException: + except httpx.TimeoutException as e: logger_service.log_error(f"Timeout connecting to processor control API") + track_event_if_configured( + "CancelProcessError", + { + "process_id": process_id, + "error_type": "TimeoutException", + }, + ) + _record_exception_on_span(e) raise HTTPException( status_code=504, detail="Timeout connecting to processor control API", ) - except httpx.ConnectError: + except httpx.ConnectError as e: logger_service.log_error(f"Failed to connect to processor control API") + track_event_if_configured( + "CancelProcessError", + { + "process_id": process_id, + "error_type": "ConnectError", + }, + ) + _record_exception_on_span(e) raise HTTPException( status_code=503, detail="Processor control API is unavailable", ) - except HTTPException: + except HTTPException as e: + track_event_if_configured( + "CancelProcessError", + { + "process_id": process_id, + "error": str(e.detail), + "error_type": type(e).__name__, + "status_code": e.status_code, + }, + ) + _record_exception_on_span(e) raise except Exception as e: logger_service.log_error(f"Error in cancel_process: {str(e)}") + track_event_if_configured( + "CancelProcessError", + { + "process_id": process_id, + "error": str(e), + "error_type": type(e).__name__, + }, + ) + _record_exception_on_span(e) raise HTTPException( status_code=500, detail=f"Error cancelling process: {str(e)}" ) @@ -696,6 +1015,7 @@ async def get_cancel_status( try: logger_service.log_info(f"Get cancel status for process_id: {process_id}") + _annotate_span({"process_id": process_id}) # Get authenticated user authenticated_user = get_authenticated_user(request) @@ -745,24 +1065,56 @@ async def get_cancel_status( result = response.json() + track_event_if_configured( + "GetCancelStatusSuccess", {"process_id": process_id} + ) return result - except httpx.TimeoutException: + except httpx.TimeoutException as e: logger_service.log_error(f"Timeout connecting to processor control API") + track_event_if_configured( + "GetCancelStatusError", + {"process_id": process_id, "error_type": "TimeoutException"}, + ) + _record_exception_on_span(e) raise HTTPException( status_code=504, detail="Timeout connecting to processor control API", ) - except httpx.ConnectError: + except httpx.ConnectError as e: logger_service.log_error(f"Failed to connect to processor control API") + track_event_if_configured( + "GetCancelStatusError", + {"process_id": process_id, "error_type": "ConnectError"}, + ) + _record_exception_on_span(e) raise HTTPException( status_code=503, detail="Processor control API is unavailable", ) - except HTTPException: + except HTTPException as e: + track_event_if_configured( + "GetCancelStatusError", + { + "process_id": process_id, + "error": str(e.detail), + "error_type": type(e).__name__, + "status_code": e.status_code, + }, + ) + _record_exception_on_span(e) raise except Exception as e: logger_service.log_error(f"Error in get_cancel_status: {str(e)}") + track_event_if_configured( + "GetCancelStatusError", + { + "process_id": process_id, + "error": str(e), + "error_type": type(e).__name__, + }, + ) + _record_exception_on_span(e) raise HTTPException( status_code=500, detail=f"Error getting cancel status: {str(e)}" ) From 992376874f89bd15a4d68e604b2e63d49f26f6c1 Mon Sep 17 00:00:00 2001 From: Shreyas-Microsoft Date: Wed, 6 May 2026 19:23:17 +0530 Subject: [PATCH 04/12] test(backend-api): cover track_event_if_configured Application Insights gating MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds the first dedicated test package for the new `libs/logging/event_utils.py` helper at `src/tests/logging/test_event_utils.py` (mirroring the existing `src/tests//test_.py` layout — no new tooling, no new config). Coverage: - `test_no_op_when_connection_string_unset`: with the env var absent, the helper does NOT call `track_event` and the SDK module is never imported (verified via a `sys.modules` fake). - `test_warning_fires_only_once_per_process`: subsequent unconfigured calls are silent (one-shot warning latch). - `test_unconfigured_warning_message_is_stable`: pins the exact warning template that the rest of the system asserts against. - `test_forwards_to_track_event_when_configured`: with the env var set, the helper forwards the (name, properties) pair through to `azure.monitor.events.extension.track_event`. - `test_none_properties_normalised_to_empty_dict`: `properties=None` surfaces as `{}` to the SDK so `customDimensions` is always an object. - `test_whitespace_only_connection_string_is_treated_as_unset`: belt-and-braces gating against accidentally-blank env values. - `test_sdk_exception_is_swallowed`: a `RuntimeError` thrown by the SDK is caught and logged; telemetry must never break a request. The tests use a `sys.modules` fake for `azure.monitor.events.extension` so they never import the real Azure Monitor SDK at runtime — keeps the suite hermetic, fast (<1s), and CI-friendly. Validation - `uv run pytest src/tests --no-cov -c /dev/null` -> 51 passed (44 pre-existing + 7 new), with `PYTHONPATH=src/app`. Implements verification scope of AC #3 of AB#37816. Work item: https://dev.azure.com/CSACTOSOL/CSA%20Solutioning/_workitems/edit/37816 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/backend-api/src/tests/logging/__init__.py | 0 .../src/tests/logging/test_event_utils.py | 190 ++++++++++++++++++ 2 files changed, 190 insertions(+) create mode 100644 src/backend-api/src/tests/logging/__init__.py create mode 100644 src/backend-api/src/tests/logging/test_event_utils.py diff --git a/src/backend-api/src/tests/logging/__init__.py b/src/backend-api/src/tests/logging/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/backend-api/src/tests/logging/test_event_utils.py b/src/backend-api/src/tests/logging/test_event_utils.py new file mode 100644 index 00000000..c05b840c --- /dev/null +++ b/src/backend-api/src/tests/logging/test_event_utils.py @@ -0,0 +1,190 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +"""Unit tests for ``libs.logging.event_utils.track_event_if_configured``. + +These tests exercise the gating behaviour that the rest of the +application depends on: + +* When ``APPLICATIONINSIGHTS_CONNECTION_STRING`` is unset / empty, the + call is a no-op and the underlying SDK is *never* imported. +* When the env var is set, the call forwards through to + ``azure.monitor.events.extension.track_event``. +* The "missing connection string" warning fires exactly once per + process, with the wording the rest of the system asserts against. +* SDK exceptions are swallowed so telemetry can never break a request. + +The tests deliberately avoid touching the real Azure Monitor SDK by +patching the symbol that the implementation imports lazily inside the +function. This keeps the suite hermetic and CI-friendly. +""" +from __future__ import annotations + +import logging +import sys +import types +from typing import Any +from unittest.mock import MagicMock + +import pytest + +from libs.logging import event_utils +from libs.logging.event_utils import ( + APP_INSIGHTS_CONN_STRING_ENV, + reset_unconfigured_warning_for_tests, + track_event_if_configured, +) + + +@pytest.fixture(autouse=True) +def _reset_module_state(monkeypatch: pytest.MonkeyPatch) -> None: + """Each test starts from a clean slate. + + Removes any process-wide env that would skew gating, resets the + one-shot warning latch, and removes any cached + ``azure.monitor.events.extension`` module so the lazy import path + inside ``track_event_if_configured`` is exercised fresh on each + test. + """ + monkeypatch.delenv(APP_INSIGHTS_CONN_STRING_ENV, raising=False) + reset_unconfigured_warning_for_tests() + sys.modules.pop("azure.monitor.events.extension", None) + + +def _install_fake_track_event(call_log: list[tuple[str, dict[str, Any]]]) -> MagicMock: + """Inject a fake ``azure.monitor.events.extension`` into ``sys.modules``. + + The implementation in ``event_utils`` does + ``from azure.monitor.events.extension import track_event`` lazily; + seeding ``sys.modules`` with a fake ensures we never hit the real + SDK during tests and keeps the assertion surface narrow. + """ + mock_track_event = MagicMock( + side_effect=lambda name, properties: call_log.append((name, properties)) + ) + fake_module = types.ModuleType("azure.monitor.events.extension") + fake_module.track_event = mock_track_event # type: ignore[attr-defined] + sys.modules["azure.monitor.events.extension"] = fake_module + return mock_track_event + + +def test_no_op_when_connection_string_unset( + caplog: pytest.LogCaptureFixture, +) -> None: + """With no env var, the helper must not call into the SDK at all.""" + call_log: list[tuple[str, dict[str, Any]]] = [] + mock_track_event = _install_fake_track_event(call_log) + + with caplog.at_level(logging.WARNING, logger=event_utils.logger.name): + track_event_if_configured("CreateProcessSuccess", {"process_id": "abc"}) + + mock_track_event.assert_not_called() + assert call_log == [] + # Warning fires exactly once and references the event name. + warnings = [r for r in caplog.records if r.levelno == logging.WARNING] + assert len(warnings) == 1 + assert "APPLICATIONINSIGHTS_CONNECTION_STRING is not set" in warnings[0].getMessage() + assert "CreateProcessSuccess" in warnings[0].getMessage() + + +def test_warning_fires_only_once_per_process( + caplog: pytest.LogCaptureFixture, +) -> None: + """Subsequent unconfigured calls must be silent (one-shot warning latch).""" + _install_fake_track_event([]) + + with caplog.at_level(logging.WARNING, logger=event_utils.logger.name): + track_event_if_configured("First", {"k": 1}) + track_event_if_configured("Second", {"k": 2}) + track_event_if_configured("Third", {"k": 3}) + + warnings = [r for r in caplog.records if r.levelno == logging.WARNING] + assert len(warnings) == 1, "Only the first unconfigured call should warn." + + +def test_unconfigured_warning_message_is_stable() -> None: + """The exact warning template is part of the helper's contract.""" + assert event_utils._UNCONFIGURED_WARNING == ( + "APPLICATIONINSIGHTS_CONNECTION_STRING is not set; " + "track_event_if_configured(name=%s) is a no-op." + ) + + +def test_forwards_to_track_event_when_configured( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """A non-empty connection string must route the call to the SDK.""" + monkeypatch.setenv( + APP_INSIGHTS_CONN_STRING_ENV, + "InstrumentationKey=00000000-0000-0000-0000-000000000000", + ) + call_log: list[tuple[str, dict[str, Any]]] = [] + mock_track_event = _install_fake_track_event(call_log) + + track_event_if_configured( + "UploadFilesSuccess", + {"process_id": "p-1", "uploaded_count": 3}, + ) + + mock_track_event.assert_called_once_with( + "UploadFilesSuccess", + {"process_id": "p-1", "uploaded_count": 3}, + ) + assert call_log == [ + ("UploadFilesSuccess", {"process_id": "p-1", "uploaded_count": 3}) + ] + + +def test_none_properties_normalised_to_empty_dict( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Passing ``properties=None`` must surface as an empty dict to the SDK.""" + monkeypatch.setenv( + APP_INSIGHTS_CONN_STRING_ENV, + "InstrumentationKey=00000000-0000-0000-0000-000000000000", + ) + call_log: list[tuple[str, dict[str, Any]]] = [] + _install_fake_track_event(call_log) + + track_event_if_configured("StartProcessingSuccess") + + assert call_log == [("StartProcessingSuccess", {})] + + +def test_whitespace_only_connection_string_is_treated_as_unset( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """A whitespace-only env var must not be considered configured.""" + monkeypatch.setenv(APP_INSIGHTS_CONN_STRING_ENV, " ") + call_log: list[tuple[str, dict[str, Any]]] = [] + mock_track_event = _install_fake_track_event(call_log) + + track_event_if_configured("Anything") + + mock_track_event.assert_not_called() + assert call_log == [] + + +def test_sdk_exception_is_swallowed( + monkeypatch: pytest.MonkeyPatch, + caplog: pytest.LogCaptureFixture, +) -> None: + """An exception from ``track_event`` must be caught and logged, not raised.""" + monkeypatch.setenv( + APP_INSIGHTS_CONN_STRING_ENV, + "InstrumentationKey=00000000-0000-0000-0000-000000000000", + ) + fake_module = types.ModuleType("azure.monitor.events.extension") + fake_module.track_event = MagicMock( # type: ignore[attr-defined] + side_effect=RuntimeError("exporter dead") + ) + sys.modules["azure.monitor.events.extension"] = fake_module + + with caplog.at_level(logging.ERROR, logger=event_utils.logger.name): + # Must not raise even though the underlying SDK does. + track_event_if_configured("DeleteProcessSuccess", {"process_id": "p-9"}) + + errors = [r for r in caplog.records if r.levelno == logging.ERROR] + assert any( + "Failed to publish App Insights custom event" in r.getMessage() + for r in errors + ) From 70fe7cbed6e1dfb3e2066a931e59012588d8f65b Mon Sep 17 00:00:00 2001 From: Shreyas-Microsoft Date: Wed, 6 May 2026 19:25:44 +0530 Subject: [PATCH 05/12] fix(infra): remove redundant diagnosticSettings on Application Insights AVM module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The AVM `insights/component@0.6.0` module already wires Application Insights to Log Analytics through the `workspaceResourceId` parameter (workspace-based App Insights). Adding a separate `diagnosticSettings` entry pointing at the SAME workspace causes the platform-level `AppAvailabilityResults`, `AppRequests`, etc. tables to be ingested twice — once via the workspace-based App Insights pipeline and again via the diagnostic-settings forwarder. Removes the redundant line from both: - `infra/main.bicep` (line 305) - `infra/main_custom.bicep` (line 283) The custom-event / OpenTelemetry path the rest of this branch wires up sends data straight to App Insights via `APPLICATIONINSIGHTS_CONNECTION_STRING`, which is already injected into the backend-api and processor container apps by the same Bicep files (no change required to those env-var blocks). Validation - `bicep build infra/main.bicep --outfile ` -> OK, no diagnostics. - `bicep build infra/main_custom.bicep --outfile ` -> OK, only a pre-existing BCP334 warning at line 694 (unrelated to this edit). Implements AC #4 of AB#37816 (continuous, non-duplicated log collection). Reference: https://github.com/microsoft/Conversation-Knowledge-Mining-Solution-Accelerator/pull/811 Work item: https://dev.azure.com/CSACTOSOL/CSA%20Solutioning/_workitems/edit/37816 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- infra/main.bicep | 6 +++++- infra/main_custom.bicep | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/infra/main.bicep b/infra/main.bicep index d229d716..904fee03 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -301,8 +301,12 @@ module applicationInsights 'br/public:avm/res/insights/component:0.6.0' = if (en disableIpMasking: false flowType: 'Bluefield' // WAF aligned configuration for Monitoring + // The AVM `insights/component` module wires Application Insights to + // the Log Analytics workspace via `workspaceResourceId` (workspace- + // based App Insights). A separate `diagnosticSettings` entry on the + // SAME workspace causes duplicate ingestion of platform logs. + // Source: AB#37816 — see CKM #811 reference implementation. workspaceResourceId: enableMonitoring ? logAnalyticsWorkspaceResourceId : '' - diagnosticSettings: enableMonitoring ? [{ workspaceResourceId: logAnalyticsWorkspaceResourceId }] : null } } diff --git a/infra/main_custom.bicep b/infra/main_custom.bicep index fea69ac3..f93b93ab 100644 --- a/infra/main_custom.bicep +++ b/infra/main_custom.bicep @@ -279,8 +279,12 @@ module applicationInsights 'br/public:avm/res/insights/component:0.6.0' = if (en disableIpMasking: false flowType: 'Bluefield' // WAF aligned configuration for Monitoring + // The AVM `insights/component` module wires Application Insights to + // the Log Analytics workspace via `workspaceResourceId` (workspace- + // based App Insights). A separate `diagnosticSettings` entry on the + // SAME workspace causes duplicate ingestion of platform logs. + // Source: AB#37816 — see CKM #811 reference implementation. workspaceResourceId: enableMonitoring ? logAnalyticsWorkspaceResourceId : '' - diagnosticSettings: enableMonitoring ? [{ workspaceResourceId: logAnalyticsWorkspaceResourceId }] : null } } From 6589e611ebd075a661cba3d44ad194d11119e577 Mon Sep 17 00:00:00 2001 From: Shreyas-Microsoft Date: Tue, 19 May 2026 15:02:09 +0530 Subject: [PATCH 06/12] fix(backend-api): include error message in CancelProcess/CancelStatus timeout/connect events Aligns the property shape of the Timeout/Connect error events with every other error event emitted by the router (which all include both `error` and `error_type`). Without `error`, an App Insights query like `customEvents | where customDimensions.error contains "..."` silently misses these two failure paths. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/backend-api/src/app/routers/router_process.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/backend-api/src/app/routers/router_process.py b/src/backend-api/src/app/routers/router_process.py index 5d8e5552..3709a5fe 100644 --- a/src/backend-api/src/app/routers/router_process.py +++ b/src/backend-api/src/app/routers/router_process.py @@ -951,6 +951,7 @@ async def cancel_process( "CancelProcessError", { "process_id": process_id, + "error": str(e), "error_type": "TimeoutException", }, ) @@ -965,6 +966,7 @@ async def cancel_process( "CancelProcessError", { "process_id": process_id, + "error": str(e), "error_type": "ConnectError", }, ) @@ -1074,7 +1076,7 @@ async def get_cancel_status( logger_service.log_error(f"Timeout connecting to processor control API") track_event_if_configured( "GetCancelStatusError", - {"process_id": process_id, "error_type": "TimeoutException"}, + {"process_id": process_id, "error": str(e), "error_type": "TimeoutException"}, ) _record_exception_on_span(e) raise HTTPException( @@ -1085,7 +1087,7 @@ async def get_cancel_status( logger_service.log_error(f"Failed to connect to processor control API") track_event_if_configured( "GetCancelStatusError", - {"process_id": process_id, "error_type": "ConnectError"}, + {"process_id": process_id, "error": str(e), "error_type": "ConnectError"}, ) _record_exception_on_span(e) raise HTTPException( From 1bfdb56a2c68757d8706e29f25824fb6cd8257ab Mon Sep 17 00:00:00 2001 From: Priyanka-Microsoft Date: Mon, 25 May 2026 14:44:56 +0530 Subject: [PATCH 07/12] token usage --- infra/dashboards/deploy-workbooks.ps1 | 86 ++++ infra/dashboards/token-usage-queries.kql | 113 +++++ infra/dashboards/workbook-eks-content.json | 1 + infra/dashboards/workbook-gke-content.json | 1 + infra/main.bicep | 12 +- infra/main.parameters.json | 23 +- infra/main_custom.bicep | 12 +- infra/modules/tokenUsageWorkbook.bicep | 458 ++++++++++++++++++ src/frontend/Dockerfile | 1 + src/processor/Dockerfile | 3 +- src/processor/pyproject.toml | 2 + .../azure_openai_response_retry.py | 181 ++++++- .../agent_framework/groupchat_orchestrator.py | 164 +++++++ .../src/libs/base/orchestrator_base.py | 23 +- src/processor/src/main.py | 29 ++ src/processor/src/main_service.py | 33 ++ src/processor/src/services/queue_service.py | 1 + .../src/steps/analysis/models/step_param.py | 1 + .../orchestration/analysis_orchestrator.py | 1 + .../yaml_convert_orchestrator.py | 1 + .../orchestration/design_orchestrator.py | 1 + .../documentation_orchestrator.py | 1 + .../src/steps/migration_processor.py | 22 + src/processor/src/utils/agent_telemetry.py | 66 +++ src/processor/src/utils/event_utils.py | 64 +++ .../src/utils/token_usage_tracker.py | 403 +++++++++++++++ 26 files changed, 1694 insertions(+), 9 deletions(-) create mode 100644 infra/dashboards/deploy-workbooks.ps1 create mode 100644 infra/dashboards/token-usage-queries.kql create mode 100644 infra/dashboards/workbook-eks-content.json create mode 100644 infra/dashboards/workbook-gke-content.json create mode 100644 infra/modules/tokenUsageWorkbook.bicep create mode 100644 src/processor/src/utils/event_utils.py create mode 100644 src/processor/src/utils/token_usage_tracker.py diff --git a/infra/dashboards/deploy-workbooks.ps1 b/infra/dashboards/deploy-workbooks.ps1 new file mode 100644 index 00000000..caa88198 --- /dev/null +++ b/infra/dashboards/deploy-workbooks.ps1 @@ -0,0 +1,86 @@ +# ============================================================= +# LLM Token Usage Workbook Deployment Script +# ============================================================= +# Usage: +# .\deploy-workbooks.ps1 -ResourceGroup -AppInsightsResourceId [-Location ] +# +# Example: +# .\deploy-workbooks.ps1 ` +# -ResourceGroup "rg-my-permanent-rg" ` +# -AppInsightsResourceId "/subscriptions//resourcegroups//providers/microsoft.insights/components/" ` +# -Location "australiaeast" +# ============================================================= + +param( + [Parameter(Mandatory=$true)] + [string]$ResourceGroup, + + [Parameter(Mandatory=$true)] + [string]$AppInsightsResourceId, + + [string]$Location = "australiaeast" +) + +$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path + +# Deploy GKE workbook +$gkeContent = Get-Content "$scriptDir\workbook-gke-content.json" -Raw +$gkeId = [guid]::NewGuid().ToString() + +$body = @{ + location = $Location + kind = "shared" + properties = @{ + displayName = "LLM Token Usage Dashboard - GKE" + serializedData = $gkeContent + version = "Notebook/1.0" + sourceId = $AppInsightsResourceId + category = "workbook" + } + tags = @{ + "hidden-title" = "LLM Token Usage Dashboard - GKE" + } +} | ConvertTo-Json -Depth 5 + +$bodyFile = [System.IO.Path]::GetTempFileName() +$body | Set-Content $bodyFile -Encoding UTF8 + +az rest --method PUT ` + --url "https://management.azure.com/subscriptions/$(az account show --query id -o tsv)/resourceGroups/$ResourceGroup/providers/microsoft.insights/workbooks/$($gkeId)?api-version=2022-04-01" ` + --body "@$bodyFile" ` + --headers "Content-Type=application/json" 2>&1 | Out-Null + +Write-Host "Deployed GKE workbook: $gkeId" +Remove-Item $bodyFile + +# Deploy EKS workbook +$eksContent = Get-Content "$scriptDir\workbook-eks-content.json" -Raw +$eksId = [guid]::NewGuid().ToString() + +$body = @{ + location = $Location + kind = "shared" + properties = @{ + displayName = "LLM Token Usage Dashboard - EKS" + serializedData = $eksContent + version = "Notebook/1.0" + sourceId = $AppInsightsResourceId + category = "workbook" + } + tags = @{ + "hidden-title" = "LLM Token Usage Dashboard - EKS" + } +} | ConvertTo-Json -Depth 5 + +$bodyFile = [System.IO.Path]::GetTempFileName() +$body | Set-Content $bodyFile -Encoding UTF8 + +az rest --method PUT ` + --url "https://management.azure.com/subscriptions/$(az account show --query id -o tsv)/resourceGroups/$ResourceGroup/providers/microsoft.insights/workbooks/$($eksId)?api-version=2022-04-01" ` + --body "@$bodyFile" ` + --headers "Content-Type=application/json" 2>&1 | Out-Null + +Write-Host "Deployed EKS workbook: $eksId" +Remove-Item $bodyFile + +Write-Host "`nDone! Both workbooks deployed to $ResourceGroup" diff --git a/infra/dashboards/token-usage-queries.kql b/infra/dashboards/token-usage-queries.kql new file mode 100644 index 00000000..38a0fc19 --- /dev/null +++ b/infra/dashboards/token-usage-queries.kql @@ -0,0 +1,113 @@ +// ============================================================================= +// LLM Token Usage Dashboard Queries for Application Insights +// ============================================================================= +// These KQL queries can be used in Azure Application Insights / Log Analytics +// to visualize token usage across agents, models, steps, and users. +// ============================================================================= + +// ---- 1. Overall Token Usage Summary (last 24h) ---- +customEvents +| where name == "LLM_Token_Usage_Summary" +| where timestamp > ago(24h) +| extend process_id = tostring(customDimensions.process_id), + total_input = toint(customDimensions.total_input_tokens), + total_output = toint(customDimensions.total_output_tokens), + total = toint(customDimensions.total_tokens), + call_count = toint(customDimensions.total_calls) +| project timestamp, process_id, total_input, total_output, total, call_count +| order by timestamp desc + +// ---- 2. Per-Agent Token Usage ---- +customEvents +| where name == "LLM_Agent_Token_Usage" +| where timestamp > ago(24h) +| extend agent_name = tostring(customDimensions.agent_name), + input_tokens = toint(customDimensions.input_tokens), + output_tokens = toint(customDimensions.output_tokens), + total_tokens = toint(customDimensions.total_tokens), + calls = toint(customDimensions.call_count), + process_id = tostring(customDimensions.process_id) +| summarize total_input = sum(input_tokens), + total_output = sum(output_tokens), + total = sum(total_tokens), + total_calls = sum(calls) + by agent_name +| order by total desc + +// ---- 3. Per-Model Token Usage ---- +customEvents +| where name == "LLM_Model_Token_Usage" +| where timestamp > ago(24h) +| extend model_name = tostring(customDimensions.model_deployment_name), + input_tokens = toint(customDimensions.input_tokens), + output_tokens = toint(customDimensions.output_tokens), + total_tokens = toint(customDimensions.total_tokens), + calls = toint(customDimensions.call_count), + process_id = tostring(customDimensions.process_id) +| summarize total_input = sum(input_tokens), + total_output = sum(output_tokens), + total = sum(total_tokens), + total_calls = sum(calls) + by model_name +| order by total desc + +// ---- 4. Per-Step (Team) Token Usage ---- +customEvents +| where name == "LLM_Step_Token_Usage" +| where timestamp > ago(24h) +| extend step_name = tostring(customDimensions.step_name), + input_tokens = toint(customDimensions.input_tokens), + output_tokens = toint(customDimensions.output_tokens), + total_tokens = toint(customDimensions.total_tokens), + calls = toint(customDimensions.call_count), + process_id = tostring(customDimensions.process_id) +| summarize total_input = sum(input_tokens), + total_output = sum(output_tokens), + total = sum(total_tokens), + total_calls = sum(calls) + by step_name +| order by total desc + +// ---- 5. Per-User Token Usage (requires user_id in process telemetry) ---- +customEvents +| where name == "LLM_Token_Usage_Summary" +| where timestamp > ago(24h) +| extend process_id = tostring(customDimensions.process_id), + total_tokens = toint(customDimensions.total_tokens), + user_id = tostring(customDimensions.user_id) +| summarize total = sum(total_tokens), runs = count() by user_id +| order by total desc + +// ---- 6. Individual LLM Call Log ---- +customEvents +| where name == "LLM_Token_Usage" +| where timestamp > ago(24h) +| extend agent_name = tostring(customDimensions.agent_name), + step_name = tostring(customDimensions.step_name), + model = tostring(customDimensions.model_deployment_name), + input_tokens = toint(customDimensions.input_tokens), + output_tokens = toint(customDimensions.output_tokens), + total_tokens = toint(customDimensions.total_tokens), + process_id = tostring(customDimensions.process_id) +| project timestamp, process_id, agent_name, step_name, model, input_tokens, output_tokens, total_tokens +| order by timestamp desc + +// ---- 7. Hourly Token Usage Trend ---- +customEvents +| where name == "LLM_Token_Usage" +| where timestamp > ago(7d) +| extend total_tokens = toint(customDimensions.total_tokens) +| summarize hourly_tokens = sum(total_tokens), calls = count() by bin(timestamp, 1h) +| order by timestamp asc +| render timechart + +// ---- 8. Estimated Cost (GPT-4o pricing: $2.50/1M input, $10/1M output) ---- +customEvents +| where name == "LLM_Token_Usage_Summary" +| where timestamp > ago(24h) +| extend process_id = tostring(customDimensions.process_id), + input_tokens = toint(customDimensions.total_input_tokens), + output_tokens = toint(customDimensions.total_output_tokens) +| extend estimated_cost_usd = (input_tokens / 1000000.0 * 2.50) + (output_tokens / 1000000.0 * 10.0) +| project timestamp, process_id, input_tokens, output_tokens, estimated_cost_usd +| order by estimated_cost_usd desc diff --git a/infra/dashboards/workbook-eks-content.json b/infra/dashboards/workbook-eks-content.json new file mode 100644 index 00000000..04433e99 --- /dev/null +++ b/infra/dashboards/workbook-eks-content.json @@ -0,0 +1 @@ +{"version":"Notebook/1.0","items":[{"type":1,"content":{"json":"# LLM Token Usage Dashboard\n\nThis workbook provides comprehensive visibility into LLM token consumption across agents, models, workflow steps, and users.\n\n---"},"name":"header"},{"type":9,"content":{"version":"KqlParameterItem/1.0","parameters":[{"id":"time-range-param","version":"KqlParameterItem/1.0","name":"TimeRange","type":4,"isRequired":true,"value":{"durationMs":1800000,"endTime":"2026-05-21T06:50:00.000Z"},"typeSettings":{"selectableValues":[{"durationMs":3600000},{"durationMs":14400000},{"durationMs":86400000},{"durationMs":259200000},{"durationMs":604800000},{"durationMs":2592000000}],"allowCustom":true},"label":"Time Range"}],"style":"pills","queryType":0,"resourceType":"microsoft.insights/components"},"name":"parameters"},{"type":1,"content":{"json":"## Overall Token Usage Summary"},"name":"summary-header"},{"type":3,"content":{"version":"KqlItem/1.0","query":"customEvents\n| where name == \"LLM_Token_Usage_Summary\"\n| where timestamp {TimeRange}\n| summarize \n total_input = sum(toint(customDimensions.total_input_tokens)),\n total_output = sum(toint(customDimensions.total_output_tokens)),\n total = sum(toint(customDimensions.total_tokens)),\n total_calls = sum(toint(customDimensions.total_calls)),\n processes = dcount(tostring(customDimensions.process_id))","size":4,"title":"Token Usage Totals","queryType":0,"resourceType":"microsoft.insights/components","visualization":"tiles","tileSettings":{"titleContent":{"columnMatch":"Column1","formatter":1},"leftContent":{"columnMatch":"total","formatter":12,"formatOptions":{"palette":"auto"},"numberFormat":{"unit":0,"options":{"style":"decimal","maximumFractionDigits":0}}},"showBorder":true}},"name":"summary-tiles"},{"type":3,"content":{"version":"KqlItem/1.0","query":"customEvents\n| where name == \"LLM_Token_Usage_Summary\"\n| where timestamp {TimeRange}\n| extend process_id = tostring(customDimensions.process_id),\n total_input = toint(customDimensions.total_input_tokens),\n total_output = toint(customDimensions.total_output_tokens),\n total = toint(customDimensions.total_tokens),\n call_count = toint(customDimensions.total_calls)\n| project timestamp, process_id, total_input, total_output, total, call_count\n| order by timestamp desc","size":0,"title":"Token Usage by Process","queryType":0,"resourceType":"microsoft.insights/components","visualization":"table","gridSettings":{"formatters":[{"columnMatch":"total","formatter":3,"formatOptions":{"palette":"blue"}}]}},"name":"summary-table"},{"type":1,"content":{"json":"## Per-Agent Token Usage"},"name":"agent-header"},{"type":3,"content":{"version":"KqlItem/1.0","query":"customEvents\n| where name == \"LLM_Agent_Token_Usage\"\n| where timestamp {TimeRange}\n| extend agent_name = tostring(customDimensions.agent_name),\n input_tokens = toint(customDimensions.input_tokens),\n output_tokens = toint(customDimensions.output_tokens),\n total_tokens = toint(customDimensions.total_tokens),\n calls = toint(customDimensions.call_count)\n| summarize total_input = sum(input_tokens),\n total_output = sum(output_tokens),\n total = sum(total_tokens),\n total_calls = sum(calls)\n by agent_name\n| order by total desc","size":0,"title":"Token Consumption by Agent","queryType":0,"resourceType":"microsoft.insights/components","visualization":"table","gridSettings":{"formatters":[{"columnMatch":"total","formatter":3,"formatOptions":{"palette":"blue"}}]}},"customWidth":"50","name":"agent-table"},{"type":3,"content":{"version":"KqlItem/1.0","query":"customEvents\n| where name == \"LLM_Agent_Token_Usage\"\n| where timestamp {TimeRange}\n| extend agent_name = tostring(customDimensions.agent_name),\n total_tokens = toint(customDimensions.total_tokens)\n| summarize total = sum(total_tokens) by agent_name\n| order by total desc","size":0,"title":"Token Distribution by Agent","queryType":0,"resourceType":"microsoft.insights/components","visualization":"piechart"},"customWidth":"50","name":"agent-chart"},{"type":1,"content":{"json":"## Per-Model Token Usage"},"name":"model-header"},{"type":3,"content":{"version":"KqlItem/1.0","query":"customEvents\n| where name == \"LLM_Model_Token_Usage\"\n| where timestamp {TimeRange}\n| extend model_name = tostring(customDimensions.model_deployment_name),\n input_tokens = toint(customDimensions.input_tokens),\n output_tokens = toint(customDimensions.output_tokens),\n total_tokens = toint(customDimensions.total_tokens),\n calls = toint(customDimensions.call_count)\n| summarize total_input = sum(input_tokens),\n total_output = sum(output_tokens),\n total = sum(total_tokens),\n total_calls = sum(calls)\n by model_name\n| order by total desc","size":0,"title":"Token Consumption by Model","queryType":0,"resourceType":"microsoft.insights/components","visualization":"table","gridSettings":{"formatters":[{"columnMatch":"total","formatter":3,"formatOptions":{"palette":"green"}}]}},"customWidth":"50","name":"model-table"},{"type":3,"content":{"version":"KqlItem/1.0","query":"customEvents\n| where name == \"LLM_Model_Token_Usage\"\n| where timestamp {TimeRange}\n| extend model_name = tostring(customDimensions.model_deployment_name),\n total_tokens = toint(customDimensions.total_tokens)\n| summarize total = sum(total_tokens) by model_name\n| order by total desc","size":0,"title":"Token Distribution by Model","queryType":0,"resourceType":"microsoft.insights/components","visualization":"piechart"},"customWidth":"50","name":"model-chart"},{"type":1,"content":{"json":"## Per-Step (Team) Token Usage"},"name":"step-header"},{"type":3,"content":{"version":"KqlItem/1.0","query":"customEvents\n| where name == \"LLM_Step_Token_Usage\"\n| where timestamp {TimeRange}\n| extend step_name = tostring(customDimensions.step_name),\n input_tokens = toint(customDimensions.input_tokens),\n output_tokens = toint(customDimensions.output_tokens),\n total_tokens = toint(customDimensions.total_tokens),\n calls = toint(customDimensions.call_count)\n| summarize total_input = sum(input_tokens),\n total_output = sum(output_tokens),\n total = sum(total_tokens),\n total_calls = sum(calls)\n by step_name\n| order by total desc","size":0,"title":"Token Consumption by Workflow Step","queryType":0,"resourceType":"microsoft.insights/components","visualization":"barchart","chartSettings":{"xAxis":"step_name","yAxis":"total","group":"step_name"}},"customWidth":"50","name":"step-chart"},{"type":3,"content":{"version":"KqlItem/1.0","query":"customEvents\n| where name == \"LLM_Step_Token_Usage\"\n| where timestamp {TimeRange}\n| extend step_name = tostring(customDimensions.step_name),\n input_tokens = toint(customDimensions.input_tokens),\n output_tokens = toint(customDimensions.output_tokens),\n total_tokens = toint(customDimensions.total_tokens),\n calls = toint(customDimensions.call_count)\n| summarize total_input = sum(input_tokens),\n total_output = sum(output_tokens),\n total = sum(total_tokens),\n total_calls = sum(calls)\n by step_name\n| order by total desc","size":0,"title":"Step Usage Details","queryType":0,"resourceType":"microsoft.insights/components","visualization":"table","gridSettings":{"formatters":[{"columnMatch":"total","formatter":3,"formatOptions":{"palette":"orange"}}]}},"customWidth":"50","name":"step-table"},{"type":1,"content":{"json":"## Per-User Token Usage"},"name":"user-header"},{"type":3,"content":{"version":"KqlItem/1.0","query":"customEvents\n| where name == \"LLM_Token_Usage_Summary\"\n| where timestamp {TimeRange}\n| extend process_id = tostring(customDimensions.process_id),\n total_tokens = toint(customDimensions.total_tokens),\n user_id = tostring(customDimensions.user_id)\n| summarize total = sum(total_tokens), runs = count() by user_id\n| order by total desc","size":0,"title":"Token Usage by User","queryType":0,"resourceType":"microsoft.insights/components","visualization":"table","gridSettings":{"formatters":[{"columnMatch":"total","formatter":3,"formatOptions":{"palette":"purple"}}]}},"name":"user-table"},{"type":1,"content":{"json":"## Token Usage Trends"},"name":"trend-header"},{"type":3,"content":{"version":"KqlItem/1.0","query":"customEvents\n| where name == \"LLM_Token_Usage\"\n| where timestamp {TimeRange}\n| extend total_tokens = toint(customDimensions.total_tokens)\n| summarize hourly_tokens = sum(total_tokens), calls = count() by bin(timestamp, 1h)\n| order by timestamp asc","size":0,"title":"Hourly Token Consumption","queryType":0,"resourceType":"microsoft.insights/components","visualization":"linechart","chartSettings":{"xAxis":"timestamp","yAxis":"hourly_tokens","showLegend":true}},"name":"trend-chart"},{"type":1,"content":{"json":"## Estimated Cost\n\n> Cost estimates use GPT-4o pricing: **$2.50 / 1M input tokens**, **$10.00 / 1M output tokens**. Adjust for your actual model pricing."},"name":"cost-header"},{"type":3,"content":{"version":"KqlItem/1.0","query":"customEvents\n| where name == \"LLM_Token_Usage_Summary\"\n| where timestamp {TimeRange}\n| extend process_id = tostring(customDimensions.process_id),\n input_tokens = toint(customDimensions.total_input_tokens),\n output_tokens = toint(customDimensions.total_output_tokens)\n| extend estimated_cost_usd = round((input_tokens / 1000000.0 * 2.50) + (output_tokens / 1000000.0 * 10.0), 4)\n| project timestamp, process_id, input_tokens, output_tokens, estimated_cost_usd\n| order by estimated_cost_usd desc","size":0,"title":"Estimated Cost per Process (USD)","queryType":0,"resourceType":"microsoft.insights/components","visualization":"table","gridSettings":{"formatters":[{"columnMatch":"estimated_cost_usd","formatter":3,"formatOptions":{"palette":"redBright"}}]}},"name":"cost-table"},{"type":1,"content":{"json":"## Individual LLM Call Log"},"name":"calls-header"},{"type":3,"content":{"version":"KqlItem/1.0","query":"customEvents\n| where name == \"LLM_Token_Usage\"\n| where timestamp {TimeRange}\n| extend agent_name = tostring(customDimensions.agent_name),\n step_name = tostring(customDimensions.step_name),\n model = tostring(customDimensions.model_deployment_name),\n input_tokens = toint(customDimensions.input_tokens),\n output_tokens = toint(customDimensions.output_tokens),\n total_tokens = toint(customDimensions.total_tokens),\n process_id = tostring(customDimensions.process_id)\n| project timestamp, process_id, agent_name, step_name, model, input_tokens, output_tokens, total_tokens\n| order by timestamp desc\n| take 200","size":0,"title":"Recent LLM Calls (last 200)","queryType":0,"resourceType":"microsoft.insights/components","visualization":"table","gridSettings":{"formatters":[{"columnMatch":"total_tokens","formatter":3,"formatOptions":{"palette":"blue"}}]}},"name":"calls-table"}],"isLocked":false,"fallbackResourceIds":["/subscriptions/1d5876cd-7603-407a-96d2-ae5ca9a9c5f3/resourcegroups/rg-pricmglogp33/providers/microsoft.insights/components/appi-pricmglogp33usmqm"]} diff --git a/infra/dashboards/workbook-gke-content.json b/infra/dashboards/workbook-gke-content.json new file mode 100644 index 00000000..ad05834c --- /dev/null +++ b/infra/dashboards/workbook-gke-content.json @@ -0,0 +1 @@ +{"version":"Notebook/1.0","items":[{"type":1,"content":{"json":"# LLM Token Usage Dashboard\n\nThis workbook provides comprehensive visibility into LLM token consumption across agents, models, workflow steps, and users.\n\n---"},"name":"header"},{"type":9,"content":{"version":"KqlParameterItem/1.0","parameters":[{"id":"time-range-param","version":"KqlParameterItem/1.0","name":"TimeRange","type":4,"isRequired":true,"value":{"durationMs":1500000,"endTime":"2026-05-21T06:08:00.000Z"},"typeSettings":{"selectableValues":[{"durationMs":3600000},{"durationMs":14400000},{"durationMs":86400000},{"durationMs":259200000},{"durationMs":604800000},{"durationMs":2592000000}],"allowCustom":true},"label":"Time Range"}],"style":"pills","queryType":0,"resourceType":"microsoft.insights/components"},"name":"parameters"},{"type":1,"content":{"json":"## Overall Token Usage Summary"},"name":"summary-header"},{"type":3,"content":{"version":"KqlItem/1.0","query":"customEvents\n| where name == \"LLM_Token_Usage_Summary\"\n| where timestamp {TimeRange}\n| summarize \n total_input = sum(toint(customDimensions.total_input_tokens)),\n total_output = sum(toint(customDimensions.total_output_tokens)),\n total = sum(toint(customDimensions.total_tokens)),\n total_calls = sum(toint(customDimensions.total_calls)),\n processes = dcount(tostring(customDimensions.process_id))","size":4,"title":"Token Usage Totals","queryType":0,"resourceType":"microsoft.insights/components","visualization":"tiles","tileSettings":{"titleContent":{"columnMatch":"Column1","formatter":1},"leftContent":{"columnMatch":"total","formatter":12,"formatOptions":{"palette":"auto"},"numberFormat":{"unit":0,"options":{"style":"decimal","maximumFractionDigits":0}}},"showBorder":true}},"name":"summary-tiles"},{"type":3,"content":{"version":"KqlItem/1.0","query":"customEvents\n| where name == \"LLM_Token_Usage_Summary\"\n| where timestamp {TimeRange}\n| extend process_id = tostring(customDimensions.process_id),\n total_input = toint(customDimensions.total_input_tokens),\n total_output = toint(customDimensions.total_output_tokens),\n total = toint(customDimensions.total_tokens),\n call_count = toint(customDimensions.total_calls)\n| project timestamp, process_id, total_input, total_output, total, call_count\n| order by timestamp desc","size":0,"title":"Token Usage by Process","queryType":0,"resourceType":"microsoft.insights/components","visualization":"table","gridSettings":{"formatters":[{"columnMatch":"total","formatter":3,"formatOptions":{"palette":"blue"}}]}},"name":"summary-table"},{"type":1,"content":{"json":"## Per-Agent Token Usage"},"name":"agent-header"},{"type":3,"content":{"version":"KqlItem/1.0","query":"customEvents\n| where name == \"LLM_Agent_Token_Usage\"\n| where timestamp {TimeRange}\n| extend agent_name = tostring(customDimensions.agent_name),\n input_tokens = toint(customDimensions.input_tokens),\n output_tokens = toint(customDimensions.output_tokens),\n total_tokens = toint(customDimensions.total_tokens),\n calls = toint(customDimensions.call_count)\n| summarize total_input = sum(input_tokens),\n total_output = sum(output_tokens),\n total = sum(total_tokens),\n total_calls = sum(calls)\n by agent_name\n| order by total desc","size":0,"title":"Token Consumption by Agent","queryType":0,"resourceType":"microsoft.insights/components","visualization":"table","gridSettings":{"formatters":[{"columnMatch":"total","formatter":3,"formatOptions":{"palette":"blue"}}]}},"customWidth":"50","name":"agent-table"},{"type":3,"content":{"version":"KqlItem/1.0","query":"customEvents\n| where name == \"LLM_Agent_Token_Usage\"\n| where timestamp {TimeRange}\n| extend agent_name = tostring(customDimensions.agent_name),\n total_tokens = toint(customDimensions.total_tokens)\n| summarize total = sum(total_tokens) by agent_name\n| order by total desc","size":0,"title":"Token Distribution by Agent","queryType":0,"resourceType":"microsoft.insights/components","visualization":"piechart"},"customWidth":"50","name":"agent-chart"},{"type":1,"content":{"json":"## Per-Model Token Usage"},"name":"model-header"},{"type":3,"content":{"version":"KqlItem/1.0","query":"customEvents\n| where name == \"LLM_Model_Token_Usage\"\n| where timestamp {TimeRange}\n| extend model_name = tostring(customDimensions.model_deployment_name),\n input_tokens = toint(customDimensions.input_tokens),\n output_tokens = toint(customDimensions.output_tokens),\n total_tokens = toint(customDimensions.total_tokens),\n calls = toint(customDimensions.call_count)\n| summarize total_input = sum(input_tokens),\n total_output = sum(output_tokens),\n total = sum(total_tokens),\n total_calls = sum(calls)\n by model_name\n| order by total desc","size":0,"title":"Token Consumption by Model","queryType":0,"resourceType":"microsoft.insights/components","visualization":"table","gridSettings":{"formatters":[{"columnMatch":"total","formatter":3,"formatOptions":{"palette":"green"}}]}},"customWidth":"50","name":"model-table"},{"type":3,"content":{"version":"KqlItem/1.0","query":"customEvents\n| where name == \"LLM_Model_Token_Usage\"\n| where timestamp {TimeRange}\n| extend model_name = tostring(customDimensions.model_deployment_name),\n total_tokens = toint(customDimensions.total_tokens)\n| summarize total = sum(total_tokens) by model_name\n| order by total desc","size":0,"title":"Token Distribution by Model","queryType":0,"resourceType":"microsoft.insights/components","visualization":"piechart"},"customWidth":"50","name":"model-chart"},{"type":1,"content":{"json":"## Per-Step (Team) Token Usage"},"name":"step-header"},{"type":3,"content":{"version":"KqlItem/1.0","query":"customEvents\n| where name == \"LLM_Step_Token_Usage\"\n| where timestamp {TimeRange}\n| extend step_name = tostring(customDimensions.step_name),\n input_tokens = toint(customDimensions.input_tokens),\n output_tokens = toint(customDimensions.output_tokens),\n total_tokens = toint(customDimensions.total_tokens),\n calls = toint(customDimensions.call_count)\n| summarize total_input = sum(input_tokens),\n total_output = sum(output_tokens),\n total = sum(total_tokens),\n total_calls = sum(calls)\n by step_name\n| order by total desc","size":0,"title":"Token Consumption by Workflow Step","queryType":0,"resourceType":"microsoft.insights/components","visualization":"barchart","chartSettings":{"xAxis":"step_name","yAxis":"total","group":"step_name"}},"customWidth":"50","name":"step-chart"},{"type":3,"content":{"version":"KqlItem/1.0","query":"customEvents\n| where name == \"LLM_Step_Token_Usage\"\n| where timestamp {TimeRange}\n| extend step_name = tostring(customDimensions.step_name),\n input_tokens = toint(customDimensions.input_tokens),\n output_tokens = toint(customDimensions.output_tokens),\n total_tokens = toint(customDimensions.total_tokens),\n calls = toint(customDimensions.call_count)\n| summarize total_input = sum(input_tokens),\n total_output = sum(output_tokens),\n total = sum(total_tokens),\n total_calls = sum(calls)\n by step_name\n| order by total desc","size":0,"title":"Step Usage Details","queryType":0,"resourceType":"microsoft.insights/components","visualization":"table","gridSettings":{"formatters":[{"columnMatch":"total","formatter":3,"formatOptions":{"palette":"orange"}}]}},"customWidth":"50","name":"step-table"},{"type":1,"content":{"json":"## Per-User Token Usage"},"name":"user-header"},{"type":3,"content":{"version":"KqlItem/1.0","query":"customEvents\n| where name == \"LLM_Token_Usage_Summary\"\n| where timestamp {TimeRange}\n| extend process_id = tostring(customDimensions.process_id),\n total_tokens = toint(customDimensions.total_tokens),\n user_id = tostring(customDimensions.user_id)\n| summarize total = sum(total_tokens), runs = count() by user_id\n| order by total desc","size":0,"title":"Token Usage by User","queryType":0,"resourceType":"microsoft.insights/components","visualization":"table","gridSettings":{"formatters":[{"columnMatch":"total","formatter":3,"formatOptions":{"palette":"purple"}}]}},"name":"user-table"},{"type":1,"content":{"json":"## Token Usage Trends"},"name":"trend-header"},{"type":3,"content":{"version":"KqlItem/1.0","query":"customEvents\n| where name == \"LLM_Token_Usage\"\n| where timestamp {TimeRange}\n| extend total_tokens = toint(customDimensions.total_tokens)\n| summarize hourly_tokens = sum(total_tokens), calls = count() by bin(timestamp, 1h)\n| order by timestamp asc","size":0,"title":"Hourly Token Consumption","queryType":0,"resourceType":"microsoft.insights/components","visualization":"linechart","chartSettings":{"xAxis":"timestamp","yAxis":"hourly_tokens","showLegend":true}},"name":"trend-chart"},{"type":1,"content":{"json":"## Estimated Cost\n\n> Cost estimates use GPT-4o pricing: **$2.50 / 1M input tokens**, **$10.00 / 1M output tokens**. Adjust for your actual model pricing."},"name":"cost-header"},{"type":3,"content":{"version":"KqlItem/1.0","query":"customEvents\n| where name == \"LLM_Token_Usage_Summary\"\n| where timestamp {TimeRange}\n| extend process_id = tostring(customDimensions.process_id),\n input_tokens = toint(customDimensions.total_input_tokens),\n output_tokens = toint(customDimensions.total_output_tokens)\n| extend estimated_cost_usd = round((input_tokens / 1000000.0 * 2.50) + (output_tokens / 1000000.0 * 10.0), 4)\n| project timestamp, process_id, input_tokens, output_tokens, estimated_cost_usd\n| order by estimated_cost_usd desc","size":0,"title":"Estimated Cost per Process (USD)","queryType":0,"resourceType":"microsoft.insights/components","visualization":"table","gridSettings":{"formatters":[{"columnMatch":"estimated_cost_usd","formatter":3,"formatOptions":{"palette":"redBright"}}]}},"name":"cost-table"},{"type":1,"content":{"json":"## Individual LLM Call Log"},"name":"calls-header"},{"type":3,"content":{"version":"KqlItem/1.0","query":"customEvents\n| where name == \"LLM_Token_Usage\"\n| where timestamp {TimeRange}\n| extend agent_name = tostring(customDimensions.agent_name),\n step_name = tostring(customDimensions.step_name),\n model = tostring(customDimensions.model_deployment_name),\n input_tokens = toint(customDimensions.input_tokens),\n output_tokens = toint(customDimensions.output_tokens),\n total_tokens = toint(customDimensions.total_tokens),\n process_id = tostring(customDimensions.process_id)\n| project timestamp, process_id, agent_name, step_name, model, input_tokens, output_tokens, total_tokens\n| order by timestamp desc\n| take 200","size":0,"title":"Recent LLM Calls (last 200)","queryType":0,"resourceType":"microsoft.insights/components","visualization":"table","gridSettings":{"formatters":[{"columnMatch":"total_tokens","formatter":3,"formatOptions":{"palette":"blue"}}]}},"name":"calls-table"}],"isLocked":false,"fallbackResourceIds":["/subscriptions/1d5876cd-7603-407a-96d2-ae5ca9a9c5f3/resourcegroups/rg-pricmglogp33/providers/microsoft.insights/components/appi-pricmglogp33usmqm"]} diff --git a/infra/main.bicep b/infra/main.bicep index 904fee03..3306c57a 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -91,7 +91,7 @@ param enableTelemetry bool = true param enablePrivateNetworking bool = false @description('Optional. Enable monitoring applicable resources, aligned with the Well Architected Framework recommendations. This setting enables Application Insights and Log Analytics and configures all the resources applicable resources to send logs. Defaults to false.') -param enableMonitoring bool = false +param enableMonitoring bool = true @description('Optional. Enable scalability for applicable resources, aligned with the Well Architected Framework recommendations. Defaults to false.') param enableScalability bool = false @@ -310,6 +310,16 @@ module applicationInsights 'br/public:avm/res/insights/component:0.6.0' = if (en } } +// ========== LLM Token Usage Workbook ========== // +module tokenUsageWorkbook './modules/tokenUsageWorkbook.bicep' = if (enableMonitoring) { + name: take('module.token-usage-workbook.${solutionSuffix}', 64) + params: { + location: solutionLocation + applicationInsightsResourceId: applicationInsights!.outputs.resourceId + tags: allTags + } +} + // ========== Virtual Network ========== // module virtualNetwork './modules/virtualNetwork.bicep' = if (enablePrivateNetworking) { name: take('module.virtual-network.${solutionSuffix}', 64) diff --git a/infra/main.parameters.json b/infra/main.parameters.json index b4a1a7cc..6df293a4 100644 --- a/infra/main.parameters.json +++ b/infra/main.parameters.json @@ -26,11 +26,17 @@ "gptDeploymentCapacity": { "value": "${AZURE_ENV_GPT_MODEL_CAPACITY}" }, - "existingLogAnalyticsWorkspaceId": { - "value": "${AZURE_ENV_EXISTING_LOG_ANALYTICS_WORKSPACE_RID}" + "enableTelemetry": { + "value": true }, - "existingFoundryProjectResourceId": { - "value": "${AZURE_EXISTING_AIPROJECT_RESOURCE_ID}" + "enableMonitoring": { + "value": true + }, + "enablePrivateNetworking": { + "value": false + }, + "enableScalability": { + "value": false }, "vmAdminUsername": { "value": "${AZURE_ENV_VM_ADMIN_USERNAME}" @@ -38,8 +44,17 @@ "vmAdminPassword": { "value": "${AZURE_ENV_VM_ADMIN_PASSWORD}" }, + "existingLogAnalyticsWorkspaceId": { + "value": "${AZURE_ENV_EXISTING_LOG_ANALYTICS_WORKSPACE_RID}" + }, + "existingFoundryProjectResourceId": { + "value": "${AZURE_EXISTING_AIPROJECT_RESOURCE_ID}" + }, "imageTag": { "value": "${AZURE_ENV_IMAGE_TAG}" + }, + "vmSize": { + "value": "${AZURE_ENV_VM_SIZE}" } } } diff --git a/infra/main_custom.bicep b/infra/main_custom.bicep index f93b93ab..9e106e59 100644 --- a/infra/main_custom.bicep +++ b/infra/main_custom.bicep @@ -84,7 +84,7 @@ param enableTelemetry bool = true param enablePrivateNetworking bool = false @description('Optional. Enable monitoring applicable resources, aligned with the Well Architected Framework recommendations. This setting enables Application Insights and Log Analytics and configures all the resources applicable resources to send logs. Defaults to false.') -param enableMonitoring bool = false +param enableMonitoring bool = true @description('Optional. Enable scalability for applicable resources, aligned with the Well Architected Framework recommendations. Defaults to false.') param enableScalability bool = false @@ -288,6 +288,16 @@ module applicationInsights 'br/public:avm/res/insights/component:0.6.0' = if (en } } +// ========== LLM Token Usage Workbook ========== // +module tokenUsageWorkbook './modules/tokenUsageWorkbook.bicep' = if (enableMonitoring) { + name: take('module.token-usage-workbook.${solutionSuffix}', 64) + params: { + location: solutionLocation + applicationInsightsResourceId: applicationInsights!.outputs.resourceId + tags: allTags + } +} + // ========== Virtual Network ========== // module virtualNetwork './modules/virtualNetwork.bicep' = if (enablePrivateNetworking) { name: take('module.virtual-network.${solutionSuffix}', 64) diff --git a/infra/modules/tokenUsageWorkbook.bicep b/infra/modules/tokenUsageWorkbook.bicep new file mode 100644 index 00000000..6531bdda --- /dev/null +++ b/infra/modules/tokenUsageWorkbook.bicep @@ -0,0 +1,458 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +@description('Required. The location for the workbook resource.') +param location string + +@description('Required. The resource ID of the Application Insights instance to query.') +param applicationInsightsResourceId string + +@description('Optional. Tags to apply to the workbook resource.') +param tags object = {} + +@description('Optional. Display name for the workbook.') +param workbookDisplayName string = 'LLM Token Usage Dashboard' + +// Generate a deterministic GUID for the workbook based on resource group and name +var workbookId = guid(resourceGroup().id, 'token-usage-workbook') + +var workbookContent = { + version: 'Notebook/1.0' + items: [ + { + type: 1 + content: { + json: '# LLM Token Usage Dashboard\n\nThis workbook provides comprehensive visibility into LLM token consumption across agents, models, workflow steps, and users.\n\n---' + } + name: 'header' + } + { + type: 9 + content: { + version: 'KqlParameterItem/1.0' + parameters: [ + { + id: 'time-range-param' + version: 'KqlParameterItem/1.0' + name: 'TimeRange' + type: 4 + isRequired: true + value: { + durationMs: 86400000 + } + typeSettings: { + selectableValues: [ + { durationMs: 3600000 } + { durationMs: 14400000 } + { durationMs: 86400000 } + { durationMs: 259200000 } + { durationMs: 604800000 } + { durationMs: 2592000000 } + ] + allowCustom: true + } + label: 'Time Range' + } + ] + style: 'pills' + queryType: 0 + resourceType: 'microsoft.insights/components' + } + name: 'parameters' + } + // ===== Row 1: Summary Tiles ===== + { + type: 1 + content: { + json: '## Overall Token Usage Summary' + } + name: 'summary-header' + } + { + type: 3 + content: { + version: 'KqlItem/1.0' + query: 'customEvents\n| where name == "LLM_Token_Usage_Summary"\n| where timestamp {TimeRange}\n| summarize \n total_input = sum(toint(customDimensions.total_input_tokens)),\n total_output = sum(toint(customDimensions.total_output_tokens)),\n total = sum(toint(customDimensions.total_tokens)),\n total_calls = sum(toint(customDimensions.total_calls)),\n processes = dcount(tostring(customDimensions.process_id))' + size: 4 + title: 'Token Usage Totals' + queryType: 0 + resourceType: 'microsoft.insights/components' + crossComponentResources: [ + applicationInsightsResourceId + ] + visualization: 'tiles' + tileSettings: { + titleContent: { + columnMatch: 'Column1' + formatter: 1 + } + leftContent: { + columnMatch: 'total' + formatter: 12 + formatOptions: { + palette: 'auto' + } + numberFormat: { + unit: 0 + options: { + style: 'decimal' + maximumFractionDigits: 0 + } + } + } + showBorder: true + } + } + name: 'summary-tiles' + } + { + type: 3 + content: { + version: 'KqlItem/1.0' + query: 'customEvents\n| where name == "LLM_Token_Usage_Summary"\n| where timestamp {TimeRange}\n| extend process_id = tostring(customDimensions.process_id),\n total_input = toint(customDimensions.total_input_tokens),\n total_output = toint(customDimensions.total_output_tokens),\n total = toint(customDimensions.total_tokens),\n call_count = toint(customDimensions.total_calls)\n| project timestamp, process_id, total_input, total_output, total, call_count\n| order by timestamp desc' + size: 0 + title: 'Token Usage by Process' + queryType: 0 + resourceType: 'microsoft.insights/components' + crossComponentResources: [ + applicationInsightsResourceId + ] + visualization: 'table' + gridSettings: { + formatters: [ + { + columnMatch: 'total' + formatter: 3 + formatOptions: { + palette: 'blue' + } + } + ] + } + } + name: 'summary-table' + } + // ===== Row 2: Per-Agent Token Usage ===== + { + type: 1 + content: { + json: '## Per-Agent Token Usage' + } + name: 'agent-header' + } + { + type: 3 + content: { + version: 'KqlItem/1.0' + query: 'customEvents\n| where name == "LLM_Agent_Token_Usage"\n| where timestamp {TimeRange}\n| extend agent_name = tostring(customDimensions.agent_name),\n input_tokens = toint(customDimensions.input_tokens),\n output_tokens = toint(customDimensions.output_tokens),\n total_tokens = toint(customDimensions.total_tokens),\n calls = toint(customDimensions.call_count)\n| summarize total_input = sum(input_tokens),\n total_output = sum(output_tokens),\n total = sum(total_tokens),\n total_calls = sum(calls)\n by agent_name\n| order by total desc' + size: 0 + title: 'Token Consumption by Agent' + queryType: 0 + resourceType: 'microsoft.insights/components' + crossComponentResources: [ + applicationInsightsResourceId + ] + visualization: 'table' + gridSettings: { + formatters: [ + { + columnMatch: 'total' + formatter: 3 + formatOptions: { + palette: 'blue' + } + } + ] + } + } + customWidth: '50' + name: 'agent-table' + } + { + type: 3 + content: { + version: 'KqlItem/1.0' + query: 'customEvents\n| where name == "LLM_Agent_Token_Usage"\n| where timestamp {TimeRange}\n| extend agent_name = tostring(customDimensions.agent_name),\n total_tokens = toint(customDimensions.total_tokens)\n| summarize total = sum(total_tokens) by agent_name\n| order by total desc' + size: 0 + title: 'Token Distribution by Agent' + queryType: 0 + resourceType: 'microsoft.insights/components' + crossComponentResources: [ + applicationInsightsResourceId + ] + visualization: 'piechart' + } + customWidth: '50' + name: 'agent-chart' + } + // ===== Row 3: Per-Model Token Usage ===== + { + type: 1 + content: { + json: '## Per-Model Token Usage' + } + name: 'model-header' + } + { + type: 3 + content: { + version: 'KqlItem/1.0' + query: 'customEvents\n| where name == "LLM_Model_Token_Usage"\n| where timestamp {TimeRange}\n| extend model_name = tostring(customDimensions.model_deployment_name),\n input_tokens = toint(customDimensions.input_tokens),\n output_tokens = toint(customDimensions.output_tokens),\n total_tokens = toint(customDimensions.total_tokens),\n calls = toint(customDimensions.call_count)\n| summarize total_input = sum(input_tokens),\n total_output = sum(output_tokens),\n total = sum(total_tokens),\n total_calls = sum(calls)\n by model_name\n| order by total desc' + size: 0 + title: 'Token Consumption by Model' + queryType: 0 + resourceType: 'microsoft.insights/components' + crossComponentResources: [ + applicationInsightsResourceId + ] + visualization: 'table' + gridSettings: { + formatters: [ + { + columnMatch: 'total' + formatter: 3 + formatOptions: { + palette: 'green' + } + } + ] + } + } + customWidth: '50' + name: 'model-table' + } + { + type: 3 + content: { + version: 'KqlItem/1.0' + query: 'customEvents\n| where name == "LLM_Model_Token_Usage"\n| where timestamp {TimeRange}\n| extend model_name = tostring(customDimensions.model_deployment_name),\n total_tokens = toint(customDimensions.total_tokens)\n| summarize total = sum(total_tokens) by model_name\n| order by total desc' + size: 0 + title: 'Token Distribution by Model' + queryType: 0 + resourceType: 'microsoft.insights/components' + crossComponentResources: [ + applicationInsightsResourceId + ] + visualization: 'piechart' + } + customWidth: '50' + name: 'model-chart' + } + // ===== Row 4: Per-Step (Team) Token Usage ===== + { + type: 1 + content: { + json: '## Per-Step (Team) Token Usage' + } + name: 'step-header' + } + { + type: 3 + content: { + version: 'KqlItem/1.0' + query: 'customEvents\n| where name == "LLM_Step_Token_Usage"\n| where timestamp {TimeRange}\n| extend step_name = tostring(customDimensions.step_name),\n input_tokens = toint(customDimensions.input_tokens),\n output_tokens = toint(customDimensions.output_tokens),\n total_tokens = toint(customDimensions.total_tokens),\n calls = toint(customDimensions.call_count)\n| summarize total_input = sum(input_tokens),\n total_output = sum(output_tokens),\n total = sum(total_tokens),\n total_calls = sum(calls)\n by step_name\n| order by total desc' + size: 0 + title: 'Token Consumption by Workflow Step' + queryType: 0 + resourceType: 'microsoft.insights/components' + crossComponentResources: [ + applicationInsightsResourceId + ] + visualization: 'barchart' + chartSettings: { + xAxis: 'step_name' + yAxis: 'total' + group: 'step_name' + } + } + customWidth: '50' + name: 'step-chart' + } + { + type: 3 + content: { + version: 'KqlItem/1.0' + query: 'customEvents\n| where name == "LLM_Step_Token_Usage"\n| where timestamp {TimeRange}\n| extend step_name = tostring(customDimensions.step_name),\n input_tokens = toint(customDimensions.input_tokens),\n output_tokens = toint(customDimensions.output_tokens),\n total_tokens = toint(customDimensions.total_tokens),\n calls = toint(customDimensions.call_count)\n| summarize total_input = sum(input_tokens),\n total_output = sum(output_tokens),\n total = sum(total_tokens),\n total_calls = sum(calls)\n by step_name\n| order by total desc' + size: 0 + title: 'Step Usage Details' + queryType: 0 + resourceType: 'microsoft.insights/components' + crossComponentResources: [ + applicationInsightsResourceId + ] + visualization: 'table' + gridSettings: { + formatters: [ + { + columnMatch: 'total' + formatter: 3 + formatOptions: { + palette: 'orange' + } + } + ] + } + } + customWidth: '50' + name: 'step-table' + } + // ===== Row 5: Per-User Token Usage ===== + { + type: 1 + content: { + json: '## Per-User Token Usage' + } + name: 'user-header' + } + { + type: 3 + content: { + version: 'KqlItem/1.0' + query: 'customEvents\n| where name == "LLM_Token_Usage_Summary"\n| where timestamp {TimeRange}\n| extend process_id = tostring(customDimensions.process_id),\n total_tokens = toint(customDimensions.total_tokens),\n user_id = tostring(customDimensions.user_id)\n| summarize total = sum(total_tokens), runs = count() by user_id\n| order by total desc' + size: 0 + title: 'Token Usage by User' + queryType: 0 + resourceType: 'microsoft.insights/components' + crossComponentResources: [ + applicationInsightsResourceId + ] + visualization: 'table' + gridSettings: { + formatters: [ + { + columnMatch: 'total' + formatter: 3 + formatOptions: { + palette: 'purple' + } + } + ] + } + } + name: 'user-table' + } + // ===== Row 6: Hourly Token Usage Trend ===== + { + type: 1 + content: { + json: '## Token Usage Trends' + } + name: 'trend-header' + } + { + type: 3 + content: { + version: 'KqlItem/1.0' + query: 'customEvents\n| where name == "LLM_Token_Usage"\n| where timestamp {TimeRange}\n| extend total_tokens = toint(customDimensions.total_tokens)\n| summarize hourly_tokens = sum(total_tokens), calls = count() by bin(timestamp, 1h)\n| order by timestamp asc' + size: 0 + title: 'Hourly Token Consumption' + queryType: 0 + resourceType: 'microsoft.insights/components' + crossComponentResources: [ + applicationInsightsResourceId + ] + visualization: 'linechart' + chartSettings: { + xAxis: 'timestamp' + yAxis: 'hourly_tokens' + showLegend: true + } + } + name: 'trend-chart' + } + // ===== Row 7: Estimated Cost ===== + { + type: 1 + content: { + json: '## Estimated Cost\n\n> Cost estimates use GPT-4o pricing: **$2.50 / 1M input tokens**, **$10.00 / 1M output tokens**. Adjust for your actual model pricing.' + } + name: 'cost-header' + } + { + type: 3 + content: { + version: 'KqlItem/1.0' + query: 'customEvents\n| where name == "LLM_Token_Usage_Summary"\n| where timestamp {TimeRange}\n| extend process_id = tostring(customDimensions.process_id),\n input_tokens = toint(customDimensions.total_input_tokens),\n output_tokens = toint(customDimensions.total_output_tokens)\n| extend estimated_cost_usd = round((input_tokens / 1000000.0 * 2.50) + (output_tokens / 1000000.0 * 10.0), 4)\n| project timestamp, process_id, input_tokens, output_tokens, estimated_cost_usd\n| order by estimated_cost_usd desc' + size: 0 + title: 'Estimated Cost per Process (USD)' + queryType: 0 + resourceType: 'microsoft.insights/components' + crossComponentResources: [ + applicationInsightsResourceId + ] + visualization: 'table' + gridSettings: { + formatters: [ + { + columnMatch: 'estimated_cost_usd' + formatter: 3 + formatOptions: { + palette: 'redBright' + } + } + ] + } + } + name: 'cost-table' + } + // ===== Row 8: Individual LLM Call Log ===== + { + type: 1 + content: { + json: '## Individual LLM Call Log' + } + name: 'calls-header' + } + { + type: 3 + content: { + version: 'KqlItem/1.0' + query: 'customEvents\n| where name == "LLM_Token_Usage"\n| where timestamp {TimeRange}\n| extend agent_name = tostring(customDimensions.agent_name),\n step_name = tostring(customDimensions.step_name),\n model = tostring(customDimensions.model_deployment_name),\n input_tokens = toint(customDimensions.input_tokens),\n output_tokens = toint(customDimensions.output_tokens),\n total_tokens = toint(customDimensions.total_tokens),\n process_id = tostring(customDimensions.process_id)\n| project timestamp, process_id, agent_name, step_name, model, input_tokens, output_tokens, total_tokens\n| order by timestamp desc\n| take 200' + size: 0 + title: 'Recent LLM Calls (last 200)' + queryType: 0 + resourceType: 'microsoft.insights/components' + crossComponentResources: [ + applicationInsightsResourceId + ] + visualization: 'table' + gridSettings: { + formatters: [ + { + columnMatch: 'total_tokens' + formatter: 3 + formatOptions: { + palette: 'blue' + } + } + ] + } + } + name: 'calls-table' + } + ] + isLocked: false + fallbackResourceIds: [ + applicationInsightsResourceId + ] +} + +resource tokenUsageWorkbook 'Microsoft.Insights/workbooks@2023-06-01' = { + name: workbookId + location: location + tags: tags + kind: 'shared' + properties: { + displayName: workbookDisplayName + category: 'workbook' + version: '1.0' + serializedData: string(workbookContent) + sourceId: applicationInsightsResourceId + } +} + +@description('The resource ID of the created workbook.') +output resourceId string = tokenUsageWorkbook.id + +@description('The name of the created workbook.') +output name string = tokenUsageWorkbook.name diff --git a/src/frontend/Dockerfile b/src/frontend/Dockerfile index 0ad16303..c7c40439 100644 --- a/src/frontend/Dockerfile +++ b/src/frontend/Dockerfile @@ -13,6 +13,7 @@ RUN npm install COPY . . # Build the app +ENV NODE_OPTIONS="--max-old-space-size=4096" RUN npm run build # Runtime stage diff --git a/src/processor/Dockerfile b/src/processor/Dockerfile index afe293a5..317c8e03 100644 --- a/src/processor/Dockerfile +++ b/src/processor/Dockerfile @@ -36,7 +36,8 @@ RUN curl -fsSLo /tmp/node.tar.gz "https://nodejs.org/dist/v${NODE_VERSION}/node- COPY pyproject.toml uv.lock ./ # Install dependencies using UV -RUN uv sync --frozen --python 3.12 +# Re-lock to pick up any pyproject.toml changes (e.g. new deps), then install. +RUN uv lock --python 3.12 && uv sync --frozen --python 3.12 # Copy the entire source code COPY src/ ./src/ diff --git a/src/processor/pyproject.toml b/src/processor/pyproject.toml index 846621b5..80a0768a 100644 --- a/src/processor/pyproject.toml +++ b/src/processor/pyproject.toml @@ -13,6 +13,8 @@ dependencies = [ "azure-ai-projects==2.0.0b3", "azure-appconfiguration==1.7.2", "azure-core==1.38.0", + "azure-monitor-events-extension==0.1.0", + "azure-monitor-opentelemetry==1.8.7", "azure-cosmos==4.15.0", "azure-identity==1.26.0b1", "azure-storage-blob==12.28.0", diff --git a/src/processor/src/libs/agent_framework/azure_openai_response_retry.py b/src/processor/src/libs/agent_framework/azure_openai_response_retry.py index 5851b809..bc7d4e4b 100644 --- a/src/processor/src/libs/agent_framework/azure_openai_response_retry.py +++ b/src/processor/src/libs/agent_framework/azure_openai_response_retry.py @@ -23,6 +23,140 @@ logger = logging.getLogger(__name__) +def _extract_tokens_from_dict_or_obj(ud: Any) -> tuple[int, int, int]: + """Extract (input, output, total) token counts from a dict or object.""" + inp = out = tot = 0 + if isinstance(ud, dict): + inp = ud.get("input_token_count", 0) or ud.get("input_tokens", 0) or 0 + out = ud.get("output_token_count", 0) or ud.get("output_tokens", 0) or 0 + tot = ud.get("total_token_count", 0) or ud.get("total_tokens", 0) or 0 + else: + inp = getattr(ud, "input_token_count", 0) or getattr(ud, "input_tokens", 0) or 0 + out = getattr(ud, "output_token_count", 0) or getattr(ud, "output_tokens", 0) or 0 + tot = getattr(ud, "total_token_count", 0) or getattr(ud, "total_tokens", 0) or 0 + if not tot: + tot = int(inp) + int(out) + return int(inp), int(out), int(tot) + + +def _try_emit_token_event(inp: int, out: int, tot: int, source: str) -> None: + """Log token usage found in stream/response for diagnostics. + + The actual LLM_Token_Usage event is emitted by TokenUsageTracker.record() + in the orchestrator, which has full context (agent, step, model, user). + This function only logs for debugging to avoid duplicate events. + """ + if tot > 0 or inp > 0 or out > 0: + logger.info( + "[TOKEN_STREAM] usage found: input=%s output=%s total=%s source=%s", + inp, out, tot, source, + ) + + +def _emit_usage_from_stream_item(item: Any) -> None: + """Check a streamed ChatResponseUpdate for usage Content and emit an App Insights event. + + Checks multiple locations where usage data may appear: + 1. item.contents[] with type="usage" and usage_details + 2. item.usage (direct attribute - some SDK versions) + 3. item.metadata with usage keys + """ + try: + item_type = type(item).__name__ + + # --- Path 1: contents list with Content(type="usage") --- + contents = getattr(item, "contents", None) + if contents: + for content in contents: + ctype = getattr(content, "type", None) + if ctype == "usage": + # SDK UsageContent uses "details"; fall back to "usage_details" + ud = getattr(content, "details", None) or getattr(content, "usage_details", None) + if ud: + inp, out, tot = _extract_tokens_from_dict_or_obj(ud) + _try_emit_token_event(inp, out, tot, "stream_contents") + return + + # --- Path 2: direct .usage attribute --- + usage = getattr(item, "usage", None) + if usage is not None: + inp, out, tot = _extract_tokens_from_dict_or_obj(usage) + _try_emit_token_event(inp, out, tot, "stream_usage_attr") + return + + # --- Path 3: .metadata dict with usage keys --- + metadata = getattr(item, "metadata", None) + if isinstance(metadata, dict): + if any(k in metadata for k in ("input_tokens", "input_token_count", "usage")): + usage_data = metadata.get("usage", metadata) + inp, out, tot = _extract_tokens_from_dict_or_obj(usage_data) + _try_emit_token_event(inp, out, tot, "stream_metadata") + return + + # --- Diagnostic: log item shape for debugging (only for non-text items) --- + if contents: + content_types = [getattr(c, "type", "?") for c in contents] + if any(t not in ("text",) for t in content_types): + logger.debug( + "[TOKEN_DIAG] item_type=%s content_types=%s attrs=%s", + item_type, + content_types, + [a for a in dir(item) if not a.startswith("_")], + ) + except Exception as e: + logger.debug("[TOKEN_STREAM] error in emit: %s", e) + + +def _emit_usage_from_response(response: Any) -> None: + """Extract and emit token usage from a non-streaming ChatResponse. + + Checks usage_details (SDK attribute) and contents for UsageContent items. + """ + try: + # Path 1: response.usage_details (ChatResponse from SDK) + ud = getattr(response, "usage_details", None) or getattr(response, "details", None) + if ud is not None: + inp, out, tot = _extract_tokens_from_dict_or_obj(ud) + _try_emit_token_event(inp, out, tot, "response_usage_details") + return + + # Path 2: response.usage direct attribute + usage = getattr(response, "usage", None) + if usage is not None: + inp, out, tot = _extract_tokens_from_dict_or_obj(usage) + _try_emit_token_event(inp, out, tot, "response_usage_attr") + return + + # Path 3: contents list with UsageContent + contents = getattr(response, "contents", None) + if contents: + for content in contents: + ctype = getattr(content, "type", None) + if ctype == "usage": + ud = getattr(content, "details", None) or getattr(content, "usage_details", None) + if ud: + inp, out, tot = _extract_tokens_from_dict_or_obj(ud) + _try_emit_token_event(inp, out, tot, "response_contents") + return + + # Path 4: messages list with usage content + messages = getattr(response, "messages", None) + if messages: + for msg in messages: + msg_contents = getattr(msg, "contents", None) + if not msg_contents: + continue + for item in msg_contents: + if getattr(item, "type", None) == "usage": + ud = getattr(item, "details", None) or getattr(item, "usage_details", None) + if ud: + inp, out, tot = _extract_tokens_from_dict_or_obj(ud) + _try_emit_token_event(inp, out, tot, "response_msg_contents") + return + except Exception as e: + logger.debug("[TOKEN_RESPONSE] error in emit: %s", e) + + def _format_exc_brief(exc: BaseException) -> str: name = type(exc).__name__ msg = str(exc) @@ -78,6 +212,20 @@ def _looks_like_rate_limit(error: BaseException) -> bool: if isinstance(status, int) and 500 <= status < 600: return True + # "The model produced invalid content" is a transient error from Azure OpenAI + # when the model output fails content/schema validation — worth retrying. + # "No tool call found" is a 400 error when the conversation has orphaned + # function call outputs with no matching tool call request. + if any( + s in msg + for s in [ + "model produced invalid content", + "invalid content", + "no tool call found", + ] + ): + return True + cause = getattr(error, "__cause__", None) if cause and cause is not error: return _looks_like_rate_limit(cause) @@ -548,12 +696,15 @@ async def _inner_get_response( ) try: - return await _retry_call( + response = await _retry_call( lambda: parent_inner_get_response( messages=effective_messages, chat_options=chat_options, **kwargs ), config=self._retry_config, ) + # Extract and emit token usage from non-streaming response + _emit_usage_from_response(response) + return response except Exception as e: if not ( self._context_trim_config.enabled @@ -643,8 +794,36 @@ async def _tail(): async for item in iterator: yield item + _item_count = 0 + _last_item = None async for item in _tail(): + _item_count += 1 + _last_item = item + _emit_usage_from_stream_item(item) yield item + + # After stream completes, log diagnostic about the last item + if _last_item is not None: + try: + _attrs = [a for a in dir(_last_item) if not a.startswith("_")] + _contents = getattr(_last_item, "contents", None) + _content_info = [] + if _contents: + for _c in _contents: + _ct = getattr(_c, "type", "?") + _ca = [a for a in dir(_c) if not a.startswith("_")] + _content_info.append({"type": _ct, "attrs": _ca}) + _usage_attr = getattr(_last_item, "usage", None) + logger.info( + "[TOKEN_DIAG_FINAL] stream_items=%d last_item_type=%s attrs=%s contents=%s usage_attr=%s", + _item_count, + type(_last_item).__name__, + _attrs, + _content_info, + repr(_usage_attr) if _usage_attr is not None else "None", + ) + except Exception: + pass return except StopAsyncIteration: return diff --git a/src/processor/src/libs/agent_framework/groupchat_orchestrator.py b/src/processor/src/libs/agent_framework/groupchat_orchestrator.py index 5cb63938..50711fd3 100644 --- a/src/processor/src/libs/agent_framework/groupchat_orchestrator.py +++ b/src/processor/src/libs/agent_framework/groupchat_orchestrator.py @@ -36,6 +36,8 @@ from mem0 import AsyncMemory from pydantic import BaseModel, ValidationError +from utils.token_usage_tracker import TokenUsageTracker, extract_usage_from_response, _parse_usage_object + logger = logging.getLogger(__name__) @@ -93,6 +95,7 @@ class OrchestrationResult(Generic[TOutput]): result: TOutput | None = None error: str | None = None execution_time_seconds: float = 0.0 + token_usage_summary: dict[str, Any] | None = None @staticmethod def _to_jsonable(value: Any) -> Any: @@ -156,6 +159,7 @@ def model_dump(self) -> dict[str, Any]: "result": self._to_jsonable(self.result), "error": self.error, "execution_time_seconds": self.execution_time_seconds, + "token_usage_summary": self.token_usage_summary, } def to_json(self, *, indent: int = 2) -> str: @@ -195,6 +199,7 @@ def __init__( max_rounds: int = 100, max_seconds: float | None = None, result_output_format: type[TOutput] | None = None, + token_usage_tracker: TokenUsageTracker | None = None, ): """ Initialize the orchestrator. @@ -224,11 +229,15 @@ def __init__( self.max_seconds = max_seconds self.result_format = result_output_format + # Token usage tracker (optional — provided by OrchestratorBase) + self.token_usage_tracker = token_usage_tracker + # Runtime state self.agents: dict[str, ChatAgent] = participants self.agent_tool_usage: dict[str, list[dict[str, Any]]] = {} self.agent_responses: list[AgentResponse] = [] self._initialized: bool = False + self._streaming_captured_usage: bool = False # Streaming response buffer self._last_executor_id: str | None = None @@ -546,6 +555,11 @@ async def run_stream( # items inside ChatMessage.contents. self._backfill_tool_usage_from_conversation(conversation) + # Backfill token usage from conversation messages. + # Streaming events may not surface usage Content items, but the final + # conversation messages reliably carry them. + self._backfill_token_usage_from_conversation(conversation) + # Post-workflow analysis (optional) final_analysis = None result_format = self.result_format @@ -606,6 +620,11 @@ async def run_stream( execution_time = (datetime.now() - start_time).total_seconds() # Build result + # Collect token usage summary if tracker is active + token_summary = None + if self.token_usage_tracker is not None: + token_summary = self.token_usage_tracker.get_summary() + result = OrchestrationResult[TOutput]( success=True, conversation=conversation, @@ -614,6 +633,7 @@ async def run_stream( result=final_analysis, error=None, execution_time_seconds=execution_time, + token_usage_summary=token_summary, ) # Callback for completion with Typed Result @@ -625,6 +645,10 @@ async def run_stream( except Exception as e: execution_time = (datetime.now() - start_time).total_seconds() + token_summary = None + if self.token_usage_tracker is not None: + token_summary = self.token_usage_tracker.get_summary() + error_result = OrchestrationResult[TOutput]( success=False, conversation=[], @@ -633,6 +657,7 @@ async def run_stream( result=None, error=str(e), execution_time_seconds=execution_time, + token_usage_summary=token_summary, ) if on_workflow_complete: @@ -660,6 +685,78 @@ async def _handle_agent_update( self._append_text_chunk(event) await self._process_tool_calls(event, agent_name, stream_callback) + # Extract token usage from the streaming update if tracker is active. + # Check multiple paths where usage data may appear. + if self.token_usage_tracker is not None: + try: + data = event.data + # Path 1: data.contents with Content(type="usage") + contents = getattr(data, "contents", None) + if contents: + for item in contents: + ctype = getattr(item, "type", None) + if ctype == "usage": + # SDK UsageContent uses "details"; fall back to "usage_details" + ud = getattr(item, "details", None) or getattr(item, "usage_details", None) + if ud: + record = _parse_usage_object(ud) + if record and record.total_tokens > 0: + self.token_usage_tracker.record( + input_tokens=record.input_tokens, + output_tokens=record.output_tokens, + total_tokens=record.total_tokens, + agent_name=agent_name, + step_name=self.name, + ) + self._streaming_captured_usage = True + logger.info( + "[TOKEN_ORCH] recorded from contents: agent=%s step=%s tokens=%s", + agent_name, self.name, record.total_tokens, + ) + return + # Path 2: data.usage direct attribute + usage = getattr(data, "usage", None) + if usage is not None: + record = _parse_usage_object(usage) + if record and record.total_tokens > 0: + self.token_usage_tracker.record( + input_tokens=record.input_tokens, + output_tokens=record.output_tokens, + total_tokens=record.total_tokens, + agent_name=agent_name, + step_name=self.name, + ) + self._streaming_captured_usage = True + logger.info( + "[TOKEN_ORCH] recorded from usage attr: agent=%s step=%s tokens=%s", + agent_name, self.name, record.total_tokens, + ) + return + # Path 3: event itself may carry usage + event_usage = getattr(event, "usage", None) + if event_usage is not None: + record = _parse_usage_object(event_usage) + if record and record.total_tokens > 0: + self.token_usage_tracker.record( + input_tokens=record.input_tokens, + output_tokens=record.output_tokens, + total_tokens=record.total_tokens, + agent_name=agent_name, + step_name=self.name, + ) + self._streaming_captured_usage = True + logger.info( + "[TOKEN_ORCH] recorded from event.usage: agent=%s step=%s tokens=%s", + agent_name, self.name, record.total_tokens, + ) + return + except Exception: + logger.debug( + "Failed to extract token usage from update (agent=%s)", + agent_name, + exc_info=True, + ) + def _normalize_executor_id(self, executor_id: str) -> str: """Normalize executor id to agent name. @@ -930,6 +1027,73 @@ def _backfill_tool_usage_from_conversation( # Best effort only; don't break orchestration continue + def _backfill_token_usage_from_conversation( + self, conversation: list[ChatMessage] + ) -> None: + """Extract token usage from the final conversation messages. + + The agent_framework attaches ``Content(type="usage")`` items to + assistant messages when the underlying LLM response completes. + Streaming updates may not surface these, so we scan the final + conversation as a **fallback only when streaming did not capture + any usage** to avoid double-counting. + """ + if self.token_usage_tracker is None: + return + + # Skip backfill if streaming already captured token usage for this orchestrator run. + if getattr(self, "_streaming_captured_usage", False): + logger.info( + "[TOKEN] Skipping backfill — streaming already captured usage (step=%s)", + self.name, + ) + return + + found_any = False + for msg in conversation: + try: + role = getattr(msg, "role", None) + author = getattr(msg, "author_name", None) or "unknown" + contents = getattr(msg, "contents", None) + if not contents: + continue + + for item in contents: + item_type = getattr(item, "type", None) + if item_type != "usage": + continue + + # SDK UsageContent uses "details"; fall back to "usage_details" + ud = getattr(item, "details", None) or getattr(item, "usage_details", None) + if ud is None: + continue + + record = _parse_usage_object(ud) + if record and record.total_tokens > 0: + agent_name = self._normalize_executor_id(author) + self.token_usage_tracker.record( + input_tokens=record.input_tokens, + output_tokens=record.output_tokens, + total_tokens=record.total_tokens, + agent_name=agent_name, + step_name=self.name, + ) + found_any = True + except Exception: + continue + + if found_any: + logger.info( + "[TOKEN] Backfilled token usage from conversation (step=%s)", + self.name, + ) + else: + logger.warning( + "[TOKEN] No usage Content found in conversation messages (step=%s, msgs=%d)", + self.name, + len(conversation), + ) + async def _complete_agent_response( self, agent_id: str, diff --git a/src/processor/src/libs/base/orchestrator_base.py b/src/processor/src/libs/base/orchestrator_base.py index 46dce8c6..cfeb8a04 100644 --- a/src/processor/src/libs/base/orchestrator_base.py +++ b/src/processor/src/libs/base/orchestrator_base.py @@ -26,6 +26,7 @@ ) from utils.agent_telemetry import TelemetryManager from utils.console_util import format_agent_message +from utils.token_usage_tracker import TokenUsageTracker from .agent_base import AgentBase @@ -42,6 +43,7 @@ def __init__(self, app_context=None): self.initialized = False self.memory_store: QdrantMemoryStore | None = None self.step_name: str = "" + self.token_tracker: TokenUsageTracker | None = None def is_console_summarization_enabled(self) -> bool: """Return True if console summarization (extra LLM call per turn) is enabled. @@ -83,6 +85,23 @@ async def initialize(self, process_id: str): self.agents = await self.create_agents(self.agentinfos, process_id=process_id) self.initialized = True + # Resolve workflow-level token usage tracker from AppContext (if registered) + if self.app_context.is_registered(TokenUsageTracker): + try: + self.token_tracker = self.app_context.get_service(TokenUsageTracker) + # Register model deployment name for all agents so per-model tracking works + try: + deployment_name = self.agent_framework_helper.settings.get_service_config( + "default" + ).chat_deployment_name + if deployment_name and self.token_tracker: + for agent_name in (self.agents or {}): + self.token_tracker.set_agent_model(agent_name, deployment_name) + except Exception: + logger.debug("Could not register agent-model mapping", exc_info=True) + except Exception: + self.token_tracker = None + async def flush_agent_memories(self) -> None: """Flush buffered memories from all agent context providers. @@ -188,10 +207,12 @@ async def create_agents( ) elif agent_info.agent_name == "ResultGenerator": # Structured JSON generation; deterministic and bounded. + # Use 25_000 to prevent truncation of complex nested JSON schemas + # which causes "model produced invalid content" errors. builder = ( builder .with_temperature(0.0) - .with_max_tokens(12_000) + .with_max_tokens(25_000) .with_tool_choice("none") ) diff --git a/src/processor/src/main.py b/src/processor/src/main.py index 79531ff4..0c7252ab 100644 --- a/src/processor/src/main.py +++ b/src/processor/src/main.py @@ -45,8 +45,37 @@ def initialize(self): self.application_context.configuration, ) + self._configure_azure_monitor() self.register_services() + def _configure_azure_monitor(self): + """Initialise Azure Monitor OpenTelemetry exporter, if configured.""" + connection_string = os.environ.get( + "APPLICATIONINSIGHTS_CONNECTION_STRING", "" + ).strip() + if not connection_string: + logger.info( + "APPLICATIONINSIGHTS_CONNECTION_STRING not set; " + "skipping Azure Monitor OpenTelemetry configuration." + ) + return + + try: + from azure.monitor.opentelemetry import configure_azure_monitor + + configure_azure_monitor( + connection_string=connection_string, + enable_live_metrics=True, + ) + logger.info( + "Azure Monitor OpenTelemetry configured (live metrics enabled)." + ) + except Exception: + logger.exception( + "Failed to configure Azure Monitor OpenTelemetry; " + "continuing without App Insights export." + ) + def register_services(self): self.application_context.add_singleton( AgentFrameworkHelper, AgentFrameworkHelper() diff --git a/src/processor/src/main_service.py b/src/processor/src/main_service.py index 3b346161..88191403 100644 --- a/src/processor/src/main_service.py +++ b/src/processor/src/main_service.py @@ -105,8 +105,41 @@ def initialize(self): "Application initialized with configuration: %s", self.application_context.configuration, ) + self._configure_azure_monitor() self.register_services() + def _configure_azure_monitor(self): + """Initialise Azure Monitor OpenTelemetry exporter, if configured. + + Required so that ``track_event`` from ``azure-monitor-events-extension`` + has an export pipeline for custom events (e.g. token usage tracking). + """ + connection_string = os.environ.get( + "APPLICATIONINSIGHTS_CONNECTION_STRING", "" + ).strip() + if not connection_string: + logger.info( + "APPLICATIONINSIGHTS_CONNECTION_STRING not set; " + "skipping Azure Monitor OpenTelemetry configuration." + ) + return + + try: + from azure.monitor.opentelemetry import configure_azure_monitor + + configure_azure_monitor( + connection_string=connection_string, + enable_live_metrics=True, + ) + logger.info( + "Azure Monitor OpenTelemetry configured (live metrics enabled)." + ) + except Exception: + logger.exception( + "Failed to configure Azure Monitor OpenTelemetry; " + "continuing without App Insights export." + ) + def register_services(self): """Register application services into the dependency injection container. diff --git a/src/processor/src/services/queue_service.py b/src/processor/src/services/queue_service.py index a6b1be1e..0c65a30f 100644 --- a/src/processor/src/services/queue_service.py +++ b/src/processor/src/services/queue_service.py @@ -1272,6 +1272,7 @@ def _build_task_param(self, queue_message: QueueMessage) -> Analysis_TaskParam: source_file_folder=req["source_file_folder"], workspace_file_folder=req["workspace_file_folder"], output_file_folder=req["output_file_folder"], + user_id=parsed.user_id or req.get("user_id", ""), ) async def _ensure_queues_exist(self): diff --git a/src/processor/src/steps/analysis/models/step_param.py b/src/processor/src/steps/analysis/models/step_param.py index ca358049..3ed75f9a 100644 --- a/src/processor/src/steps/analysis/models/step_param.py +++ b/src/processor/src/steps/analysis/models/step_param.py @@ -16,3 +16,4 @@ class Analysis_TaskParam(BaseModel): source_file_folder: str = Field(description="Path to the source files folder") output_file_folder: str = Field(description="Path to the output files folder") workspace_file_folder: str = Field(description="Path to the workspace files folder") + user_id: str = Field(default="", description="User identifier for token usage tracking") diff --git a/src/processor/src/steps/analysis/orchestration/analysis_orchestrator.py b/src/processor/src/steps/analysis/orchestration/analysis_orchestrator.py index 93f8f2f0..5a182b04 100644 --- a/src/processor/src/steps/analysis/orchestration/analysis_orchestrator.py +++ b/src/processor/src/steps/analysis/orchestration/analysis_orchestrator.py @@ -84,6 +84,7 @@ async def execute( participants=self.agents, memory_client=None, result_output_format=Analysis_BooleanExtendedResult, + token_usage_tracker=self.token_tracker, ) orchestration_result = await orchestrator.run_stream( diff --git a/src/processor/src/steps/convert/orchestration/yaml_convert_orchestrator.py b/src/processor/src/steps/convert/orchestration/yaml_convert_orchestrator.py index f1fe8b4d..f2ee60ca 100644 --- a/src/processor/src/steps/convert/orchestration/yaml_convert_orchestrator.py +++ b/src/processor/src/steps/convert/orchestration/yaml_convert_orchestrator.py @@ -93,6 +93,7 @@ async def execute( memory_client=None, max_seconds=900, result_output_format=Yaml_ExtendedBooleanResult, + token_usage_tracker=self.token_tracker, ) orchestration_result = await orchestrator.run_stream( diff --git a/src/processor/src/steps/design/orchestration/design_orchestrator.py b/src/processor/src/steps/design/orchestration/design_orchestrator.py index d2dd47f0..68fea0c5 100644 --- a/src/processor/src/steps/design/orchestration/design_orchestrator.py +++ b/src/processor/src/steps/design/orchestration/design_orchestrator.py @@ -84,6 +84,7 @@ async def execute( participants=self.agents, memory_client=None, result_output_format=Design_ExtendedBooleanResult, + token_usage_tracker=self.token_tracker, ) orchestration_result = await orchestrator.run_stream( diff --git a/src/processor/src/steps/documentation/orchestration/documentation_orchestrator.py b/src/processor/src/steps/documentation/orchestration/documentation_orchestrator.py index 0aa6c443..cb995f91 100644 --- a/src/processor/src/steps/documentation/orchestration/documentation_orchestrator.py +++ b/src/processor/src/steps/documentation/orchestration/documentation_orchestrator.py @@ -98,6 +98,7 @@ async def execute( participants=self.agents, memory_client=None, result_output_format=Documentation_ExtendedBooleanResult, + token_usage_tracker=self.token_tracker, ) orchestration_result = await orchestrator.run_stream( diff --git a/src/processor/src/steps/migration_processor.py b/src/processor/src/steps/migration_processor.py index 0ded130f..549038e3 100644 --- a/src/processor/src/steps/migration_processor.py +++ b/src/processor/src/steps/migration_processor.py @@ -56,6 +56,7 @@ from libs.reporting.models.failure_context import FailureType from utils.agent_telemetry import TelemetryManager from utils.credential_util import get_bearer_token_provider +from utils.token_usage_tracker import TokenUsageTracker from .analysis.models.step_param import Analysis_TaskParam from .analysis.workflow.analysis_executor import AnalysisExecutor @@ -297,6 +298,14 @@ async def run(self, input_data: Analysis_TaskParam) -> Any: self.app_context._instances.pop(QdrantMemoryStore, None) self.app_context.add_singleton(QdrantMemoryStore, memory_store) + # Create workflow-level token usage tracker and register in app context + token_tracker = TokenUsageTracker( + process_id=input_data.process_id, + user_id=getattr(input_data, "user_id", "") or "", + ) + self.app_context._instances.pop(TokenUsageTracker, None) + self.app_context.add_singleton(TokenUsageTracker, token_tracker) + try: telemetry: TelemetryManager = await self.app_context.get_service_async( TelemetryManager @@ -714,6 +723,19 @@ async def _generate_report_summary( # print(f"{event.__class__.__name__} ({event.origin.value}): {event}") pass finally: + # Emit token usage summary events to Application Insights and persist to Cosmos + try: + token_tracker.emit_summary_events() + telemetry_mgr: TelemetryManager = ( + await self.app_context.get_service_async(TelemetryManager) + ) + await telemetry_mgr.persist_token_usage( + process_id=input_data.process_id, + token_summary=token_tracker.get_summary(), + ) + except Exception as e: + logger.warning("Failed to emit/persist token usage: %s", e) + # Clean up shared memory store if memory_store is not None: try: diff --git a/src/processor/src/utils/agent_telemetry.py b/src/processor/src/utils/agent_telemetry.py index 9e574377..15a0d188 100644 --- a/src/processor/src/utils/agent_telemetry.py +++ b/src/processor/src/utils/agent_telemetry.py @@ -296,6 +296,23 @@ class ProcessStatus(RootEntityBase["ProcessStatus", str]): description="Comprehensive UI data including file manifests, dashboard metrics, and downloadable artifacts", ) + # Token Usage Tracking + total_input_tokens: int = 0 + total_output_tokens: int = 0 + total_tokens: int = 0 + token_usage_by_agent: dict[str, dict[str, Any]] = Field( + default_factory=dict, + description="Token usage per agent: {agent_name: {input_tokens, output_tokens, total_tokens, call_count, model_deployment_name}}", + ) + token_usage_by_model: dict[str, dict[str, Any]] = Field( + default_factory=dict, + description="Token usage per model: {model_deployment_name: {input_tokens, output_tokens, total_tokens, call_count}}", + ) + token_usage_by_step: dict[str, dict[str, Any]] = Field( + default_factory=dict, + description="Token usage per step: {step_name: {input_tokens, output_tokens, total_tokens, call_count}}", + ) + class AgentActivityRepository(RepositoryBase[ProcessStatus, str]): def __init__(self, app_context: AppContext): @@ -1625,3 +1642,52 @@ async def get_ui_telemetry_data(self, process_id: str) -> dict[str, Any]: except Exception as e: logger.error(f"[UI-TELEMETRY] Failed to retrieve UI data: {e}") return {} + + async def persist_token_usage( + self, + process_id: str, + token_summary: dict[str, Any], + ) -> None: + """Persist aggregated token usage data to the ProcessStatus in Cosmos DB. + + Parameters + ---------- + process_id: + The process whose telemetry record should be updated. + token_summary: + The output of ``TokenUsageTracker.get_summary()``, containing + ``total``, ``by_agent``, ``by_model``, and ``by_step`` dictionaries. + """ + if not self.repository: + logger.info("[TELEMETRY] Development mode — token usage not persisted") + return + + try: + current_process = await self.repository.get_async(process_id) + if not current_process: + logger.warning( + "[TOKEN] Process %s not found — cannot persist token usage", + process_id, + ) + return + + total = token_summary.get("total", {}) + current_process.total_input_tokens = total.get("input_tokens", 0) + current_process.total_output_tokens = total.get("output_tokens", 0) + current_process.total_tokens = total.get("total_tokens", 0) + current_process.token_usage_by_agent = token_summary.get("by_agent", {}) + current_process.token_usage_by_model = token_summary.get("by_model", {}) + current_process.token_usage_by_step = token_summary.get("by_step", {}) + current_process.last_update_time = _get_utc_timestamp() + + await self.repository.update_async(current_process) + logger.info( + "[TOKEN] Persisted token usage for process %s: total=%d", + process_id, + current_process.total_tokens, + ) + except Exception: + logger.exception( + "[TOKEN] Failed to persist token usage (process_id=%s)", + process_id, + ) diff --git a/src/processor/src/utils/event_utils.py b/src/processor/src/utils/event_utils.py new file mode 100644 index 00000000..abbc1a89 --- /dev/null +++ b/src/processor/src/utils/event_utils.py @@ -0,0 +1,64 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +"""Lightweight helpers for emitting Application Insights custom events from the processor service. + +Mirrors the backend-api ``track_event_if_configured`` pattern so the processor +can emit structured custom events (e.g. token usage) to the same Application +Insights workspace. +""" +from __future__ import annotations + +import logging +import os +from typing import Any, Mapping + +logger = logging.getLogger(__name__) + +APP_INSIGHTS_CONN_STRING_ENV = "APPLICATIONINSIGHTS_CONNECTION_STRING" + +_warned_unconfigured: bool = False + + +def _is_app_insights_configured() -> bool: + value = os.environ.get(APP_INSIGHTS_CONN_STRING_ENV) + return bool(value and value.strip()) + + +def track_event_if_configured( + name: str, properties: Mapping[str, Any] | None = None +) -> None: + """Emit an Application Insights custom event, gated on configuration. + + No-op when ``APPLICATIONINSIGHTS_CONNECTION_STRING`` is unset. + Swallows export failures so telemetry never breaks processing. + """ + global _warned_unconfigured + + if not _is_app_insights_configured(): + if not _warned_unconfigured: + logger.warning( + "APPLICATIONINSIGHTS_CONNECTION_STRING is not set; " + "track_event_if_configured(name=%s) is a no-op.", + name, + ) + _warned_unconfigured = True + return + + safe_properties: dict[str, Any] = dict(properties) if properties else {} + + try: + from azure.monitor.events.extension import track_event # type: ignore[import-not-found] + except ImportError: + logger.warning( + "azure-monitor-events-extension is not installed; " + "skipping track_event(name=%s).", + name, + ) + return + + try: + track_event(name, safe_properties) + except Exception: + logger.exception( + "Failed to publish App Insights custom event name=%s.", name + ) diff --git a/src/processor/src/utils/token_usage_tracker.py b/src/processor/src/utils/token_usage_tracker.py new file mode 100644 index 00000000..ae10a68d --- /dev/null +++ b/src/processor/src/utils/token_usage_tracker.py @@ -0,0 +1,403 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +"""LLM Token Usage Tracker for comprehensive consumption monitoring. + +Tracks token usage across four dimensions: +- Per agent (e.g. Chief_Architect, EKS_Expert) +- Per team/step (analysis, design, yaml, documentation) +- Per user/process +- Per model deployment + +Usage data is emitted to Application Insights as custom events and can be +persisted to Cosmos DB via the TelemetryManager. +""" +from __future__ import annotations + +import logging +import threading +from dataclasses import dataclass, field +from typing import Any + +from utils.event_utils import track_event_if_configured + +logger = logging.getLogger(__name__) + + +@dataclass +class TokenUsageRecord: + """Token counts for a single LLM interaction.""" + + input_tokens: int = 0 + output_tokens: int = 0 + total_tokens: int = 0 + + +@dataclass +class AggregatedTokenUsage: + """Accumulated token usage with call count.""" + + input_tokens: int = 0 + output_tokens: int = 0 + total_tokens: int = 0 + call_count: int = 0 + + def add(self, record: TokenUsageRecord) -> None: + self.input_tokens += record.input_tokens + self.output_tokens += record.output_tokens + self.total_tokens += record.total_tokens + self.call_count += 1 + + def to_dict(self) -> dict[str, int]: + return { + "input_tokens": self.input_tokens, + "output_tokens": self.output_tokens, + "total_tokens": self.total_tokens, + "call_count": self.call_count, + } + + +class TokenUsageTracker: + """Thread-safe tracker that aggregates LLM token usage across multiple dimensions. + + Accumulates usage per agent, per step (team), per model, and overall per process. + Emits Application Insights custom events for each recorded interaction and + provides summary emission at process completion. + """ + + def __init__(self, process_id: str, user_id: str = ""): + self.process_id = process_id + self.user_id = user_id + self._lock = threading.Lock() + + # Aggregation buckets + self._by_agent: dict[str, AggregatedTokenUsage] = {} + self._by_step: dict[str, AggregatedTokenUsage] = {} + self._by_model: dict[str, AggregatedTokenUsage] = {} + self._total = AggregatedTokenUsage() + + # Agent-to-model mapping for richer telemetry + self._agent_model_map: dict[str, str] = {} + + def set_agent_model(self, agent_name: str, model_deployment_name: str) -> None: + """Register the model deployment used by a specific agent.""" + with self._lock: + self._agent_model_map[agent_name] = model_deployment_name + + def record( + self, + *, + input_tokens: int, + output_tokens: int, + total_tokens: int, + agent_name: str = "", + step_name: str = "", + model_deployment_name: str = "", + ) -> None: + """Record a single LLM call's token usage. + + Accumulates into all relevant dimensions and emits a per-call + Application Insights event. + """ + if total_tokens <= 0 and input_tokens <= 0 and output_tokens <= 0: + return + + if total_tokens <= 0: + total_tokens = input_tokens + output_tokens + + record = TokenUsageRecord( + input_tokens=input_tokens, + output_tokens=output_tokens, + total_tokens=total_tokens, + ) + + # Resolve model from agent map if not provided + if not model_deployment_name and agent_name: + model_deployment_name = self._agent_model_map.get(agent_name, "") + + with self._lock: + self._total.add(record) + + if agent_name: + if agent_name not in self._by_agent: + self._by_agent[agent_name] = AggregatedTokenUsage() + self._by_agent[agent_name].add(record) + + if step_name: + if step_name not in self._by_step: + self._by_step[step_name] = AggregatedTokenUsage() + self._by_step[step_name].add(record) + + if model_deployment_name: + if model_deployment_name not in self._by_model: + self._by_model[model_deployment_name] = AggregatedTokenUsage() + self._by_model[model_deployment_name].add(record) + + # Emit per-call event to Application Insights + try: + track_event_if_configured( + "LLM_Token_Usage", + { + "process_id": self.process_id, + "user_id": self.user_id, + "agent_name": agent_name, + "step_name": step_name, + "model_deployment_name": model_deployment_name, + "input_tokens": str(input_tokens), + "output_tokens": str(output_tokens), + "total_tokens": str(total_tokens), + }, + ) + except Exception: + logger.debug("Failed to emit per-call token usage event", exc_info=True) + + logger.info( + "[TOKEN] Recorded: agent=%s step=%s model=%s input=%d output=%d total=%d | cumulative=%d", + agent_name, + step_name, + model_deployment_name, + input_tokens, + output_tokens, + total_tokens, + self._total.total_tokens, + ) + + def get_summary(self) -> dict[str, Any]: + """Return a snapshot of all accumulated token usage.""" + with self._lock: + return { + "process_id": self.process_id, + "user_id": self.user_id, + "total": self._total.to_dict(), + "by_agent": {k: v.to_dict() for k, v in self._by_agent.items()}, + "by_step": {k: v.to_dict() for k, v in self._by_step.items()}, + "by_model": {k: v.to_dict() for k, v in self._by_model.items()}, + } + + def emit_summary_events(self) -> None: + """Emit summary-level Application Insights custom events. + + Call this at the end of a process/workflow to produce aggregated events + that are easy to query in KQL. + """ + summary = self.get_summary() + + try: + # Overall summary + track_event_if_configured( + "LLM_Token_Usage_Summary", + { + "process_id": self.process_id, + "user_id": self.user_id, + "total_input_tokens": str(summary["total"]["input_tokens"]), + "total_output_tokens": str(summary["total"]["output_tokens"]), + "total_tokens": str(summary["total"]["total_tokens"]), + "total_calls": str(summary["total"]["call_count"]), + "agent_count": str(len(summary["by_agent"])), + "model_count": str(len(summary["by_model"])), + "step_count": str(len(summary["by_step"])), + }, + ) + + # Per-agent events + for agent_name, usage in summary["by_agent"].items(): + model = self._agent_model_map.get(agent_name, "") + track_event_if_configured( + "LLM_Agent_Token_Usage", + { + "process_id": self.process_id, + "user_id": self.user_id, + "agent_name": agent_name, + "model_deployment_name": model, + "input_tokens": str(usage["input_tokens"]), + "output_tokens": str(usage["output_tokens"]), + "total_tokens": str(usage["total_tokens"]), + "call_count": str(usage["call_count"]), + }, + ) + + # Per-model events + for model_name, usage in summary["by_model"].items(): + track_event_if_configured( + "LLM_Model_Token_Usage", + { + "process_id": self.process_id, + "user_id": self.user_id, + "model_deployment_name": model_name, + "input_tokens": str(usage["input_tokens"]), + "output_tokens": str(usage["output_tokens"]), + "total_tokens": str(usage["total_tokens"]), + "call_count": str(usage["call_count"]), + }, + ) + + # Per-step (team) events + for step_name, usage in summary["by_step"].items(): + track_event_if_configured( + "LLM_Step_Token_Usage", + { + "process_id": self.process_id, + "user_id": self.user_id, + "step_name": step_name, + "input_tokens": str(usage["input_tokens"]), + "output_tokens": str(usage["output_tokens"]), + "total_tokens": str(usage["total_tokens"]), + "call_count": str(usage["call_count"]), + }, + ) + + logger.info( + "[TOKEN] Emitted summary events: total=%d agents=%d models=%d steps=%d", + summary["total"]["total_tokens"], + len(summary["by_agent"]), + len(summary["by_model"]), + len(summary["by_step"]), + ) + except Exception: + logger.exception("[TOKEN] Failed to emit summary events") + + +def extract_usage_from_response(response: Any) -> TokenUsageRecord | None: + """Extract token usage from an agent_framework or OpenAI SDK response object. + + Handles multiple response shapes: + 1. response.usage (OpenAI SDK ChatCompletion) + 2. response.usage_details (agent_framework Content objects) + 3. response dict with usage keys + 4. AgentResponseUpdate with contents containing usage + """ + if response is None: + return None + + # 1. Direct .usage attribute (OpenAI ChatCompletion, Responses API) + usage = getattr(response, "usage", None) + if usage is not None: + record = _parse_usage_object(usage) + if record: + return record + + # 2. .usage_details or .details attribute + usage_details = getattr(response, "details", None) or getattr(response, "usage_details", None) + if usage_details is not None: + record = _parse_usage_object(usage_details) + if record: + return record + + # 3. raw_representation with usage + raw = getattr(response, "raw_representation", None) + if raw is not None: + raw_usage = getattr(raw, "usage", None) + if raw_usage is not None: + record = _parse_usage_object(raw_usage) + if record: + return record + if isinstance(raw, dict) and "usage" in raw: + record = _parse_usage_object(raw["usage"]) + if record: + return record + + # 4. contents list with usage items (AgentResponseUpdate) + contents = getattr(response, "contents", None) + if contents: + for item in contents: + item_type = getattr(item, "type", None) + if item_type == "usage": + # SDK UsageContent uses "details"; fall back to "usage_details" + ud = getattr(item, "details", None) or getattr(item, "usage_details", None) + if isinstance(ud, dict): + record = _parse_usage_object(ud) + if record: + return record + elif ud is not None: + record = _parse_usage_object(ud) + if record: + return record + # Direct details/usage_details on content item + ud = getattr(item, "details", None) or getattr(item, "usage_details", None) + if isinstance(ud, dict) and ud: + record = _parse_usage_object(ud) + if record: + return record + # Dict content item + if isinstance(item, dict): + if "details" in item: + record = _parse_usage_object(item["details"]) + if record: + return record + if "usage_details" in item: + record = _parse_usage_object(item["usage_details"]) + if record: + return record + if "input_token_count" in item or "total_token_count" in item: + record = _parse_usage_object(item) + if record: + return record + + # 5. additional_properties + addl = getattr(response, "additional_properties", None) + if isinstance(addl, dict) and "usage" in addl: + record = _parse_usage_object(addl["usage"]) + if record: + return record + + # 6. Dict response + if isinstance(response, dict): + if "usage" in response: + record = _parse_usage_object(response["usage"]) + if record: + return record + record = _parse_usage_object(response) + if record: + return record + + return None + + +def _parse_usage_object(usage: Any) -> TokenUsageRecord | None: + """Parse a usage object (dict or object with attrs) into a TokenUsageRecord.""" + if usage is None: + return None + + if isinstance(usage, dict): + inp = ( + usage.get("input_token_count", 0) + or usage.get("prompt_tokens", 0) + or usage.get("input_tokens", 0) + or 0 + ) + out = ( + usage.get("output_token_count", 0) + or usage.get("completion_tokens", 0) + or usage.get("output_tokens", 0) + or 0 + ) + tot = ( + usage.get("total_token_count", 0) + or usage.get("total_tokens", 0) + or (inp + out) + ) + else: + inp = ( + getattr(usage, "input_token_count", 0) + or getattr(usage, "prompt_tokens", 0) + or getattr(usage, "input_tokens", 0) + or 0 + ) + out = ( + getattr(usage, "output_token_count", 0) + or getattr(usage, "completion_tokens", 0) + or getattr(usage, "output_tokens", 0) + or 0 + ) + tot = ( + getattr(usage, "total_token_count", 0) + or getattr(usage, "total_tokens", 0) + or (inp + out) + ) + + if tot > 0 or inp > 0 or out > 0: + return TokenUsageRecord( + input_tokens=int(inp), + output_tokens=int(out), + total_tokens=int(tot) if tot > 0 else int(inp) + int(out), + ) + return None From a31f8f035338a366d9a23eb8162a96034830a41f Mon Sep 17 00:00:00 2001 From: Priyanka-Microsoft Date: Mon, 25 May 2026 15:27:11 +0530 Subject: [PATCH 08/12] optimize the code --- .../agent_framework/groupchat_orchestrator.py | 111 ++++++++---------- src/processor/src/utils/event_utils.py | 17 ++- .../src/utils/token_usage_tracker.py | 91 +++++--------- 3 files changed, 91 insertions(+), 128 deletions(-) diff --git a/src/processor/src/libs/agent_framework/groupchat_orchestrator.py b/src/processor/src/libs/agent_framework/groupchat_orchestrator.py index 50711fd3..1607b752 100644 --- a/src/processor/src/libs/agent_framework/groupchat_orchestrator.py +++ b/src/processor/src/libs/agent_framework/groupchat_orchestrator.py @@ -484,6 +484,7 @@ async def run_stream( self._tool_call_emitted.clear() self._tool_call_recorded.clear() self._tool_call_index.clear() + self._streaming_captured_usage = False self._conversation: list[ChatMessage] = [] # Track conversation during workflow try: @@ -686,70 +687,9 @@ async def _handle_agent_update( await self._process_tool_calls(event, agent_name, stream_callback) # Extract token usage from the streaming update if tracker is active. - # Check multiple paths where usage data may appear. if self.token_usage_tracker is not None: try: - data = event.data - # Path 1: data.contents with Content(type="usage") - contents = getattr(data, "contents", None) - if contents: - for item in contents: - ctype = getattr(item, "type", None) - if ctype == "usage": - # SDK UsageContent uses "details"; fall back to "usage_details" - ud = getattr(item, "details", None) or getattr(item, "usage_details", None) - if ud: - record = _parse_usage_object(ud) - if record and record.total_tokens > 0: - self.token_usage_tracker.record( - input_tokens=record.input_tokens, - output_tokens=record.output_tokens, - total_tokens=record.total_tokens, - agent_name=agent_name, - step_name=self.name, - ) - self._streaming_captured_usage = True - logger.info( - "[TOKEN_ORCH] recorded from contents: agent=%s step=%s tokens=%s", - agent_name, self.name, record.total_tokens, - ) - return - # Path 2: data.usage direct attribute - usage = getattr(data, "usage", None) - if usage is not None: - record = _parse_usage_object(usage) - if record and record.total_tokens > 0: - self.token_usage_tracker.record( - input_tokens=record.input_tokens, - output_tokens=record.output_tokens, - total_tokens=record.total_tokens, - agent_name=agent_name, - step_name=self.name, - ) - self._streaming_captured_usage = True - logger.info( - "[TOKEN_ORCH] recorded from usage attr: agent=%s step=%s tokens=%s", - agent_name, self.name, record.total_tokens, - ) - return - # Path 3: event itself may carry usage - event_usage = getattr(event, "usage", None) - if event_usage is not None: - record = _parse_usage_object(event_usage) - if record and record.total_tokens > 0: - self.token_usage_tracker.record( - input_tokens=record.input_tokens, - output_tokens=record.output_tokens, - total_tokens=record.total_tokens, - agent_name=agent_name, - step_name=self.name, - ) - self._streaming_captured_usage = True - logger.info( - "[TOKEN_ORCH] recorded from event.usage: agent=%s step=%s tokens=%s", - agent_name, self.name, record.total_tokens, - ) - return + self._try_record_streaming_usage(event, agent_name) except Exception: logger.debug( "Failed to extract token usage from update (agent=%s)", @@ -757,6 +697,53 @@ async def _handle_agent_update( exc_info=True, ) + def _try_record_streaming_usage(self, event: Any, agent_name: str) -> None: + """Try to extract and record token usage from a streaming event. + + Checks three paths in priority order: + 1. event.data.contents with Content(type="usage") + 2. event.data.usage direct attribute + 3. event.usage top-level attribute + """ + candidates: list[tuple[Any, str]] = [] + data = event.data + + # Path 1: data.contents with Content(type="usage") + contents = getattr(data, "contents", None) + if contents: + for item in contents: + if getattr(item, "type", None) == "usage": + ud = getattr(item, "details", None) or getattr(item, "usage_details", None) + if ud: + candidates.append((ud, "contents")) + + # Path 2: data.usage + usage = getattr(data, "usage", None) + if usage is not None: + candidates.append((usage, "data.usage")) + + # Path 3: event.usage + event_usage = getattr(event, "usage", None) + if event_usage is not None: + candidates.append((event_usage, "event.usage")) + + for candidate, source in candidates: + record = _parse_usage_object(candidate) + if record and record.total_tokens > 0: + self.token_usage_tracker.record( + input_tokens=record.input_tokens, + output_tokens=record.output_tokens, + total_tokens=record.total_tokens, + agent_name=agent_name, + step_name=self.name, + ) + self._streaming_captured_usage = True + logger.info( + "[TOKEN_ORCH] recorded from %s: agent=%s step=%s tokens=%s", + source, agent_name, self.name, record.total_tokens, + ) + return + def _normalize_executor_id(self, executor_id: str) -> str: """Normalize executor id to agent name. diff --git a/src/processor/src/utils/event_utils.py b/src/processor/src/utils/event_utils.py index abbc1a89..dc43e8fd 100644 --- a/src/processor/src/utils/event_utils.py +++ b/src/processor/src/utils/event_utils.py @@ -16,6 +16,11 @@ APP_INSIGHTS_CONN_STRING_ENV = "APPLICATIONINSIGHTS_CONNECTION_STRING" +_UNCONFIGURED_WARNING = ( + "APPLICATIONINSIGHTS_CONNECTION_STRING is not set; " + "track_event_if_configured(name=%s) is a no-op." +) + _warned_unconfigured: bool = False @@ -24,6 +29,12 @@ def _is_app_insights_configured() -> bool: return bool(value and value.strip()) +def reset_unconfigured_warning_for_tests() -> None: + """Test-only helper: reset the once-per-process warning latch.""" + global _warned_unconfigured + _warned_unconfigured = False + + def track_event_if_configured( name: str, properties: Mapping[str, Any] | None = None ) -> None: @@ -36,11 +47,7 @@ def track_event_if_configured( if not _is_app_insights_configured(): if not _warned_unconfigured: - logger.warning( - "APPLICATIONINSIGHTS_CONNECTION_STRING is not set; " - "track_event_if_configured(name=%s) is a no-op.", - name, - ) + logger.warning(_UNCONFIGURED_WARNING, name) _warned_unconfigured = True return diff --git a/src/processor/src/utils/token_usage_tracker.py b/src/processor/src/utils/token_usage_tracker.py index ae10a68d..25e5e0b6 100644 --- a/src/processor/src/utils/token_usage_tracker.py +++ b/src/processor/src/utils/token_usage_tracker.py @@ -299,34 +299,23 @@ def extract_usage_from_response(response: Any) -> TokenUsageRecord | None: contents = getattr(response, "contents", None) if contents: for item in contents: - item_type = getattr(item, "type", None) - if item_type == "usage": - # SDK UsageContent uses "details"; fall back to "usage_details" + # Try usage-typed content items first, then any item with details + ud = None + if getattr(item, "type", None) == "usage": ud = getattr(item, "details", None) or getattr(item, "usage_details", None) - if isinstance(ud, dict): - record = _parse_usage_object(ud) - if record: - return record - elif ud is not None: - record = _parse_usage_object(ud) - if record: - return record - # Direct details/usage_details on content item - ud = getattr(item, "details", None) or getattr(item, "usage_details", None) - if isinstance(ud, dict) and ud: + if ud is None: + ud = getattr(item, "details", None) or getattr(item, "usage_details", None) + if ud is not None: record = _parse_usage_object(ud) if record: return record # Dict content item if isinstance(item, dict): - if "details" in item: - record = _parse_usage_object(item["details"]) - if record: - return record - if "usage_details" in item: - record = _parse_usage_object(item["usage_details"]) - if record: - return record + for key in ("details", "usage_details"): + if key in item: + record = _parse_usage_object(item[key]) + if record: + return record if "input_token_count" in item or "total_token_count" in item: record = _parse_usage_object(item) if record: @@ -352,52 +341,32 @@ def extract_usage_from_response(response: Any) -> TokenUsageRecord | None: return None +def _get_field(obj: Any, *names: str) -> int: + """Read the first non-zero value from *obj* for the given field names. + + Works uniformly for dicts (via ``get``) and objects (via ``getattr``). + """ + getter = obj.get if isinstance(obj, dict) else lambda k, d=0: getattr(obj, k, d) + for name in names: + val = getter(name, 0) + if val: + return int(val) + return 0 + + def _parse_usage_object(usage: Any) -> TokenUsageRecord | None: """Parse a usage object (dict or object with attrs) into a TokenUsageRecord.""" if usage is None: return None - if isinstance(usage, dict): - inp = ( - usage.get("input_token_count", 0) - or usage.get("prompt_tokens", 0) - or usage.get("input_tokens", 0) - or 0 - ) - out = ( - usage.get("output_token_count", 0) - or usage.get("completion_tokens", 0) - or usage.get("output_tokens", 0) - or 0 - ) - tot = ( - usage.get("total_token_count", 0) - or usage.get("total_tokens", 0) - or (inp + out) - ) - else: - inp = ( - getattr(usage, "input_token_count", 0) - or getattr(usage, "prompt_tokens", 0) - or getattr(usage, "input_tokens", 0) - or 0 - ) - out = ( - getattr(usage, "output_token_count", 0) - or getattr(usage, "completion_tokens", 0) - or getattr(usage, "output_tokens", 0) - or 0 - ) - tot = ( - getattr(usage, "total_token_count", 0) - or getattr(usage, "total_tokens", 0) - or (inp + out) - ) + inp = _get_field(usage, "input_token_count", "prompt_tokens", "input_tokens") + out = _get_field(usage, "output_token_count", "completion_tokens", "output_tokens") + tot = _get_field(usage, "total_token_count", "total_tokens") or (inp + out) if tot > 0 or inp > 0 or out > 0: return TokenUsageRecord( - input_tokens=int(inp), - output_tokens=int(out), - total_tokens=int(tot) if tot > 0 else int(inp) + int(out), + input_tokens=inp, + output_tokens=out, + total_tokens=tot if tot > 0 else inp + out, ) return None From f11248b55a3ff81ceb66f735148688dbb7061b83 Mon Sep 17 00:00:00 2001 From: Priyanka-Microsoft Date: Wed, 27 May 2026 17:45:42 +0530 Subject: [PATCH 09/12] optimize the code --- .../app/libs/logging/llm_token_telemetry.py | 935 ++++++++++++++++++ .../src/tests/test_llm_token_telemetry.py | 572 +++++++++++ .../src/utils/llm_token_telemetry.py | 935 ++++++++++++++++++ .../src/utils/token_usage_tracker.py | 287 ++---- 4 files changed, 2548 insertions(+), 181 deletions(-) create mode 100644 src/backend-api/src/app/libs/logging/llm_token_telemetry.py create mode 100644 src/processor/src/tests/test_llm_token_telemetry.py create mode 100644 src/processor/src/utils/llm_token_telemetry.py diff --git a/src/backend-api/src/app/libs/logging/llm_token_telemetry.py b/src/backend-api/src/app/libs/logging/llm_token_telemetry.py new file mode 100644 index 00000000..b3035fc8 --- /dev/null +++ b/src/backend-api/src/app/libs/logging/llm_token_telemetry.py @@ -0,0 +1,935 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +"""Cross-accelerator LLM token-usage telemetry helpers. + +A single, dependency-light helper module that can be dropped into any Microsoft +Solution Accelerator to capture LLM token usage and emit standardized custom +events to Application Insights. + +Why this file exists +-------------------- +Seven solution accelerators have independently shipped near-identical +``token_usage_utils.py`` modules (see PRs: content-generation #860, CKM #933, +content-processing #586, Container-Migration #257, agentic-data-foundation +#383, customer-chatbot #218, MACAE #1003). They all: + +* extract token counts from agent_framework / Azure OpenAI responses, +* emit the same three custom events (``LLM_Token_Usage_Summary``, + ``LLM_Agent_Token_Usage``, ``LLM_Model_Token_Usage``), +* defensively swallow telemetry errors, +* duplicate the same KQL queries and Azure Workbook. + +This module consolidates the union of those behaviours behind one stable API +so each accelerator can replace its bespoke helper with an import. + +Public API +---------- +- ``TokenUsage`` -- immutable dataclass for counts +- ``extract_usage(obj)`` -- agent_framework run result / message +- ``extract_usage_from_dict(d)`` -- raw dict from any SDK +- ``extract_usage_from_stream_chunk`` -- streaming chunks +- ``extract_realtime_usage(resp)`` -- Azure AI Voice Live response.done +- ``TokenUsageEmitter`` -- emits the three events + optional + per-user / per-team / speech events +- ``TokenUsageScope`` -- context-manager that accumulates and + auto-emits on exit +- ``track_tokens`` -- decorator wrapper around the scope + +Design rules +------------ +* Telemetry NEVER raises. Extraction failures return ``None``; emission + failures are logged at WARNING. +* No hard dependency on ``azure-monitor-events-extension``; if absent the + emitter degrades to logging only. +* Arbitrary correlation dimensions are passed as ``**dimensions`` kwargs and + surface verbatim as custom-event properties. This is how each accelerator + attaches its own keys (``conversation_id``, ``process_id``, ``team_name``, + ``file_name``, ``tenant``, etc.) without forking the helper. +""" +from __future__ import annotations + +import asyncio +import functools +import logging +import os +import random +from contextlib import AbstractContextManager +from dataclasses import dataclass, field +from typing import Any, Callable, Iterable, Mapping, Optional +from unittest.mock import NonCallableMock + +logger = logging.getLogger(__name__) + + +# --------------------------------------------------------------------------- +# Event-name constants -- keep these stable; KQL queries and workbooks bind +# to these exact strings. +# --------------------------------------------------------------------------- +EVENT_SUMMARY = "LLM_Token_Usage_Summary" +EVENT_AGENT = "LLM_Agent_Token_Usage" +EVENT_MODEL = "LLM_Model_Token_Usage" +EVENT_USER = "LLM_User_Token_Usage" +EVENT_TEAM = "LLM_Team_Token_Usage" +EVENT_SPEECH = "Speech_Usage" + + +# Token-count field aliases observed across model providers / SDK versions. +_INPUT_KEYS = ( + "input_token_count", + "input_tokens", + "prompt_tokens", + "promptTokens", +) +_OUTPUT_KEYS = ( + "output_token_count", + "output_tokens", + "completion_tokens", + "completionTokens", +) +_TOTAL_KEYS = ( + "total_token_count", + "total_tokens", + "totalTokens", +) + + +# --------------------------------------------------------------------------- +# Data model +# --------------------------------------------------------------------------- +@dataclass(frozen=True) +class TokenUsage: + """Normalized token-usage record.""" + + input_tokens: int = 0 + output_tokens: int = 0 + total_tokens: int = 0 + + # Optional realtime / voice fields (None unless populated) + input_audio_tokens: Optional[int] = None + input_text_tokens: Optional[int] = None + input_cached_tokens: Optional[int] = None + output_audio_tokens: Optional[int] = None + output_text_tokens: Optional[int] = None + + @property + def has_any(self) -> bool: + return bool(self.input_tokens or self.output_tokens or self.total_tokens) + + def __add__(self, other: "TokenUsage") -> "TokenUsage": + if not isinstance(other, TokenUsage): + return NotImplemented + + def _sum(a: Optional[int], b: Optional[int]) -> Optional[int]: + if a is None and b is None: + return None + return (a or 0) + (b or 0) + + return TokenUsage( + input_tokens=self.input_tokens + other.input_tokens, + output_tokens=self.output_tokens + other.output_tokens, + total_tokens=self.total_tokens + other.total_tokens, + input_audio_tokens=_sum(self.input_audio_tokens, other.input_audio_tokens), + input_text_tokens=_sum(self.input_text_tokens, other.input_text_tokens), + input_cached_tokens=_sum(self.input_cached_tokens, other.input_cached_tokens), + output_audio_tokens=_sum(self.output_audio_tokens, other.output_audio_tokens), + output_text_tokens=_sum(self.output_text_tokens, other.output_text_tokens), + ) + + def to_event_props(self) -> dict[str, str]: + """Stringified property bag suitable for App Insights custom events.""" + props: dict[str, str] = { + "input_tokens": str(self.input_tokens), + "output_tokens": str(self.output_tokens), + "total_tokens": str(self.total_tokens), + } + for name in ( + "input_audio_tokens", + "input_text_tokens", + "input_cached_tokens", + "output_audio_tokens", + "output_text_tokens", + ): + value = getattr(self, name) + if value is not None: + props[name] = str(value) + return props + + +# --------------------------------------------------------------------------- +# Low-level coercion helpers +# --------------------------------------------------------------------------- +def _to_int(value: Any, default: int = 0) -> int: + """Best-effort int conversion; bool excluded; never raises.""" + if value is None or isinstance(value, bool): + return default + if isinstance(value, int): + return value + if isinstance(value, float): + return int(value) + if isinstance(value, str): + s = value.strip() + if s.isdigit(): + return int(s) + try: + return int(value) + except (TypeError, ValueError): + return default + + +def _get(obj: Any, key: str, default: Any = None) -> Any: + """Read an attribute or dict key uniformly.""" + if obj is None: + return default + if isinstance(obj, Mapping): + return obj.get(key, default) + return getattr(obj, key, default) + + +def _is_iterable(obj: Any) -> bool: + """True only for real iterables (lists/tuples/sets/generators), NOT for + arbitrary objects (e.g. ``unittest.mock.Mock``) that happen to expose + ``__iter__`` but blow up on iteration.""" + if obj is None: + return False + if isinstance(obj, (list, tuple, set, frozenset)): + return True + # Strings are iterable but never the right answer for "messages". + if isinstance(obj, (str, bytes, bytearray, Mapping)): + return False + # Fall back to a duck-typed check, but reject Mock instances which would + # otherwise pretend to support iteration. + if isinstance(obj, NonCallableMock): + return False + return hasattr(obj, "__iter__") + + +def _read_counts(usage_obj: Any) -> Optional[TokenUsage]: + """Read ``input/output/total`` from any usage-bearing object/dict.""" + if usage_obj is None: + return None + + inp = out = tot = 0 + for k in _INPUT_KEYS: + v = _get(usage_obj, k) + if v: + inp = _to_int(v) + break + for k in _OUTPUT_KEYS: + v = _get(usage_obj, k) + if v: + out = _to_int(v) + break + for k in _TOTAL_KEYS: + v = _get(usage_obj, k) + if v: + tot = _to_int(v) + break + + if tot == 0 and (inp or out): + tot = inp + out + if not (inp or out or tot): + return None + return TokenUsage(input_tokens=inp, output_tokens=out, total_tokens=tot) + + +# --------------------------------------------------------------------------- +# Extraction -- public +# --------------------------------------------------------------------------- +def extract_usage(result: Any) -> Optional[TokenUsage]: + """Extract usage from an agent_framework run result, ChatMessage, or + OpenAI-style ChatCompletion. + + Checks (in order): + 1. ``result.usage_details`` or ``result.usage`` + 2. ``result.raw_representation.usage`` (OpenAI ChatCompletion shape) + 3. Aggregated ``result.messages[*].contents[*].usage_details`` + + Never raises -- returns ``None`` on any unexpected shape. + """ + if result is None: + return None + + try: + for attr in ("usage_details", "usage"): + found = _read_counts(_get(result, attr)) + if found: + return found + + raw = _get(result, "raw_representation") + if raw is not None: + found = _read_counts(_get(raw, "usage")) + if found: + return found + + aggregated = TokenUsage() + found_any = False + messages = _get(result, "messages") + if not _is_iterable(messages): + return None + for msg in messages: + contents = _get(msg, "contents") + if not _is_iterable(contents): + continue + for content in contents: + usage = _get(content, "usage_details") or _get(content, "usage") + piece = _read_counts(usage) + if piece: + aggregated = aggregated + piece + found_any = True + return aggregated if found_any else None + except Exception as exc: + logger.debug("extract_usage failed: %s", exc, exc_info=True) + return None + + +def extract_usage_from_dict(data: Any) -> Optional[TokenUsage]: + """Extract from a raw dict / SDK usage object.""" + return _read_counts(data) + + +def extract_usage_from_stream_chunk(chunk: Any) -> Optional[TokenUsage]: + """Streaming chunks: try the top-level shape, then ``chunk.metadata.usage``.""" + found = extract_usage(chunk) + if found: + return found + metadata = _get(chunk, "metadata") + if metadata is not None: + return _read_counts(_get(metadata, "usage")) + return None + + +def extract_realtime_usage(response_obj: Any) -> Optional[TokenUsage]: + """Azure AI Voice Live ``response.done`` payload extractor. + + Includes audio / text / cached sub-counts when present. + """ + usage = _get(response_obj, "usage") + if usage is None: + return None + + inp = _to_int(_get(usage, "input_tokens")) + out = _to_int(_get(usage, "output_tokens")) + tot = _to_int(_get(usage, "total_tokens")) + if tot == 0 and (inp or out): + tot = inp + out + + in_details = _get(usage, "input_token_details") or {} + out_details = _get(usage, "output_token_details") or {} + + record = TokenUsage( + input_tokens=inp, + output_tokens=out, + total_tokens=tot, + input_audio_tokens=_to_int(_get(in_details, "audio_tokens")), + input_text_tokens=_to_int(_get(in_details, "text_tokens")), + input_cached_tokens=_to_int(_get(in_details, "cached_tokens")), + output_audio_tokens=_to_int(_get(out_details, "audio_tokens")), + output_text_tokens=_to_int(_get(out_details, "text_tokens")), + ) + # Only return if at least one non-zero count surfaced. + if record.has_any or any( + v for v in ( + record.input_audio_tokens, + record.input_text_tokens, + record.input_cached_tokens, + record.output_audio_tokens, + record.output_text_tokens, + ) + ): + return record + return None + + +# --------------------------------------------------------------------------- +# Tool / sub-agent attribution +# --------------------------------------------------------------------------- +def detect_invoked_tools(result: Any) -> set[str]: + """Return the set of tool/function names invoked in an agent result, + inferred from ``function_call`` content items. + + Used by orchestrators that expose sub-agents via ``.as_tool()`` to attribute + token usage only to the sub-agents that were actually called. Never raises. + """ + invoked: set[str] = set() + try: + messages = _get(result, "messages") + if not _is_iterable(messages): + return invoked + for msg in messages: + contents = _get(msg, "contents") + if not _is_iterable(contents): + continue + for content in contents: + if _get(content, "type") == "function_call": + name = _get(content, "name") + if name: + invoked.add(str(name)) + except Exception as exc: + logger.debug("detect_invoked_tools failed: %s", exc, exc_info=True) + return invoked + + +# --------------------------------------------------------------------------- +# Event sink (optional Application Insights dependency) +# --------------------------------------------------------------------------- +EventSink = Callable[[str, Mapping[str, str]], None] + + +def _default_event_sink() -> Optional[EventSink]: + """Return ``azure.monitor.events.extension.track_event`` if importable, + else ``None``. Resolved lazily so the helper still works in unit tests + without the dependency installed.""" + try: + from azure.monitor.events.extension import track_event # type: ignore + except Exception: # pragma: no cover - optional dep + return None + return track_event + + +# --------------------------------------------------------------------------- +# Emitter +# --------------------------------------------------------------------------- +class TokenUsageEmitter: + """Emit standardized token-usage custom events. + + Parameters + ---------- + connection_string: + Application Insights connection string. If ``None`` (default), the + ``APPLICATIONINSIGHTS_CONNECTION_STRING`` env var is consulted. When + no connection string is configured the emitter logs and skips the + ``track_event`` call. + static_dimensions: + Properties merged into every event (e.g. ``{"app": "customer-chatbot"}``). + event_sink: + Callable ``(event_name, props_dict) -> None``. Defaults to + ``azure.monitor.events.extension.track_event``. Override in tests. + pricing: + Optional mapping ``{model_deployment_name -> (usd_per_1k_input, + usd_per_1k_output)}``. When provided, an ``estimated_cost_usd`` + property is attached to agent / model / summary events. Model lookup + is case-insensitive. Use this to avoid hard-coding rates in KQL. + user_id_hasher: + Optional callable ``str -> str`` applied to any ``user_id`` value + before it leaves the emitter. Use this to satisfy PII / GDPR + requirements (e.g. HMAC-SHA256 with a tenant-scoped salt). Applied + to both ``static_dimensions['user_id']`` (at construction) and + per-call ``user_id`` kwargs. + sample_rate: + Fraction of high-cardinality events (agent / model / user / team / + speech) actually shipped, in ``[0.0, 1.0]``. The cheap **summary + event always fires** regardless of sample_rate so per-request totals + remain accurate; only the per-dimension breakdown is sampled. + Defaults to ``1.0`` (no sampling). + logger: + Override the module logger. + """ + + def __init__( + self, + *, + connection_string: Optional[str] = None, + static_dimensions: Optional[Mapping[str, Any]] = None, + event_sink: Optional[EventSink] = None, + pricing: Optional[Mapping[str, tuple[float, float]]] = None, + user_id_hasher: Optional[Callable[[str], str]] = None, + sample_rate: float = 1.0, + logger: Optional[logging.Logger] = None, + ) -> None: + self._cs = connection_string if connection_string is not None else os.getenv( + "APPLICATIONINSIGHTS_CONNECTION_STRING" + ) + self._sink = event_sink if event_sink is not None else _default_event_sink() + self._log = logger or logging.getLogger(__name__) + + # PII hashing applied to user_id everywhere. + self._user_id_hasher = user_id_hasher + + # Sampling clamp to [0, 1]. + try: + sr = float(sample_rate) + except (TypeError, ValueError): + sr = 1.0 + self._sample_rate = max(0.0, min(1.0, sr)) + + # Case-insensitive pricing lookup. Values stored as a (in, out) tuple. + self._pricing: dict[str, tuple[float, float]] = {} + for model, rates in (pricing or {}).items(): + if not model or rates is None: + continue + try: + inp, out = rates + self._pricing[str(model).lower()] = (float(inp), float(out)) + except (TypeError, ValueError): + self._log.warning("Ignoring malformed pricing entry: %s=%r", model, rates) + + # Pre-stringify static dims once. user_id (if present) is hashed here + # so the raw value is never retained on the emitter. + raw_static = dict(static_dimensions or {}) + if "user_id" in raw_static: + raw_static["user_id"] = self._apply_user_id_hash(raw_static["user_id"]) + self._static: dict[str, str] = { + k: ("" if v is None else str(v)) for k, v in raw_static.items() + } + + # -- public surface --------------------------------------------------- + @property + def enabled(self) -> bool: + return bool(self._cs) and self._sink is not None + + @property + def sample_rate(self) -> float: + return self._sample_rate + + # -- internal helpers ------------------------------------------------- + def _apply_user_id_hash(self, value: Any) -> Any: + """Apply the configured user_id_hasher; never raises.""" + if value is None or value == "" or self._user_id_hasher is None: + return value + try: + return self._user_id_hasher(str(value)) + except Exception as exc: # never let hashing break telemetry + self._log.warning("user_id_hasher raised: %s", exc) + return value + + def _should_sample(self) -> bool: + """Sampling decision for high-cardinality events.""" + if self._sample_rate >= 1.0: + return True + if self._sample_rate <= 0.0: + return False + return random.random() < self._sample_rate + + def _cost_props( + self, model_deployment_name: Optional[str], usage: TokenUsage + ) -> dict[str, str]: + """Return ``{'estimated_cost_usd': '...'}`` when pricing is configured + for the given model, else ``{}``. 6-decimal formatting.""" + if not self._pricing or not model_deployment_name: + return {} + rate = self._pricing.get(model_deployment_name.lower()) + if not rate: + return {} + inp_rate, out_rate = rate + cost = (usage.input_tokens * inp_rate + usage.output_tokens * out_rate) / 1000.0 + return {"estimated_cost_usd": f"{cost:.6f}"} + + def _summary_cost_props( + self, + primary_model: Optional[str], + additional_agents: Mapping[str, str], + usage: TokenUsage, + ) -> dict[str, str]: + """Best-effort cost for the summary event: charge full usage at the + primary model's rate (the SDK aggregates sub-agent tokens to the + orchestrator, so apportioning is not possible without per-agent + usage). Falls back to silent skip when no rate is known.""" + if primary_model: + cost = self._cost_props(primary_model, usage) + if cost: + return cost + for m in additional_agents.values(): + cost = self._cost_props(m, usage) + if cost: + return cost + return {} + + def emit(self, event_name: str, **dimensions: Any) -> None: + """Low-level: emit an event with arbitrary properties. + + Non-string values are stringified. ``None`` values are dropped. Any + ``user_id`` value is passed through the configured hasher. + Never raises. + """ + props = dict(self._static) # cheap shallow copy of pre-stringified dims + for k, v in dimensions.items(): + if v is None: + continue + if k == "user_id": + v = self._apply_user_id_hash(v) + if v is None or v == "": + continue + props[k] = v if isinstance(v, str) else str(v) + + if not self.enabled: + self._log.debug( + "App Insights not configured -- skipping event %s (%s)", + event_name, props, + ) + return + try: + self._sink(event_name, props) # type: ignore[misc] + except Exception as exc: # never break the caller + self._log.warning("track_event(%s) failed: %s", event_name, exc) + + # -- typed convenience emitters -------------------------------------- + def emit_agent( + self, + *, + agent_name: str, + model_deployment_name: str, + usage: TokenUsage, + **dimensions: Any, + ) -> None: + if not usage.has_any or not self._should_sample(): + return + self.emit( + EVENT_AGENT, + agent_name=agent_name, + model_deployment_name=model_deployment_name, + **usage.to_event_props(), + **self._cost_props(model_deployment_name, usage), + **dimensions, + ) + + def emit_model( + self, + *, + model_deployment_name: str, + usage: TokenUsage, + **dimensions: Any, + ) -> None: + if not usage.has_any or not self._should_sample(): + return + self.emit( + EVENT_MODEL, + model_deployment_name=model_deployment_name, + **usage.to_event_props(), + **self._cost_props(model_deployment_name, usage), + **dimensions, + ) + + def emit_user( + self, + *, + user_id: str, + usage: TokenUsage, + **dimensions: Any, + ) -> None: + if not usage.has_any or not user_id or not self._should_sample(): + return + self.emit( + EVENT_USER, + user_id=user_id, + **usage.to_event_props(), + **dimensions, + ) + + def emit_team( + self, + *, + team_name: str, + usage: TokenUsage, + **dimensions: Any, + ) -> None: + if not usage.has_any or not team_name or not self._should_sample(): + return + self.emit( + EVENT_TEAM, + team_name=team_name, + **usage.to_event_props(), + **dimensions, + ) + + def emit_summary( + self, + *, + usage: TokenUsage, + agent_count: int = 1, + model_count: int = 1, + primary_model: Optional[str] = None, + additional_agents: Optional[Mapping[str, str]] = None, + **dimensions: Any, + ) -> None: + """The summary event always fires (ignores ``sample_rate``) so per- + request totals remain accurate even when high-cardinality events are + sampled.""" + if not usage.has_any: + return + # Summary historically uses ``total_input_tokens`` / ``total_output_tokens`` + # field names; preserve that wire format for backward compatibility. + props = { + "total_input_tokens": str(usage.input_tokens), + "total_output_tokens": str(usage.output_tokens), + "total_tokens": str(usage.total_tokens), + "agent_count": str(agent_count), + "model_count": str(model_count), + "sample_rate": f"{self._sample_rate:.4f}", + } + # Carry over realtime sub-counts if present. + for k, v in usage.to_event_props().items(): + props.setdefault(k, v) + # Optional total cost. + props.update(self._summary_cost_props(primary_model, additional_agents or {}, usage)) + self.emit(EVENT_SUMMARY, **props, **dimensions) + + def emit_speech( + self, + *, + model_deployment_name: str, + source: str, + usage: TokenUsage, + **dimensions: Any, + ) -> None: + """Voice-Live / realtime speech usage event.""" + if not self._should_sample(): + return + self.emit( + EVENT_SPEECH, + model_deployment_name=model_deployment_name, + source=source, + **usage.to_event_props(), + **self._cost_props(model_deployment_name, usage), + **dimensions, + ) + + # -- combined emit: summary + agent + per-distinct-model --------------- + def emit_all( + self, + *, + agent_name: str, + model_deployment_name: str, + usage: TokenUsage, + additional_agents: Optional[Mapping[str, str]] = None, + emit_user_event: bool = False, + emit_team_event: bool = False, + **dimensions: Any, + ) -> None: + """Convenience: emit summary, agent, and one model event per distinct + model deployment in one shot. + + ``additional_agents`` maps sub-agent name -> its model deployment name + so callers can describe orchestrators that involve multiple agents. + + ``emit_user_event`` / ``emit_team_event`` opt in to the user/team + events; ``user_id`` / ``team_name`` must be present in dimensions for + those to fire. + """ + if not usage.has_any: + return + + agents = {agent_name: model_deployment_name} + if additional_agents: + agents.update({k: v for k, v in additional_agents.items() if k}) + models = {m for m in agents.values() if m} + + self.emit_summary( + usage=usage, + agent_count=len(agents), + model_count=len(models) or 1, + primary_model=model_deployment_name, + additional_agents=additional_agents, + **dimensions, + ) + self.emit_agent( + agent_name=agent_name, + model_deployment_name=model_deployment_name, + usage=usage, + **dimensions, + ) + for model in models: + self.emit_model( + model_deployment_name=model, + usage=usage, + **dimensions, + ) + if emit_user_event and dimensions.get("user_id"): + self.emit_user( + user_id=str(dimensions["user_id"]), + usage=usage, + agent_name=agent_name, + model_deployment_name=model_deployment_name, + ) + if emit_team_event and dimensions.get("team_name"): + self.emit_team( + team_name=str(dimensions["team_name"]), + usage=usage, + agent_name=agent_name, + model_deployment_name=model_deployment_name, + ) + + self._log.info( + "[TOKEN USAGE] agent=%s model=%s input=%d output=%d total=%d %s", + agent_name, + model_deployment_name, + usage.input_tokens, + usage.output_tokens, + usage.total_tokens, + " ".join(f"{k}={v}" for k, v in dimensions.items() if v), + ) + + +# --------------------------------------------------------------------------- +# Scope / decorator sugar +# --------------------------------------------------------------------------- +@dataclass +class TokenUsageScope(AbstractContextManager): + """Accumulate usage across multiple results, then emit on exit. + + Example:: + + with TokenUsageScope(emitter, + agent_name="chat", + model_deployment_name=cfg.model, + user_id=user_id) as scope: + result = await agent.run(prompt) + scope.add(result) # extracts and accumulates + """ + + emitter: TokenUsageEmitter + agent_name: str + model_deployment_name: str + dimensions: dict[str, Any] = field(default_factory=dict) + additional_agents: dict[str, str] = field(default_factory=dict) + emit_user_event: bool = False + emit_team_event: bool = False + usage: TokenUsage = field(default_factory=TokenUsage) + + def __init__( + self, + emitter: TokenUsageEmitter, + *, + agent_name: str, + model_deployment_name: str, + additional_agents: Optional[Mapping[str, str]] = None, + emit_user_event: bool = False, + emit_team_event: bool = False, + **dimensions: Any, + ) -> None: + self.emitter = emitter + self.agent_name = agent_name + self.model_deployment_name = model_deployment_name + self.additional_agents = dict(additional_agents or {}) + self.emit_user_event = emit_user_event + self.emit_team_event = emit_team_event + self.dimensions = dict(dimensions) + self.usage = TokenUsage() + + # -- accumulation ----------------------------------------------------- + def add(self, source: Any) -> Optional[TokenUsage]: + """Extract usage from any supported shape and add to the running total. + + Never raises -- extraction failures return ``None`` and are logged + at DEBUG. + """ + try: + found = extract_usage(source) or extract_usage_from_stream_chunk(source) + except Exception as exc: # belt + braces; extractors are already safe + logger.debug("TokenUsageScope.add failed: %s", exc, exc_info=True) + return None + if found: + self.usage = self.usage + found + return found + + def add_usage(self, usage: TokenUsage) -> None: + self.usage = self.usage + usage + + def add_chunks(self, chunks: Iterable[Any]) -> None: + for c in chunks: + self.add(c) + + # -- context manager -------------------------------------------------- + def __exit__(self, exc_type, exc, tb) -> None: + # Always emit (best-effort) regardless of exception status. + try: + self.emitter.emit_all( + agent_name=self.agent_name, + model_deployment_name=self.model_deployment_name, + usage=self.usage, + additional_agents=self.additional_agents, + emit_user_event=self.emit_user_event, + emit_team_event=self.emit_team_event, + **self.dimensions, + ) + except Exception as emit_exc: # pragma: no cover - belt + braces + logger.warning("TokenUsageScope emit failed: %s", emit_exc) + return None # do not suppress exceptions + + +def track_tokens( + emitter: TokenUsageEmitter, + *, + agent_name: str, + model_deployment_name: str, + dimension_args: Optional[Mapping[str, str]] = None, + additional_agents: Optional[Mapping[str, str]] = None, + emit_user_event: bool = False, + emit_team_event: bool = False, +): + """Decorator: wrap an async or sync function that returns an LLM result. + + ``dimension_args`` maps emitted-property-name -> callable-keyword-argument + name so per-call values (e.g. ``user_id``) are forwarded to the event. + + Example:: + + @track_tokens(emitter, + agent_name="chat", + model_deployment_name=settings.model, + dimension_args={"user_id": "user_id", + "session_id": "session_id"}) + async def run_chat(prompt, *, user_id, session_id): ... + """ + + dim_args = dict(dimension_args or {}) + + def _decorator(fn: Callable[..., Any]): + is_coro = _is_coroutine_function(fn) + + if is_coro: + @functools.wraps(fn) + async def _aw(*args, **kwargs) -> Any: + with _scope_for(kwargs) as scope: + result = await fn(*args, **kwargs) + scope.add(result) + return result + return _aw + + @functools.wraps(fn) + def _sw(*args, **kwargs) -> Any: + with _scope_for(kwargs) as scope: + result = fn(*args, **kwargs) + scope.add(result) + return result + return _sw + + def _scope_for(call_kwargs: Mapping[str, Any]) -> TokenUsageScope: + dimensions = { + prop: call_kwargs.get(kw) + for prop, kw in dim_args.items() + if call_kwargs.get(kw) is not None + } + return TokenUsageScope( + emitter, + agent_name=agent_name, + model_deployment_name=model_deployment_name, + additional_agents=additional_agents, + emit_user_event=emit_user_event, + emit_team_event=emit_team_event, + **dimensions, + ) + + return _decorator + + +def _is_coroutine_function(fn: Callable[..., Any]) -> bool: + return asyncio.iscoroutinefunction(fn) + + +__all__ = [ + "EVENT_SUMMARY", + "EVENT_AGENT", + "EVENT_MODEL", + "EVENT_USER", + "EVENT_TEAM", + "EVENT_SPEECH", + "TokenUsage", + "TokenUsageEmitter", + "TokenUsageScope", + "track_tokens", + "extract_usage", + "extract_usage_from_dict", + "extract_usage_from_stream_chunk", + "extract_realtime_usage", + "detect_invoked_tools", +] diff --git a/src/processor/src/tests/test_llm_token_telemetry.py b/src/processor/src/tests/test_llm_token_telemetry.py new file mode 100644 index 00000000..6a24f5b1 --- /dev/null +++ b/src/processor/src/tests/test_llm_token_telemetry.py @@ -0,0 +1,572 @@ +"""Unit tests for app.utils.llm_token_telemetry. + +Covers: +- TokenUsage arithmetic and realtime sub-fields +- All extractors (dict / object / raw_representation / aggregated messages / + streaming chunks / realtime / Mock-input safety) +- detect_invoked_tools +- TokenUsageEmitter: enabled/disabled, sink-throws-doesn't-propagate, + static_dimensions merge, all typed emitters, emit_all distinct models +- TokenUsageScope: happy path, exception in body still emits, multi-add +""" +from __future__ import annotations + +import logging +from unittest.mock import Mock + +import pytest + +from app.utils.llm_token_telemetry import ( + EVENT_AGENT, + EVENT_MODEL, + EVENT_SPEECH, + EVENT_SUMMARY, + TokenUsage, + TokenUsageEmitter, + TokenUsageScope, + detect_invoked_tools, + extract_realtime_usage, + extract_usage, + extract_usage_from_dict, + extract_usage_from_stream_chunk, +) + + +# --------------------------------------------------------------------------- +# TokenUsage +# --------------------------------------------------------------------------- +class TestTokenUsage: + def test_has_any_false_when_zero(self): + assert TokenUsage().has_any is False + + def test_has_any_true_when_any_nonzero(self): + assert TokenUsage(input_tokens=1).has_any is True + assert TokenUsage(total_tokens=5).has_any is True + + def test_addition_basic(self): + a = TokenUsage(1, 2, 3) + b = TokenUsage(4, 5, 9) + assert a + b == TokenUsage(5, 7, 12) + + def test_addition_realtime_subfields(self): + a = TokenUsage(1, 2, 3, input_audio_tokens=10) + b = TokenUsage(4, 5, 9, input_audio_tokens=20, output_audio_tokens=7) + c = a + b + assert c.input_audio_tokens == 30 + assert c.output_audio_tokens == 7 # None + 7 -> 7 + + def test_addition_returns_notimplemented_for_other_types(self): + assert TokenUsage(1).__add__("nope") is NotImplemented + + def test_to_event_props_omits_none_subfields(self): + props = TokenUsage(1, 2, 3).to_event_props() + assert props == {"input_tokens": "1", "output_tokens": "2", "total_tokens": "3"} + + def test_to_event_props_includes_realtime_when_present(self): + props = TokenUsage(1, 2, 3, input_audio_tokens=4).to_event_props() + assert props["input_audio_tokens"] == "4" + + +# --------------------------------------------------------------------------- +# extract_usage_from_dict +# --------------------------------------------------------------------------- +class TestExtractFromDict: + @pytest.mark.parametrize("data,expected", [ + ({"prompt_tokens": 12, "completion_tokens": 8}, (12, 8, 20)), + ({"input_tokens": 5, "output_tokens": 7, "total_tokens": 12}, (5, 7, 12)), + ({"input_token_count": 3, "output_token_count": 4}, (3, 4, 7)), + ({"promptTokens": 1, "completionTokens": 2, "totalTokens": 3}, (1, 2, 3)), + ]) + def test_aliases(self, data, expected): + u = extract_usage_from_dict(data) + assert (u.input_tokens, u.output_tokens, u.total_tokens) == expected + + def test_none_returns_none(self): + assert extract_usage_from_dict(None) is None + + def test_empty_returns_none(self): + assert extract_usage_from_dict({}) is None + + def test_total_falls_back_to_sum(self): + u = extract_usage_from_dict({"input_tokens": 4, "output_tokens": 6}) + assert u.total_tokens == 10 + + def test_string_digits_coerced(self): + u = extract_usage_from_dict({"input_tokens": "10", "output_tokens": "20"}) + assert u.input_tokens == 10 + assert u.output_tokens == 20 + + +# --------------------------------------------------------------------------- +# extract_usage (object shapes) +# --------------------------------------------------------------------------- +class _Bag: + """Minimal attribute bag (acts like an SDK model object).""" + pass + + +class TestExtractUsage: + def test_usage_details_dict(self): + r = _Bag() + r.usage_details = {"input_token_count": 5, "output_token_count": 7} + u = extract_usage(r) + assert u.total_tokens == 12 + + def test_usage_details_object(self): + r = _Bag() + details = _Bag() + details.input_token_count = 5 + details.output_token_count = 7 + details.total_token_count = 12 + r.usage_details = details + u = extract_usage(r) + assert u.total_tokens == 12 + + def test_raw_representation_openai_shape(self): + r = _Bag() + raw = _Bag() + raw.usage = {"prompt_tokens": 3, "completion_tokens": 4, "total_tokens": 7} + r.raw_representation = raw + u = extract_usage(r) + assert (u.input_tokens, u.output_tokens, u.total_tokens) == (3, 4, 7) + + def test_aggregated_messages(self): + r = _Bag() + msg = _Bag() + c1 = _Bag() + c1.usage_details = {"input_tokens": 2, "output_tokens": 3} + c2 = _Bag() + c2.usage_details = {"input_tokens": 4, "output_tokens": 1} + msg.contents = [c1, c2] + r.messages = [msg] + u = extract_usage(r) + assert u.input_tokens == 6 + assert u.output_tokens == 4 + + def test_none_input_returns_none(self): + assert extract_usage(None) is None + + def test_no_usage_returns_none(self): + assert extract_usage(_Bag()) is None + + def test_mock_input_does_not_raise(self): + """Mock objects expose every attribute as another Mock -- previously + this caused TypeError on iteration of .messages.""" + m = Mock() + # Should silently return None, never raise. + assert extract_usage(m) is None + + +# --------------------------------------------------------------------------- +# extract_usage_from_stream_chunk +# --------------------------------------------------------------------------- +class TestStreamChunk: + def test_chunk_with_metadata_usage(self): + c = _Bag() + c.metadata = {"usage": {"input_tokens": 1, "output_tokens": 2}} + u = extract_usage_from_stream_chunk(c) + assert u.input_tokens == 1 + assert u.output_tokens == 2 + + def test_no_usage_returns_none(self): + assert extract_usage_from_stream_chunk(_Bag()) is None + + +# --------------------------------------------------------------------------- +# extract_realtime_usage +# --------------------------------------------------------------------------- +class TestRealtime: + def test_basic(self): + r = _Bag() + r.usage = { + "input_tokens": 3, "output_tokens": 4, "total_tokens": 7, + "input_token_details": {"audio_tokens": 2, "text_tokens": 1, "cached_tokens": 0}, + "output_token_details": {"audio_tokens": 4, "text_tokens": 0}, + } + u = extract_realtime_usage(r) + assert u.input_audio_tokens == 2 + assert u.output_audio_tokens == 4 + assert u.total_tokens == 7 + + def test_total_derived_when_missing(self): + r = _Bag() + r.usage = {"input_tokens": 3, "output_tokens": 4} + u = extract_realtime_usage(r) + assert u.total_tokens == 7 + + def test_no_usage_returns_none(self): + assert extract_realtime_usage(_Bag()) is None + + +# --------------------------------------------------------------------------- +# detect_invoked_tools +# --------------------------------------------------------------------------- +class TestDetectInvokedTools: + def test_finds_function_calls(self): + r = _Bag() + c1 = _Bag() + c1.type = "function_call" + c1.name = "product_agent" + c2 = _Bag() + c2.type = "text" + c2.name = "n/a" + c3 = _Bag() + c3.type = "function_call" + c3.name = "policy_agent" + msg = _Bag() + msg.contents = [c1, c2, c3] + r.messages = [msg] + assert detect_invoked_tools(r) == {"product_agent", "policy_agent"} + + def test_empty_when_no_messages(self): + assert detect_invoked_tools(_Bag()) == set() + + def test_mock_input_safe(self): + assert detect_invoked_tools(Mock()) == set() + + def test_skips_function_calls_without_name(self): + r = _Bag() + c = _Bag() + c.type = "function_call" + c.name = None + msg = _Bag() + msg.contents = [c] + r.messages = [msg] + assert detect_invoked_tools(r) == set() + + +# --------------------------------------------------------------------------- +# TokenUsageEmitter +# --------------------------------------------------------------------------- +class TestEmitter: + def _make(self, **kw): + captured: list[tuple[str, dict]] = [] + kw.setdefault("connection_string", "fake-conn") + kw.setdefault("event_sink", lambda n, p: captured.append((n, dict(p)))) + em = TokenUsageEmitter(**kw) + return em, captured + + def test_disabled_when_no_connection_string(self): + em = TokenUsageEmitter(connection_string="", event_sink=lambda *a: None) + assert em.enabled is False + + def test_disabled_when_no_sink(self): + em = TokenUsageEmitter(connection_string="x", event_sink=None) + # _default_event_sink may or may not be available; force-disable: + em._sink = None + assert em.enabled is False + + def test_static_dimensions_prestringified_and_merged(self): + em, captured = self._make(static_dimensions={"app": "x", "tenant": 42}) + em.emit("X", user_id="u1") + name, props = captured[0] + assert name == "X" + assert props["app"] == "x" + assert props["tenant"] == "42" # stringified + assert props["user_id"] == "u1" + + def test_call_dimension_overrides_static(self): + em, captured = self._make(static_dimensions={"app": "default"}) + em.emit("X", app="override") + assert captured[0][1]["app"] == "override" + + def test_none_dimension_dropped(self): + em, captured = self._make() + em.emit("X", user_id=None, session_id="s1") + assert "user_id" not in captured[0][1] + assert captured[0][1]["session_id"] == "s1" + + def test_sink_exception_does_not_propagate(self, caplog): + def boom(_n, _p): + raise RuntimeError("sink broken") + em = TokenUsageEmitter(connection_string="x", event_sink=boom) + with caplog.at_level(logging.WARNING): + em.emit("X") # must not raise + + def test_emit_agent_skips_zero_usage(self): + em, captured = self._make() + em.emit_agent(agent_name="a", model_deployment_name="m", usage=TokenUsage()) + assert captured == [] + + def test_emit_agent_populates_props(self): + em, captured = self._make() + em.emit_agent(agent_name="chat", model_deployment_name="gpt-4o", + usage=TokenUsage(10, 20, 30), user_id="u") + name, props = captured[0] + assert name == EVENT_AGENT + assert props["agent_name"] == "chat" + assert props["model_deployment_name"] == "gpt-4o" + assert props["total_tokens"] == "30" + assert props["user_id"] == "u" + + def test_emit_all_emits_summary_agent_and_per_distinct_model(self): + em, captured = self._make() + em.emit_all( + agent_name="orchestrator", + model_deployment_name="gpt-4o", + usage=TokenUsage(10, 20, 30), + additional_agents={"tool_a": "gpt-4o", "tool_b": "gpt-35"}, + user_id="u1", + ) + names = [n for n, _ in captured] + # exactly one summary + one agent + two model events (gpt-4o, gpt-35) + assert names.count(EVENT_SUMMARY) == 1 + assert names.count(EVENT_AGENT) == 1 + assert names.count(EVENT_MODEL) == 2 + # summary records agent + model counts + summary = next(p for n, p in captured if n == EVENT_SUMMARY) + assert summary["agent_count"] == "3" + assert summary["model_count"] == "2" + assert summary["total_input_tokens"] == "10" + + def test_emit_speech_includes_audio_subfields(self): + em, captured = self._make() + em.emit_speech( + model_deployment_name="gpt-4o-realtime", + source="voice_chat", + usage=TokenUsage(1, 2, 3, input_audio_tokens=5, output_audio_tokens=6), + ) + name, props = captured[0] + assert name == EVENT_SPEECH + assert props["source"] == "voice_chat" + assert props["input_audio_tokens"] == "5" + assert props["output_audio_tokens"] == "6" + + +# --------------------------------------------------------------------------- +# Pricing / cost computation +# --------------------------------------------------------------------------- +class TestPricing: + def _make(self, pricing): + captured: list[tuple[str, dict]] = [] + em = TokenUsageEmitter( + connection_string="x", + event_sink=lambda n, p: captured.append((n, dict(p))), + pricing=pricing, + ) + return em, captured + + def test_cost_attached_to_agent_event(self): + em, captured = self._make({"gpt-4o": (0.0025, 0.01)}) + em.emit_agent(agent_name="a", model_deployment_name="gpt-4o", + usage=TokenUsage(1000, 500, 1500)) + # 1000 * 0.0025/1k + 500 * 0.01/1k = 0.0025 + 0.005 = 0.0075 + assert captured[0][1]["estimated_cost_usd"] == "0.007500" + + def test_cost_case_insensitive_model_lookup(self): + em, captured = self._make({"GPT-4o": (0.001, 0.001)}) + em.emit_model(model_deployment_name="gpt-4o", + usage=TokenUsage(1000, 1000, 2000)) + assert "estimated_cost_usd" in captured[0][1] + + def test_no_cost_when_model_unknown(self): + em, captured = self._make({"gpt-4o": (0.001, 0.001)}) + em.emit_agent(agent_name="a", model_deployment_name="gpt-mystery", + usage=TokenUsage(10, 10, 20)) + assert "estimated_cost_usd" not in captured[0][1] + + def test_summary_picks_up_cost_via_emit_all(self): + em, captured = self._make({"gpt-4o": (0.0025, 0.01)}) + em.emit_all(agent_name="chat", model_deployment_name="gpt-4o", + usage=TokenUsage(1000, 500, 1500)) + summary = next(p for n, p in captured if n == EVENT_SUMMARY) + assert summary["estimated_cost_usd"] == "0.007500" + + def test_malformed_pricing_entry_ignored(self, caplog): + with caplog.at_level(logging.WARNING): + em = TokenUsageEmitter( + connection_string="x", + event_sink=lambda *a: None, + pricing={"bad-model": "not-a-tuple"}, # type: ignore[dict-item] + ) + # Emitter still constructs; bad entry skipped. + assert "bad-model" not in em._pricing + + +# --------------------------------------------------------------------------- +# user_id PII hashing +# --------------------------------------------------------------------------- +class TestUserIdHasher: + def _make(self, hasher): + captured: list[tuple[str, dict]] = [] + em = TokenUsageEmitter( + connection_string="x", + event_sink=lambda n, p: captured.append((n, dict(p))), + user_id_hasher=hasher, + ) + return em, captured + + def test_hasher_applied_to_call_kwargs(self): + em, captured = self._make(lambda v: f"H({v})") + em.emit("X", user_id="alice") + assert captured[0][1]["user_id"] == "H(alice)" + + def test_hasher_applied_to_static_dimensions_at_construction(self): + em = TokenUsageEmitter( + connection_string="x", + event_sink=lambda *a: None, + user_id_hasher=lambda v: f"H({v})", + static_dimensions={"user_id": "bob"}, + ) + assert em._static["user_id"] == "H(bob)" + + def test_hasher_exception_falls_back_to_raw(self, caplog): + def boom(_v): + raise RuntimeError("hasher broken") + em, captured = self._make(boom) + with caplog.at_level(logging.WARNING): + em.emit("X", user_id="alice") + # Falls back to original value -- never breaks telemetry. + assert captured[0][1]["user_id"] == "alice" + + def test_no_hasher_passes_through(self): + em, captured = self._make(None) + em.emit("X", user_id="alice") + assert captured[0][1]["user_id"] == "alice" + + def test_empty_user_id_not_hashed_or_emitted(self): + em, captured = self._make(lambda v: f"H({v})") + em.emit("X", user_id="") + # Empty user_id should be dropped, not hashed to "H()". + assert "user_id" not in captured[0][1] + + +# --------------------------------------------------------------------------- +# Sampling +# --------------------------------------------------------------------------- +class TestSampling: + def _make(self, rate): + captured: list[tuple[str, dict]] = [] + em = TokenUsageEmitter( + connection_string="x", + event_sink=lambda n, p: captured.append((n, dict(p))), + sample_rate=rate, + ) + return em, captured + + def test_rate_clamped_to_unit_interval(self): + assert TokenUsageEmitter(connection_string="x", sample_rate=-0.5, + event_sink=lambda *a: None).sample_rate == 0.0 + assert TokenUsageEmitter(connection_string="x", sample_rate=2.0, + event_sink=lambda *a: None).sample_rate == 1.0 + + def test_invalid_rate_defaults_to_one(self): + em = TokenUsageEmitter(connection_string="x", sample_rate="nope", # type: ignore[arg-type] + event_sink=lambda *a: None) + assert em.sample_rate == 1.0 + + def test_zero_rate_drops_agent_event(self): + em, captured = self._make(0.0) + em.emit_agent(agent_name="a", model_deployment_name="m", + usage=TokenUsage(1, 2, 3)) + assert captured == [] + + def test_zero_rate_still_emits_summary(self): + em, captured = self._make(0.0) + em.emit_summary(usage=TokenUsage(1, 2, 3)) + assert captured and captured[0][0] == EVENT_SUMMARY + + def test_summary_records_sample_rate(self): + em, captured = self._make(0.25) + em.emit_summary(usage=TokenUsage(1, 2, 3)) + assert captured[0][1]["sample_rate"] == "0.2500" + + def test_emit_all_with_zero_rate_only_emits_summary(self): + em, captured = self._make(0.0) + em.emit_all(agent_name="chat", model_deployment_name="gpt-4o", + usage=TokenUsage(10, 20, 30)) + assert [n for n, _ in captured] == [EVENT_SUMMARY] + + def test_full_rate_emits_everything(self): + em, captured = self._make(1.0) + em.emit_all(agent_name="chat", model_deployment_name="gpt-4o", + usage=TokenUsage(10, 20, 30), + additional_agents={"a2": "gpt-35"}) + names = [n for n, _ in captured] + assert EVENT_SUMMARY in names + assert EVENT_AGENT in names + assert names.count(EVENT_MODEL) == 2 + + +# --------------------------------------------------------------------------- +# TokenUsageScope (continued) +# --------------------------------------------------------------------------- +class TestScope: + def _emitter(self): + captured: list[tuple[str, dict]] = [] + em = TokenUsageEmitter( + connection_string="x", + event_sink=lambda n, p: captured.append((n, dict(p))), + ) + return em, captured + + def test_happy_path_emits_on_exit(self): + em, captured = self._emitter() + r = _Bag() + r.usage_details = {"input_tokens": 1, "output_tokens": 2, "total_tokens": 3} + with TokenUsageScope(em, agent_name="a", model_deployment_name="m") as s: + s.add(r) + assert any(n == EVENT_SUMMARY for n, _ in captured) + assert any(n == EVENT_AGENT for n, _ in captured) + + def test_multi_add_accumulates(self): + em, captured = self._emitter() + r1 = _Bag() + r1.usage_details = {"input_tokens": 1, "output_tokens": 2} + r2 = _Bag() + r2.usage_details = {"input_tokens": 4, "output_tokens": 5} + with TokenUsageScope(em, agent_name="a", model_deployment_name="m") as s: + s.add(r1) + s.add(r2) + agent = next(p for n, p in captured if n == EVENT_AGENT) + assert agent["input_tokens"] == "5" + assert agent["output_tokens"] == "7" + assert agent["total_tokens"] == "12" + + def test_exception_in_body_still_emits(self): + em, captured = self._emitter() + r = _Bag() + r.usage_details = {"input_tokens": 1, "output_tokens": 2, "total_tokens": 3} + with pytest.raises(ValueError): + with TokenUsageScope(em, agent_name="a", model_deployment_name="m") as s: + s.add(r) + raise ValueError("boom") + # Emission still happened + assert any(n == EVENT_AGENT for n, _ in captured) + + def test_add_with_mock_does_not_raise(self): + em, _ = self._emitter() + with TokenUsageScope(em, agent_name="a", model_deployment_name="m") as s: + assert s.add(Mock()) is None + + def test_zero_usage_does_not_emit(self): + em, captured = self._emitter() + with TokenUsageScope(em, agent_name="a", model_deployment_name="m"): + pass + assert captured == [] + + def test_dimensions_flow_to_events(self): + em, captured = self._emitter() + r = _Bag() + r.usage_details = {"input_tokens": 1, "output_tokens": 2, "total_tokens": 3} + with TokenUsageScope(em, agent_name="a", model_deployment_name="m", + user_id="u1", session_id="s1") as s: + s.add(r) + for _, p in captured: + assert p["user_id"] == "u1" + assert p["session_id"] == "s1" + + def test_additional_agents_after_scope_open(self): + em, captured = self._emitter() + r = _Bag() + r.usage_details = {"input_tokens": 1, "output_tokens": 2, "total_tokens": 3} + with TokenUsageScope(em, agent_name="orchestrator", + model_deployment_name="gpt-4o") as s: + s.add(r) + # Mutate additional_agents after the call -- mirrors the + # detect_invoked_tools usage pattern. + s.additional_agents["tool_a"] = "gpt-35" + model_events = [p for n, p in captured if n == EVENT_MODEL] + models = {p["model_deployment_name"] for p in model_events} + assert models == {"gpt-4o", "gpt-35"} + diff --git a/src/processor/src/utils/llm_token_telemetry.py b/src/processor/src/utils/llm_token_telemetry.py new file mode 100644 index 00000000..b3035fc8 --- /dev/null +++ b/src/processor/src/utils/llm_token_telemetry.py @@ -0,0 +1,935 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +"""Cross-accelerator LLM token-usage telemetry helpers. + +A single, dependency-light helper module that can be dropped into any Microsoft +Solution Accelerator to capture LLM token usage and emit standardized custom +events to Application Insights. + +Why this file exists +-------------------- +Seven solution accelerators have independently shipped near-identical +``token_usage_utils.py`` modules (see PRs: content-generation #860, CKM #933, +content-processing #586, Container-Migration #257, agentic-data-foundation +#383, customer-chatbot #218, MACAE #1003). They all: + +* extract token counts from agent_framework / Azure OpenAI responses, +* emit the same three custom events (``LLM_Token_Usage_Summary``, + ``LLM_Agent_Token_Usage``, ``LLM_Model_Token_Usage``), +* defensively swallow telemetry errors, +* duplicate the same KQL queries and Azure Workbook. + +This module consolidates the union of those behaviours behind one stable API +so each accelerator can replace its bespoke helper with an import. + +Public API +---------- +- ``TokenUsage`` -- immutable dataclass for counts +- ``extract_usage(obj)`` -- agent_framework run result / message +- ``extract_usage_from_dict(d)`` -- raw dict from any SDK +- ``extract_usage_from_stream_chunk`` -- streaming chunks +- ``extract_realtime_usage(resp)`` -- Azure AI Voice Live response.done +- ``TokenUsageEmitter`` -- emits the three events + optional + per-user / per-team / speech events +- ``TokenUsageScope`` -- context-manager that accumulates and + auto-emits on exit +- ``track_tokens`` -- decorator wrapper around the scope + +Design rules +------------ +* Telemetry NEVER raises. Extraction failures return ``None``; emission + failures are logged at WARNING. +* No hard dependency on ``azure-monitor-events-extension``; if absent the + emitter degrades to logging only. +* Arbitrary correlation dimensions are passed as ``**dimensions`` kwargs and + surface verbatim as custom-event properties. This is how each accelerator + attaches its own keys (``conversation_id``, ``process_id``, ``team_name``, + ``file_name``, ``tenant``, etc.) without forking the helper. +""" +from __future__ import annotations + +import asyncio +import functools +import logging +import os +import random +from contextlib import AbstractContextManager +from dataclasses import dataclass, field +from typing import Any, Callable, Iterable, Mapping, Optional +from unittest.mock import NonCallableMock + +logger = logging.getLogger(__name__) + + +# --------------------------------------------------------------------------- +# Event-name constants -- keep these stable; KQL queries and workbooks bind +# to these exact strings. +# --------------------------------------------------------------------------- +EVENT_SUMMARY = "LLM_Token_Usage_Summary" +EVENT_AGENT = "LLM_Agent_Token_Usage" +EVENT_MODEL = "LLM_Model_Token_Usage" +EVENT_USER = "LLM_User_Token_Usage" +EVENT_TEAM = "LLM_Team_Token_Usage" +EVENT_SPEECH = "Speech_Usage" + + +# Token-count field aliases observed across model providers / SDK versions. +_INPUT_KEYS = ( + "input_token_count", + "input_tokens", + "prompt_tokens", + "promptTokens", +) +_OUTPUT_KEYS = ( + "output_token_count", + "output_tokens", + "completion_tokens", + "completionTokens", +) +_TOTAL_KEYS = ( + "total_token_count", + "total_tokens", + "totalTokens", +) + + +# --------------------------------------------------------------------------- +# Data model +# --------------------------------------------------------------------------- +@dataclass(frozen=True) +class TokenUsage: + """Normalized token-usage record.""" + + input_tokens: int = 0 + output_tokens: int = 0 + total_tokens: int = 0 + + # Optional realtime / voice fields (None unless populated) + input_audio_tokens: Optional[int] = None + input_text_tokens: Optional[int] = None + input_cached_tokens: Optional[int] = None + output_audio_tokens: Optional[int] = None + output_text_tokens: Optional[int] = None + + @property + def has_any(self) -> bool: + return bool(self.input_tokens or self.output_tokens or self.total_tokens) + + def __add__(self, other: "TokenUsage") -> "TokenUsage": + if not isinstance(other, TokenUsage): + return NotImplemented + + def _sum(a: Optional[int], b: Optional[int]) -> Optional[int]: + if a is None and b is None: + return None + return (a or 0) + (b or 0) + + return TokenUsage( + input_tokens=self.input_tokens + other.input_tokens, + output_tokens=self.output_tokens + other.output_tokens, + total_tokens=self.total_tokens + other.total_tokens, + input_audio_tokens=_sum(self.input_audio_tokens, other.input_audio_tokens), + input_text_tokens=_sum(self.input_text_tokens, other.input_text_tokens), + input_cached_tokens=_sum(self.input_cached_tokens, other.input_cached_tokens), + output_audio_tokens=_sum(self.output_audio_tokens, other.output_audio_tokens), + output_text_tokens=_sum(self.output_text_tokens, other.output_text_tokens), + ) + + def to_event_props(self) -> dict[str, str]: + """Stringified property bag suitable for App Insights custom events.""" + props: dict[str, str] = { + "input_tokens": str(self.input_tokens), + "output_tokens": str(self.output_tokens), + "total_tokens": str(self.total_tokens), + } + for name in ( + "input_audio_tokens", + "input_text_tokens", + "input_cached_tokens", + "output_audio_tokens", + "output_text_tokens", + ): + value = getattr(self, name) + if value is not None: + props[name] = str(value) + return props + + +# --------------------------------------------------------------------------- +# Low-level coercion helpers +# --------------------------------------------------------------------------- +def _to_int(value: Any, default: int = 0) -> int: + """Best-effort int conversion; bool excluded; never raises.""" + if value is None or isinstance(value, bool): + return default + if isinstance(value, int): + return value + if isinstance(value, float): + return int(value) + if isinstance(value, str): + s = value.strip() + if s.isdigit(): + return int(s) + try: + return int(value) + except (TypeError, ValueError): + return default + + +def _get(obj: Any, key: str, default: Any = None) -> Any: + """Read an attribute or dict key uniformly.""" + if obj is None: + return default + if isinstance(obj, Mapping): + return obj.get(key, default) + return getattr(obj, key, default) + + +def _is_iterable(obj: Any) -> bool: + """True only for real iterables (lists/tuples/sets/generators), NOT for + arbitrary objects (e.g. ``unittest.mock.Mock``) that happen to expose + ``__iter__`` but blow up on iteration.""" + if obj is None: + return False + if isinstance(obj, (list, tuple, set, frozenset)): + return True + # Strings are iterable but never the right answer for "messages". + if isinstance(obj, (str, bytes, bytearray, Mapping)): + return False + # Fall back to a duck-typed check, but reject Mock instances which would + # otherwise pretend to support iteration. + if isinstance(obj, NonCallableMock): + return False + return hasattr(obj, "__iter__") + + +def _read_counts(usage_obj: Any) -> Optional[TokenUsage]: + """Read ``input/output/total`` from any usage-bearing object/dict.""" + if usage_obj is None: + return None + + inp = out = tot = 0 + for k in _INPUT_KEYS: + v = _get(usage_obj, k) + if v: + inp = _to_int(v) + break + for k in _OUTPUT_KEYS: + v = _get(usage_obj, k) + if v: + out = _to_int(v) + break + for k in _TOTAL_KEYS: + v = _get(usage_obj, k) + if v: + tot = _to_int(v) + break + + if tot == 0 and (inp or out): + tot = inp + out + if not (inp or out or tot): + return None + return TokenUsage(input_tokens=inp, output_tokens=out, total_tokens=tot) + + +# --------------------------------------------------------------------------- +# Extraction -- public +# --------------------------------------------------------------------------- +def extract_usage(result: Any) -> Optional[TokenUsage]: + """Extract usage from an agent_framework run result, ChatMessage, or + OpenAI-style ChatCompletion. + + Checks (in order): + 1. ``result.usage_details`` or ``result.usage`` + 2. ``result.raw_representation.usage`` (OpenAI ChatCompletion shape) + 3. Aggregated ``result.messages[*].contents[*].usage_details`` + + Never raises -- returns ``None`` on any unexpected shape. + """ + if result is None: + return None + + try: + for attr in ("usage_details", "usage"): + found = _read_counts(_get(result, attr)) + if found: + return found + + raw = _get(result, "raw_representation") + if raw is not None: + found = _read_counts(_get(raw, "usage")) + if found: + return found + + aggregated = TokenUsage() + found_any = False + messages = _get(result, "messages") + if not _is_iterable(messages): + return None + for msg in messages: + contents = _get(msg, "contents") + if not _is_iterable(contents): + continue + for content in contents: + usage = _get(content, "usage_details") or _get(content, "usage") + piece = _read_counts(usage) + if piece: + aggregated = aggregated + piece + found_any = True + return aggregated if found_any else None + except Exception as exc: + logger.debug("extract_usage failed: %s", exc, exc_info=True) + return None + + +def extract_usage_from_dict(data: Any) -> Optional[TokenUsage]: + """Extract from a raw dict / SDK usage object.""" + return _read_counts(data) + + +def extract_usage_from_stream_chunk(chunk: Any) -> Optional[TokenUsage]: + """Streaming chunks: try the top-level shape, then ``chunk.metadata.usage``.""" + found = extract_usage(chunk) + if found: + return found + metadata = _get(chunk, "metadata") + if metadata is not None: + return _read_counts(_get(metadata, "usage")) + return None + + +def extract_realtime_usage(response_obj: Any) -> Optional[TokenUsage]: + """Azure AI Voice Live ``response.done`` payload extractor. + + Includes audio / text / cached sub-counts when present. + """ + usage = _get(response_obj, "usage") + if usage is None: + return None + + inp = _to_int(_get(usage, "input_tokens")) + out = _to_int(_get(usage, "output_tokens")) + tot = _to_int(_get(usage, "total_tokens")) + if tot == 0 and (inp or out): + tot = inp + out + + in_details = _get(usage, "input_token_details") or {} + out_details = _get(usage, "output_token_details") or {} + + record = TokenUsage( + input_tokens=inp, + output_tokens=out, + total_tokens=tot, + input_audio_tokens=_to_int(_get(in_details, "audio_tokens")), + input_text_tokens=_to_int(_get(in_details, "text_tokens")), + input_cached_tokens=_to_int(_get(in_details, "cached_tokens")), + output_audio_tokens=_to_int(_get(out_details, "audio_tokens")), + output_text_tokens=_to_int(_get(out_details, "text_tokens")), + ) + # Only return if at least one non-zero count surfaced. + if record.has_any or any( + v for v in ( + record.input_audio_tokens, + record.input_text_tokens, + record.input_cached_tokens, + record.output_audio_tokens, + record.output_text_tokens, + ) + ): + return record + return None + + +# --------------------------------------------------------------------------- +# Tool / sub-agent attribution +# --------------------------------------------------------------------------- +def detect_invoked_tools(result: Any) -> set[str]: + """Return the set of tool/function names invoked in an agent result, + inferred from ``function_call`` content items. + + Used by orchestrators that expose sub-agents via ``.as_tool()`` to attribute + token usage only to the sub-agents that were actually called. Never raises. + """ + invoked: set[str] = set() + try: + messages = _get(result, "messages") + if not _is_iterable(messages): + return invoked + for msg in messages: + contents = _get(msg, "contents") + if not _is_iterable(contents): + continue + for content in contents: + if _get(content, "type") == "function_call": + name = _get(content, "name") + if name: + invoked.add(str(name)) + except Exception as exc: + logger.debug("detect_invoked_tools failed: %s", exc, exc_info=True) + return invoked + + +# --------------------------------------------------------------------------- +# Event sink (optional Application Insights dependency) +# --------------------------------------------------------------------------- +EventSink = Callable[[str, Mapping[str, str]], None] + + +def _default_event_sink() -> Optional[EventSink]: + """Return ``azure.monitor.events.extension.track_event`` if importable, + else ``None``. Resolved lazily so the helper still works in unit tests + without the dependency installed.""" + try: + from azure.monitor.events.extension import track_event # type: ignore + except Exception: # pragma: no cover - optional dep + return None + return track_event + + +# --------------------------------------------------------------------------- +# Emitter +# --------------------------------------------------------------------------- +class TokenUsageEmitter: + """Emit standardized token-usage custom events. + + Parameters + ---------- + connection_string: + Application Insights connection string. If ``None`` (default), the + ``APPLICATIONINSIGHTS_CONNECTION_STRING`` env var is consulted. When + no connection string is configured the emitter logs and skips the + ``track_event`` call. + static_dimensions: + Properties merged into every event (e.g. ``{"app": "customer-chatbot"}``). + event_sink: + Callable ``(event_name, props_dict) -> None``. Defaults to + ``azure.monitor.events.extension.track_event``. Override in tests. + pricing: + Optional mapping ``{model_deployment_name -> (usd_per_1k_input, + usd_per_1k_output)}``. When provided, an ``estimated_cost_usd`` + property is attached to agent / model / summary events. Model lookup + is case-insensitive. Use this to avoid hard-coding rates in KQL. + user_id_hasher: + Optional callable ``str -> str`` applied to any ``user_id`` value + before it leaves the emitter. Use this to satisfy PII / GDPR + requirements (e.g. HMAC-SHA256 with a tenant-scoped salt). Applied + to both ``static_dimensions['user_id']`` (at construction) and + per-call ``user_id`` kwargs. + sample_rate: + Fraction of high-cardinality events (agent / model / user / team / + speech) actually shipped, in ``[0.0, 1.0]``. The cheap **summary + event always fires** regardless of sample_rate so per-request totals + remain accurate; only the per-dimension breakdown is sampled. + Defaults to ``1.0`` (no sampling). + logger: + Override the module logger. + """ + + def __init__( + self, + *, + connection_string: Optional[str] = None, + static_dimensions: Optional[Mapping[str, Any]] = None, + event_sink: Optional[EventSink] = None, + pricing: Optional[Mapping[str, tuple[float, float]]] = None, + user_id_hasher: Optional[Callable[[str], str]] = None, + sample_rate: float = 1.0, + logger: Optional[logging.Logger] = None, + ) -> None: + self._cs = connection_string if connection_string is not None else os.getenv( + "APPLICATIONINSIGHTS_CONNECTION_STRING" + ) + self._sink = event_sink if event_sink is not None else _default_event_sink() + self._log = logger or logging.getLogger(__name__) + + # PII hashing applied to user_id everywhere. + self._user_id_hasher = user_id_hasher + + # Sampling clamp to [0, 1]. + try: + sr = float(sample_rate) + except (TypeError, ValueError): + sr = 1.0 + self._sample_rate = max(0.0, min(1.0, sr)) + + # Case-insensitive pricing lookup. Values stored as a (in, out) tuple. + self._pricing: dict[str, tuple[float, float]] = {} + for model, rates in (pricing or {}).items(): + if not model or rates is None: + continue + try: + inp, out = rates + self._pricing[str(model).lower()] = (float(inp), float(out)) + except (TypeError, ValueError): + self._log.warning("Ignoring malformed pricing entry: %s=%r", model, rates) + + # Pre-stringify static dims once. user_id (if present) is hashed here + # so the raw value is never retained on the emitter. + raw_static = dict(static_dimensions or {}) + if "user_id" in raw_static: + raw_static["user_id"] = self._apply_user_id_hash(raw_static["user_id"]) + self._static: dict[str, str] = { + k: ("" if v is None else str(v)) for k, v in raw_static.items() + } + + # -- public surface --------------------------------------------------- + @property + def enabled(self) -> bool: + return bool(self._cs) and self._sink is not None + + @property + def sample_rate(self) -> float: + return self._sample_rate + + # -- internal helpers ------------------------------------------------- + def _apply_user_id_hash(self, value: Any) -> Any: + """Apply the configured user_id_hasher; never raises.""" + if value is None or value == "" or self._user_id_hasher is None: + return value + try: + return self._user_id_hasher(str(value)) + except Exception as exc: # never let hashing break telemetry + self._log.warning("user_id_hasher raised: %s", exc) + return value + + def _should_sample(self) -> bool: + """Sampling decision for high-cardinality events.""" + if self._sample_rate >= 1.0: + return True + if self._sample_rate <= 0.0: + return False + return random.random() < self._sample_rate + + def _cost_props( + self, model_deployment_name: Optional[str], usage: TokenUsage + ) -> dict[str, str]: + """Return ``{'estimated_cost_usd': '...'}`` when pricing is configured + for the given model, else ``{}``. 6-decimal formatting.""" + if not self._pricing or not model_deployment_name: + return {} + rate = self._pricing.get(model_deployment_name.lower()) + if not rate: + return {} + inp_rate, out_rate = rate + cost = (usage.input_tokens * inp_rate + usage.output_tokens * out_rate) / 1000.0 + return {"estimated_cost_usd": f"{cost:.6f}"} + + def _summary_cost_props( + self, + primary_model: Optional[str], + additional_agents: Mapping[str, str], + usage: TokenUsage, + ) -> dict[str, str]: + """Best-effort cost for the summary event: charge full usage at the + primary model's rate (the SDK aggregates sub-agent tokens to the + orchestrator, so apportioning is not possible without per-agent + usage). Falls back to silent skip when no rate is known.""" + if primary_model: + cost = self._cost_props(primary_model, usage) + if cost: + return cost + for m in additional_agents.values(): + cost = self._cost_props(m, usage) + if cost: + return cost + return {} + + def emit(self, event_name: str, **dimensions: Any) -> None: + """Low-level: emit an event with arbitrary properties. + + Non-string values are stringified. ``None`` values are dropped. Any + ``user_id`` value is passed through the configured hasher. + Never raises. + """ + props = dict(self._static) # cheap shallow copy of pre-stringified dims + for k, v in dimensions.items(): + if v is None: + continue + if k == "user_id": + v = self._apply_user_id_hash(v) + if v is None or v == "": + continue + props[k] = v if isinstance(v, str) else str(v) + + if not self.enabled: + self._log.debug( + "App Insights not configured -- skipping event %s (%s)", + event_name, props, + ) + return + try: + self._sink(event_name, props) # type: ignore[misc] + except Exception as exc: # never break the caller + self._log.warning("track_event(%s) failed: %s", event_name, exc) + + # -- typed convenience emitters -------------------------------------- + def emit_agent( + self, + *, + agent_name: str, + model_deployment_name: str, + usage: TokenUsage, + **dimensions: Any, + ) -> None: + if not usage.has_any or not self._should_sample(): + return + self.emit( + EVENT_AGENT, + agent_name=agent_name, + model_deployment_name=model_deployment_name, + **usage.to_event_props(), + **self._cost_props(model_deployment_name, usage), + **dimensions, + ) + + def emit_model( + self, + *, + model_deployment_name: str, + usage: TokenUsage, + **dimensions: Any, + ) -> None: + if not usage.has_any or not self._should_sample(): + return + self.emit( + EVENT_MODEL, + model_deployment_name=model_deployment_name, + **usage.to_event_props(), + **self._cost_props(model_deployment_name, usage), + **dimensions, + ) + + def emit_user( + self, + *, + user_id: str, + usage: TokenUsage, + **dimensions: Any, + ) -> None: + if not usage.has_any or not user_id or not self._should_sample(): + return + self.emit( + EVENT_USER, + user_id=user_id, + **usage.to_event_props(), + **dimensions, + ) + + def emit_team( + self, + *, + team_name: str, + usage: TokenUsage, + **dimensions: Any, + ) -> None: + if not usage.has_any or not team_name or not self._should_sample(): + return + self.emit( + EVENT_TEAM, + team_name=team_name, + **usage.to_event_props(), + **dimensions, + ) + + def emit_summary( + self, + *, + usage: TokenUsage, + agent_count: int = 1, + model_count: int = 1, + primary_model: Optional[str] = None, + additional_agents: Optional[Mapping[str, str]] = None, + **dimensions: Any, + ) -> None: + """The summary event always fires (ignores ``sample_rate``) so per- + request totals remain accurate even when high-cardinality events are + sampled.""" + if not usage.has_any: + return + # Summary historically uses ``total_input_tokens`` / ``total_output_tokens`` + # field names; preserve that wire format for backward compatibility. + props = { + "total_input_tokens": str(usage.input_tokens), + "total_output_tokens": str(usage.output_tokens), + "total_tokens": str(usage.total_tokens), + "agent_count": str(agent_count), + "model_count": str(model_count), + "sample_rate": f"{self._sample_rate:.4f}", + } + # Carry over realtime sub-counts if present. + for k, v in usage.to_event_props().items(): + props.setdefault(k, v) + # Optional total cost. + props.update(self._summary_cost_props(primary_model, additional_agents or {}, usage)) + self.emit(EVENT_SUMMARY, **props, **dimensions) + + def emit_speech( + self, + *, + model_deployment_name: str, + source: str, + usage: TokenUsage, + **dimensions: Any, + ) -> None: + """Voice-Live / realtime speech usage event.""" + if not self._should_sample(): + return + self.emit( + EVENT_SPEECH, + model_deployment_name=model_deployment_name, + source=source, + **usage.to_event_props(), + **self._cost_props(model_deployment_name, usage), + **dimensions, + ) + + # -- combined emit: summary + agent + per-distinct-model --------------- + def emit_all( + self, + *, + agent_name: str, + model_deployment_name: str, + usage: TokenUsage, + additional_agents: Optional[Mapping[str, str]] = None, + emit_user_event: bool = False, + emit_team_event: bool = False, + **dimensions: Any, + ) -> None: + """Convenience: emit summary, agent, and one model event per distinct + model deployment in one shot. + + ``additional_agents`` maps sub-agent name -> its model deployment name + so callers can describe orchestrators that involve multiple agents. + + ``emit_user_event`` / ``emit_team_event`` opt in to the user/team + events; ``user_id`` / ``team_name`` must be present in dimensions for + those to fire. + """ + if not usage.has_any: + return + + agents = {agent_name: model_deployment_name} + if additional_agents: + agents.update({k: v for k, v in additional_agents.items() if k}) + models = {m for m in agents.values() if m} + + self.emit_summary( + usage=usage, + agent_count=len(agents), + model_count=len(models) or 1, + primary_model=model_deployment_name, + additional_agents=additional_agents, + **dimensions, + ) + self.emit_agent( + agent_name=agent_name, + model_deployment_name=model_deployment_name, + usage=usage, + **dimensions, + ) + for model in models: + self.emit_model( + model_deployment_name=model, + usage=usage, + **dimensions, + ) + if emit_user_event and dimensions.get("user_id"): + self.emit_user( + user_id=str(dimensions["user_id"]), + usage=usage, + agent_name=agent_name, + model_deployment_name=model_deployment_name, + ) + if emit_team_event and dimensions.get("team_name"): + self.emit_team( + team_name=str(dimensions["team_name"]), + usage=usage, + agent_name=agent_name, + model_deployment_name=model_deployment_name, + ) + + self._log.info( + "[TOKEN USAGE] agent=%s model=%s input=%d output=%d total=%d %s", + agent_name, + model_deployment_name, + usage.input_tokens, + usage.output_tokens, + usage.total_tokens, + " ".join(f"{k}={v}" for k, v in dimensions.items() if v), + ) + + +# --------------------------------------------------------------------------- +# Scope / decorator sugar +# --------------------------------------------------------------------------- +@dataclass +class TokenUsageScope(AbstractContextManager): + """Accumulate usage across multiple results, then emit on exit. + + Example:: + + with TokenUsageScope(emitter, + agent_name="chat", + model_deployment_name=cfg.model, + user_id=user_id) as scope: + result = await agent.run(prompt) + scope.add(result) # extracts and accumulates + """ + + emitter: TokenUsageEmitter + agent_name: str + model_deployment_name: str + dimensions: dict[str, Any] = field(default_factory=dict) + additional_agents: dict[str, str] = field(default_factory=dict) + emit_user_event: bool = False + emit_team_event: bool = False + usage: TokenUsage = field(default_factory=TokenUsage) + + def __init__( + self, + emitter: TokenUsageEmitter, + *, + agent_name: str, + model_deployment_name: str, + additional_agents: Optional[Mapping[str, str]] = None, + emit_user_event: bool = False, + emit_team_event: bool = False, + **dimensions: Any, + ) -> None: + self.emitter = emitter + self.agent_name = agent_name + self.model_deployment_name = model_deployment_name + self.additional_agents = dict(additional_agents or {}) + self.emit_user_event = emit_user_event + self.emit_team_event = emit_team_event + self.dimensions = dict(dimensions) + self.usage = TokenUsage() + + # -- accumulation ----------------------------------------------------- + def add(self, source: Any) -> Optional[TokenUsage]: + """Extract usage from any supported shape and add to the running total. + + Never raises -- extraction failures return ``None`` and are logged + at DEBUG. + """ + try: + found = extract_usage(source) or extract_usage_from_stream_chunk(source) + except Exception as exc: # belt + braces; extractors are already safe + logger.debug("TokenUsageScope.add failed: %s", exc, exc_info=True) + return None + if found: + self.usage = self.usage + found + return found + + def add_usage(self, usage: TokenUsage) -> None: + self.usage = self.usage + usage + + def add_chunks(self, chunks: Iterable[Any]) -> None: + for c in chunks: + self.add(c) + + # -- context manager -------------------------------------------------- + def __exit__(self, exc_type, exc, tb) -> None: + # Always emit (best-effort) regardless of exception status. + try: + self.emitter.emit_all( + agent_name=self.agent_name, + model_deployment_name=self.model_deployment_name, + usage=self.usage, + additional_agents=self.additional_agents, + emit_user_event=self.emit_user_event, + emit_team_event=self.emit_team_event, + **self.dimensions, + ) + except Exception as emit_exc: # pragma: no cover - belt + braces + logger.warning("TokenUsageScope emit failed: %s", emit_exc) + return None # do not suppress exceptions + + +def track_tokens( + emitter: TokenUsageEmitter, + *, + agent_name: str, + model_deployment_name: str, + dimension_args: Optional[Mapping[str, str]] = None, + additional_agents: Optional[Mapping[str, str]] = None, + emit_user_event: bool = False, + emit_team_event: bool = False, +): + """Decorator: wrap an async or sync function that returns an LLM result. + + ``dimension_args`` maps emitted-property-name -> callable-keyword-argument + name so per-call values (e.g. ``user_id``) are forwarded to the event. + + Example:: + + @track_tokens(emitter, + agent_name="chat", + model_deployment_name=settings.model, + dimension_args={"user_id": "user_id", + "session_id": "session_id"}) + async def run_chat(prompt, *, user_id, session_id): ... + """ + + dim_args = dict(dimension_args or {}) + + def _decorator(fn: Callable[..., Any]): + is_coro = _is_coroutine_function(fn) + + if is_coro: + @functools.wraps(fn) + async def _aw(*args, **kwargs) -> Any: + with _scope_for(kwargs) as scope: + result = await fn(*args, **kwargs) + scope.add(result) + return result + return _aw + + @functools.wraps(fn) + def _sw(*args, **kwargs) -> Any: + with _scope_for(kwargs) as scope: + result = fn(*args, **kwargs) + scope.add(result) + return result + return _sw + + def _scope_for(call_kwargs: Mapping[str, Any]) -> TokenUsageScope: + dimensions = { + prop: call_kwargs.get(kw) + for prop, kw in dim_args.items() + if call_kwargs.get(kw) is not None + } + return TokenUsageScope( + emitter, + agent_name=agent_name, + model_deployment_name=model_deployment_name, + additional_agents=additional_agents, + emit_user_event=emit_user_event, + emit_team_event=emit_team_event, + **dimensions, + ) + + return _decorator + + +def _is_coroutine_function(fn: Callable[..., Any]) -> bool: + return asyncio.iscoroutinefunction(fn) + + +__all__ = [ + "EVENT_SUMMARY", + "EVENT_AGENT", + "EVENT_MODEL", + "EVENT_USER", + "EVENT_TEAM", + "EVENT_SPEECH", + "TokenUsage", + "TokenUsageEmitter", + "TokenUsageScope", + "track_tokens", + "extract_usage", + "extract_usage_from_dict", + "extract_usage_from_stream_chunk", + "extract_realtime_usage", + "detect_invoked_tools", +] diff --git a/src/processor/src/utils/token_usage_tracker.py b/src/processor/src/utils/token_usage_tracker.py index 25e5e0b6..0799058a 100644 --- a/src/processor/src/utils/token_usage_tracker.py +++ b/src/processor/src/utils/token_usage_tracker.py @@ -8,20 +8,30 @@ - Per user/process - Per model deployment -Usage data is emitted to Application Insights as custom events and can be -persisted to Cosmos DB via the TelemetryManager. +ses the cross-accelerator ``llm_token_telemetry`` module for extraction +and emission, keeping this tracker as a thin orchestration-specific layer +that adds thread-safe aggregation and per-step tracking. """ from __future__ import annotations import logging import threading -from dataclasses import dataclass, field +from dataclasses import dataclass from typing import Any -from utils.event_utils import track_event_if_configured +from utils.llm_token_telemetry import ( + TokenUsage, + TokenUsageEmitter, + extract_usage, + extract_usage_from_dict, + extract_usage_from_stream_chunk, +) logger = logging.getLogger(__name__) +# Module-level emitter instance shared across the processor service. +_emitter = TokenUsageEmitter() + @dataclass class TokenUsageRecord: @@ -60,8 +70,8 @@ class TokenUsageTracker: """Thread-safe tracker that aggregates LLM token usage across multiple dimensions. Accumulates usage per agent, per step (team), per model, and overall per process. - Emits Application Insights custom events for each recorded interaction and - provides summary emission at process completion. + Emits Application Insights custom events via ``TokenUsageEmitter`` from the + cross-accelerator ``llm_token_telemetry`` module. """ def __init__(self, process_id: str, user_id: str = ""): @@ -95,8 +105,8 @@ def record( ) -> None: """Record a single LLM call's token usage. - Accumulates into all relevant dimensions and emits a per-call - Application Insights event. + Accumulates into all relevant dimensions and emits per-call + Application Insights events via ``TokenUsageEmitter``. """ if total_tokens <= 0 and input_tokens <= 0 and output_tokens <= 0: return @@ -132,23 +142,20 @@ def record( self._by_model[model_deployment_name] = AggregatedTokenUsage() self._by_model[model_deployment_name].add(record) - # Emit per-call event to Application Insights - try: - track_event_if_configured( - "LLM_Token_Usage", - { - "process_id": self.process_id, - "user_id": self.user_id, - "agent_name": agent_name, - "step_name": step_name, - "model_deployment_name": model_deployment_name, - "input_tokens": str(input_tokens), - "output_tokens": str(output_tokens), - "total_tokens": str(total_tokens), - }, - ) - except Exception: - logger.debug("Failed to emit per-call token usage event", exc_info=True) + # Emit per-call events via the cross-accelerator emitter + usage = TokenUsage( + input_tokens=input_tokens, + output_tokens=output_tokens, + total_tokens=total_tokens, + ) + _emitter.emit_all( + agent_name=agent_name or "unknown", + model_deployment_name=model_deployment_name or "unknown", + usage=usage, + process_id=self.process_id, + user_id=self.user_id, + step_name=step_name, + ) logger.info( "[TOKEN] Recorded: agent=%s step=%s model=%s input=%d output=%d total=%d | cumulative=%d", @@ -177,72 +184,75 @@ def emit_summary_events(self) -> None: """Emit summary-level Application Insights custom events. Call this at the end of a process/workflow to produce aggregated events - that are easy to query in KQL. + that are easy to query in KQL. Uses ``TokenUsageEmitter`` from + ``llm_token_telemetry`` for all event emission. """ summary = self.get_summary() try: + total = summary["total"] + total_usage = TokenUsage( + input_tokens=total["input_tokens"], + output_tokens=total["output_tokens"], + total_tokens=total["total_tokens"], + ) + # Overall summary - track_event_if_configured( - "LLM_Token_Usage_Summary", - { - "process_id": self.process_id, - "user_id": self.user_id, - "total_input_tokens": str(summary["total"]["input_tokens"]), - "total_output_tokens": str(summary["total"]["output_tokens"]), - "total_tokens": str(summary["total"]["total_tokens"]), - "total_calls": str(summary["total"]["call_count"]), - "agent_count": str(len(summary["by_agent"])), - "model_count": str(len(summary["by_model"])), - "step_count": str(len(summary["by_step"])), - }, + _emitter.emit_summary( + usage=total_usage, + agent_count=len(summary["by_agent"]), + model_count=len(summary["by_model"]), + process_id=self.process_id, + user_id=self.user_id, + total_calls=str(total["call_count"]), + step_count=str(len(summary["by_step"])), ) # Per-agent events - for agent_name, usage in summary["by_agent"].items(): + for agent_name, usage_dict in summary["by_agent"].items(): model = self._agent_model_map.get(agent_name, "") - track_event_if_configured( - "LLM_Agent_Token_Usage", - { - "process_id": self.process_id, - "user_id": self.user_id, - "agent_name": agent_name, - "model_deployment_name": model, - "input_tokens": str(usage["input_tokens"]), - "output_tokens": str(usage["output_tokens"]), - "total_tokens": str(usage["total_tokens"]), - "call_count": str(usage["call_count"]), - }, + agent_usage = TokenUsage( + input_tokens=usage_dict["input_tokens"], + output_tokens=usage_dict["output_tokens"], + total_tokens=usage_dict["total_tokens"], + ) + _emitter.emit_agent( + agent_name=agent_name, + model_deployment_name=model, + usage=agent_usage, + process_id=self.process_id, + user_id=self.user_id, + call_count=str(usage_dict["call_count"]), ) # Per-model events - for model_name, usage in summary["by_model"].items(): - track_event_if_configured( - "LLM_Model_Token_Usage", - { - "process_id": self.process_id, - "user_id": self.user_id, - "model_deployment_name": model_name, - "input_tokens": str(usage["input_tokens"]), - "output_tokens": str(usage["output_tokens"]), - "total_tokens": str(usage["total_tokens"]), - "call_count": str(usage["call_count"]), - }, + for model_name, usage_dict in summary["by_model"].items(): + model_usage = TokenUsage( + input_tokens=usage_dict["input_tokens"], + output_tokens=usage_dict["output_tokens"], + total_tokens=usage_dict["total_tokens"], + ) + _emitter.emit_model( + model_deployment_name=model_name, + usage=model_usage, + process_id=self.process_id, + user_id=self.user_id, + call_count=str(usage_dict["call_count"]), ) # Per-step (team) events - for step_name, usage in summary["by_step"].items(): - track_event_if_configured( - "LLM_Step_Token_Usage", - { - "process_id": self.process_id, - "user_id": self.user_id, - "step_name": step_name, - "input_tokens": str(usage["input_tokens"]), - "output_tokens": str(usage["output_tokens"]), - "total_tokens": str(usage["total_tokens"]), - "call_count": str(usage["call_count"]), - }, + for step_name, usage_dict in summary["by_step"].items(): + step_usage = TokenUsage( + input_tokens=usage_dict["input_tokens"], + output_tokens=usage_dict["output_tokens"], + total_tokens=usage_dict["total_tokens"], + ) + _emitter.emit_team( + team_name=step_name, + usage=step_usage, + process_id=self.process_id, + user_id=self.user_id, + call_count=str(usage_dict["call_count"]), ) logger.info( @@ -259,114 +269,29 @@ def emit_summary_events(self) -> None: def extract_usage_from_response(response: Any) -> TokenUsageRecord | None: """Extract token usage from an agent_framework or OpenAI SDK response object. - Handles multiple response shapes: - 1. response.usage (OpenAI SDK ChatCompletion) - 2. response.usage_details (agent_framework Content objects) - 3. response dict with usage keys - 4. AgentResponseUpdate with contents containing usage + Delegates to ``llm_token_telemetry.extract_usage()`` and converts the + result to a ``TokenUsageRecord`` for backward compatibility. """ - if response is None: + usage = extract_usage(response) + if usage is None: return None - - # 1. Direct .usage attribute (OpenAI ChatCompletion, Responses API) - usage = getattr(response, "usage", None) - if usage is not None: - record = _parse_usage_object(usage) - if record: - return record - - # 2. .usage_details or .details attribute - usage_details = getattr(response, "details", None) or getattr(response, "usage_details", None) - if usage_details is not None: - record = _parse_usage_object(usage_details) - if record: - return record - - # 3. raw_representation with usage - raw = getattr(response, "raw_representation", None) - if raw is not None: - raw_usage = getattr(raw, "usage", None) - if raw_usage is not None: - record = _parse_usage_object(raw_usage) - if record: - return record - if isinstance(raw, dict) and "usage" in raw: - record = _parse_usage_object(raw["usage"]) - if record: - return record - - # 4. contents list with usage items (AgentResponseUpdate) - contents = getattr(response, "contents", None) - if contents: - for item in contents: - # Try usage-typed content items first, then any item with details - ud = None - if getattr(item, "type", None) == "usage": - ud = getattr(item, "details", None) or getattr(item, "usage_details", None) - if ud is None: - ud = getattr(item, "details", None) or getattr(item, "usage_details", None) - if ud is not None: - record = _parse_usage_object(ud) - if record: - return record - # Dict content item - if isinstance(item, dict): - for key in ("details", "usage_details"): - if key in item: - record = _parse_usage_object(item[key]) - if record: - return record - if "input_token_count" in item or "total_token_count" in item: - record = _parse_usage_object(item) - if record: - return record - - # 5. additional_properties - addl = getattr(response, "additional_properties", None) - if isinstance(addl, dict) and "usage" in addl: - record = _parse_usage_object(addl["usage"]) - if record: - return record - - # 6. Dict response - if isinstance(response, dict): - if "usage" in response: - record = _parse_usage_object(response["usage"]) - if record: - return record - record = _parse_usage_object(response) - if record: - return record - - return None - - -def _get_field(obj: Any, *names: str) -> int: - """Read the first non-zero value from *obj* for the given field names. - - Works uniformly for dicts (via ``get``) and objects (via ``getattr``). - """ - getter = obj.get if isinstance(obj, dict) else lambda k, d=0: getattr(obj, k, d) - for name in names: - val = getter(name, 0) - if val: - return int(val) - return 0 + return TokenUsageRecord( + input_tokens=usage.input_tokens, + output_tokens=usage.output_tokens, + total_tokens=usage.total_tokens, + ) def _parse_usage_object(usage: Any) -> TokenUsageRecord | None: - """Parse a usage object (dict or object with attrs) into a TokenUsageRecord.""" - if usage is None: - return None - - inp = _get_field(usage, "input_token_count", "prompt_tokens", "input_tokens") - out = _get_field(usage, "output_token_count", "completion_tokens", "output_tokens") - tot = _get_field(usage, "total_token_count", "total_tokens") or (inp + out) + """Parse a usage object (dict or object with attrs) into a TokenUsageRecord. - if tot > 0 or inp > 0 or out > 0: - return TokenUsageRecord( - input_tokens=inp, - output_tokens=out, - total_tokens=tot if tot > 0 else inp + out, - ) - return None + Delegates to ``llm_token_telemetry.extract_usage_from_dict()``. + """ + result = extract_usage_from_dict(usage) + if result is None: + return None + return TokenUsageRecord( + input_tokens=result.input_tokens, + output_tokens=result.output_tokens, + total_tokens=result.total_tokens, + ) From 73ef8a080626c93d086209e1b1ab5b05273fe476 Mon Sep 17 00:00:00 2001 From: Priyanka-Microsoft Date: Mon, 1 Jun 2026 13:50:53 +0530 Subject: [PATCH 10/12] refactor: update llm_token_telemetry.py to latest cross-accelerator version and remove deployment/workbook changes - Updated llm_token_telemetry.py (processor + backend-api) to match Ajit's latest from customer-chatbot PR #236 (adds perf counters, emit timing, slow-emit warnings, batch overhead tracking) - Removed workbook/dashboard files (deploy-workbooks.ps1, KQL queries, workbook JSON files, tokenUsageWorkbook.bicep) - Reverted infra changes (main.bicep, main_custom.bicep, main.parameters.json) - Reverted Dockerfile changes (frontend, processor) - Removed test file (test_llm_token_telemetry.py) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- infra/dashboards/deploy-workbooks.ps1 | 86 --- infra/dashboards/token-usage-queries.kql | 113 ---- infra/dashboards/workbook-eks-content.json | 1 - infra/dashboards/workbook-gke-content.json | 1 - infra/main.bicep | 12 +- infra/main.parameters.json | 23 +- infra/main_custom.bicep | 12 +- infra/modules/tokenUsageWorkbook.bicep | 458 -------------- .../app/libs/logging/llm_token_telemetry.py | 156 ++++- src/frontend/Dockerfile | 1 - src/processor/Dockerfile | 3 +- .../src/tests/test_llm_token_telemetry.py | 572 ------------------ .../src/utils/llm_token_telemetry.py | 156 ++++- 13 files changed, 265 insertions(+), 1329 deletions(-) delete mode 100644 infra/dashboards/deploy-workbooks.ps1 delete mode 100644 infra/dashboards/token-usage-queries.kql delete mode 100644 infra/dashboards/workbook-eks-content.json delete mode 100644 infra/dashboards/workbook-gke-content.json delete mode 100644 infra/modules/tokenUsageWorkbook.bicep delete mode 100644 src/processor/src/tests/test_llm_token_telemetry.py diff --git a/infra/dashboards/deploy-workbooks.ps1 b/infra/dashboards/deploy-workbooks.ps1 deleted file mode 100644 index caa88198..00000000 --- a/infra/dashboards/deploy-workbooks.ps1 +++ /dev/null @@ -1,86 +0,0 @@ -# ============================================================= -# LLM Token Usage Workbook Deployment Script -# ============================================================= -# Usage: -# .\deploy-workbooks.ps1 -ResourceGroup -AppInsightsResourceId [-Location ] -# -# Example: -# .\deploy-workbooks.ps1 ` -# -ResourceGroup "rg-my-permanent-rg" ` -# -AppInsightsResourceId "/subscriptions//resourcegroups//providers/microsoft.insights/components/" ` -# -Location "australiaeast" -# ============================================================= - -param( - [Parameter(Mandatory=$true)] - [string]$ResourceGroup, - - [Parameter(Mandatory=$true)] - [string]$AppInsightsResourceId, - - [string]$Location = "australiaeast" -) - -$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path - -# Deploy GKE workbook -$gkeContent = Get-Content "$scriptDir\workbook-gke-content.json" -Raw -$gkeId = [guid]::NewGuid().ToString() - -$body = @{ - location = $Location - kind = "shared" - properties = @{ - displayName = "LLM Token Usage Dashboard - GKE" - serializedData = $gkeContent - version = "Notebook/1.0" - sourceId = $AppInsightsResourceId - category = "workbook" - } - tags = @{ - "hidden-title" = "LLM Token Usage Dashboard - GKE" - } -} | ConvertTo-Json -Depth 5 - -$bodyFile = [System.IO.Path]::GetTempFileName() -$body | Set-Content $bodyFile -Encoding UTF8 - -az rest --method PUT ` - --url "https://management.azure.com/subscriptions/$(az account show --query id -o tsv)/resourceGroups/$ResourceGroup/providers/microsoft.insights/workbooks/$($gkeId)?api-version=2022-04-01" ` - --body "@$bodyFile" ` - --headers "Content-Type=application/json" 2>&1 | Out-Null - -Write-Host "Deployed GKE workbook: $gkeId" -Remove-Item $bodyFile - -# Deploy EKS workbook -$eksContent = Get-Content "$scriptDir\workbook-eks-content.json" -Raw -$eksId = [guid]::NewGuid().ToString() - -$body = @{ - location = $Location - kind = "shared" - properties = @{ - displayName = "LLM Token Usage Dashboard - EKS" - serializedData = $eksContent - version = "Notebook/1.0" - sourceId = $AppInsightsResourceId - category = "workbook" - } - tags = @{ - "hidden-title" = "LLM Token Usage Dashboard - EKS" - } -} | ConvertTo-Json -Depth 5 - -$bodyFile = [System.IO.Path]::GetTempFileName() -$body | Set-Content $bodyFile -Encoding UTF8 - -az rest --method PUT ` - --url "https://management.azure.com/subscriptions/$(az account show --query id -o tsv)/resourceGroups/$ResourceGroup/providers/microsoft.insights/workbooks/$($eksId)?api-version=2022-04-01" ` - --body "@$bodyFile" ` - --headers "Content-Type=application/json" 2>&1 | Out-Null - -Write-Host "Deployed EKS workbook: $eksId" -Remove-Item $bodyFile - -Write-Host "`nDone! Both workbooks deployed to $ResourceGroup" diff --git a/infra/dashboards/token-usage-queries.kql b/infra/dashboards/token-usage-queries.kql deleted file mode 100644 index 38a0fc19..00000000 --- a/infra/dashboards/token-usage-queries.kql +++ /dev/null @@ -1,113 +0,0 @@ -// ============================================================================= -// LLM Token Usage Dashboard Queries for Application Insights -// ============================================================================= -// These KQL queries can be used in Azure Application Insights / Log Analytics -// to visualize token usage across agents, models, steps, and users. -// ============================================================================= - -// ---- 1. Overall Token Usage Summary (last 24h) ---- -customEvents -| where name == "LLM_Token_Usage_Summary" -| where timestamp > ago(24h) -| extend process_id = tostring(customDimensions.process_id), - total_input = toint(customDimensions.total_input_tokens), - total_output = toint(customDimensions.total_output_tokens), - total = toint(customDimensions.total_tokens), - call_count = toint(customDimensions.total_calls) -| project timestamp, process_id, total_input, total_output, total, call_count -| order by timestamp desc - -// ---- 2. Per-Agent Token Usage ---- -customEvents -| where name == "LLM_Agent_Token_Usage" -| where timestamp > ago(24h) -| extend agent_name = tostring(customDimensions.agent_name), - input_tokens = toint(customDimensions.input_tokens), - output_tokens = toint(customDimensions.output_tokens), - total_tokens = toint(customDimensions.total_tokens), - calls = toint(customDimensions.call_count), - process_id = tostring(customDimensions.process_id) -| summarize total_input = sum(input_tokens), - total_output = sum(output_tokens), - total = sum(total_tokens), - total_calls = sum(calls) - by agent_name -| order by total desc - -// ---- 3. Per-Model Token Usage ---- -customEvents -| where name == "LLM_Model_Token_Usage" -| where timestamp > ago(24h) -| extend model_name = tostring(customDimensions.model_deployment_name), - input_tokens = toint(customDimensions.input_tokens), - output_tokens = toint(customDimensions.output_tokens), - total_tokens = toint(customDimensions.total_tokens), - calls = toint(customDimensions.call_count), - process_id = tostring(customDimensions.process_id) -| summarize total_input = sum(input_tokens), - total_output = sum(output_tokens), - total = sum(total_tokens), - total_calls = sum(calls) - by model_name -| order by total desc - -// ---- 4. Per-Step (Team) Token Usage ---- -customEvents -| where name == "LLM_Step_Token_Usage" -| where timestamp > ago(24h) -| extend step_name = tostring(customDimensions.step_name), - input_tokens = toint(customDimensions.input_tokens), - output_tokens = toint(customDimensions.output_tokens), - total_tokens = toint(customDimensions.total_tokens), - calls = toint(customDimensions.call_count), - process_id = tostring(customDimensions.process_id) -| summarize total_input = sum(input_tokens), - total_output = sum(output_tokens), - total = sum(total_tokens), - total_calls = sum(calls) - by step_name -| order by total desc - -// ---- 5. Per-User Token Usage (requires user_id in process telemetry) ---- -customEvents -| where name == "LLM_Token_Usage_Summary" -| where timestamp > ago(24h) -| extend process_id = tostring(customDimensions.process_id), - total_tokens = toint(customDimensions.total_tokens), - user_id = tostring(customDimensions.user_id) -| summarize total = sum(total_tokens), runs = count() by user_id -| order by total desc - -// ---- 6. Individual LLM Call Log ---- -customEvents -| where name == "LLM_Token_Usage" -| where timestamp > ago(24h) -| extend agent_name = tostring(customDimensions.agent_name), - step_name = tostring(customDimensions.step_name), - model = tostring(customDimensions.model_deployment_name), - input_tokens = toint(customDimensions.input_tokens), - output_tokens = toint(customDimensions.output_tokens), - total_tokens = toint(customDimensions.total_tokens), - process_id = tostring(customDimensions.process_id) -| project timestamp, process_id, agent_name, step_name, model, input_tokens, output_tokens, total_tokens -| order by timestamp desc - -// ---- 7. Hourly Token Usage Trend ---- -customEvents -| where name == "LLM_Token_Usage" -| where timestamp > ago(7d) -| extend total_tokens = toint(customDimensions.total_tokens) -| summarize hourly_tokens = sum(total_tokens), calls = count() by bin(timestamp, 1h) -| order by timestamp asc -| render timechart - -// ---- 8. Estimated Cost (GPT-4o pricing: $2.50/1M input, $10/1M output) ---- -customEvents -| where name == "LLM_Token_Usage_Summary" -| where timestamp > ago(24h) -| extend process_id = tostring(customDimensions.process_id), - input_tokens = toint(customDimensions.total_input_tokens), - output_tokens = toint(customDimensions.total_output_tokens) -| extend estimated_cost_usd = (input_tokens / 1000000.0 * 2.50) + (output_tokens / 1000000.0 * 10.0) -| project timestamp, process_id, input_tokens, output_tokens, estimated_cost_usd -| order by estimated_cost_usd desc diff --git a/infra/dashboards/workbook-eks-content.json b/infra/dashboards/workbook-eks-content.json deleted file mode 100644 index 04433e99..00000000 --- a/infra/dashboards/workbook-eks-content.json +++ /dev/null @@ -1 +0,0 @@ -{"version":"Notebook/1.0","items":[{"type":1,"content":{"json":"# LLM Token Usage Dashboard\n\nThis workbook provides comprehensive visibility into LLM token consumption across agents, models, workflow steps, and users.\n\n---"},"name":"header"},{"type":9,"content":{"version":"KqlParameterItem/1.0","parameters":[{"id":"time-range-param","version":"KqlParameterItem/1.0","name":"TimeRange","type":4,"isRequired":true,"value":{"durationMs":1800000,"endTime":"2026-05-21T06:50:00.000Z"},"typeSettings":{"selectableValues":[{"durationMs":3600000},{"durationMs":14400000},{"durationMs":86400000},{"durationMs":259200000},{"durationMs":604800000},{"durationMs":2592000000}],"allowCustom":true},"label":"Time Range"}],"style":"pills","queryType":0,"resourceType":"microsoft.insights/components"},"name":"parameters"},{"type":1,"content":{"json":"## Overall Token Usage Summary"},"name":"summary-header"},{"type":3,"content":{"version":"KqlItem/1.0","query":"customEvents\n| where name == \"LLM_Token_Usage_Summary\"\n| where timestamp {TimeRange}\n| summarize \n total_input = sum(toint(customDimensions.total_input_tokens)),\n total_output = sum(toint(customDimensions.total_output_tokens)),\n total = sum(toint(customDimensions.total_tokens)),\n total_calls = sum(toint(customDimensions.total_calls)),\n processes = dcount(tostring(customDimensions.process_id))","size":4,"title":"Token Usage Totals","queryType":0,"resourceType":"microsoft.insights/components","visualization":"tiles","tileSettings":{"titleContent":{"columnMatch":"Column1","formatter":1},"leftContent":{"columnMatch":"total","formatter":12,"formatOptions":{"palette":"auto"},"numberFormat":{"unit":0,"options":{"style":"decimal","maximumFractionDigits":0}}},"showBorder":true}},"name":"summary-tiles"},{"type":3,"content":{"version":"KqlItem/1.0","query":"customEvents\n| where name == \"LLM_Token_Usage_Summary\"\n| where timestamp {TimeRange}\n| extend process_id = tostring(customDimensions.process_id),\n total_input = toint(customDimensions.total_input_tokens),\n total_output = toint(customDimensions.total_output_tokens),\n total = toint(customDimensions.total_tokens),\n call_count = toint(customDimensions.total_calls)\n| project timestamp, process_id, total_input, total_output, total, call_count\n| order by timestamp desc","size":0,"title":"Token Usage by Process","queryType":0,"resourceType":"microsoft.insights/components","visualization":"table","gridSettings":{"formatters":[{"columnMatch":"total","formatter":3,"formatOptions":{"palette":"blue"}}]}},"name":"summary-table"},{"type":1,"content":{"json":"## Per-Agent Token Usage"},"name":"agent-header"},{"type":3,"content":{"version":"KqlItem/1.0","query":"customEvents\n| where name == \"LLM_Agent_Token_Usage\"\n| where timestamp {TimeRange}\n| extend agent_name = tostring(customDimensions.agent_name),\n input_tokens = toint(customDimensions.input_tokens),\n output_tokens = toint(customDimensions.output_tokens),\n total_tokens = toint(customDimensions.total_tokens),\n calls = toint(customDimensions.call_count)\n| summarize total_input = sum(input_tokens),\n total_output = sum(output_tokens),\n total = sum(total_tokens),\n total_calls = sum(calls)\n by agent_name\n| order by total desc","size":0,"title":"Token Consumption by Agent","queryType":0,"resourceType":"microsoft.insights/components","visualization":"table","gridSettings":{"formatters":[{"columnMatch":"total","formatter":3,"formatOptions":{"palette":"blue"}}]}},"customWidth":"50","name":"agent-table"},{"type":3,"content":{"version":"KqlItem/1.0","query":"customEvents\n| where name == \"LLM_Agent_Token_Usage\"\n| where timestamp {TimeRange}\n| extend agent_name = tostring(customDimensions.agent_name),\n total_tokens = toint(customDimensions.total_tokens)\n| summarize total = sum(total_tokens) by agent_name\n| order by total desc","size":0,"title":"Token Distribution by Agent","queryType":0,"resourceType":"microsoft.insights/components","visualization":"piechart"},"customWidth":"50","name":"agent-chart"},{"type":1,"content":{"json":"## Per-Model Token Usage"},"name":"model-header"},{"type":3,"content":{"version":"KqlItem/1.0","query":"customEvents\n| where name == \"LLM_Model_Token_Usage\"\n| where timestamp {TimeRange}\n| extend model_name = tostring(customDimensions.model_deployment_name),\n input_tokens = toint(customDimensions.input_tokens),\n output_tokens = toint(customDimensions.output_tokens),\n total_tokens = toint(customDimensions.total_tokens),\n calls = toint(customDimensions.call_count)\n| summarize total_input = sum(input_tokens),\n total_output = sum(output_tokens),\n total = sum(total_tokens),\n total_calls = sum(calls)\n by model_name\n| order by total desc","size":0,"title":"Token Consumption by Model","queryType":0,"resourceType":"microsoft.insights/components","visualization":"table","gridSettings":{"formatters":[{"columnMatch":"total","formatter":3,"formatOptions":{"palette":"green"}}]}},"customWidth":"50","name":"model-table"},{"type":3,"content":{"version":"KqlItem/1.0","query":"customEvents\n| where name == \"LLM_Model_Token_Usage\"\n| where timestamp {TimeRange}\n| extend model_name = tostring(customDimensions.model_deployment_name),\n total_tokens = toint(customDimensions.total_tokens)\n| summarize total = sum(total_tokens) by model_name\n| order by total desc","size":0,"title":"Token Distribution by Model","queryType":0,"resourceType":"microsoft.insights/components","visualization":"piechart"},"customWidth":"50","name":"model-chart"},{"type":1,"content":{"json":"## Per-Step (Team) Token Usage"},"name":"step-header"},{"type":3,"content":{"version":"KqlItem/1.0","query":"customEvents\n| where name == \"LLM_Step_Token_Usage\"\n| where timestamp {TimeRange}\n| extend step_name = tostring(customDimensions.step_name),\n input_tokens = toint(customDimensions.input_tokens),\n output_tokens = toint(customDimensions.output_tokens),\n total_tokens = toint(customDimensions.total_tokens),\n calls = toint(customDimensions.call_count)\n| summarize total_input = sum(input_tokens),\n total_output = sum(output_tokens),\n total = sum(total_tokens),\n total_calls = sum(calls)\n by step_name\n| order by total desc","size":0,"title":"Token Consumption by Workflow Step","queryType":0,"resourceType":"microsoft.insights/components","visualization":"barchart","chartSettings":{"xAxis":"step_name","yAxis":"total","group":"step_name"}},"customWidth":"50","name":"step-chart"},{"type":3,"content":{"version":"KqlItem/1.0","query":"customEvents\n| where name == \"LLM_Step_Token_Usage\"\n| where timestamp {TimeRange}\n| extend step_name = tostring(customDimensions.step_name),\n input_tokens = toint(customDimensions.input_tokens),\n output_tokens = toint(customDimensions.output_tokens),\n total_tokens = toint(customDimensions.total_tokens),\n calls = toint(customDimensions.call_count)\n| summarize total_input = sum(input_tokens),\n total_output = sum(output_tokens),\n total = sum(total_tokens),\n total_calls = sum(calls)\n by step_name\n| order by total desc","size":0,"title":"Step Usage Details","queryType":0,"resourceType":"microsoft.insights/components","visualization":"table","gridSettings":{"formatters":[{"columnMatch":"total","formatter":3,"formatOptions":{"palette":"orange"}}]}},"customWidth":"50","name":"step-table"},{"type":1,"content":{"json":"## Per-User Token Usage"},"name":"user-header"},{"type":3,"content":{"version":"KqlItem/1.0","query":"customEvents\n| where name == \"LLM_Token_Usage_Summary\"\n| where timestamp {TimeRange}\n| extend process_id = tostring(customDimensions.process_id),\n total_tokens = toint(customDimensions.total_tokens),\n user_id = tostring(customDimensions.user_id)\n| summarize total = sum(total_tokens), runs = count() by user_id\n| order by total desc","size":0,"title":"Token Usage by User","queryType":0,"resourceType":"microsoft.insights/components","visualization":"table","gridSettings":{"formatters":[{"columnMatch":"total","formatter":3,"formatOptions":{"palette":"purple"}}]}},"name":"user-table"},{"type":1,"content":{"json":"## Token Usage Trends"},"name":"trend-header"},{"type":3,"content":{"version":"KqlItem/1.0","query":"customEvents\n| where name == \"LLM_Token_Usage\"\n| where timestamp {TimeRange}\n| extend total_tokens = toint(customDimensions.total_tokens)\n| summarize hourly_tokens = sum(total_tokens), calls = count() by bin(timestamp, 1h)\n| order by timestamp asc","size":0,"title":"Hourly Token Consumption","queryType":0,"resourceType":"microsoft.insights/components","visualization":"linechart","chartSettings":{"xAxis":"timestamp","yAxis":"hourly_tokens","showLegend":true}},"name":"trend-chart"},{"type":1,"content":{"json":"## Estimated Cost\n\n> Cost estimates use GPT-4o pricing: **$2.50 / 1M input tokens**, **$10.00 / 1M output tokens**. Adjust for your actual model pricing."},"name":"cost-header"},{"type":3,"content":{"version":"KqlItem/1.0","query":"customEvents\n| where name == \"LLM_Token_Usage_Summary\"\n| where timestamp {TimeRange}\n| extend process_id = tostring(customDimensions.process_id),\n input_tokens = toint(customDimensions.total_input_tokens),\n output_tokens = toint(customDimensions.total_output_tokens)\n| extend estimated_cost_usd = round((input_tokens / 1000000.0 * 2.50) + (output_tokens / 1000000.0 * 10.0), 4)\n| project timestamp, process_id, input_tokens, output_tokens, estimated_cost_usd\n| order by estimated_cost_usd desc","size":0,"title":"Estimated Cost per Process (USD)","queryType":0,"resourceType":"microsoft.insights/components","visualization":"table","gridSettings":{"formatters":[{"columnMatch":"estimated_cost_usd","formatter":3,"formatOptions":{"palette":"redBright"}}]}},"name":"cost-table"},{"type":1,"content":{"json":"## Individual LLM Call Log"},"name":"calls-header"},{"type":3,"content":{"version":"KqlItem/1.0","query":"customEvents\n| where name == \"LLM_Token_Usage\"\n| where timestamp {TimeRange}\n| extend agent_name = tostring(customDimensions.agent_name),\n step_name = tostring(customDimensions.step_name),\n model = tostring(customDimensions.model_deployment_name),\n input_tokens = toint(customDimensions.input_tokens),\n output_tokens = toint(customDimensions.output_tokens),\n total_tokens = toint(customDimensions.total_tokens),\n process_id = tostring(customDimensions.process_id)\n| project timestamp, process_id, agent_name, step_name, model, input_tokens, output_tokens, total_tokens\n| order by timestamp desc\n| take 200","size":0,"title":"Recent LLM Calls (last 200)","queryType":0,"resourceType":"microsoft.insights/components","visualization":"table","gridSettings":{"formatters":[{"columnMatch":"total_tokens","formatter":3,"formatOptions":{"palette":"blue"}}]}},"name":"calls-table"}],"isLocked":false,"fallbackResourceIds":["/subscriptions/1d5876cd-7603-407a-96d2-ae5ca9a9c5f3/resourcegroups/rg-pricmglogp33/providers/microsoft.insights/components/appi-pricmglogp33usmqm"]} diff --git a/infra/dashboards/workbook-gke-content.json b/infra/dashboards/workbook-gke-content.json deleted file mode 100644 index ad05834c..00000000 --- a/infra/dashboards/workbook-gke-content.json +++ /dev/null @@ -1 +0,0 @@ -{"version":"Notebook/1.0","items":[{"type":1,"content":{"json":"# LLM Token Usage Dashboard\n\nThis workbook provides comprehensive visibility into LLM token consumption across agents, models, workflow steps, and users.\n\n---"},"name":"header"},{"type":9,"content":{"version":"KqlParameterItem/1.0","parameters":[{"id":"time-range-param","version":"KqlParameterItem/1.0","name":"TimeRange","type":4,"isRequired":true,"value":{"durationMs":1500000,"endTime":"2026-05-21T06:08:00.000Z"},"typeSettings":{"selectableValues":[{"durationMs":3600000},{"durationMs":14400000},{"durationMs":86400000},{"durationMs":259200000},{"durationMs":604800000},{"durationMs":2592000000}],"allowCustom":true},"label":"Time Range"}],"style":"pills","queryType":0,"resourceType":"microsoft.insights/components"},"name":"parameters"},{"type":1,"content":{"json":"## Overall Token Usage Summary"},"name":"summary-header"},{"type":3,"content":{"version":"KqlItem/1.0","query":"customEvents\n| where name == \"LLM_Token_Usage_Summary\"\n| where timestamp {TimeRange}\n| summarize \n total_input = sum(toint(customDimensions.total_input_tokens)),\n total_output = sum(toint(customDimensions.total_output_tokens)),\n total = sum(toint(customDimensions.total_tokens)),\n total_calls = sum(toint(customDimensions.total_calls)),\n processes = dcount(tostring(customDimensions.process_id))","size":4,"title":"Token Usage Totals","queryType":0,"resourceType":"microsoft.insights/components","visualization":"tiles","tileSettings":{"titleContent":{"columnMatch":"Column1","formatter":1},"leftContent":{"columnMatch":"total","formatter":12,"formatOptions":{"palette":"auto"},"numberFormat":{"unit":0,"options":{"style":"decimal","maximumFractionDigits":0}}},"showBorder":true}},"name":"summary-tiles"},{"type":3,"content":{"version":"KqlItem/1.0","query":"customEvents\n| where name == \"LLM_Token_Usage_Summary\"\n| where timestamp {TimeRange}\n| extend process_id = tostring(customDimensions.process_id),\n total_input = toint(customDimensions.total_input_tokens),\n total_output = toint(customDimensions.total_output_tokens),\n total = toint(customDimensions.total_tokens),\n call_count = toint(customDimensions.total_calls)\n| project timestamp, process_id, total_input, total_output, total, call_count\n| order by timestamp desc","size":0,"title":"Token Usage by Process","queryType":0,"resourceType":"microsoft.insights/components","visualization":"table","gridSettings":{"formatters":[{"columnMatch":"total","formatter":3,"formatOptions":{"palette":"blue"}}]}},"name":"summary-table"},{"type":1,"content":{"json":"## Per-Agent Token Usage"},"name":"agent-header"},{"type":3,"content":{"version":"KqlItem/1.0","query":"customEvents\n| where name == \"LLM_Agent_Token_Usage\"\n| where timestamp {TimeRange}\n| extend agent_name = tostring(customDimensions.agent_name),\n input_tokens = toint(customDimensions.input_tokens),\n output_tokens = toint(customDimensions.output_tokens),\n total_tokens = toint(customDimensions.total_tokens),\n calls = toint(customDimensions.call_count)\n| summarize total_input = sum(input_tokens),\n total_output = sum(output_tokens),\n total = sum(total_tokens),\n total_calls = sum(calls)\n by agent_name\n| order by total desc","size":0,"title":"Token Consumption by Agent","queryType":0,"resourceType":"microsoft.insights/components","visualization":"table","gridSettings":{"formatters":[{"columnMatch":"total","formatter":3,"formatOptions":{"palette":"blue"}}]}},"customWidth":"50","name":"agent-table"},{"type":3,"content":{"version":"KqlItem/1.0","query":"customEvents\n| where name == \"LLM_Agent_Token_Usage\"\n| where timestamp {TimeRange}\n| extend agent_name = tostring(customDimensions.agent_name),\n total_tokens = toint(customDimensions.total_tokens)\n| summarize total = sum(total_tokens) by agent_name\n| order by total desc","size":0,"title":"Token Distribution by Agent","queryType":0,"resourceType":"microsoft.insights/components","visualization":"piechart"},"customWidth":"50","name":"agent-chart"},{"type":1,"content":{"json":"## Per-Model Token Usage"},"name":"model-header"},{"type":3,"content":{"version":"KqlItem/1.0","query":"customEvents\n| where name == \"LLM_Model_Token_Usage\"\n| where timestamp {TimeRange}\n| extend model_name = tostring(customDimensions.model_deployment_name),\n input_tokens = toint(customDimensions.input_tokens),\n output_tokens = toint(customDimensions.output_tokens),\n total_tokens = toint(customDimensions.total_tokens),\n calls = toint(customDimensions.call_count)\n| summarize total_input = sum(input_tokens),\n total_output = sum(output_tokens),\n total = sum(total_tokens),\n total_calls = sum(calls)\n by model_name\n| order by total desc","size":0,"title":"Token Consumption by Model","queryType":0,"resourceType":"microsoft.insights/components","visualization":"table","gridSettings":{"formatters":[{"columnMatch":"total","formatter":3,"formatOptions":{"palette":"green"}}]}},"customWidth":"50","name":"model-table"},{"type":3,"content":{"version":"KqlItem/1.0","query":"customEvents\n| where name == \"LLM_Model_Token_Usage\"\n| where timestamp {TimeRange}\n| extend model_name = tostring(customDimensions.model_deployment_name),\n total_tokens = toint(customDimensions.total_tokens)\n| summarize total = sum(total_tokens) by model_name\n| order by total desc","size":0,"title":"Token Distribution by Model","queryType":0,"resourceType":"microsoft.insights/components","visualization":"piechart"},"customWidth":"50","name":"model-chart"},{"type":1,"content":{"json":"## Per-Step (Team) Token Usage"},"name":"step-header"},{"type":3,"content":{"version":"KqlItem/1.0","query":"customEvents\n| where name == \"LLM_Step_Token_Usage\"\n| where timestamp {TimeRange}\n| extend step_name = tostring(customDimensions.step_name),\n input_tokens = toint(customDimensions.input_tokens),\n output_tokens = toint(customDimensions.output_tokens),\n total_tokens = toint(customDimensions.total_tokens),\n calls = toint(customDimensions.call_count)\n| summarize total_input = sum(input_tokens),\n total_output = sum(output_tokens),\n total = sum(total_tokens),\n total_calls = sum(calls)\n by step_name\n| order by total desc","size":0,"title":"Token Consumption by Workflow Step","queryType":0,"resourceType":"microsoft.insights/components","visualization":"barchart","chartSettings":{"xAxis":"step_name","yAxis":"total","group":"step_name"}},"customWidth":"50","name":"step-chart"},{"type":3,"content":{"version":"KqlItem/1.0","query":"customEvents\n| where name == \"LLM_Step_Token_Usage\"\n| where timestamp {TimeRange}\n| extend step_name = tostring(customDimensions.step_name),\n input_tokens = toint(customDimensions.input_tokens),\n output_tokens = toint(customDimensions.output_tokens),\n total_tokens = toint(customDimensions.total_tokens),\n calls = toint(customDimensions.call_count)\n| summarize total_input = sum(input_tokens),\n total_output = sum(output_tokens),\n total = sum(total_tokens),\n total_calls = sum(calls)\n by step_name\n| order by total desc","size":0,"title":"Step Usage Details","queryType":0,"resourceType":"microsoft.insights/components","visualization":"table","gridSettings":{"formatters":[{"columnMatch":"total","formatter":3,"formatOptions":{"palette":"orange"}}]}},"customWidth":"50","name":"step-table"},{"type":1,"content":{"json":"## Per-User Token Usage"},"name":"user-header"},{"type":3,"content":{"version":"KqlItem/1.0","query":"customEvents\n| where name == \"LLM_Token_Usage_Summary\"\n| where timestamp {TimeRange}\n| extend process_id = tostring(customDimensions.process_id),\n total_tokens = toint(customDimensions.total_tokens),\n user_id = tostring(customDimensions.user_id)\n| summarize total = sum(total_tokens), runs = count() by user_id\n| order by total desc","size":0,"title":"Token Usage by User","queryType":0,"resourceType":"microsoft.insights/components","visualization":"table","gridSettings":{"formatters":[{"columnMatch":"total","formatter":3,"formatOptions":{"palette":"purple"}}]}},"name":"user-table"},{"type":1,"content":{"json":"## Token Usage Trends"},"name":"trend-header"},{"type":3,"content":{"version":"KqlItem/1.0","query":"customEvents\n| where name == \"LLM_Token_Usage\"\n| where timestamp {TimeRange}\n| extend total_tokens = toint(customDimensions.total_tokens)\n| summarize hourly_tokens = sum(total_tokens), calls = count() by bin(timestamp, 1h)\n| order by timestamp asc","size":0,"title":"Hourly Token Consumption","queryType":0,"resourceType":"microsoft.insights/components","visualization":"linechart","chartSettings":{"xAxis":"timestamp","yAxis":"hourly_tokens","showLegend":true}},"name":"trend-chart"},{"type":1,"content":{"json":"## Estimated Cost\n\n> Cost estimates use GPT-4o pricing: **$2.50 / 1M input tokens**, **$10.00 / 1M output tokens**. Adjust for your actual model pricing."},"name":"cost-header"},{"type":3,"content":{"version":"KqlItem/1.0","query":"customEvents\n| where name == \"LLM_Token_Usage_Summary\"\n| where timestamp {TimeRange}\n| extend process_id = tostring(customDimensions.process_id),\n input_tokens = toint(customDimensions.total_input_tokens),\n output_tokens = toint(customDimensions.total_output_tokens)\n| extend estimated_cost_usd = round((input_tokens / 1000000.0 * 2.50) + (output_tokens / 1000000.0 * 10.0), 4)\n| project timestamp, process_id, input_tokens, output_tokens, estimated_cost_usd\n| order by estimated_cost_usd desc","size":0,"title":"Estimated Cost per Process (USD)","queryType":0,"resourceType":"microsoft.insights/components","visualization":"table","gridSettings":{"formatters":[{"columnMatch":"estimated_cost_usd","formatter":3,"formatOptions":{"palette":"redBright"}}]}},"name":"cost-table"},{"type":1,"content":{"json":"## Individual LLM Call Log"},"name":"calls-header"},{"type":3,"content":{"version":"KqlItem/1.0","query":"customEvents\n| where name == \"LLM_Token_Usage\"\n| where timestamp {TimeRange}\n| extend agent_name = tostring(customDimensions.agent_name),\n step_name = tostring(customDimensions.step_name),\n model = tostring(customDimensions.model_deployment_name),\n input_tokens = toint(customDimensions.input_tokens),\n output_tokens = toint(customDimensions.output_tokens),\n total_tokens = toint(customDimensions.total_tokens),\n process_id = tostring(customDimensions.process_id)\n| project timestamp, process_id, agent_name, step_name, model, input_tokens, output_tokens, total_tokens\n| order by timestamp desc\n| take 200","size":0,"title":"Recent LLM Calls (last 200)","queryType":0,"resourceType":"microsoft.insights/components","visualization":"table","gridSettings":{"formatters":[{"columnMatch":"total_tokens","formatter":3,"formatOptions":{"palette":"blue"}}]}},"name":"calls-table"}],"isLocked":false,"fallbackResourceIds":["/subscriptions/1d5876cd-7603-407a-96d2-ae5ca9a9c5f3/resourcegroups/rg-pricmglogp33/providers/microsoft.insights/components/appi-pricmglogp33usmqm"]} diff --git a/infra/main.bicep b/infra/main.bicep index 3306c57a..904fee03 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -91,7 +91,7 @@ param enableTelemetry bool = true param enablePrivateNetworking bool = false @description('Optional. Enable monitoring applicable resources, aligned with the Well Architected Framework recommendations. This setting enables Application Insights and Log Analytics and configures all the resources applicable resources to send logs. Defaults to false.') -param enableMonitoring bool = true +param enableMonitoring bool = false @description('Optional. Enable scalability for applicable resources, aligned with the Well Architected Framework recommendations. Defaults to false.') param enableScalability bool = false @@ -310,16 +310,6 @@ module applicationInsights 'br/public:avm/res/insights/component:0.6.0' = if (en } } -// ========== LLM Token Usage Workbook ========== // -module tokenUsageWorkbook './modules/tokenUsageWorkbook.bicep' = if (enableMonitoring) { - name: take('module.token-usage-workbook.${solutionSuffix}', 64) - params: { - location: solutionLocation - applicationInsightsResourceId: applicationInsights!.outputs.resourceId - tags: allTags - } -} - // ========== Virtual Network ========== // module virtualNetwork './modules/virtualNetwork.bicep' = if (enablePrivateNetworking) { name: take('module.virtual-network.${solutionSuffix}', 64) diff --git a/infra/main.parameters.json b/infra/main.parameters.json index 6df293a4..b4a1a7cc 100644 --- a/infra/main.parameters.json +++ b/infra/main.parameters.json @@ -26,17 +26,11 @@ "gptDeploymentCapacity": { "value": "${AZURE_ENV_GPT_MODEL_CAPACITY}" }, - "enableTelemetry": { - "value": true - }, - "enableMonitoring": { - "value": true - }, - "enablePrivateNetworking": { - "value": false + "existingLogAnalyticsWorkspaceId": { + "value": "${AZURE_ENV_EXISTING_LOG_ANALYTICS_WORKSPACE_RID}" }, - "enableScalability": { - "value": false + "existingFoundryProjectResourceId": { + "value": "${AZURE_EXISTING_AIPROJECT_RESOURCE_ID}" }, "vmAdminUsername": { "value": "${AZURE_ENV_VM_ADMIN_USERNAME}" @@ -44,17 +38,8 @@ "vmAdminPassword": { "value": "${AZURE_ENV_VM_ADMIN_PASSWORD}" }, - "existingLogAnalyticsWorkspaceId": { - "value": "${AZURE_ENV_EXISTING_LOG_ANALYTICS_WORKSPACE_RID}" - }, - "existingFoundryProjectResourceId": { - "value": "${AZURE_EXISTING_AIPROJECT_RESOURCE_ID}" - }, "imageTag": { "value": "${AZURE_ENV_IMAGE_TAG}" - }, - "vmSize": { - "value": "${AZURE_ENV_VM_SIZE}" } } } diff --git a/infra/main_custom.bicep b/infra/main_custom.bicep index 9e106e59..f93b93ab 100644 --- a/infra/main_custom.bicep +++ b/infra/main_custom.bicep @@ -84,7 +84,7 @@ param enableTelemetry bool = true param enablePrivateNetworking bool = false @description('Optional. Enable monitoring applicable resources, aligned with the Well Architected Framework recommendations. This setting enables Application Insights and Log Analytics and configures all the resources applicable resources to send logs. Defaults to false.') -param enableMonitoring bool = true +param enableMonitoring bool = false @description('Optional. Enable scalability for applicable resources, aligned with the Well Architected Framework recommendations. Defaults to false.') param enableScalability bool = false @@ -288,16 +288,6 @@ module applicationInsights 'br/public:avm/res/insights/component:0.6.0' = if (en } } -// ========== LLM Token Usage Workbook ========== // -module tokenUsageWorkbook './modules/tokenUsageWorkbook.bicep' = if (enableMonitoring) { - name: take('module.token-usage-workbook.${solutionSuffix}', 64) - params: { - location: solutionLocation - applicationInsightsResourceId: applicationInsights!.outputs.resourceId - tags: allTags - } -} - // ========== Virtual Network ========== // module virtualNetwork './modules/virtualNetwork.bicep' = if (enablePrivateNetworking) { name: take('module.virtual-network.${solutionSuffix}', 64) diff --git a/infra/modules/tokenUsageWorkbook.bicep b/infra/modules/tokenUsageWorkbook.bicep deleted file mode 100644 index 6531bdda..00000000 --- a/infra/modules/tokenUsageWorkbook.bicep +++ /dev/null @@ -1,458 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -@description('Required. The location for the workbook resource.') -param location string - -@description('Required. The resource ID of the Application Insights instance to query.') -param applicationInsightsResourceId string - -@description('Optional. Tags to apply to the workbook resource.') -param tags object = {} - -@description('Optional. Display name for the workbook.') -param workbookDisplayName string = 'LLM Token Usage Dashboard' - -// Generate a deterministic GUID for the workbook based on resource group and name -var workbookId = guid(resourceGroup().id, 'token-usage-workbook') - -var workbookContent = { - version: 'Notebook/1.0' - items: [ - { - type: 1 - content: { - json: '# LLM Token Usage Dashboard\n\nThis workbook provides comprehensive visibility into LLM token consumption across agents, models, workflow steps, and users.\n\n---' - } - name: 'header' - } - { - type: 9 - content: { - version: 'KqlParameterItem/1.0' - parameters: [ - { - id: 'time-range-param' - version: 'KqlParameterItem/1.0' - name: 'TimeRange' - type: 4 - isRequired: true - value: { - durationMs: 86400000 - } - typeSettings: { - selectableValues: [ - { durationMs: 3600000 } - { durationMs: 14400000 } - { durationMs: 86400000 } - { durationMs: 259200000 } - { durationMs: 604800000 } - { durationMs: 2592000000 } - ] - allowCustom: true - } - label: 'Time Range' - } - ] - style: 'pills' - queryType: 0 - resourceType: 'microsoft.insights/components' - } - name: 'parameters' - } - // ===== Row 1: Summary Tiles ===== - { - type: 1 - content: { - json: '## Overall Token Usage Summary' - } - name: 'summary-header' - } - { - type: 3 - content: { - version: 'KqlItem/1.0' - query: 'customEvents\n| where name == "LLM_Token_Usage_Summary"\n| where timestamp {TimeRange}\n| summarize \n total_input = sum(toint(customDimensions.total_input_tokens)),\n total_output = sum(toint(customDimensions.total_output_tokens)),\n total = sum(toint(customDimensions.total_tokens)),\n total_calls = sum(toint(customDimensions.total_calls)),\n processes = dcount(tostring(customDimensions.process_id))' - size: 4 - title: 'Token Usage Totals' - queryType: 0 - resourceType: 'microsoft.insights/components' - crossComponentResources: [ - applicationInsightsResourceId - ] - visualization: 'tiles' - tileSettings: { - titleContent: { - columnMatch: 'Column1' - formatter: 1 - } - leftContent: { - columnMatch: 'total' - formatter: 12 - formatOptions: { - palette: 'auto' - } - numberFormat: { - unit: 0 - options: { - style: 'decimal' - maximumFractionDigits: 0 - } - } - } - showBorder: true - } - } - name: 'summary-tiles' - } - { - type: 3 - content: { - version: 'KqlItem/1.0' - query: 'customEvents\n| where name == "LLM_Token_Usage_Summary"\n| where timestamp {TimeRange}\n| extend process_id = tostring(customDimensions.process_id),\n total_input = toint(customDimensions.total_input_tokens),\n total_output = toint(customDimensions.total_output_tokens),\n total = toint(customDimensions.total_tokens),\n call_count = toint(customDimensions.total_calls)\n| project timestamp, process_id, total_input, total_output, total, call_count\n| order by timestamp desc' - size: 0 - title: 'Token Usage by Process' - queryType: 0 - resourceType: 'microsoft.insights/components' - crossComponentResources: [ - applicationInsightsResourceId - ] - visualization: 'table' - gridSettings: { - formatters: [ - { - columnMatch: 'total' - formatter: 3 - formatOptions: { - palette: 'blue' - } - } - ] - } - } - name: 'summary-table' - } - // ===== Row 2: Per-Agent Token Usage ===== - { - type: 1 - content: { - json: '## Per-Agent Token Usage' - } - name: 'agent-header' - } - { - type: 3 - content: { - version: 'KqlItem/1.0' - query: 'customEvents\n| where name == "LLM_Agent_Token_Usage"\n| where timestamp {TimeRange}\n| extend agent_name = tostring(customDimensions.agent_name),\n input_tokens = toint(customDimensions.input_tokens),\n output_tokens = toint(customDimensions.output_tokens),\n total_tokens = toint(customDimensions.total_tokens),\n calls = toint(customDimensions.call_count)\n| summarize total_input = sum(input_tokens),\n total_output = sum(output_tokens),\n total = sum(total_tokens),\n total_calls = sum(calls)\n by agent_name\n| order by total desc' - size: 0 - title: 'Token Consumption by Agent' - queryType: 0 - resourceType: 'microsoft.insights/components' - crossComponentResources: [ - applicationInsightsResourceId - ] - visualization: 'table' - gridSettings: { - formatters: [ - { - columnMatch: 'total' - formatter: 3 - formatOptions: { - palette: 'blue' - } - } - ] - } - } - customWidth: '50' - name: 'agent-table' - } - { - type: 3 - content: { - version: 'KqlItem/1.0' - query: 'customEvents\n| where name == "LLM_Agent_Token_Usage"\n| where timestamp {TimeRange}\n| extend agent_name = tostring(customDimensions.agent_name),\n total_tokens = toint(customDimensions.total_tokens)\n| summarize total = sum(total_tokens) by agent_name\n| order by total desc' - size: 0 - title: 'Token Distribution by Agent' - queryType: 0 - resourceType: 'microsoft.insights/components' - crossComponentResources: [ - applicationInsightsResourceId - ] - visualization: 'piechart' - } - customWidth: '50' - name: 'agent-chart' - } - // ===== Row 3: Per-Model Token Usage ===== - { - type: 1 - content: { - json: '## Per-Model Token Usage' - } - name: 'model-header' - } - { - type: 3 - content: { - version: 'KqlItem/1.0' - query: 'customEvents\n| where name == "LLM_Model_Token_Usage"\n| where timestamp {TimeRange}\n| extend model_name = tostring(customDimensions.model_deployment_name),\n input_tokens = toint(customDimensions.input_tokens),\n output_tokens = toint(customDimensions.output_tokens),\n total_tokens = toint(customDimensions.total_tokens),\n calls = toint(customDimensions.call_count)\n| summarize total_input = sum(input_tokens),\n total_output = sum(output_tokens),\n total = sum(total_tokens),\n total_calls = sum(calls)\n by model_name\n| order by total desc' - size: 0 - title: 'Token Consumption by Model' - queryType: 0 - resourceType: 'microsoft.insights/components' - crossComponentResources: [ - applicationInsightsResourceId - ] - visualization: 'table' - gridSettings: { - formatters: [ - { - columnMatch: 'total' - formatter: 3 - formatOptions: { - palette: 'green' - } - } - ] - } - } - customWidth: '50' - name: 'model-table' - } - { - type: 3 - content: { - version: 'KqlItem/1.0' - query: 'customEvents\n| where name == "LLM_Model_Token_Usage"\n| where timestamp {TimeRange}\n| extend model_name = tostring(customDimensions.model_deployment_name),\n total_tokens = toint(customDimensions.total_tokens)\n| summarize total = sum(total_tokens) by model_name\n| order by total desc' - size: 0 - title: 'Token Distribution by Model' - queryType: 0 - resourceType: 'microsoft.insights/components' - crossComponentResources: [ - applicationInsightsResourceId - ] - visualization: 'piechart' - } - customWidth: '50' - name: 'model-chart' - } - // ===== Row 4: Per-Step (Team) Token Usage ===== - { - type: 1 - content: { - json: '## Per-Step (Team) Token Usage' - } - name: 'step-header' - } - { - type: 3 - content: { - version: 'KqlItem/1.0' - query: 'customEvents\n| where name == "LLM_Step_Token_Usage"\n| where timestamp {TimeRange}\n| extend step_name = tostring(customDimensions.step_name),\n input_tokens = toint(customDimensions.input_tokens),\n output_tokens = toint(customDimensions.output_tokens),\n total_tokens = toint(customDimensions.total_tokens),\n calls = toint(customDimensions.call_count)\n| summarize total_input = sum(input_tokens),\n total_output = sum(output_tokens),\n total = sum(total_tokens),\n total_calls = sum(calls)\n by step_name\n| order by total desc' - size: 0 - title: 'Token Consumption by Workflow Step' - queryType: 0 - resourceType: 'microsoft.insights/components' - crossComponentResources: [ - applicationInsightsResourceId - ] - visualization: 'barchart' - chartSettings: { - xAxis: 'step_name' - yAxis: 'total' - group: 'step_name' - } - } - customWidth: '50' - name: 'step-chart' - } - { - type: 3 - content: { - version: 'KqlItem/1.0' - query: 'customEvents\n| where name == "LLM_Step_Token_Usage"\n| where timestamp {TimeRange}\n| extend step_name = tostring(customDimensions.step_name),\n input_tokens = toint(customDimensions.input_tokens),\n output_tokens = toint(customDimensions.output_tokens),\n total_tokens = toint(customDimensions.total_tokens),\n calls = toint(customDimensions.call_count)\n| summarize total_input = sum(input_tokens),\n total_output = sum(output_tokens),\n total = sum(total_tokens),\n total_calls = sum(calls)\n by step_name\n| order by total desc' - size: 0 - title: 'Step Usage Details' - queryType: 0 - resourceType: 'microsoft.insights/components' - crossComponentResources: [ - applicationInsightsResourceId - ] - visualization: 'table' - gridSettings: { - formatters: [ - { - columnMatch: 'total' - formatter: 3 - formatOptions: { - palette: 'orange' - } - } - ] - } - } - customWidth: '50' - name: 'step-table' - } - // ===== Row 5: Per-User Token Usage ===== - { - type: 1 - content: { - json: '## Per-User Token Usage' - } - name: 'user-header' - } - { - type: 3 - content: { - version: 'KqlItem/1.0' - query: 'customEvents\n| where name == "LLM_Token_Usage_Summary"\n| where timestamp {TimeRange}\n| extend process_id = tostring(customDimensions.process_id),\n total_tokens = toint(customDimensions.total_tokens),\n user_id = tostring(customDimensions.user_id)\n| summarize total = sum(total_tokens), runs = count() by user_id\n| order by total desc' - size: 0 - title: 'Token Usage by User' - queryType: 0 - resourceType: 'microsoft.insights/components' - crossComponentResources: [ - applicationInsightsResourceId - ] - visualization: 'table' - gridSettings: { - formatters: [ - { - columnMatch: 'total' - formatter: 3 - formatOptions: { - palette: 'purple' - } - } - ] - } - } - name: 'user-table' - } - // ===== Row 6: Hourly Token Usage Trend ===== - { - type: 1 - content: { - json: '## Token Usage Trends' - } - name: 'trend-header' - } - { - type: 3 - content: { - version: 'KqlItem/1.0' - query: 'customEvents\n| where name == "LLM_Token_Usage"\n| where timestamp {TimeRange}\n| extend total_tokens = toint(customDimensions.total_tokens)\n| summarize hourly_tokens = sum(total_tokens), calls = count() by bin(timestamp, 1h)\n| order by timestamp asc' - size: 0 - title: 'Hourly Token Consumption' - queryType: 0 - resourceType: 'microsoft.insights/components' - crossComponentResources: [ - applicationInsightsResourceId - ] - visualization: 'linechart' - chartSettings: { - xAxis: 'timestamp' - yAxis: 'hourly_tokens' - showLegend: true - } - } - name: 'trend-chart' - } - // ===== Row 7: Estimated Cost ===== - { - type: 1 - content: { - json: '## Estimated Cost\n\n> Cost estimates use GPT-4o pricing: **$2.50 / 1M input tokens**, **$10.00 / 1M output tokens**. Adjust for your actual model pricing.' - } - name: 'cost-header' - } - { - type: 3 - content: { - version: 'KqlItem/1.0' - query: 'customEvents\n| where name == "LLM_Token_Usage_Summary"\n| where timestamp {TimeRange}\n| extend process_id = tostring(customDimensions.process_id),\n input_tokens = toint(customDimensions.total_input_tokens),\n output_tokens = toint(customDimensions.total_output_tokens)\n| extend estimated_cost_usd = round((input_tokens / 1000000.0 * 2.50) + (output_tokens / 1000000.0 * 10.0), 4)\n| project timestamp, process_id, input_tokens, output_tokens, estimated_cost_usd\n| order by estimated_cost_usd desc' - size: 0 - title: 'Estimated Cost per Process (USD)' - queryType: 0 - resourceType: 'microsoft.insights/components' - crossComponentResources: [ - applicationInsightsResourceId - ] - visualization: 'table' - gridSettings: { - formatters: [ - { - columnMatch: 'estimated_cost_usd' - formatter: 3 - formatOptions: { - palette: 'redBright' - } - } - ] - } - } - name: 'cost-table' - } - // ===== Row 8: Individual LLM Call Log ===== - { - type: 1 - content: { - json: '## Individual LLM Call Log' - } - name: 'calls-header' - } - { - type: 3 - content: { - version: 'KqlItem/1.0' - query: 'customEvents\n| where name == "LLM_Token_Usage"\n| where timestamp {TimeRange}\n| extend agent_name = tostring(customDimensions.agent_name),\n step_name = tostring(customDimensions.step_name),\n model = tostring(customDimensions.model_deployment_name),\n input_tokens = toint(customDimensions.input_tokens),\n output_tokens = toint(customDimensions.output_tokens),\n total_tokens = toint(customDimensions.total_tokens),\n process_id = tostring(customDimensions.process_id)\n| project timestamp, process_id, agent_name, step_name, model, input_tokens, output_tokens, total_tokens\n| order by timestamp desc\n| take 200' - size: 0 - title: 'Recent LLM Calls (last 200)' - queryType: 0 - resourceType: 'microsoft.insights/components' - crossComponentResources: [ - applicationInsightsResourceId - ] - visualization: 'table' - gridSettings: { - formatters: [ - { - columnMatch: 'total_tokens' - formatter: 3 - formatOptions: { - palette: 'blue' - } - } - ] - } - } - name: 'calls-table' - } - ] - isLocked: false - fallbackResourceIds: [ - applicationInsightsResourceId - ] -} - -resource tokenUsageWorkbook 'Microsoft.Insights/workbooks@2023-06-01' = { - name: workbookId - location: location - tags: tags - kind: 'shared' - properties: { - displayName: workbookDisplayName - category: 'workbook' - version: '1.0' - serializedData: string(workbookContent) - sourceId: applicationInsightsResourceId - } -} - -@description('The resource ID of the created workbook.') -output resourceId string = tokenUsageWorkbook.id - -@description('The name of the created workbook.') -output name string = tokenUsageWorkbook.name diff --git a/src/backend-api/src/app/libs/logging/llm_token_telemetry.py b/src/backend-api/src/app/libs/logging/llm_token_telemetry.py index b3035fc8..91f670c5 100644 --- a/src/backend-api/src/app/libs/logging/llm_token_telemetry.py +++ b/src/backend-api/src/app/libs/logging/llm_token_telemetry.py @@ -53,6 +53,7 @@ import logging import os import random +import time from contextlib import AbstractContextManager from dataclasses import dataclass, field from typing import Any, Callable, Iterable, Mapping, Optional @@ -472,6 +473,16 @@ def __init__( k: ("" if v is None else str(v)) for k, v in raw_static.items() } + # Performance counters. ``perf_*`` accumulate wall-clock nanoseconds + # spent inside ``emit()`` so callers can verify telemetry overhead is + # negligible. ``perf_slow_emit_threshold_ms`` is the soft threshold + # above which a WARNING is logged for an individual emit (default + # 50 ms -- emits should normally take well under 1 ms). + self._perf_total_ns: int = 0 + self._perf_emit_count: int = 0 + self._perf_max_ns: int = 0 + self.perf_slow_emit_threshold_ms: float = 50.0 + # -- public surface --------------------------------------------------- @property def enabled(self) -> bool: @@ -539,28 +550,74 @@ def emit(self, event_name: str, **dimensions: Any) -> None: Non-string values are stringified. ``None`` values are dropped. Any ``user_id`` value is passed through the configured hasher. - Never raises. + Never raises. Wall-clock duration is recorded for performance audit + (see :meth:`perf_stats`). """ - props = dict(self._static) # cheap shallow copy of pre-stringified dims - for k, v in dimensions.items(): - if v is None: - continue - if k == "user_id": - v = self._apply_user_id_hash(v) - if v is None or v == "": + start_ns = time.perf_counter_ns() + try: + props = dict(self._static) # cheap shallow copy of pre-stringified dims + for k, v in dimensions.items(): + if v is None: continue - props[k] = v if isinstance(v, str) else str(v) + if k == "user_id": + v = self._apply_user_id_hash(v) + if v is None or v == "": + continue + props[k] = v if isinstance(v, str) else str(v) + + if not self.enabled: + self._log.debug( + "App Insights not configured -- skipping event %s (%s)", + event_name, props, + ) + return + try: + self._sink(event_name, props) # type: ignore[misc] + except Exception as exc: # never break the caller + self._log.warning("track_event(%s) failed: %s", event_name, exc) + finally: + elapsed_ns = time.perf_counter_ns() - start_ns + self._perf_total_ns += elapsed_ns + self._perf_emit_count += 1 + if elapsed_ns > self._perf_max_ns: + self._perf_max_ns = elapsed_ns + elapsed_ms = elapsed_ns / 1_000_000.0 + if elapsed_ms > self.perf_slow_emit_threshold_ms: + self._log.warning( + "Token telemetry emit slow: event=%s duration_ms=%.3f", + event_name, elapsed_ms, + ) + else: + self._log.debug( + "Token telemetry emit: event=%s duration_ms=%.3f", + event_name, elapsed_ms, + ) + + # -- performance audit ------------------------------------------------ + def perf_stats(self) -> dict[str, float]: + """Return cumulative telemetry-overhead stats since process start + (or since :meth:`reset_perf_stats`). + + Keys: + ``emit_count`` -- number of events emitted + ``total_ms`` -- total wall-clock time spent inside ``emit`` + ``avg_ms`` -- mean per-event duration + ``max_ms`` -- slowest single emit observed + """ + count = self._perf_emit_count + total_ms = self._perf_total_ns / 1_000_000.0 + return { + "emit_count": float(count), + "total_ms": total_ms, + "avg_ms": (total_ms / count) if count else 0.0, + "max_ms": self._perf_max_ns / 1_000_000.0, + } - if not self.enabled: - self._log.debug( - "App Insights not configured -- skipping event %s (%s)", - event_name, props, - ) - return - try: - self._sink(event_name, props) # type: ignore[misc] - except Exception as exc: # never break the caller - self._log.warning("track_event(%s) failed: %s", event_name, exc) + def reset_perf_stats(self) -> None: + """Zero the perf counters (useful for tests and load-tests).""" + self._perf_total_ns = 0 + self._perf_emit_count = 0 + self._perf_max_ns = 0 # -- typed convenience emitters -------------------------------------- def emit_agent( @@ -713,14 +770,12 @@ def emit_all( agents.update({k: v for k, v in additional_agents.items() if k}) models = {m for m in agents.values() if m} - self.emit_summary( - usage=usage, - agent_count=len(agents), - model_count=len(models) or 1, - primary_model=model_deployment_name, - additional_agents=additional_agents, - **dimensions, - ) + # Wall-clock timing of the whole emit_all path so callers (or tests) + # can verify the telemetry path stays cheap relative to the LLM call + # it instruments. + batch_start_ns = time.perf_counter_ns() + + # Defer summary until last so we can stamp the batch overhead on it. self.emit_agent( agent_name=agent_name, model_deployment_name=model_deployment_name, @@ -748,6 +803,17 @@ def emit_all( model_deployment_name=model_deployment_name, ) + batch_overhead_ms = (time.perf_counter_ns() - batch_start_ns) / 1_000_000.0 + self.emit_summary( + usage=usage, + agent_count=len(agents), + model_count=len(models) or 1, + primary_model=model_deployment_name, + additional_agents=additional_agents, + telemetry_overhead_ms=f"{batch_overhead_ms:.3f}", + **dimensions, + ) + self._log.info( "[TOKEN USAGE] agent=%s model=%s input=%d output=%d total=%d %s", agent_name, @@ -804,6 +870,12 @@ def __init__( self.emit_team_event = emit_team_event self.dimensions = dict(dimensions) self.usage = TokenUsage() + # Wall-clock nanoseconds spent inside extraction (``add*``) and the + # final ``__exit__`` emit, respectively. Surfaced for callers that + # want to verify the helper doesn't add measurable latency. Available + # as ``scope.extract_ms`` / ``scope.emit_ms`` after the scope closes. + self._extract_ns: int = 0 + self._emit_ns: int = 0 # -- accumulation ----------------------------------------------------- def add(self, source: Any) -> Optional[TokenUsage]: @@ -812,11 +884,14 @@ def add(self, source: Any) -> Optional[TokenUsage]: Never raises -- extraction failures return ``None`` and are logged at DEBUG. """ + start_ns = time.perf_counter_ns() try: found = extract_usage(source) or extract_usage_from_stream_chunk(source) except Exception as exc: # belt + braces; extractors are already safe logger.debug("TokenUsageScope.add failed: %s", exc, exc_info=True) return None + finally: + self._extract_ns += time.perf_counter_ns() - start_ns if found: self.usage = self.usage + found return found @@ -828,9 +903,26 @@ def add_chunks(self, chunks: Iterable[Any]) -> None: for c in chunks: self.add(c) + # -- timing properties ----------------------------------------------- + @property + def extract_ms(self) -> float: + """Total ms spent inside :meth:`add` / :meth:`add_chunks`.""" + return self._extract_ns / 1_000_000.0 + + @property + def emit_ms(self) -> float: + """Total ms spent in the on-exit emit batch.""" + return self._emit_ns / 1_000_000.0 + + @property + def total_overhead_ms(self) -> float: + """Total telemetry overhead added by this scope (extract + emit).""" + return self.extract_ms + self.emit_ms + # -- context manager -------------------------------------------------- def __exit__(self, exc_type, exc, tb) -> None: # Always emit (best-effort) regardless of exception status. + emit_start_ns = time.perf_counter_ns() try: self.emitter.emit_all( agent_name=self.agent_name, @@ -843,6 +935,16 @@ def __exit__(self, exc_type, exc, tb) -> None: ) except Exception as emit_exc: # pragma: no cover - belt + braces logger.warning("TokenUsageScope emit failed: %s", emit_exc) + finally: + self._emit_ns += time.perf_counter_ns() - emit_start_ns + logger.debug( + "TokenUsageScope overhead: agent=%s extract_ms=%.3f " + "emit_ms=%.3f total_ms=%.3f", + self.agent_name, + self.extract_ms, + self.emit_ms, + self.total_overhead_ms, + ) return None # do not suppress exceptions diff --git a/src/frontend/Dockerfile b/src/frontend/Dockerfile index c7c40439..0ad16303 100644 --- a/src/frontend/Dockerfile +++ b/src/frontend/Dockerfile @@ -13,7 +13,6 @@ RUN npm install COPY . . # Build the app -ENV NODE_OPTIONS="--max-old-space-size=4096" RUN npm run build # Runtime stage diff --git a/src/processor/Dockerfile b/src/processor/Dockerfile index 317c8e03..afe293a5 100644 --- a/src/processor/Dockerfile +++ b/src/processor/Dockerfile @@ -36,8 +36,7 @@ RUN curl -fsSLo /tmp/node.tar.gz "https://nodejs.org/dist/v${NODE_VERSION}/node- COPY pyproject.toml uv.lock ./ # Install dependencies using UV -# Re-lock to pick up any pyproject.toml changes (e.g. new deps), then install. -RUN uv lock --python 3.12 && uv sync --frozen --python 3.12 +RUN uv sync --frozen --python 3.12 # Copy the entire source code COPY src/ ./src/ diff --git a/src/processor/src/tests/test_llm_token_telemetry.py b/src/processor/src/tests/test_llm_token_telemetry.py deleted file mode 100644 index 6a24f5b1..00000000 --- a/src/processor/src/tests/test_llm_token_telemetry.py +++ /dev/null @@ -1,572 +0,0 @@ -"""Unit tests for app.utils.llm_token_telemetry. - -Covers: -- TokenUsage arithmetic and realtime sub-fields -- All extractors (dict / object / raw_representation / aggregated messages / - streaming chunks / realtime / Mock-input safety) -- detect_invoked_tools -- TokenUsageEmitter: enabled/disabled, sink-throws-doesn't-propagate, - static_dimensions merge, all typed emitters, emit_all distinct models -- TokenUsageScope: happy path, exception in body still emits, multi-add -""" -from __future__ import annotations - -import logging -from unittest.mock import Mock - -import pytest - -from app.utils.llm_token_telemetry import ( - EVENT_AGENT, - EVENT_MODEL, - EVENT_SPEECH, - EVENT_SUMMARY, - TokenUsage, - TokenUsageEmitter, - TokenUsageScope, - detect_invoked_tools, - extract_realtime_usage, - extract_usage, - extract_usage_from_dict, - extract_usage_from_stream_chunk, -) - - -# --------------------------------------------------------------------------- -# TokenUsage -# --------------------------------------------------------------------------- -class TestTokenUsage: - def test_has_any_false_when_zero(self): - assert TokenUsage().has_any is False - - def test_has_any_true_when_any_nonzero(self): - assert TokenUsage(input_tokens=1).has_any is True - assert TokenUsage(total_tokens=5).has_any is True - - def test_addition_basic(self): - a = TokenUsage(1, 2, 3) - b = TokenUsage(4, 5, 9) - assert a + b == TokenUsage(5, 7, 12) - - def test_addition_realtime_subfields(self): - a = TokenUsage(1, 2, 3, input_audio_tokens=10) - b = TokenUsage(4, 5, 9, input_audio_tokens=20, output_audio_tokens=7) - c = a + b - assert c.input_audio_tokens == 30 - assert c.output_audio_tokens == 7 # None + 7 -> 7 - - def test_addition_returns_notimplemented_for_other_types(self): - assert TokenUsage(1).__add__("nope") is NotImplemented - - def test_to_event_props_omits_none_subfields(self): - props = TokenUsage(1, 2, 3).to_event_props() - assert props == {"input_tokens": "1", "output_tokens": "2", "total_tokens": "3"} - - def test_to_event_props_includes_realtime_when_present(self): - props = TokenUsage(1, 2, 3, input_audio_tokens=4).to_event_props() - assert props["input_audio_tokens"] == "4" - - -# --------------------------------------------------------------------------- -# extract_usage_from_dict -# --------------------------------------------------------------------------- -class TestExtractFromDict: - @pytest.mark.parametrize("data,expected", [ - ({"prompt_tokens": 12, "completion_tokens": 8}, (12, 8, 20)), - ({"input_tokens": 5, "output_tokens": 7, "total_tokens": 12}, (5, 7, 12)), - ({"input_token_count": 3, "output_token_count": 4}, (3, 4, 7)), - ({"promptTokens": 1, "completionTokens": 2, "totalTokens": 3}, (1, 2, 3)), - ]) - def test_aliases(self, data, expected): - u = extract_usage_from_dict(data) - assert (u.input_tokens, u.output_tokens, u.total_tokens) == expected - - def test_none_returns_none(self): - assert extract_usage_from_dict(None) is None - - def test_empty_returns_none(self): - assert extract_usage_from_dict({}) is None - - def test_total_falls_back_to_sum(self): - u = extract_usage_from_dict({"input_tokens": 4, "output_tokens": 6}) - assert u.total_tokens == 10 - - def test_string_digits_coerced(self): - u = extract_usage_from_dict({"input_tokens": "10", "output_tokens": "20"}) - assert u.input_tokens == 10 - assert u.output_tokens == 20 - - -# --------------------------------------------------------------------------- -# extract_usage (object shapes) -# --------------------------------------------------------------------------- -class _Bag: - """Minimal attribute bag (acts like an SDK model object).""" - pass - - -class TestExtractUsage: - def test_usage_details_dict(self): - r = _Bag() - r.usage_details = {"input_token_count": 5, "output_token_count": 7} - u = extract_usage(r) - assert u.total_tokens == 12 - - def test_usage_details_object(self): - r = _Bag() - details = _Bag() - details.input_token_count = 5 - details.output_token_count = 7 - details.total_token_count = 12 - r.usage_details = details - u = extract_usage(r) - assert u.total_tokens == 12 - - def test_raw_representation_openai_shape(self): - r = _Bag() - raw = _Bag() - raw.usage = {"prompt_tokens": 3, "completion_tokens": 4, "total_tokens": 7} - r.raw_representation = raw - u = extract_usage(r) - assert (u.input_tokens, u.output_tokens, u.total_tokens) == (3, 4, 7) - - def test_aggregated_messages(self): - r = _Bag() - msg = _Bag() - c1 = _Bag() - c1.usage_details = {"input_tokens": 2, "output_tokens": 3} - c2 = _Bag() - c2.usage_details = {"input_tokens": 4, "output_tokens": 1} - msg.contents = [c1, c2] - r.messages = [msg] - u = extract_usage(r) - assert u.input_tokens == 6 - assert u.output_tokens == 4 - - def test_none_input_returns_none(self): - assert extract_usage(None) is None - - def test_no_usage_returns_none(self): - assert extract_usage(_Bag()) is None - - def test_mock_input_does_not_raise(self): - """Mock objects expose every attribute as another Mock -- previously - this caused TypeError on iteration of .messages.""" - m = Mock() - # Should silently return None, never raise. - assert extract_usage(m) is None - - -# --------------------------------------------------------------------------- -# extract_usage_from_stream_chunk -# --------------------------------------------------------------------------- -class TestStreamChunk: - def test_chunk_with_metadata_usage(self): - c = _Bag() - c.metadata = {"usage": {"input_tokens": 1, "output_tokens": 2}} - u = extract_usage_from_stream_chunk(c) - assert u.input_tokens == 1 - assert u.output_tokens == 2 - - def test_no_usage_returns_none(self): - assert extract_usage_from_stream_chunk(_Bag()) is None - - -# --------------------------------------------------------------------------- -# extract_realtime_usage -# --------------------------------------------------------------------------- -class TestRealtime: - def test_basic(self): - r = _Bag() - r.usage = { - "input_tokens": 3, "output_tokens": 4, "total_tokens": 7, - "input_token_details": {"audio_tokens": 2, "text_tokens": 1, "cached_tokens": 0}, - "output_token_details": {"audio_tokens": 4, "text_tokens": 0}, - } - u = extract_realtime_usage(r) - assert u.input_audio_tokens == 2 - assert u.output_audio_tokens == 4 - assert u.total_tokens == 7 - - def test_total_derived_when_missing(self): - r = _Bag() - r.usage = {"input_tokens": 3, "output_tokens": 4} - u = extract_realtime_usage(r) - assert u.total_tokens == 7 - - def test_no_usage_returns_none(self): - assert extract_realtime_usage(_Bag()) is None - - -# --------------------------------------------------------------------------- -# detect_invoked_tools -# --------------------------------------------------------------------------- -class TestDetectInvokedTools: - def test_finds_function_calls(self): - r = _Bag() - c1 = _Bag() - c1.type = "function_call" - c1.name = "product_agent" - c2 = _Bag() - c2.type = "text" - c2.name = "n/a" - c3 = _Bag() - c3.type = "function_call" - c3.name = "policy_agent" - msg = _Bag() - msg.contents = [c1, c2, c3] - r.messages = [msg] - assert detect_invoked_tools(r) == {"product_agent", "policy_agent"} - - def test_empty_when_no_messages(self): - assert detect_invoked_tools(_Bag()) == set() - - def test_mock_input_safe(self): - assert detect_invoked_tools(Mock()) == set() - - def test_skips_function_calls_without_name(self): - r = _Bag() - c = _Bag() - c.type = "function_call" - c.name = None - msg = _Bag() - msg.contents = [c] - r.messages = [msg] - assert detect_invoked_tools(r) == set() - - -# --------------------------------------------------------------------------- -# TokenUsageEmitter -# --------------------------------------------------------------------------- -class TestEmitter: - def _make(self, **kw): - captured: list[tuple[str, dict]] = [] - kw.setdefault("connection_string", "fake-conn") - kw.setdefault("event_sink", lambda n, p: captured.append((n, dict(p)))) - em = TokenUsageEmitter(**kw) - return em, captured - - def test_disabled_when_no_connection_string(self): - em = TokenUsageEmitter(connection_string="", event_sink=lambda *a: None) - assert em.enabled is False - - def test_disabled_when_no_sink(self): - em = TokenUsageEmitter(connection_string="x", event_sink=None) - # _default_event_sink may or may not be available; force-disable: - em._sink = None - assert em.enabled is False - - def test_static_dimensions_prestringified_and_merged(self): - em, captured = self._make(static_dimensions={"app": "x", "tenant": 42}) - em.emit("X", user_id="u1") - name, props = captured[0] - assert name == "X" - assert props["app"] == "x" - assert props["tenant"] == "42" # stringified - assert props["user_id"] == "u1" - - def test_call_dimension_overrides_static(self): - em, captured = self._make(static_dimensions={"app": "default"}) - em.emit("X", app="override") - assert captured[0][1]["app"] == "override" - - def test_none_dimension_dropped(self): - em, captured = self._make() - em.emit("X", user_id=None, session_id="s1") - assert "user_id" not in captured[0][1] - assert captured[0][1]["session_id"] == "s1" - - def test_sink_exception_does_not_propagate(self, caplog): - def boom(_n, _p): - raise RuntimeError("sink broken") - em = TokenUsageEmitter(connection_string="x", event_sink=boom) - with caplog.at_level(logging.WARNING): - em.emit("X") # must not raise - - def test_emit_agent_skips_zero_usage(self): - em, captured = self._make() - em.emit_agent(agent_name="a", model_deployment_name="m", usage=TokenUsage()) - assert captured == [] - - def test_emit_agent_populates_props(self): - em, captured = self._make() - em.emit_agent(agent_name="chat", model_deployment_name="gpt-4o", - usage=TokenUsage(10, 20, 30), user_id="u") - name, props = captured[0] - assert name == EVENT_AGENT - assert props["agent_name"] == "chat" - assert props["model_deployment_name"] == "gpt-4o" - assert props["total_tokens"] == "30" - assert props["user_id"] == "u" - - def test_emit_all_emits_summary_agent_and_per_distinct_model(self): - em, captured = self._make() - em.emit_all( - agent_name="orchestrator", - model_deployment_name="gpt-4o", - usage=TokenUsage(10, 20, 30), - additional_agents={"tool_a": "gpt-4o", "tool_b": "gpt-35"}, - user_id="u1", - ) - names = [n for n, _ in captured] - # exactly one summary + one agent + two model events (gpt-4o, gpt-35) - assert names.count(EVENT_SUMMARY) == 1 - assert names.count(EVENT_AGENT) == 1 - assert names.count(EVENT_MODEL) == 2 - # summary records agent + model counts - summary = next(p for n, p in captured if n == EVENT_SUMMARY) - assert summary["agent_count"] == "3" - assert summary["model_count"] == "2" - assert summary["total_input_tokens"] == "10" - - def test_emit_speech_includes_audio_subfields(self): - em, captured = self._make() - em.emit_speech( - model_deployment_name="gpt-4o-realtime", - source="voice_chat", - usage=TokenUsage(1, 2, 3, input_audio_tokens=5, output_audio_tokens=6), - ) - name, props = captured[0] - assert name == EVENT_SPEECH - assert props["source"] == "voice_chat" - assert props["input_audio_tokens"] == "5" - assert props["output_audio_tokens"] == "6" - - -# --------------------------------------------------------------------------- -# Pricing / cost computation -# --------------------------------------------------------------------------- -class TestPricing: - def _make(self, pricing): - captured: list[tuple[str, dict]] = [] - em = TokenUsageEmitter( - connection_string="x", - event_sink=lambda n, p: captured.append((n, dict(p))), - pricing=pricing, - ) - return em, captured - - def test_cost_attached_to_agent_event(self): - em, captured = self._make({"gpt-4o": (0.0025, 0.01)}) - em.emit_agent(agent_name="a", model_deployment_name="gpt-4o", - usage=TokenUsage(1000, 500, 1500)) - # 1000 * 0.0025/1k + 500 * 0.01/1k = 0.0025 + 0.005 = 0.0075 - assert captured[0][1]["estimated_cost_usd"] == "0.007500" - - def test_cost_case_insensitive_model_lookup(self): - em, captured = self._make({"GPT-4o": (0.001, 0.001)}) - em.emit_model(model_deployment_name="gpt-4o", - usage=TokenUsage(1000, 1000, 2000)) - assert "estimated_cost_usd" in captured[0][1] - - def test_no_cost_when_model_unknown(self): - em, captured = self._make({"gpt-4o": (0.001, 0.001)}) - em.emit_agent(agent_name="a", model_deployment_name="gpt-mystery", - usage=TokenUsage(10, 10, 20)) - assert "estimated_cost_usd" not in captured[0][1] - - def test_summary_picks_up_cost_via_emit_all(self): - em, captured = self._make({"gpt-4o": (0.0025, 0.01)}) - em.emit_all(agent_name="chat", model_deployment_name="gpt-4o", - usage=TokenUsage(1000, 500, 1500)) - summary = next(p for n, p in captured if n == EVENT_SUMMARY) - assert summary["estimated_cost_usd"] == "0.007500" - - def test_malformed_pricing_entry_ignored(self, caplog): - with caplog.at_level(logging.WARNING): - em = TokenUsageEmitter( - connection_string="x", - event_sink=lambda *a: None, - pricing={"bad-model": "not-a-tuple"}, # type: ignore[dict-item] - ) - # Emitter still constructs; bad entry skipped. - assert "bad-model" not in em._pricing - - -# --------------------------------------------------------------------------- -# user_id PII hashing -# --------------------------------------------------------------------------- -class TestUserIdHasher: - def _make(self, hasher): - captured: list[tuple[str, dict]] = [] - em = TokenUsageEmitter( - connection_string="x", - event_sink=lambda n, p: captured.append((n, dict(p))), - user_id_hasher=hasher, - ) - return em, captured - - def test_hasher_applied_to_call_kwargs(self): - em, captured = self._make(lambda v: f"H({v})") - em.emit("X", user_id="alice") - assert captured[0][1]["user_id"] == "H(alice)" - - def test_hasher_applied_to_static_dimensions_at_construction(self): - em = TokenUsageEmitter( - connection_string="x", - event_sink=lambda *a: None, - user_id_hasher=lambda v: f"H({v})", - static_dimensions={"user_id": "bob"}, - ) - assert em._static["user_id"] == "H(bob)" - - def test_hasher_exception_falls_back_to_raw(self, caplog): - def boom(_v): - raise RuntimeError("hasher broken") - em, captured = self._make(boom) - with caplog.at_level(logging.WARNING): - em.emit("X", user_id="alice") - # Falls back to original value -- never breaks telemetry. - assert captured[0][1]["user_id"] == "alice" - - def test_no_hasher_passes_through(self): - em, captured = self._make(None) - em.emit("X", user_id="alice") - assert captured[0][1]["user_id"] == "alice" - - def test_empty_user_id_not_hashed_or_emitted(self): - em, captured = self._make(lambda v: f"H({v})") - em.emit("X", user_id="") - # Empty user_id should be dropped, not hashed to "H()". - assert "user_id" not in captured[0][1] - - -# --------------------------------------------------------------------------- -# Sampling -# --------------------------------------------------------------------------- -class TestSampling: - def _make(self, rate): - captured: list[tuple[str, dict]] = [] - em = TokenUsageEmitter( - connection_string="x", - event_sink=lambda n, p: captured.append((n, dict(p))), - sample_rate=rate, - ) - return em, captured - - def test_rate_clamped_to_unit_interval(self): - assert TokenUsageEmitter(connection_string="x", sample_rate=-0.5, - event_sink=lambda *a: None).sample_rate == 0.0 - assert TokenUsageEmitter(connection_string="x", sample_rate=2.0, - event_sink=lambda *a: None).sample_rate == 1.0 - - def test_invalid_rate_defaults_to_one(self): - em = TokenUsageEmitter(connection_string="x", sample_rate="nope", # type: ignore[arg-type] - event_sink=lambda *a: None) - assert em.sample_rate == 1.0 - - def test_zero_rate_drops_agent_event(self): - em, captured = self._make(0.0) - em.emit_agent(agent_name="a", model_deployment_name="m", - usage=TokenUsage(1, 2, 3)) - assert captured == [] - - def test_zero_rate_still_emits_summary(self): - em, captured = self._make(0.0) - em.emit_summary(usage=TokenUsage(1, 2, 3)) - assert captured and captured[0][0] == EVENT_SUMMARY - - def test_summary_records_sample_rate(self): - em, captured = self._make(0.25) - em.emit_summary(usage=TokenUsage(1, 2, 3)) - assert captured[0][1]["sample_rate"] == "0.2500" - - def test_emit_all_with_zero_rate_only_emits_summary(self): - em, captured = self._make(0.0) - em.emit_all(agent_name="chat", model_deployment_name="gpt-4o", - usage=TokenUsage(10, 20, 30)) - assert [n for n, _ in captured] == [EVENT_SUMMARY] - - def test_full_rate_emits_everything(self): - em, captured = self._make(1.0) - em.emit_all(agent_name="chat", model_deployment_name="gpt-4o", - usage=TokenUsage(10, 20, 30), - additional_agents={"a2": "gpt-35"}) - names = [n for n, _ in captured] - assert EVENT_SUMMARY in names - assert EVENT_AGENT in names - assert names.count(EVENT_MODEL) == 2 - - -# --------------------------------------------------------------------------- -# TokenUsageScope (continued) -# --------------------------------------------------------------------------- -class TestScope: - def _emitter(self): - captured: list[tuple[str, dict]] = [] - em = TokenUsageEmitter( - connection_string="x", - event_sink=lambda n, p: captured.append((n, dict(p))), - ) - return em, captured - - def test_happy_path_emits_on_exit(self): - em, captured = self._emitter() - r = _Bag() - r.usage_details = {"input_tokens": 1, "output_tokens": 2, "total_tokens": 3} - with TokenUsageScope(em, agent_name="a", model_deployment_name="m") as s: - s.add(r) - assert any(n == EVENT_SUMMARY for n, _ in captured) - assert any(n == EVENT_AGENT for n, _ in captured) - - def test_multi_add_accumulates(self): - em, captured = self._emitter() - r1 = _Bag() - r1.usage_details = {"input_tokens": 1, "output_tokens": 2} - r2 = _Bag() - r2.usage_details = {"input_tokens": 4, "output_tokens": 5} - with TokenUsageScope(em, agent_name="a", model_deployment_name="m") as s: - s.add(r1) - s.add(r2) - agent = next(p for n, p in captured if n == EVENT_AGENT) - assert agent["input_tokens"] == "5" - assert agent["output_tokens"] == "7" - assert agent["total_tokens"] == "12" - - def test_exception_in_body_still_emits(self): - em, captured = self._emitter() - r = _Bag() - r.usage_details = {"input_tokens": 1, "output_tokens": 2, "total_tokens": 3} - with pytest.raises(ValueError): - with TokenUsageScope(em, agent_name="a", model_deployment_name="m") as s: - s.add(r) - raise ValueError("boom") - # Emission still happened - assert any(n == EVENT_AGENT for n, _ in captured) - - def test_add_with_mock_does_not_raise(self): - em, _ = self._emitter() - with TokenUsageScope(em, agent_name="a", model_deployment_name="m") as s: - assert s.add(Mock()) is None - - def test_zero_usage_does_not_emit(self): - em, captured = self._emitter() - with TokenUsageScope(em, agent_name="a", model_deployment_name="m"): - pass - assert captured == [] - - def test_dimensions_flow_to_events(self): - em, captured = self._emitter() - r = _Bag() - r.usage_details = {"input_tokens": 1, "output_tokens": 2, "total_tokens": 3} - with TokenUsageScope(em, agent_name="a", model_deployment_name="m", - user_id="u1", session_id="s1") as s: - s.add(r) - for _, p in captured: - assert p["user_id"] == "u1" - assert p["session_id"] == "s1" - - def test_additional_agents_after_scope_open(self): - em, captured = self._emitter() - r = _Bag() - r.usage_details = {"input_tokens": 1, "output_tokens": 2, "total_tokens": 3} - with TokenUsageScope(em, agent_name="orchestrator", - model_deployment_name="gpt-4o") as s: - s.add(r) - # Mutate additional_agents after the call -- mirrors the - # detect_invoked_tools usage pattern. - s.additional_agents["tool_a"] = "gpt-35" - model_events = [p for n, p in captured if n == EVENT_MODEL] - models = {p["model_deployment_name"] for p in model_events} - assert models == {"gpt-4o", "gpt-35"} - diff --git a/src/processor/src/utils/llm_token_telemetry.py b/src/processor/src/utils/llm_token_telemetry.py index b3035fc8..91f670c5 100644 --- a/src/processor/src/utils/llm_token_telemetry.py +++ b/src/processor/src/utils/llm_token_telemetry.py @@ -53,6 +53,7 @@ import logging import os import random +import time from contextlib import AbstractContextManager from dataclasses import dataclass, field from typing import Any, Callable, Iterable, Mapping, Optional @@ -472,6 +473,16 @@ def __init__( k: ("" if v is None else str(v)) for k, v in raw_static.items() } + # Performance counters. ``perf_*`` accumulate wall-clock nanoseconds + # spent inside ``emit()`` so callers can verify telemetry overhead is + # negligible. ``perf_slow_emit_threshold_ms`` is the soft threshold + # above which a WARNING is logged for an individual emit (default + # 50 ms -- emits should normally take well under 1 ms). + self._perf_total_ns: int = 0 + self._perf_emit_count: int = 0 + self._perf_max_ns: int = 0 + self.perf_slow_emit_threshold_ms: float = 50.0 + # -- public surface --------------------------------------------------- @property def enabled(self) -> bool: @@ -539,28 +550,74 @@ def emit(self, event_name: str, **dimensions: Any) -> None: Non-string values are stringified. ``None`` values are dropped. Any ``user_id`` value is passed through the configured hasher. - Never raises. + Never raises. Wall-clock duration is recorded for performance audit + (see :meth:`perf_stats`). """ - props = dict(self._static) # cheap shallow copy of pre-stringified dims - for k, v in dimensions.items(): - if v is None: - continue - if k == "user_id": - v = self._apply_user_id_hash(v) - if v is None or v == "": + start_ns = time.perf_counter_ns() + try: + props = dict(self._static) # cheap shallow copy of pre-stringified dims + for k, v in dimensions.items(): + if v is None: continue - props[k] = v if isinstance(v, str) else str(v) + if k == "user_id": + v = self._apply_user_id_hash(v) + if v is None or v == "": + continue + props[k] = v if isinstance(v, str) else str(v) + + if not self.enabled: + self._log.debug( + "App Insights not configured -- skipping event %s (%s)", + event_name, props, + ) + return + try: + self._sink(event_name, props) # type: ignore[misc] + except Exception as exc: # never break the caller + self._log.warning("track_event(%s) failed: %s", event_name, exc) + finally: + elapsed_ns = time.perf_counter_ns() - start_ns + self._perf_total_ns += elapsed_ns + self._perf_emit_count += 1 + if elapsed_ns > self._perf_max_ns: + self._perf_max_ns = elapsed_ns + elapsed_ms = elapsed_ns / 1_000_000.0 + if elapsed_ms > self.perf_slow_emit_threshold_ms: + self._log.warning( + "Token telemetry emit slow: event=%s duration_ms=%.3f", + event_name, elapsed_ms, + ) + else: + self._log.debug( + "Token telemetry emit: event=%s duration_ms=%.3f", + event_name, elapsed_ms, + ) + + # -- performance audit ------------------------------------------------ + def perf_stats(self) -> dict[str, float]: + """Return cumulative telemetry-overhead stats since process start + (or since :meth:`reset_perf_stats`). + + Keys: + ``emit_count`` -- number of events emitted + ``total_ms`` -- total wall-clock time spent inside ``emit`` + ``avg_ms`` -- mean per-event duration + ``max_ms`` -- slowest single emit observed + """ + count = self._perf_emit_count + total_ms = self._perf_total_ns / 1_000_000.0 + return { + "emit_count": float(count), + "total_ms": total_ms, + "avg_ms": (total_ms / count) if count else 0.0, + "max_ms": self._perf_max_ns / 1_000_000.0, + } - if not self.enabled: - self._log.debug( - "App Insights not configured -- skipping event %s (%s)", - event_name, props, - ) - return - try: - self._sink(event_name, props) # type: ignore[misc] - except Exception as exc: # never break the caller - self._log.warning("track_event(%s) failed: %s", event_name, exc) + def reset_perf_stats(self) -> None: + """Zero the perf counters (useful for tests and load-tests).""" + self._perf_total_ns = 0 + self._perf_emit_count = 0 + self._perf_max_ns = 0 # -- typed convenience emitters -------------------------------------- def emit_agent( @@ -713,14 +770,12 @@ def emit_all( agents.update({k: v for k, v in additional_agents.items() if k}) models = {m for m in agents.values() if m} - self.emit_summary( - usage=usage, - agent_count=len(agents), - model_count=len(models) or 1, - primary_model=model_deployment_name, - additional_agents=additional_agents, - **dimensions, - ) + # Wall-clock timing of the whole emit_all path so callers (or tests) + # can verify the telemetry path stays cheap relative to the LLM call + # it instruments. + batch_start_ns = time.perf_counter_ns() + + # Defer summary until last so we can stamp the batch overhead on it. self.emit_agent( agent_name=agent_name, model_deployment_name=model_deployment_name, @@ -748,6 +803,17 @@ def emit_all( model_deployment_name=model_deployment_name, ) + batch_overhead_ms = (time.perf_counter_ns() - batch_start_ns) / 1_000_000.0 + self.emit_summary( + usage=usage, + agent_count=len(agents), + model_count=len(models) or 1, + primary_model=model_deployment_name, + additional_agents=additional_agents, + telemetry_overhead_ms=f"{batch_overhead_ms:.3f}", + **dimensions, + ) + self._log.info( "[TOKEN USAGE] agent=%s model=%s input=%d output=%d total=%d %s", agent_name, @@ -804,6 +870,12 @@ def __init__( self.emit_team_event = emit_team_event self.dimensions = dict(dimensions) self.usage = TokenUsage() + # Wall-clock nanoseconds spent inside extraction (``add*``) and the + # final ``__exit__`` emit, respectively. Surfaced for callers that + # want to verify the helper doesn't add measurable latency. Available + # as ``scope.extract_ms`` / ``scope.emit_ms`` after the scope closes. + self._extract_ns: int = 0 + self._emit_ns: int = 0 # -- accumulation ----------------------------------------------------- def add(self, source: Any) -> Optional[TokenUsage]: @@ -812,11 +884,14 @@ def add(self, source: Any) -> Optional[TokenUsage]: Never raises -- extraction failures return ``None`` and are logged at DEBUG. """ + start_ns = time.perf_counter_ns() try: found = extract_usage(source) or extract_usage_from_stream_chunk(source) except Exception as exc: # belt + braces; extractors are already safe logger.debug("TokenUsageScope.add failed: %s", exc, exc_info=True) return None + finally: + self._extract_ns += time.perf_counter_ns() - start_ns if found: self.usage = self.usage + found return found @@ -828,9 +903,26 @@ def add_chunks(self, chunks: Iterable[Any]) -> None: for c in chunks: self.add(c) + # -- timing properties ----------------------------------------------- + @property + def extract_ms(self) -> float: + """Total ms spent inside :meth:`add` / :meth:`add_chunks`.""" + return self._extract_ns / 1_000_000.0 + + @property + def emit_ms(self) -> float: + """Total ms spent in the on-exit emit batch.""" + return self._emit_ns / 1_000_000.0 + + @property + def total_overhead_ms(self) -> float: + """Total telemetry overhead added by this scope (extract + emit).""" + return self.extract_ms + self.emit_ms + # -- context manager -------------------------------------------------- def __exit__(self, exc_type, exc, tb) -> None: # Always emit (best-effort) regardless of exception status. + emit_start_ns = time.perf_counter_ns() try: self.emitter.emit_all( agent_name=self.agent_name, @@ -843,6 +935,16 @@ def __exit__(self, exc_type, exc, tb) -> None: ) except Exception as emit_exc: # pragma: no cover - belt + braces logger.warning("TokenUsageScope emit failed: %s", emit_exc) + finally: + self._emit_ns += time.perf_counter_ns() - emit_start_ns + logger.debug( + "TokenUsageScope overhead: agent=%s extract_ms=%.3f " + "emit_ms=%.3f total_ms=%.3f", + self.agent_name, + self.extract_ms, + self.emit_ms, + self.total_overhead_ms, + ) return None # do not suppress exceptions From 4393540fa73d310b9b071407adeeed404a902be8 Mon Sep 17 00:00:00 2001 From: Priyanka-Microsoft Date: Mon, 1 Jun 2026 14:10:27 +0530 Subject: [PATCH 11/12] fix: update uv.lock to include azure-monitor-events-extension The lockfile was missing the azure-monitor-events-extension and azure-monitor-opentelemetry packages, causing uv sync --frozen in Docker to skip them. This made the emitter silently disabled. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/processor/uv.lock | 474 +++++++++++++++++++++++++++++++++++------- 1 file changed, 398 insertions(+), 76 deletions(-) diff --git a/src/processor/uv.lock b/src/processor/uv.lock index dec50656..f95de739 100644 --- a/src/processor/uv.lock +++ b/src/processor/uv.lock @@ -424,9 +424,9 @@ name = "aiologic" version = "0.16.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "sniffio" }, + { name = "sniffio", marker = "python_full_version < '3.13'" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, - { name = "wrapt" }, + { name = "wrapt", marker = "python_full_version < '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a8/13/50b91a3ea6b030d280d2654be97c48b6ed81753a50286ee43c646ba36d3c/aiologic-0.16.0.tar.gz", hash = "sha256:c267ccbd3ff417ec93e78d28d4d577ccca115d5797cdbd16785a551d9658858f", size = 225952, upload-time = "2025-11-27T23:48:41.195Z" } wheels = [ @@ -505,6 +505,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/67/29/57b06fdb3abdf52c621d3ca3caea735e2db4c8d48288ebd26af448e8e247/art-6.5-py3-none-any.whl", hash = "sha256:70706408144c45c666caab690627d5c74aea7b6c7ce8cc968408ddeef8d84afd", size = 610382, upload-time = "2025-04-12T17:02:21.97Z" }, ] +[[package]] +name = "asgiref" +version = "3.11.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/63/40/f03da1264ae8f7cfdbf9146542e5e7e8100a4c66ab48e791df9a03d3f6c0/asgiref-3.11.1.tar.gz", hash = "sha256:5f184dc43b7e763efe848065441eac62229c9f7b0475f41f80e207a114eda4ce", size = 38550, upload-time = "2026-02-03T13:30:14.33Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/0a/a72d10ed65068e115044937873362e6e32fab1b7dce0046aeb224682c989/asgiref-3.11.1-py3-none-any.whl", hash = "sha256:e8667a091e69529631969fd45dc268fa79b99c92c5fcdda727757e52146ec133", size = 24345, upload-time = "2026-02-03T13:30:13.039Z" }, +] + [[package]] name = "asyncio" version = "4.0.0" @@ -627,6 +636,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fc/d8/b8fcba9464f02b121f39de2db2bf57f0b216fe11d014513d666e8634380d/azure_core-1.38.0-py3-none-any.whl", hash = "sha256:ab0c9b2cd71fecb1842d52c965c95285d3cfb38902f6766e4a471f1cd8905335", size = 217825, upload-time = "2026-01-12T17:03:07.291Z" }, ] +[[package]] +name = "azure-core-tracing-opentelemetry" +version = "1.0.0b13" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "azure-core" }, + { name = "opentelemetry-api" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ce/ab/a937e4af8afec9d437d55252f2a3a4419fc3fc7d5e5d54022622bd11b2b6/azure_core_tracing_opentelemetry-1.0.0b13.tar.gz", hash = "sha256:6cb2f8dfd5dee6c11843db0205fc92e2434e1a272c169c953afe92483aafc7eb", size = 25832, upload-time = "2026-05-01T00:59:57.941Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/01/8898c2506cae6a57c1b76d930d2af94764a65354bc863feb2684235851ce/azure_core_tracing_opentelemetry-1.0.0b13-py3-none-any.whl", hash = "sha256:4dacd3a9f117f11f98e89305e161c951b8df85b984f3b56130614de9cd9887f9", size = 12112, upload-time = "2026-05-01T00:59:59.149Z" }, +] + [[package]] name = "azure-cosmos" version = "4.15.0" @@ -706,6 +728,60 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e1/28/af9ef022f21e3b51b3718d4348f771b490678c1116563895547c0a771362/azure_identity-1.26.0b1-py3-none-any.whl", hash = "sha256:dc608b59ae628a38611208ee761adeb1a2b9390258b58d6edcda2d24c50a4348", size = 197227, upload-time = "2025-11-07T03:04:16.923Z" }, ] +[[package]] +name = "azure-monitor-events-extension" +version = "0.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-sdk" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cd/51/976c8cd4a76d41bcd4d3f6400aeed8fdd70d516d271badf9c4a5893a558d/azure-monitor-events-extension-0.1.0.tar.gz", hash = "sha256:094773685171a50aa5cc548279c9141c8a26682f6acef397815c528b53b838b5", size = 4165, upload-time = "2023-09-19T20:01:17.887Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/09/44/cbb68c55505a604de61caa44375be7371368e71aa8386b1576be5b789e11/azure_monitor_events_extension-0.1.0-py2.py3-none-any.whl", hash = "sha256:5d92abb5e6a32ab23b12c726def9f9607c6fa1d84900d493b906ff9ec489af4a", size = 4514, upload-time = "2023-09-19T20:01:16.162Z" }, +] + +[[package]] +name = "azure-monitor-opentelemetry" +version = "1.8.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "azure-core" }, + { name = "azure-core-tracing-opentelemetry" }, + { name = "azure-monitor-opentelemetry-exporter" }, + { name = "opentelemetry-instrumentation-django" }, + { name = "opentelemetry-instrumentation-fastapi" }, + { name = "opentelemetry-instrumentation-flask" }, + { name = "opentelemetry-instrumentation-logging" }, + { name = "opentelemetry-instrumentation-psycopg2" }, + { name = "opentelemetry-instrumentation-requests" }, + { name = "opentelemetry-instrumentation-urllib" }, + { name = "opentelemetry-instrumentation-urllib3" }, + { name = "opentelemetry-resource-detector-azure" }, + { name = "opentelemetry-sdk" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/89/42/ea67bebb400a7561b1ad1dd59d06b67e880daf8081ec0d41d3b0ce8fcc26/azure_monitor_opentelemetry-1.8.7.tar.gz", hash = "sha256:d0a430c69451f8fa09362769d2d65471713989fb78e4ad0f50832b597921efbb", size = 76970, upload-time = "2026-03-19T21:43:57.056Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/22/245a4f75a834430759a6fab9c5ab10e18719786ae684cf234c7bb6a693d1/azure_monitor_opentelemetry-1.8.7-py3-none-any.whl", hash = "sha256:0d3a228a183d76cf22698a3eed6e836d1cf57608b8ee879c634609b26f384eb2", size = 41268, upload-time = "2026-03-19T21:43:58.188Z" }, +] + +[[package]] +name = "azure-monitor-opentelemetry-exporter" +version = "1.0.0b52" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "azure-core" }, + { name = "azure-identity" }, + { name = "msrest" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-sdk" }, + { name = "psutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7c/7e/bfc03436b88c48f5adc21a3ebbf4392b6b7fbbfe33ef3b1e88d07ba9f380/azure_monitor_opentelemetry_exporter-1.0.0b52.tar.gz", hash = "sha256:7eac679fca32dee9e426df65f2a538161db4514fc322fc66107f7826567d86e1", size = 326179, upload-time = "2026-05-11T22:47:02.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/e8/d13e6a74c98ecc3011bce9ab09fc2e75aec48ab46288f72be57c2fa21460/azure_monitor_opentelemetry_exporter-1.0.0b52-py2.py3-none-any.whl", hash = "sha256:a38c503e5e2cc0ec8a4bf336b23cce23488719f5361a45cdd01a514080f0e7fc", size = 244751, upload-time = "2026-05-11T22:47:04.304Z" }, +] + [[package]] name = "azure-search-documents" version = "11.7.0b2" @@ -1015,7 +1091,7 @@ name = "clr-loader" version = "0.2.10" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cffi" }, + { name = "cffi", marker = "python_full_version < '3.14'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/18/24/c12faf3f61614b3131b5c98d3bf0d376b49c7feaa73edca559aeb2aee080/clr_loader-0.2.10.tar.gz", hash = "sha256:81f114afbc5005bafc5efe5af1341d400e22137e275b042a8979f3feb9fc9446", size = 83605, upload-time = "2026-01-03T23:13:06.984Z" } wheels = [ @@ -1097,7 +1173,7 @@ name = "culsans" version = "0.11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "aiologic" }, + { name = "aiologic", marker = "python_full_version < '3.13'" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/d9/e3/49afa1bc180e0d28008ec6bcdf82a4072d1c7a41032b5b759b60814ca4b0/culsans-0.11.0.tar.gz", hash = "sha256:0b43d0d05dce6106293d114c86e3fb4bfc63088cfe8ff08ed3fe36891447fe33", size = 107546, upload-time = "2025-12-31T23:15:38.196Z" } @@ -2124,6 +2200,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5e/75/bd9b7bb966668920f06b200e84454c8f3566b102183bc55c5473d96cb2b9/msal_extensions-1.3.1-py3-none-any.whl", hash = "sha256:96d3de4d034504e969ac5e85bae8106c8373b5c6568e4c8fa7af2eca9dbe6bca", size = 20583, upload-time = "2025-03-14T23:51:03.016Z" }, ] +[[package]] +name = "msrest" +version = "0.7.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "azure-core" }, + { name = "certifi" }, + { name = "isodate" }, + { name = "requests" }, + { name = "requests-oauthlib" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/68/77/8397c8fb8fc257d8ea0fa66f8068e073278c65f05acb17dcb22a02bfdc42/msrest-0.7.1.zip", hash = "sha256:6e7661f46f3afd88b75667b7187a92829924446c7ea1d169be8c4bb7eeb788b9", size = 175332, upload-time = "2022-06-13T22:41:25.111Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/15/cf/f2966a2638144491f8696c27320d5219f48a072715075d168b31d3237720/msrest-0.7.1-py3-none-any.whl", hash = "sha256:21120a810e1233e5e6cc7fe40b474eeb4ec6f757a15d7cf86702c369f9567c32", size = 85384, upload-time = "2022-06-13T22:41:22.42Z" }, +] + [[package]] name = "multidict" version = "6.7.1" @@ -2336,6 +2428,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/58/78/548fb8e07b1a341746bfbecb32f2c268470f45fa028aacdbd10d9bc73aab/numpy-2.4.4-cp314-cp314t-win_arm64.whl", hash = "sha256:ba203255017337d39f89bdd58417f03c4426f12beed0440cfd933cb15f8669c7", size = 10566643, upload-time = "2026-03-29T13:21:34.339Z" }, ] +[[package]] +name = "oauthlib" +version = "3.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/5f/19930f824ffeb0ad4372da4812c50edbd1434f678c90c2733e1188edfc63/oauthlib-3.3.1.tar.gz", hash = "sha256:0f0f8aa759826a193cf66c12ea1af1637f87b9b4622d46e866952bb022e538c9", size = 185918, upload-time = "2025-06-19T22:48:08.269Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/9c/92789c596b8df838baa98fa71844d84283302f7604ed565dafe5a6b5041a/oauthlib-3.3.1-py3-none-any.whl", hash = "sha256:88119c938d2b8fb88561af5f6ee0eec8cc8d552b7bb1f712743136eb7523b7a1", size = 160065, upload-time = "2025-06-19T22:48:06.508Z" }, +] + [[package]] name = "ollama" version = "0.6.2" @@ -2416,42 +2517,237 @@ wheels = [ [[package]] name = "opentelemetry-api" -version = "1.41.1" +version = "1.40.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "importlib-metadata" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fa/fc/b7564cbef36601aef0d6c9bc01f7badb64be8e862c2e1c3c5c3b43b53e4f/opentelemetry_api-1.41.1.tar.gz", hash = "sha256:0ad1814d73b875f84494387dae86ce0b12c68556331ce6ce8fe789197c949621", size = 71416, upload-time = "2026-04-24T13:15:38.262Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/1d/4049a9e8698361cc1a1aa03a6c59e4fa4c71e0c0f94a30f988a6876a2ae6/opentelemetry_api-1.40.0.tar.gz", hash = "sha256:159be641c0b04d11e9ecd576906462773eb97ae1b657730f0ecf64d32071569f", size = 70851, upload-time = "2026-03-04T14:17:21.555Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/bf/93795954016c522008da367da292adceed71cca6ee1717e1d64c83089099/opentelemetry_api-1.40.0-py3-none-any.whl", hash = "sha256:82dd69331ae74b06f6a874704be0cfaa49a1650e1537d4a813b86ecef7d0ecf9", size = 68676, upload-time = "2026-03-04T14:17:01.24Z" }, +] + +[[package]] +name = "opentelemetry-instrumentation" +version = "0.61b0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "packaging" }, + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/da/37/6bf8e66bfcee5d3c6515b79cb2ee9ad05fe573c20f7ceb288d0e7eeec28c/opentelemetry_instrumentation-0.61b0.tar.gz", hash = "sha256:cb21b48db738c9de196eba6b805b4ff9de3b7f187e4bbf9a466fa170514f1fc7", size = 32606, upload-time = "2026-03-04T14:20:16.825Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d8/3e/f6f10f178b6316de67f0dfdbbb699a24fbe8917cf1743c1595fb9dcdd461/opentelemetry_instrumentation-0.61b0-py3-none-any.whl", hash = "sha256:92a93a280e69788e8f88391247cc530fd81f16f2b011979d4d6398f805cfbc63", size = 33448, upload-time = "2026-03-04T14:19:02.447Z" }, +] + +[[package]] +name = "opentelemetry-instrumentation-asgi" +version = "0.61b0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "asgiref" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-instrumentation" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "opentelemetry-util-http" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/00/3e/143cf5c034e58037307e6a24f06e0dd64b2c49ae60a965fc580027581931/opentelemetry_instrumentation_asgi-0.61b0.tar.gz", hash = "sha256:9d08e127244361dc33976d39dd4ca8f128b5aa5a7ae425208400a80a095019b5", size = 26691, upload-time = "2026-03-04T14:20:21.038Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/19/78/154470cf9d741a7487fbb5067357b87386475bbb77948a6707cae982e158/opentelemetry_instrumentation_asgi-0.61b0-py3-none-any.whl", hash = "sha256:e4b3ce6b66074e525e717efff20745434e5efd5d9df6557710856fba356da7a4", size = 16980, upload-time = "2026-03-04T14:19:10.894Z" }, +] + +[[package]] +name = "opentelemetry-instrumentation-dbapi" +version = "0.61b0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-instrumentation" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d6/ed/ba91c9e4a3ec65781e9c59982109f0a36de9fa574f622596b33d1985dab5/opentelemetry_instrumentation_dbapi-0.61b0.tar.gz", hash = "sha256:02fa800682c1de87dcad0e59f2092b3b6fb8b8ea0636518f989e1166b418dcb9", size = 16761, upload-time = "2026-03-04T14:20:29.782Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/a5/d26c68f3fd33eb7410985cef7700bb426e2c4a26de9207902cbbffb19a3f/opentelemetry_instrumentation_dbapi-0.61b0-py3-none-any.whl", hash = "sha256:8f762c39c8edd20c6aef3282550a2cfbfec76c3f431bf5c36327dcf9ece2e5a0", size = 14134, upload-time = "2026-03-04T14:19:24.718Z" }, +] + +[[package]] +name = "opentelemetry-instrumentation-django" +version = "0.61b0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-instrumentation" }, + { name = "opentelemetry-instrumentation-wsgi" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "opentelemetry-util-http" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/74/ef/6bc1a6560630f26b1c010af86b28f42bfbe6a601bd1647d1436e0d3436aa/opentelemetry_instrumentation_django-0.61b0.tar.gz", hash = "sha256:9885154dc128578de0e6b5ce49e965c786f8ab071175bec005dcd454510be951", size = 25996, upload-time = "2026-03-04T14:20:30.453Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/3b/74dad6d98fdee1d137f1c2748548d4159578508f21e3aef581c110e64041/opentelemetry_instrumentation_django-0.61b0-py3-none-any.whl", hash = "sha256:26c1b0b325a9783d4a2f4df660ba05cf929c3eda2ae9b07916b649bb44e1c5b6", size = 20773, upload-time = "2026-03-04T14:19:25.675Z" }, +] + +[[package]] +name = "opentelemetry-instrumentation-fastapi" +version = "0.61b0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-instrumentation" }, + { name = "opentelemetry-instrumentation-asgi" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "opentelemetry-util-http" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/37/35/aa727bb6e6ef930dcdc96a617b83748fece57b43c47d83ba8d83fbeca657/opentelemetry_instrumentation_fastapi-0.61b0.tar.gz", hash = "sha256:3a24f35b07c557ae1bbc483bf8412221f25d79a405f8b047de8b670722e2fa9f", size = 24800, upload-time = "2026-03-04T14:20:32.759Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/05/acfeb2cccd434242a0a7d0ea29afaf077e04b42b35b485d89aee4e0d9340/opentelemetry_instrumentation_fastapi-0.61b0-py3-none-any.whl", hash = "sha256:a1a844d846540d687d377516b2ff698b51d87c781b59f47c214359c4a241047c", size = 13485, upload-time = "2026-03-04T14:19:30.351Z" }, +] + +[[package]] +name = "opentelemetry-instrumentation-flask" +version = "0.61b0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-instrumentation" }, + { name = "opentelemetry-instrumentation-wsgi" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "opentelemetry-util-http" }, + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d9/33/d6852d8f2c3eef86f2f8c858d6f5315983c7063e07e595519e96d4c31c06/opentelemetry_instrumentation_flask-0.61b0.tar.gz", hash = "sha256:e9faf58dfd9860a1868442d180142645abdafc1a652dd73d469a5efd106a7d49", size = 24071, upload-time = "2026-03-04T14:20:33.437Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3e/41/619f3530324a58491f2d20f216a10dd7393629b29db4610dda642a27f4ed/opentelemetry_instrumentation_flask-0.61b0-py3-none-any.whl", hash = "sha256:e8ce474d7ce543bfbbb3e93f8a6f8263348af9d7b45502f387420cf3afa71253", size = 15996, upload-time = "2026-03-04T14:19:31.304Z" }, +] + +[[package]] +name = "opentelemetry-instrumentation-logging" +version = "0.61b0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-instrumentation" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ae/e0/69473f925acfe2d4edf5c23bcced36906ac3627aa7c5722a8e3f60825f3b/opentelemetry_instrumentation_logging-0.61b0.tar.gz", hash = "sha256:feaa30b700acd2a37cc81db5f562ab0c3a5b6cc2453595e98b72c01dcf649584", size = 17906, upload-time = "2026-03-04T14:20:37.398Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/29/59/3e7118ed140f76b0982ba4321bdaed1997a0473f9720de2d10788a577033/opentelemetry_api-1.41.1-py3-none-any.whl", hash = "sha256:a22df900e75c76dc08440710e51f52f1aa6b451b429298896023e60db5b3139f", size = 69007, upload-time = "2026-04-24T13:15:15.662Z" }, + { url = "https://files.pythonhosted.org/packages/e0/0e/2137db5239cc5e564495549a4d11488a7af9b48fc76520a0eea20e69ddae/opentelemetry_instrumentation_logging-0.61b0-py3-none-any.whl", hash = "sha256:6d87e5ded6a0128d775d41511f8380910a1b610671081d16efb05ac3711c0074", size = 17076, upload-time = "2026-03-04T14:19:36.765Z" }, +] + +[[package]] +name = "opentelemetry-instrumentation-psycopg2" +version = "0.61b0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-instrumentation" }, + { name = "opentelemetry-instrumentation-dbapi" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0e/28/f28d52b1088e7a09761566f8700507b54d3d83a6f9c93c0ce02f53619e83/opentelemetry_instrumentation_psycopg2-0.61b0.tar.gz", hash = "sha256:863ccf9687b71e73dd489c7bb117278768bdf26aa0dafe7dc974a2425e05b5d7", size = 11676, upload-time = "2026-03-04T14:20:41.269Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2f/f1/4341d0584c288765c73e28c30ba58e7aedb50c01108f17f947b872657f79/opentelemetry_instrumentation_psycopg2-0.61b0-py3-none-any.whl", hash = "sha256:36b96983beda05c927179bb66b6c72f07a8d9a591f76ce9da88b1dd1587cb083", size = 11491, upload-time = "2026-03-04T14:19:42.018Z" }, +] + +[[package]] +name = "opentelemetry-instrumentation-requests" +version = "0.61b0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-instrumentation" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "opentelemetry-util-http" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5a/c7/7a47cb85c7aa93a9c820552e414889185bcf91245271d12e5d443e5f834d/opentelemetry_instrumentation_requests-0.61b0.tar.gz", hash = "sha256:15f879ce8fb206bd7e6fdc61663ea63481040a845218c0cf42902ce70bd7e9d9", size = 18379, upload-time = "2026-03-04T14:20:46.959Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5e/a1/a7a133b273d1f53950f16a370fc94367eff472c9c2576e8e9e28c62dcc9f/opentelemetry_instrumentation_requests-0.61b0-py3-none-any.whl", hash = "sha256:cce19b379949fe637eb73ba39b02c57d2d0805447ca6d86534aa33fcb141f683", size = 14207, upload-time = "2026-03-04T14:19:51.765Z" }, +] + +[[package]] +name = "opentelemetry-instrumentation-urllib" +version = "0.61b0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-instrumentation" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "opentelemetry-util-http" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/81/37/77cd326b083390e74280c08bbd585153809619dad068e2d1b253fec1164d/opentelemetry_instrumentation_urllib-0.61b0.tar.gz", hash = "sha256:6a15ff862fc1603e0ea5ea75558f76f36436b02e0ae48daecedcb5e574cce160", size = 16894, upload-time = "2026-03-04T14:20:52.726Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/fc/a88fbfd8b9eb16ba1c21f0514c12696441be7fc42c7e319f3ee793bf9e96/opentelemetry_instrumentation_urllib-0.61b0-py3-none-any.whl", hash = "sha256:d7e409876580fb41102e3522ce81a756e53a74073c036a267a1c280cc0fa09b0", size = 13970, upload-time = "2026-03-04T14:20:01.24Z" }, +] + +[[package]] +name = "opentelemetry-instrumentation-urllib3" +version = "0.61b0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-instrumentation" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "opentelemetry-util-http" }, + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fa/80/7ad8da30f479c6117768e72d6f2f3f0bd3495338707d6f61de042149578a/opentelemetry_instrumentation_urllib3-0.61b0.tar.gz", hash = "sha256:f00037bc8ff813153c4b79306f55a14618c40469a69c6c03a3add29dc7e8b928", size = 19325, upload-time = "2026-03-04T14:20:53.386Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/0c/01359e55b9f2fb2b1d4d9e85e77773a96697207895118533f3be718a3326/opentelemetry_instrumentation_urllib3-0.61b0-py3-none-any.whl", hash = "sha256:9644f8c07870266e52f129e6226859ff3a35192555abe46fa0ef9bbbf5b6b46d", size = 14339, upload-time = "2026-03-04T14:20:02.681Z" }, +] + +[[package]] +name = "opentelemetry-instrumentation-wsgi" +version = "0.61b0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-instrumentation" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "opentelemetry-util-http" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/89/e5/189f2845362cfe78e356ba127eab21456309def411c6874aa4800c3de816/opentelemetry_instrumentation_wsgi-0.61b0.tar.gz", hash = "sha256:380f2ae61714e5303275a80b2e14c58571573cd1fddf496d8c39fb9551c5e532", size = 19898, upload-time = "2026-03-04T14:20:54.068Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/75/d6b42ba26f3c921be6d01b16561b7bb863f843bad7ac3a5011f62617bcab/opentelemetry_instrumentation_wsgi-0.61b0-py3-none-any.whl", hash = "sha256:bd33b0824166f24134a3400648805e8d2e6a7951f070241294e8b8866611d7fa", size = 14628, upload-time = "2026-03-04T14:20:03.934Z" }, +] + +[[package]] +name = "opentelemetry-resource-detector-azure" +version = "0.1.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-sdk" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/67/e4/0d359d48d03d447225b30c3dd889d5d454e3b413763ff721f9b0e4ac2e59/opentelemetry_resource_detector_azure-0.1.5.tar.gz", hash = "sha256:e0ba658a87c69eebc806e75398cd0e9f68a8898ea62de99bc1b7083136403710", size = 11503, upload-time = "2024-05-16T21:54:58.994Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c3/ae/c26d8da88ba2e438e9653a408b0c2ad6f17267801250a8f3cc6405a93a72/opentelemetry_resource_detector_azure-0.1.5-py3-none-any.whl", hash = "sha256:4dcc5d54ab5c3b11226af39509bc98979a8b9e0f8a24c1b888783755d3bf00eb", size = 14252, upload-time = "2024-05-16T21:54:57.208Z" }, ] [[package]] name = "opentelemetry-sdk" -version = "1.41.1" +version = "1.40.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "opentelemetry-semantic-conventions" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/58/d0/54ee30dab82fb0acda23d144502771ff76ef8728459c83c3e89ef9fb1825/opentelemetry_sdk-1.41.1.tar.gz", hash = "sha256:724b615e1215b5aeacda0abb8a6a8922c9a1853068948bd0bd225a56d0c792e6", size = 230180, upload-time = "2026-04-24T13:15:50.991Z" } +sdist = { url = "https://files.pythonhosted.org/packages/58/fd/3c3125b20ba18ce2155ba9ea74acb0ae5d25f8cd39cfd37455601b7955cc/opentelemetry_sdk-1.40.0.tar.gz", hash = "sha256:18e9f5ec20d859d268c7cb3c5198c8d105d073714db3de50b593b8c1345a48f2", size = 184252, upload-time = "2026-03-04T14:17:31.87Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b4/e7/a1420b698aad018e1cf60fdbaaccbe49021fb415e2a0d81c242f4c518f54/opentelemetry_sdk-1.41.1-py3-none-any.whl", hash = "sha256:edee379c126c1bce952b0c812b48fe8ff35b30df0eecf17e98afa4d598b7d85d", size = 180213, upload-time = "2026-04-24T13:15:33.767Z" }, + { url = "https://files.pythonhosted.org/packages/2c/c5/6a852903d8bfac758c6dc6e9a68b015d3c33f2f1be5e9591e0f4b69c7e0a/opentelemetry_sdk-1.40.0-py3-none-any.whl", hash = "sha256:787d2154a71f4b3d81f20524a8ce061b7db667d24e46753f32a7bc48f1c1f3f1", size = 141951, upload-time = "2026-03-04T14:17:17.961Z" }, ] [[package]] name = "opentelemetry-semantic-conventions" -version = "0.62b1" +version = "0.61b0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9e/de/911ac9e309052aca1b20b2d5549d3db45d1011e1a610e552c6ccdd1b64f8/opentelemetry_semantic_conventions-0.62b1.tar.gz", hash = "sha256:c5cc6e04a7f8c7cdd30be2ed81499fa4e75bfbd52c9cb70d40af1f9cd3619802", size = 145750, upload-time = "2026-04-24T13:15:52.236Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6d/c0/4ae7973f3c2cfd2b6e321f1675626f0dab0a97027cc7a297474c9c8f3d04/opentelemetry_semantic_conventions-0.61b0.tar.gz", hash = "sha256:072f65473c5d7c6dc0355b27d6c9d1a679d63b6d4b4b16a9773062cb7e31192a", size = 145755, upload-time = "2026-03-04T14:17:32.664Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/eb/a6/83dc2ab6fa397ee66fba04fe2e74bdf7be3b3870005359ceb7689103c058/opentelemetry_semantic_conventions-0.62b1-py3-none-any.whl", hash = "sha256:cf506938103d331fbb78eded0d9788095f7fd59016f2bda813c3324e5a74a93c", size = 231620, upload-time = "2026-04-24T13:15:35.454Z" }, + { url = "https://files.pythonhosted.org/packages/b2/37/cc6a55e448deaa9b27377d087da8615a3416d8ad523d5960b78dbeadd02a/opentelemetry_semantic_conventions-0.61b0-py3-none-any.whl", hash = "sha256:fa530a96be229795f8cef353739b618148b0fe2b4b3f005e60e262926c4d38e2", size = 231621, upload-time = "2026-03-04T14:17:19.33Z" }, ] [[package]] @@ -2467,6 +2763,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/55/22/41fb05f1dc5fda2c468e05a41814c20859016c85117b66c8a257cae814f6/opentelemetry_semantic_conventions_ai-0.5.1-py3-none-any.whl", hash = "sha256:25aeb22bd261543b4898a73824026d96770e5351209c7d07a0b1314762b1f6e4", size = 11250, upload-time = "2026-03-26T14:20:37.108Z" }, ] +[[package]] +name = "opentelemetry-util-http" +version = "0.61b0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/3c/f0196223efc5c4ca19f8fad3d5462b171ac6333013335ce540c01af419e9/opentelemetry_util_http-0.61b0.tar.gz", hash = "sha256:1039cb891334ad2731affdf034d8fb8b48c239af9b6dd295e5fabd07f1c95572", size = 11361, upload-time = "2026-03-04T14:20:57.01Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/e5/c08aaaf2f64288d2b6ef65741d2de5454e64af3e050f34285fb1907492fe/opentelemetry_util_http-0.61b0-py3-none-any.whl", hash = "sha256:8e715e848233e9527ea47e275659ea60a57a75edf5206a3b937e236a6da5fc33", size = 9281, upload-time = "2026-03-04T14:20:08.364Z" }, +] + [[package]] name = "orderedmultidict" version = "1.0.2" @@ -2574,8 +2879,8 @@ name = "powerfx" version = "0.0.34" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cffi" }, - { name = "pythonnet" }, + { name = "cffi", marker = "python_full_version < '3.14'" }, + { name = "pythonnet", marker = "python_full_version < '3.14'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/9f/fb/6c4bf87e0c74ca1c563921ce89ca1c5785b7576bca932f7255cdf81082a7/powerfx-0.0.34.tar.gz", hash = "sha256:956992e7afd272657ed16d80f4cad24ec95d9e4a79fb9dfa4a068a09e136af32", size = 3237555, upload-time = "2025-12-22T15:50:59.682Z" } wheels = [ @@ -2613,6 +2918,8 @@ dependencies = [ { name = "azure-core" }, { name = "azure-cosmos" }, { name = "azure-identity" }, + { name = "azure-monitor-events-extension" }, + { name = "azure-monitor-opentelemetry" }, { name = "azure-storage-blob" }, { name = "azure-storage-file-datalake" }, { name = "azure-storage-queue" }, @@ -2646,6 +2953,8 @@ requires-dist = [ { name = "azure-core", specifier = "==1.38.0" }, { name = "azure-cosmos", specifier = "==4.15.0" }, { name = "azure-identity", specifier = "==1.26.0b1" }, + { name = "azure-monitor-events-extension", specifier = "==0.1.0" }, + { name = "azure-monitor-opentelemetry", specifier = "==1.8.7" }, { name = "azure-storage-blob", specifier = "==12.28.0" }, { name = "azure-storage-file-datalake", specifier = "==12.23.0" }, { name = "azure-storage-queue", specifier = "==12.15.0" }, @@ -3184,7 +3493,7 @@ name = "pythonnet" version = "3.0.5" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "clr-loader" }, + { name = "clr-loader", marker = "python_full_version < '3.14'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/9a/d6/1afd75edd932306ae9bd2c2d961d603dc2b52fcec51b04afea464f1f6646/pythonnet-3.0.5.tar.gz", hash = "sha256:48e43ca463941b3608b32b4e236db92d8d40db4c58a75ace902985f76dac21cf", size = 239212, upload-time = "2024-12-13T08:30:44.393Z" } wheels = [ @@ -3389,6 +3698,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0c/53/ddb8b8fa96367976cf52bb0610ffd529bd7d2795b2e4c1724724d071718c/requests-2.34.0.dev1-py3-none-any.whl", hash = "sha256:c8749aeb3c4b204f80fd288f7507378c9afe66a3f189fb43fd77ea33e74d7564", size = 73077, upload-time = "2026-05-03T20:21:40.509Z" }, ] +[[package]] +name = "requests-oauthlib" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "oauthlib" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/f2/05f29bc3913aea15eb670be136045bf5c5bbf4b99ecb839da9b422bb2c85/requests-oauthlib-2.0.0.tar.gz", hash = "sha256:b3dffaebd884d8cd778494369603a9e7b58d29111bf6b41bdc2dcd87203af4e9", size = 55650, upload-time = "2024-03-22T20:32:29.939Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/5d/63d4ae3b9daea098d5d6f5da83984853c1bbacd5dc826764b249fe119d24/requests_oauthlib-2.0.0-py2.py3-none-any.whl", hash = "sha256:7dd8a5c40426b779b0868c404bdef9768deccf22749cde15852df527e6269b36", size = 24179, upload-time = "2024-03-22T20:32:28.055Z" }, +] + [[package]] name = "requests-toolbelt" version = "1.0.0" @@ -3985,66 +4307,66 @@ wheels = [ [[package]] name = "wrapt" -version = "2.2.0rc11" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/78/d0/9c3b43631321c0fe61b9e2873b0542165a8f90393f49006f115d1e06eefc/wrapt-2.2.0rc11.tar.gz", hash = "sha256:fee2cf69591f32f16e5242ae4909bc9f43c66688c1f73f837c9c81313771ceba", size = 125088, upload-time = "2026-04-24T10:15:19.951Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cd/15/7b058ae7e7fe5bc042b3b0904a06a4038143113aca92684eed3e02f6a663/wrapt-2.2.0rc11-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7b9f264afa18c5fc12983698b4dcf66bdd521ff268f40d34db575b651c891d1e", size = 80950, upload-time = "2026-04-24T10:16:57.967Z" }, - { url = "https://files.pythonhosted.org/packages/b3/5e/605d3425b7533ee881ad4a3130699a7c48aba6e7134975438530ae7610a3/wrapt-2.2.0rc11-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b5eddef9db8eb34e277b2dcf9ab4bd7898fcd8246380516cc34180496655e335", size = 81604, upload-time = "2026-04-24T10:17:04.913Z" }, - { url = "https://files.pythonhosted.org/packages/9b/e2/91be7dcc9519fc35ce46b3b7955219ff99e219cd62eb43de89fa4d6653b4/wrapt-2.2.0rc11-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:469c67a6326a6a269f2cf391035bec7b4ab1aeac6acf56645e3b6c721a3153cc", size = 168642, upload-time = "2026-04-24T10:17:20.819Z" }, - { url = "https://files.pythonhosted.org/packages/f7/11/65135058543b659be3ae772c7510f56792968a752dfc617ac2577b0a5a5d/wrapt-2.2.0rc11-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7b4cd66a906667355bb72fbb7e2a7a1fe688671d6d68dad7efdbfa22ae165366", size = 170942, upload-time = "2026-04-24T10:16:25.138Z" }, - { url = "https://files.pythonhosted.org/packages/d3/f8/83a59d35982ef5f26a2f0301d2f642b2063be8d20ec276b718a3f951b52c/wrapt-2.2.0rc11-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5df0ac11591b554fb028c93586cfb6991e3cefd5d8d5d0b0f6881dd1b3c1814c", size = 159962, upload-time = "2026-04-24T10:15:58.563Z" }, - { url = "https://files.pythonhosted.org/packages/0a/1a/8f905774a951cc976d4a772c15329a3f542105265c24d6ce1e718d65dbaf/wrapt-2.2.0rc11-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d74d14da54197a9b35eb6992212f7cdd6106e3bc75e69e2a9dc031e6ec806d9f", size = 168785, upload-time = "2026-04-24T10:17:35.07Z" }, - { url = "https://files.pythonhosted.org/packages/c8/a3/61f54ea74c6d797271aa8e312bb5e0c98ef0cf39110aac863f579f2b5a83/wrapt-2.2.0rc11-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:b3c93ee7a1721234c00020662ecddab4fdd248fa8a2a12c9f0268c4bad085855", size = 158119, upload-time = "2026-04-24T10:16:54.622Z" }, - { url = "https://files.pythonhosted.org/packages/44/fc/4c36aaf560f273d53467a38fd91fda8ecc5aa5b4c96e495a9b33022d6daa/wrapt-2.2.0rc11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3ee95ef8d643ddc7fbb76d8a1bffde5bb8c0f8a707d821fa9e95fde76e870d5d", size = 167068, upload-time = "2026-04-24T10:16:40.351Z" }, - { url = "https://files.pythonhosted.org/packages/5a/6c/5aeae8600e23ddd5e31f096351912caf5548bbff3799f846799d6b86f9d3/wrapt-2.2.0rc11-cp312-cp312-win32.whl", hash = "sha256:36a2f254c1e183d4404d8b816d453f639c1422d9a374cffb6e9e90e5ccb2a40c", size = 77804, upload-time = "2026-04-24T10:16:21.994Z" }, - { url = "https://files.pythonhosted.org/packages/99/70/c39167f608e1c8b03bf607e8902cbd663961a007d9eb7e2847bd96ce696c/wrapt-2.2.0rc11-cp312-cp312-win_amd64.whl", hash = "sha256:c1f27bb9866a53445fe28dabb5e0770c8a625f00072537f5981afd08f5188e64", size = 80769, upload-time = "2026-04-24T10:16:56.208Z" }, - { url = "https://files.pythonhosted.org/packages/29/91/ecfff0b6dbdd1598b347baacbe6c57f9b4ffc67bf737618baef8cc3be36a/wrapt-2.2.0rc11-cp312-cp312-win_arm64.whl", hash = "sha256:bf0e904769d96b1d68971ae4015771e2ceecf1cbba2dff606468cc312444a258", size = 79038, upload-time = "2026-04-24T10:15:25.801Z" }, - { url = "https://files.pythonhosted.org/packages/53/b8/de8018f7bafa5b550654f3eb564645c7c5e7bf853e0c3f90546a7ec49e54/wrapt-2.2.0rc11-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8ca1d5666fc5a26452ff369085b605a8f791532eed00c62af272ea330d636c16", size = 80773, upload-time = "2026-04-24T10:16:06.936Z" }, - { url = "https://files.pythonhosted.org/packages/49/56/2afbe0bdb0f31a6a29292eadd037ebe4d2ad5a9dc51cd375b5e82589d332/wrapt-2.2.0rc11-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f759fff25b56481ae16aadb0c91dfcd77ff1e66be6da3bd5664766ed4adde59f", size = 81303, upload-time = "2026-04-24T10:16:23.695Z" }, - { url = "https://files.pythonhosted.org/packages/15/c5/e30fe36a5f5f4a4dbc3bceba9d0fa5271ec8d7639bc59f096bac47dfd198/wrapt-2.2.0rc11-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0d4445e090535c73af539aad56f681d8a0e40bc02d4717d5ea1c39fcbf367bc1", size = 166728, upload-time = "2026-04-24T10:15:27.724Z" }, - { url = "https://files.pythonhosted.org/packages/f5/b0/cae82622e6e834eaa40b21a16918d7cc8bec32550d2cc9aa0d386b8697f5/wrapt-2.2.0rc11-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eb8690e53d5801bf8a79446dd3898a957f34d2384593daf78dd181fda716319e", size = 166782, upload-time = "2026-04-24T10:17:16.491Z" }, - { url = "https://files.pythonhosted.org/packages/4c/41/20556af3b9f9c605c0eac3432c724135064b6ed4a21b475dd8666c636276/wrapt-2.2.0rc11-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:25ef8e95c76c82821342336b1979e11583ff0bca1d4402e9a7f9a9b689d81f65", size = 157843, upload-time = "2026-04-24T10:15:31.307Z" }, - { url = "https://files.pythonhosted.org/packages/15/d2/8711d5c936d8328dbaf650291f59a979ce14ff49b52d8b6aabfa8cfb8acb/wrapt-2.2.0rc11-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9d4d055acfe55633ff260d8c268a230ddf6cd408ac5a3bacfaeaa59ca13431cb", size = 165748, upload-time = "2026-04-24T10:17:03.385Z" }, - { url = "https://files.pythonhosted.org/packages/86/fa/f4dc4f9b1c8c8d61b7efbf4d53e1aa6193073199749a796115f2d8f1b0e6/wrapt-2.2.0rc11-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:4d97d727dcd414391c476dfdfb77ebe45082d9499d8b1cbc1149a96991d5e2b2", size = 156532, upload-time = "2026-04-24T10:16:20.511Z" }, - { url = "https://files.pythonhosted.org/packages/75/4d/4d876bf16b89278b269550aece6520406d58669f0651e6852b3c0293336e/wrapt-2.2.0rc11-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9af734e2a6d48ef84e5c6f48e5b18d6e5904447aa9298d1e3457ba9d8865689d", size = 165901, upload-time = "2026-04-24T10:15:53.283Z" }, - { url = "https://files.pythonhosted.org/packages/4e/6a/54c60b18148ca3d017137de3f902d308bdb4ec31f5a1f9465f03afc98724/wrapt-2.2.0rc11-cp313-cp313-win32.whl", hash = "sha256:ce1dbe00d7aad0205d469f85b69f5dc1532e8e85f4989cc1258376c6b98d8246", size = 77763, upload-time = "2026-04-24T10:17:31.486Z" }, - { url = "https://files.pythonhosted.org/packages/a1/8d/9c7e7d01704e8a2b6d49c4ccc641a4dca8f37700f22a888fd3ba5937bc41/wrapt-2.2.0rc11-cp313-cp313-win_amd64.whl", hash = "sha256:abfb2f5f455783a03390a5b020efa7590a0c9a9059cc5b6a6badb5f15dad38c9", size = 80678, upload-time = "2026-04-24T10:17:23.695Z" }, - { url = "https://files.pythonhosted.org/packages/a4/1a/645faa10a61f8d75806fa2031f509fd0720142a1f24cedb7e79bcc97c03a/wrapt-2.2.0rc11-cp313-cp313-win_arm64.whl", hash = "sha256:3b3efc5b5325e7f63982a52246f58b2c3eb0afc360017dad22485cfc7ecc6b40", size = 79036, upload-time = "2026-04-24T10:16:26.444Z" }, - { url = "https://files.pythonhosted.org/packages/1c/fa/806052da337fc153603eb461c6ffd67af4ec4e52adaa60a882af2a0d786c/wrapt-2.2.0rc11-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:923490056004b62079d810a8214457329c4bcf9eecf6e3a3508fd387c0d0ed4c", size = 82706, upload-time = "2026-04-24T10:17:22.198Z" }, - { url = "https://files.pythonhosted.org/packages/16/60/ee692cec34f3b91e904e358dc29b4554d00ef47aef71cad1166a89f3f1d4/wrapt-2.2.0rc11-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:785f93eb4a8a417ab732338708dcf0d2cca3ac5d1df0b749ba9d79a7d9d8c3fa", size = 83277, upload-time = "2026-04-24T10:15:29.471Z" }, - { url = "https://files.pythonhosted.org/packages/51/01/4ea8f9e9098277dce4953f2ac2cfac7e9dff3b48de312685c5b8cb4ab237/wrapt-2.2.0rc11-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f49955a6ad62c2011d2049d4cb80d903aa7fa0c75a4a092a0e12d26f6234d005", size = 203709, upload-time = "2026-04-24T10:17:24.938Z" }, - { url = "https://files.pythonhosted.org/packages/7e/ea/a03697f3b18b5bb07c304ba16027707dcf9beded31c8f0898db0b969c9ba/wrapt-2.2.0rc11-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:00fc51db6261d47b97b12d2b377347c15e3ffccc3a18bc1ac2bc296e420088aa", size = 209622, upload-time = "2026-04-24T10:17:06.603Z" }, - { url = "https://files.pythonhosted.org/packages/4b/06/e41ef42c16adb7ad783d03854d6ca90353780d468f21ff9b52a1ffa772d8/wrapt-2.2.0rc11-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8d4cc5fceffc47390dc1ce0acd2163e7d6d1f145ee57d489e7f099d1876c6e2e", size = 194637, upload-time = "2026-04-24T10:15:09.927Z" }, - { url = "https://files.pythonhosted.org/packages/23/8c/099a117f155423dd0be7d90c46b63757fb6e6d62f71950c36fcb966040b4/wrapt-2.2.0rc11-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:60b7ac5a746713f034d3b0e6c65f215d90f872b170b82efa118df8af31838799", size = 205321, upload-time = "2026-04-24T10:16:31.031Z" }, - { url = "https://files.pythonhosted.org/packages/33/8f/0b652dd807fd25769cc8b19fe82bc39bcf65b225c69be038a92c3d5e7518/wrapt-2.2.0rc11-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:cb85f8211d1bda71cf8b6f9a425e0824573a799c293a6b79e906c33d7fb296ba", size = 192095, upload-time = "2026-04-24T10:16:32.605Z" }, - { url = "https://files.pythonhosted.org/packages/62/61/fb913f197e647fbbc49399c3fbb9addc1f6cee953185da1014bbad6c5c2e/wrapt-2.2.0rc11-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:eb3dd184ab1ad3f4b0692a599dc91ebdf2e4a689e8d4a73014b5c7e333c8ee8d", size = 199180, upload-time = "2026-04-24T10:16:08.82Z" }, - { url = "https://files.pythonhosted.org/packages/47/82/7cb3d889191203250d3ab9335fac20c0eb70ea25146d68789d0ff74a7eb1/wrapt-2.2.0rc11-cp313-cp313t-win32.whl", hash = "sha256:08dc26d3d7a6efb21fdd644b91235a509ad9cdaa158a25fb5c3eff0d64e31450", size = 79380, upload-time = "2026-04-24T10:15:11.961Z" }, - { url = "https://files.pythonhosted.org/packages/33/ee/63cbd676d011a78077ad03062c3b32deeac07a52bf61078b4a2964fc006b/wrapt-2.2.0rc11-cp313-cp313t-win_amd64.whl", hash = "sha256:3883b31768f3381c96b8f59c75cf0f3070b457d44e2ed9ed41896f0725df4a35", size = 82963, upload-time = "2026-04-24T10:15:43.899Z" }, - { url = "https://files.pythonhosted.org/packages/5a/7c/dfd8135e5988740eff4e41a87155508db28bc5877c0fdc27942354304d31/wrapt-2.2.0rc11-cp313-cp313t-win_arm64.whl", hash = "sha256:9dd990000f133f2961a5bfee6a4aad07ad075792122df8e797b0854d31373a58", size = 80224, upload-time = "2026-04-24T10:17:14.992Z" }, - { url = "https://files.pythonhosted.org/packages/6d/61/fbf6a0f4193b9beef222a14638d176d346532971bc7df499d120538e71ce/wrapt-2.2.0rc11-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:6decf7275b26ed3397b4a3beefe2436ebd75e2348c15f75e3a5223e65231a1d7", size = 80817, upload-time = "2026-04-24T10:17:17.818Z" }, - { url = "https://files.pythonhosted.org/packages/af/5c/02ee0ddd25f2e8d7f1b61646858ea48748c08603d38b45192b32c2bc4765/wrapt-2.2.0rc11-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:21686c1d2625346c90a6a8abb019ae2e985f77b51d4b28be9290dcbde0036f81", size = 81398, upload-time = "2026-04-24T10:16:41.631Z" }, - { url = "https://files.pythonhosted.org/packages/0c/a6/41ff243e781d127e429f79f2e8ecd907efeb0bb990412b7bb05c945ef57d/wrapt-2.2.0rc11-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5481f1406125cc9cdffd8c054e1ba45213f58a28d62cb5854654bc37dbc1ffb9", size = 166614, upload-time = "2026-04-24T10:16:37.217Z" }, - { url = "https://files.pythonhosted.org/packages/68/28/47ae8e1bfe412762f08b97a824ee7d2e4bb9284951a1e280921fe112c414/wrapt-2.2.0rc11-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbc9681f2adaf789cf04688430169969c206c9b67904feba092cea53377f0919", size = 166215, upload-time = "2026-04-24T10:15:05.466Z" }, - { url = "https://files.pythonhosted.org/packages/cf/c0/67b6f568ae1858983c1702f303be4bb009bc551b3a48c2e52161bd60056e/wrapt-2.2.0rc11-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fb24cc8134bd03be435e0272c692fbe7450658939291501c3496c65f155c1b7b", size = 157651, upload-time = "2026-04-24T10:15:33.278Z" }, - { url = "https://files.pythonhosted.org/packages/f6/48/88982438be70262037eaca70dd128f03abd9600694d114c8671e8cde4c78/wrapt-2.2.0rc11-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:294f8ed73cc4f498150903553f50f582772cc194c72fc7c60382c7de30410ecf", size = 165992, upload-time = "2026-04-24T10:16:18.995Z" }, - { url = "https://files.pythonhosted.org/packages/80/32/fa7f70286cdc235af0239535d8ec5da4c2049c83e0ec2b2d6c44d89231eb/wrapt-2.2.0rc11-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:8bd9c2b5d8f799aca53a0a1a8f81355447c42b00826f93fc7a1ca20325c2139e", size = 156394, upload-time = "2026-04-24T10:15:35.033Z" }, - { url = "https://files.pythonhosted.org/packages/9b/f7/b58a85a4fd651ad540eda37eedcbe3a4abdc70c1981ea2674eee8b0f005d/wrapt-2.2.0rc11-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7c4076d31907715869df3a97366d114e02f909d3e41ce0b1c3b6b00df82a6226", size = 165448, upload-time = "2026-04-24T10:17:37.199Z" }, - { url = "https://files.pythonhosted.org/packages/f1/87/904307947657b2b1cce7304968c69e72fa6195e87435288e970942e8a385/wrapt-2.2.0rc11-cp314-cp314-win32.whl", hash = "sha256:d0fe901e422671d45c09bd1a8a5f36130eeea1711ec10a0c5e017c7af4a4d044", size = 78284, upload-time = "2026-04-24T10:17:19.081Z" }, - { url = "https://files.pythonhosted.org/packages/7f/06/d0de22123f64259518baa385b2e7fc8c5913547cca37072174f4bc2f6f23/wrapt-2.2.0rc11-cp314-cp314-win_amd64.whl", hash = "sha256:8109f72963b6b6e15fa8511be18bbb3a369f5033b444b5b97c853deb813b0553", size = 81086, upload-time = "2026-04-24T10:16:38.819Z" }, - { url = "https://files.pythonhosted.org/packages/b4/b2/44f0e04cadb1f57890235ed2aa57e2519518ccbb1d1bb88bcaf80cc18693/wrapt-2.2.0rc11-cp314-cp314-win_arm64.whl", hash = "sha256:51c87d3285669347383705118347b7f446cdc23cb13cc4b0baed5b04032df106", size = 79516, upload-time = "2026-04-24T10:16:14.585Z" }, - { url = "https://files.pythonhosted.org/packages/7e/b8/015cd6157537d9c80f60783fc6df2240af3b12b382732ab7eeecb46febff/wrapt-2.2.0rc11-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:703b2f8c21d1be1027742ba4f34536f5b5717e34077bb04e09b205eb6c493a3a", size = 82801, upload-time = "2026-04-24T10:15:54.77Z" }, - { url = "https://files.pythonhosted.org/packages/2e/ba/cb228a7c98be16d4920b5230693cadceb3feadbd6e658466dc79f0de0049/wrapt-2.2.0rc11-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1bfe526ca947c4d830bb0a18caabc5d1aee52a7714cfe898981434a2e03f1002", size = 83276, upload-time = "2026-04-24T10:16:12.756Z" }, - { url = "https://files.pythonhosted.org/packages/0e/b7/15976c633431310c955c2a935211b734e236136d9f4475e2b5212536dadc/wrapt-2.2.0rc11-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:700978189597d950cf7714fb50923afa5c98f931da804bafbc5b41d83dcbb0a8", size = 203698, upload-time = "2026-04-24T10:15:56.75Z" }, - { url = "https://files.pythonhosted.org/packages/6a/71/45592fa1517ddabb5ddef0331f4938077e3c672e59de5a352341579e4349/wrapt-2.2.0rc11-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:78a7447b83cfb007b2b09e7f32131b43a9a662072701fed68cec42a835025214", size = 209628, upload-time = "2026-04-24T10:16:43.389Z" }, - { url = "https://files.pythonhosted.org/packages/95/b5/86f46e4a1c7cfbe456984be10593b5a871aa69e853b3ef5640021e3d4f0d/wrapt-2.2.0rc11-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4853b4ed7c806985bc366a5b3600b83a7c7c4609f8ea5599df45ddc94a32db94", size = 194677, upload-time = "2026-04-24T10:17:09.417Z" }, - { url = "https://files.pythonhosted.org/packages/1b/61/28184784b6ea7b17e6bd5b3253055665c907feb1fbacc7633908b9e82738/wrapt-2.2.0rc11-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:77cc036f79eaf72861329bab07f180b9ca192e3b17d17f3466b88b4f04372b33", size = 205291, upload-time = "2026-04-24T10:15:39.848Z" }, - { url = "https://files.pythonhosted.org/packages/af/c7/8afd82fc060d1e958a958c0be505cf983da0f7949b05a55c9cc8c1847490/wrapt-2.2.0rc11-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:bd6dc7339f6eb2b3e5556125d202bb2172ea8c9ebe68f0abbca67e6e1661a3c8", size = 192127, upload-time = "2026-04-24T10:16:05.053Z" }, - { url = "https://files.pythonhosted.org/packages/c6/80/18ae952432ffec22ae9e1f37cec4570fb3f321c83d05527813dae31fcc26/wrapt-2.2.0rc11-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b5623b1f2495cae98baadb2f4e4f37323128050c43b7e994047cf3618a5227af", size = 199157, upload-time = "2026-04-24T10:15:16.586Z" }, - { url = "https://files.pythonhosted.org/packages/f8/94/291693ae8e6706a08ed5e9368d883f14da8aab408bfa88117f4945c0db7c/wrapt-2.2.0rc11-cp314-cp314t-win32.whl", hash = "sha256:e6f4e23aadd29401414ae9c8ee12189cf93ceac63814bb7c2e54e38d42b1da79", size = 80146, upload-time = "2026-04-24T10:16:10.093Z" }, - { url = "https://files.pythonhosted.org/packages/40/08/cee79e056b80f510bf30a86b2f44649a2aa07e0331e77afa226df18ab9d6/wrapt-2.2.0rc11-cp314-cp314t-win_amd64.whl", hash = "sha256:4c03de92788b3b9f7d862212d93c8b8f19328a97f1371e9c8560ce6178b21d48", size = 83770, upload-time = "2026-04-24T10:17:32.965Z" }, - { url = "https://files.pythonhosted.org/packages/3e/53/8f4348643e9b3fef1efede571b0f3aa282846e73b1e2bd16289d9cbba180/wrapt-2.2.0rc11-cp314-cp314t-win_arm64.whl", hash = "sha256:be23d203b7cbbf35147efae0db17feffee59d540138989cd3838c233505db8a3", size = 80650, upload-time = "2026-04-24T10:16:11.574Z" }, - { url = "https://files.pythonhosted.org/packages/42/d9/bee80519aaf88101996d653050e6d78aa3a63d87d6f735fd63955414f7c9/wrapt-2.2.0rc11-py3-none-any.whl", hash = "sha256:48a0ea119e937ec94452b4b6a4301bb6a435f18262298e141cc49b7e495df782", size = 60936, upload-time = "2026-04-24T10:16:48.108Z" }, +version = "1.17.4rc1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/61/e0/c6c3e66c6ca371728de87b44102b61f3fdacc03c8b0b1e4ac5f30d71c5ce/wrapt-1.17.4rc1.tar.gz", hash = "sha256:19c0363cb46f42cf5536c7b9d9c921cc1ae24e55fe4d45c3a19315e9f2aa8964", size = 55653, upload-time = "2026-03-06T05:27:09.446Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/09/f4b0c4c5098ee0a4e89542d259f2ce2a15124efb43cbd0aae442d284d4f8/wrapt-1.17.4rc1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3ed597307c29facdfe47eb5521123b121d189a2bbae3e34dfaf10a1f8ebb9bc1", size = 39033, upload-time = "2026-03-06T05:28:00.852Z" }, + { url = "https://files.pythonhosted.org/packages/c6/61/779692b7228a9e2f430edc2137737821f7e249f73be30d589ddc3c92532c/wrapt-1.17.4rc1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:97a6d6b40c2347fc6ea5017c715a4ac0a29716ae17b70060f24c1ca22757289a", size = 39296, upload-time = "2026-03-06T05:27:42.668Z" }, + { url = "https://files.pythonhosted.org/packages/9d/20/6cf5d4cae58fd19a0b89f977aaac957795930123c917d44536d6a04c0745/wrapt-1.17.4rc1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c5e9e219bd65d89356da8af2168fb23e3480736949ffad617d6d73a16039a5dc", size = 88141, upload-time = "2026-03-06T05:27:44.037Z" }, + { url = "https://files.pythonhosted.org/packages/77/fb/eba8b87158819858fcd2a6d1c80276a22085292866554bb82faa731e042f/wrapt-1.17.4rc1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:647fae8af1ac1789023ba267fd84522096db737a522597b53fbf3fe2b45482db", size = 88256, upload-time = "2026-03-06T05:27:40.31Z" }, + { url = "https://files.pythonhosted.org/packages/a4/b5/d547471fd5eb77280157f70698ae5e91913d6fecc1bc2eb9a90ccb7e27f1/wrapt-1.17.4rc1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4d8ed3e2538fbacd8b62462c58676aabba38ca8e9e8ad6c11ed94ec0db926e29", size = 84248, upload-time = "2026-03-06T05:28:15.363Z" }, + { url = "https://files.pythonhosted.org/packages/46/ce/cc8e75f1bcc230031037940cf33e0361fca3229296ebf706459598ef00da/wrapt-1.17.4rc1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ae3dabc85555f5a8330b324d466d577f3cc60669150fe8e381719b5b680113b0", size = 87208, upload-time = "2026-03-06T05:28:23.392Z" }, + { url = "https://files.pythonhosted.org/packages/4a/62/f879b4f4c320049708ba5e02fda0756dd93c78e8831ef881323074594404/wrapt-1.17.4rc1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:e27719a9b75517191cbe23e5f54dd410f39076d6e8369c259a1b990c6ac924f4", size = 83645, upload-time = "2026-03-06T05:21:23.051Z" }, + { url = "https://files.pythonhosted.org/packages/35/c8/ff8bf340cf45aaf300ac864772085a1fac27a265b6565081da91294b0176/wrapt-1.17.4rc1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c53fcd7cf09a223eaab9f425dda6e38929f4534112df0def102ffa5ef9da6086", size = 87828, upload-time = "2026-03-06T05:21:20.84Z" }, + { url = "https://files.pythonhosted.org/packages/2e/ed/e3c750db3c19eae7bcd2506291d85595127637b37e180be72cf65951f779/wrapt-1.17.4rc1-cp312-cp312-win32.whl", hash = "sha256:7062f45cc386554e94521da25cf1b89b65e72ff5e1b62c2c6735a5c4dfe61b19", size = 36803, upload-time = "2026-03-06T05:21:07.939Z" }, + { url = "https://files.pythonhosted.org/packages/58/07/a58ebce46e2258989c4147b26bcf4926aeebc9aea2a21f581a3a6a4ba3ac/wrapt-1.17.4rc1-cp312-cp312-win_amd64.whl", hash = "sha256:aaf599f8535cbc8c7c016763e72486cfeae933382f23b2c1b632952bee4f11ae", size = 38968, upload-time = "2026-03-06T05:21:12.013Z" }, + { url = "https://files.pythonhosted.org/packages/c3/52/f3a464bc629690b8d0551be8187fdbce57e26337eabc11acda67c2bf18bd/wrapt-1.17.4rc1-cp312-cp312-win_arm64.whl", hash = "sha256:488c903c475c54ef062f6a2c0c49dffd608d501bc8d05e061ff19eb794f31fb2", size = 36940, upload-time = "2026-03-06T05:26:57.759Z" }, + { url = "https://files.pythonhosted.org/packages/94/c9/bfb0840b9d1a3e9478c9d6bd1b5e2fb82fdca7c046bc10e8c44f9273cd46/wrapt-1.17.4rc1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5d1a4c658bea05c1b22ae374f74c25b400535f3dccbf795b121153d5628216f0", size = 39037, upload-time = "2026-03-06T05:28:18.986Z" }, + { url = "https://files.pythonhosted.org/packages/41/82/1e234ad6b64cd705557a0a682dbdce499db082a1932f9c95f200ed0843da/wrapt-1.17.4rc1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:491d11b84ac47568ee88777304c42d047d33307ec82162235d7e8261ee983eaa", size = 39295, upload-time = "2026-03-06T05:28:26.945Z" }, + { url = "https://files.pythonhosted.org/packages/f2/d9/2143f5825ef49046b376a2d9136621a7aa66a9e93ccc82b162d9e79ab678/wrapt-1.17.4rc1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:16426870299de6370b93760a50ae5bc813548f4666e6e515dcce3ec7601b9c59", size = 88175, upload-time = "2026-03-06T05:27:25.018Z" }, + { url = "https://files.pythonhosted.org/packages/f3/dd/8add4d24770a2e960f2bb8cb062a83f880a6aa91664b01d6de1e62917e45/wrapt-1.17.4rc1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a35158b0bf2c2d2033eba3c56832e803d73658dc4e92f14f1ea4c92ab0dfaafe", size = 88320, upload-time = "2026-03-06T05:27:37.675Z" }, + { url = "https://files.pythonhosted.org/packages/9c/34/c47fd4837b07b9f8ae8cfe749ea0d6fe5ea506c2d324850f3067f5f66ca2/wrapt-1.17.4rc1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a33bff65de96bc32f7f1df1492c2808068070ed0f42f1fcef2b47846f6a6a03a", size = 84302, upload-time = "2026-03-06T05:21:09.738Z" }, + { url = "https://files.pythonhosted.org/packages/9d/b0/20542954e5929383f55da30d4b9a47764866a2d253d8bea0a5d366ea1e7c/wrapt-1.17.4rc1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:931dae558932c8ba8e4de77ce92ed505fe5a8fd9dab66a2cbbc9d5d3a3a32bb4", size = 87210, upload-time = "2026-03-06T05:27:39.024Z" }, + { url = "https://files.pythonhosted.org/packages/d6/ff/37a5295d7f01b270186191035f67142f3052882210f673b9a62f82fcfc9f/wrapt-1.17.4rc1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:42ea3a5f62f5980031aaf6e28074cb17cea8df06cb828bcd2882d525f7ccc2f9", size = 83709, upload-time = "2026-03-06T05:27:17.469Z" }, + { url = "https://files.pythonhosted.org/packages/05/ff/11f668fba8ab6436c3a0167d0dc2aacb1c9fca675c1268a83d9106457b0b/wrapt-1.17.4rc1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b302dc5e126057f74b82223c3b19a41dfeead10292667be1538985ef75034f3b", size = 87866, upload-time = "2026-03-06T05:27:21.872Z" }, + { url = "https://files.pythonhosted.org/packages/fe/41/b7e49896146dd95fc8e9ecda84ede824c5d34105dd84aa5f4e108a0c137a/wrapt-1.17.4rc1-cp313-cp313-win32.whl", hash = "sha256:27bf0d37ebcd4a43e8369eaf60dd9ea45f30933a921453f61bd6476ffe39bbfb", size = 36810, upload-time = "2026-03-06T05:28:25.862Z" }, + { url = "https://files.pythonhosted.org/packages/86/8d/ba014ec122b07b6441eb9ed341514045a4c79677186623733be460c379b3/wrapt-1.17.4rc1-cp313-cp313-win_amd64.whl", hash = "sha256:22e85eab852e7182c41acef5f9d95d5d63a1b115910951fb38feccf67b514818", size = 38977, upload-time = "2026-03-06T05:26:55.473Z" }, + { url = "https://files.pythonhosted.org/packages/f4/ef/6561940fba308d086f5967827c63bce7dbf8c54717bc33c7f523f0018400/wrapt-1.17.4rc1-cp313-cp313-win_arm64.whl", hash = "sha256:5be27331b6eae2317350c4adee1cf92edc0866cd7db726f574f10c8db227c134", size = 36944, upload-time = "2026-03-06T05:27:00.79Z" }, + { url = "https://files.pythonhosted.org/packages/58/3e/1ab40e5f926d0650fd5b7e23cebcdd4eab6bed961ac6e7ed5307638ddd27/wrapt-1.17.4rc1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:474a45ee2dfa6bb8c1a2a63fbc91c53da010caece85464a334fdb9aabafb6ecb", size = 40438, upload-time = "2026-03-06T05:27:58.196Z" }, + { url = "https://files.pythonhosted.org/packages/af/26/8d288da55259592a9aff160af4192db56799a74d3389ce032f54c8c8b74c/wrapt-1.17.4rc1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3dcf7b65ca203123c8613ae609441812b53ae047495e72b0dc423e5d31510128", size = 40586, upload-time = "2026-03-06T05:27:03.329Z" }, + { url = "https://files.pythonhosted.org/packages/bf/d4/cd7b78cb59ea4d348a77906dfac3d30ed1c598732d9ee3cd8edcf7762bca/wrapt-1.17.4rc1-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1713dac1faf01465058481dd07f7632847ca8867e77347527788aff0bdb32d8a", size = 108627, upload-time = "2026-03-06T05:27:28.884Z" }, + { url = "https://files.pythonhosted.org/packages/0b/26/6ae3790d46b56010f01dd74a207af7aebb7357b95487e222d1a6ad912f84/wrapt-1.17.4rc1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:094bef74a0ef4c04416775a4f1965b2a29847d6aafa935229c1bf9d18f1d8c58", size = 113179, upload-time = "2026-03-06T05:27:48.59Z" }, + { url = "https://files.pythonhosted.org/packages/9f/0e/c0b0b05de9ebf705ca8daa1e86c20a244ce0862f08faf1b23784d3abf766/wrapt-1.17.4rc1-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9e1f828a32b4e71b6349a00a0a3bcc9e41413e0005160fb70601b83cb171ce6e", size = 103238, upload-time = "2026-03-06T05:21:24.306Z" }, + { url = "https://files.pythonhosted.org/packages/ec/33/b2cd9f6b86bf322cb1711c6070b9efa6b28a8e8c063f56d165b30c8d1668/wrapt-1.17.4rc1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e24a05dae0ba49ce5f490bffc4e369a5770663c789c0bc862de8ac235b18394d", size = 110742, upload-time = "2026-03-06T05:27:46.881Z" }, + { url = "https://files.pythonhosted.org/packages/9c/f4/b9709eea1e0087c8ccb1c7a38076a76ec3eb6f0555f74c3a65eaadf5c987/wrapt-1.17.4rc1-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:7d00e2453975a6519cbdde4812234ab0183860011aae2316acbad46f3b8e84e9", size = 102364, upload-time = "2026-03-06T05:27:51.288Z" }, + { url = "https://files.pythonhosted.org/packages/87/c5/bd4a00aef4d4b1a7eff25456b2f9c15de8ec9a3f4ccf98f0acdb2c48c879/wrapt-1.17.4rc1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:89b5fe920975e4e63099aeb194b51ad0ad84b45995dada353aa1e5a551462fd0", size = 107013, upload-time = "2026-03-06T05:28:03.308Z" }, + { url = "https://files.pythonhosted.org/packages/86/73/aedee294890bde90b262b21156c20eb36450ee812a20384ea5df9ba49bbd/wrapt-1.17.4rc1-cp313-cp313t-win32.whl", hash = "sha256:c94efd8ca87b9333590b6ee0384a0863ad92b54646232396c3c8043b0d115d49", size = 38129, upload-time = "2026-03-06T05:27:12.074Z" }, + { url = "https://files.pythonhosted.org/packages/96/17/dbf146893d31705872d2e515cd2ef70e01e305aa441a1736cdeee856deb9/wrapt-1.17.4rc1-cp313-cp313t-win_amd64.whl", hash = "sha256:db3ea738ffd95b88a5874ed6c7d26ffad1b482a5b8036e7b4b667926d3d5d728", size = 40751, upload-time = "2026-03-06T05:27:18.843Z" }, + { url = "https://files.pythonhosted.org/packages/70/a1/2bafa54d3621ca0c8a0b7cd78150d6239e83553f8f2bf8e6fc17286bac34/wrapt-1.17.4rc1-cp313-cp313t-win_arm64.whl", hash = "sha256:d8f67707f553821691228bf3596bf60cf83e112c230ca4ebfae759feed20cf57", size = 38262, upload-time = "2026-03-06T05:26:54.324Z" }, + { url = "https://files.pythonhosted.org/packages/84/e2/203c4a94a4f2cb5bd1b2180261f213b6ecf386839d9c4a7b03b187e1d973/wrapt-1.17.4rc1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:4384529d0f82bcdebec1d01f7b714b31ea34ee1b43a8399df5ed0db443bf6551", size = 39210, upload-time = "2026-03-06T05:21:13.2Z" }, + { url = "https://files.pythonhosted.org/packages/b9/de/0f3940df4cf001cc79cfd321c7e7856e6cdeac4c53b8292b4d318884a9be/wrapt-1.17.4rc1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d665e1f4bdeb551c55a56fe076f3da2aa4acea9b5108723adf4347b9af17bb70", size = 39339, upload-time = "2026-03-06T05:28:28.027Z" }, + { url = "https://files.pythonhosted.org/packages/28/87/1b13a950ad90919078951cadc8c8418241f55f6355bc1b64420072453d2f/wrapt-1.17.4rc1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:95be0b13dcde68f73921026c66b4bb464a299683365a7243b5db49f220e5463f", size = 87262, upload-time = "2026-03-06T05:27:30.624Z" }, + { url = "https://files.pythonhosted.org/packages/00/a9/c3015e3929b715ae2737eb332dc5e056bb0a3a450d26dca962dc93da8a32/wrapt-1.17.4rc1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b7e86063ed1d5b46e2c6ac7c3c8c9bb1b47e47d3ceb804a93f566d1294810505", size = 88061, upload-time = "2026-03-06T05:27:33.243Z" }, + { url = "https://files.pythonhosted.org/packages/15/8f/83d676e926c2c6390e6019aacb3f598c929426d67d1d97d3ed26536a0ac9/wrapt-1.17.4rc1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c710707166eed80e37242d754a204f4c07b8f3ab8024b07d583f48024d260a05", size = 84543, upload-time = "2026-03-06T05:28:12.622Z" }, + { url = "https://files.pythonhosted.org/packages/87/8d/f48862187bcee1d7d0a6c2c8cf4830ecd9e06bf0d770e6efbd2a78b70dad/wrapt-1.17.4rc1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c85cf9d6017e5188697a5947dd76f29ba1c56707ea612173b1b1ee1bc27b9601", size = 87050, upload-time = "2026-03-06T05:27:31.958Z" }, + { url = "https://files.pythonhosted.org/packages/b3/34/1e3c265902f02b3c1644568be86ddc3cf0d76552723ae71b7ca11e10bdc3/wrapt-1.17.4rc1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:44edeaf45e144c2de1102427530790c32eeb0084451f7816a58d744d077e0b3c", size = 83965, upload-time = "2026-03-06T05:27:08.164Z" }, + { url = "https://files.pythonhosted.org/packages/ca/4c/24a7c0fa058212cb53a7f582c9631b1b9ce9d5a81400095c745a1cb7a4be/wrapt-1.17.4rc1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:201acefeff4fc6d497f411595c46f79eb91e562fa4883847db8148474a1e3d80", size = 86958, upload-time = "2026-03-06T05:28:24.737Z" }, + { url = "https://files.pythonhosted.org/packages/99/85/445569dc31ee7a23c199afae532a41cc2f446d434d288e7544b1a38fbd19/wrapt-1.17.4rc1-cp314-cp314-win32.whl", hash = "sha256:73016054d0e32a65fa5da708e839be3036c786416adca00a0444aec5837b1b83", size = 37276, upload-time = "2026-03-06T05:28:21.776Z" }, + { url = "https://files.pythonhosted.org/packages/fb/a8/1636a670886dec6c59fa60a8112fc3fd56c194b23b07106dbee465af73c2/wrapt-1.17.4rc1-cp314-cp314-win_amd64.whl", hash = "sha256:66b0485668cff7bfac0eaccccb3a991dba3f0d5205d6bc5a9c69aa120b2b6ccf", size = 39405, upload-time = "2026-03-06T05:26:51.717Z" }, + { url = "https://files.pythonhosted.org/packages/b9/5e/9f820a1d60ea579b048a8486c319918fdf06b83cc37f67f8dd4c53b80df6/wrapt-1.17.4rc1-cp314-cp314-win_arm64.whl", hash = "sha256:2712e6caad2a5032d6496612eeca5cdb65fadd6da55c5f931d556ac656e3ebdd", size = 37367, upload-time = "2026-03-06T05:27:23.446Z" }, + { url = "https://files.pythonhosted.org/packages/14/92/617f98da4517f2bf2a63b1a929f5bec029292d6bd31c7fd79ee25d54635e/wrapt-1.17.4rc1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:3102bbdc650a7e8fd8672e51c6d204688fc75257e2d3c6a12172a8e05c2ab0cd", size = 40565, upload-time = "2026-03-06T05:26:50.47Z" }, + { url = "https://files.pythonhosted.org/packages/6b/80/8c4444c471d90f9cfe1b453e5bf605fccadb2d3399d2ed60ed3240c188b3/wrapt-1.17.4rc1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a3ef8f9aad3593f3b00527da3815e15941caf169c51da5da18e64d1949da3f29", size = 40585, upload-time = "2026-03-06T05:21:14.419Z" }, + { url = "https://files.pythonhosted.org/packages/a5/fb/c3938d7fef6ce445d32e5a757268adc4e5c298d1985dff95c535e1ceca38/wrapt-1.17.4rc1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:033b67f5cc44d992221617ea6be6f12d8857b90a5d0901738f4f6c92498d3298", size = 108671, upload-time = "2026-03-06T05:28:16.715Z" }, + { url = "https://files.pythonhosted.org/packages/ad/54/d5ae3c39c871ff63c973848558c1657fa09cf84c19e5242e25f57e8b251a/wrapt-1.17.4rc1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b6b3c400c7c7b6346e9d3d22f036443ff033fa924d472715d127f169e8f9e137", size = 113193, upload-time = "2026-03-06T05:27:16.153Z" }, + { url = "https://files.pythonhosted.org/packages/18/c0/37f69e1231e8cfd3e642ff24f002cd71cbe477fca2abe6ec43978426f09a/wrapt-1.17.4rc1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8b3a9ed0f966b6a199e251800f5ee895bb41694ad1bb92f19446cbb90e68cdec", size = 103256, upload-time = "2026-03-06T05:27:52.645Z" }, + { url = "https://files.pythonhosted.org/packages/e0/5b/71f5f63bb3c4bfa909ae320ebcf290250cd86207d54cdffc3b12c1a57b8a/wrapt-1.17.4rc1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:076702de22f5df07bfaeb67ac750aabe2167fd703ed60ac8e2edb42a082119e8", size = 110756, upload-time = "2026-03-06T05:26:59.375Z" }, + { url = "https://files.pythonhosted.org/packages/fe/52/6ef9887520e0038cacb97bfd4375a83e3cf947d82a11e4017af2a98647cb/wrapt-1.17.4rc1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:1374e2051eff90875b3331dc5930209807db9e03ba863c2a9009ab7ba77daa7c", size = 102369, upload-time = "2026-03-06T05:26:52.912Z" }, + { url = "https://files.pythonhosted.org/packages/8b/95/670237dcee12fb293cb4674f93db112806783a33cc8cc18fa64214c12614/wrapt-1.17.4rc1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6a02b14dfc3ded8f1be82d824628ccda63ac37d1833c8328adf7a6b019f6a230", size = 107045, upload-time = "2026-03-06T05:27:06.879Z" }, + { url = "https://files.pythonhosted.org/packages/1a/15/2ecc4112171d195ff1c4f0baf7d345ca5f0ec464381bc7024857b3db47d5/wrapt-1.17.4rc1-cp314-cp314t-win32.whl", hash = "sha256:2bdf836e6c8e8f26c85716c08a0063309a2d9362e090b499f32fc4de8f2c651d", size = 38809, upload-time = "2026-03-06T05:21:15.397Z" }, + { url = "https://files.pythonhosted.org/packages/d7/45/81fec744e8c88f6255a5ccc317997a01b1a08fa925b211e2078fa8bfbddf/wrapt-1.17.4rc1-cp314-cp314t-win_amd64.whl", hash = "sha256:f75df0a7f1dab354cd092ee9c466efb3556f87ecf103683cecc0f7488e9dbf77", size = 41427, upload-time = "2026-03-06T05:28:17.885Z" }, + { url = "https://files.pythonhosted.org/packages/3d/72/d6ecf86cb5f3574a55fd2ba58c6eca447bee90a8757f1f32fba4b14ff9d5/wrapt-1.17.4rc1-cp314-cp314t-win_arm64.whl", hash = "sha256:3e2f5e602d656b53118bfdc9d5d94b840069f1753923e48726f0bc02dd65deb8", size = 38531, upload-time = "2026-03-06T05:27:57.157Z" }, + { url = "https://files.pythonhosted.org/packages/29/b2/367cc462b6ad84bfb7a93b00f5c4b01c7bc880a0e7ce36c1a3900eee153a/wrapt-1.17.4rc1-py3-none-any.whl", hash = "sha256:9cc3fb27bc5f564895c967b9b06dd2b799ee107b33a7f8ad8b8346b5d6b35b60", size = 23719, upload-time = "2026-03-06T05:27:55.715Z" }, ] [[package]] From 727bbb7515d9917addef342cdb3929820dd485c8 Mon Sep 17 00:00:00 2001 From: llPriyanka <40491221+llPriyanka@users.noreply.github.com> Date: Thu, 18 Jun 2026 11:17:34 +0000 Subject: [PATCH 12/12] created new uv lock file --- src/processor/uv.lock | 1647 +++++++++++++++++++++-------------------- 1 file changed, 829 insertions(+), 818 deletions(-) diff --git a/src/processor/uv.lock b/src/processor/uv.lock index 190ec21c..d9e98a2f 100644 --- a/src/processor/uv.lock +++ b/src/processor/uv.lock @@ -18,7 +18,7 @@ overrides = [ [[package]] name = "a2a-sdk" -version = "1.0.2" +version = "1.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "culsans", marker = "python_full_version < '3.13'" }, @@ -31,21 +31,21 @@ dependencies = [ { name = "protobuf" }, { name = "pydantic" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/88/f3/1c312eae0298542eef1a096be378a3ad2d20b171ea0ac6be26b81f542720/a2a_sdk-1.0.2.tar.gz", hash = "sha256:e4ee4dd509894c32c9a6df728319875fa4f049e70ae82476fa447353e3a4b648", size = 375193, upload-time = "2026-04-24T13:50:24.303Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/7e/8ac10bbf8b15b16574355f39b17dbdf617a282c27b41c7ff2116e30336df/a2a_sdk-1.1.0.tar.gz", hash = "sha256:e8102dad1b36709dbdc3d19319e38e6dfa3b3a79c30416030eb2d482576be204", size = 375726, upload-time = "2026-05-29T09:34:43.015Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c9/03/58c92a44e7b94a42614880df2365f074969e47067c4c736e31e855aca2fd/a2a_sdk-1.0.2-py3-none-any.whl", hash = "sha256:4dbc083b6808ee28207ac6daad263360f87612c37b2d06f5521efb530318141c", size = 234302, upload-time = "2026-04-24T13:50:22.412Z" }, + { url = "https://files.pythonhosted.org/packages/d4/ea/3a5b160cfd51c67759b08748051094d9365ceff18127633d0021950c9860/a2a_sdk-1.1.0-py3-none-any.whl", hash = "sha256:d7f5846caf18033d8bf3108b11ec827dd8dd32f867c98848ede0e39474be93be", size = 241886, upload-time = "2026-05-29T09:34:41.484Z" }, ] [[package]] name = "ag-ui-protocol" -version = "0.1.18" +version = "0.1.19" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4c/d7/5711eada86da9bd7684e58645653a1693ef20b66cc3efbb1deeafef80f8d/ag_ui_protocol-0.1.18.tar.gz", hash = "sha256:b37c672c3fd6bac12b316c39f45ad9db9f137bbb885489c79f268507029a22ff", size = 9937, upload-time = "2026-04-21T20:44:59.151Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a7/10/4ad299267a7d04b89935aa99eef62979758fcf95aee9f8bb5d70c35b1be1/ag_ui_protocol-0.1.19.tar.gz", hash = "sha256:43c27f60d41712dcad0e9e0a203cbdf1c8e248b22417374c5c68321c448af4ea", size = 10720, upload-time = "2026-06-02T17:26:15.627Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d8/74/913c9b8fc566c6da650aecbddf25a5d8186b54138df265eb9eb546f56141/ag_ui_protocol-0.1.18-py3-none-any.whl", hash = "sha256:d151c0f0a34160647f1571163f7185746f4326b15a56d1560de5082a7a0e7a12", size = 12607, upload-time = "2026-04-21T20:45:00.097Z" }, + { url = "https://files.pythonhosted.org/packages/4c/0a/bcad8116eb058e4b4a305e3fc37ebd7efc879deeb86b854f1c5b8b6e97dd/ag_ui_protocol-0.1.19-py3-none-any.whl", hash = "sha256:898843b1410d378824da0c6a776486288b9c5828689d0bf563118868e37f390f", size = 13490, upload-time = "2026-06-02T17:26:16.313Z" }, ] [[package]] @@ -136,8 +136,8 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "agent-framework-core" }, { name = "agent-framework-durabletask" }, - { name = "azure-functions", version = "1.26.0b1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, - { name = "azure-functions", version = "2.2.0b2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, + { name = "azure-functions", version = "1.26.0b3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, + { name = "azure-functions", version = "2.2.0b5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, { name = "azure-functions-durable" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c4/0e/59c4c45c380b4d0dcfb71be45ec60a8d52b271979b5cf9e5be1f9e974653/agent_framework_azurefunctions-1.0.0b260130.tar.gz", hash = "sha256:b6a971036c7088a61e5079549f11e0c7972b955452bdb6d576769ed8da27b920", size = 16340, upload-time = "2026-01-30T19:01:06.649Z" } @@ -324,14 +324,14 @@ wheels = [ [[package]] name = "aiofile" -version = "3.9.0" +version = "3.11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "caio" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/67/e2/d7cb819de8df6b5c1968a2756c3cb4122d4fa2b8fc768b53b7c9e5edb646/aiofile-3.9.0.tar.gz", hash = "sha256:e5ad718bb148b265b6df1b3752c4d1d83024b93da9bd599df74b9d9ffcf7919b", size = 17943, upload-time = "2024-10-08T10:39:35.846Z" } +sdist = { url = "https://files.pythonhosted.org/packages/48/41/2fea7e193e061ce54eacc3b7bc0e6a99e4fcff43c78cf0a76dd781ed8334/aiofile-3.11.1.tar.gz", hash = "sha256:1f91912c6643d2a4e49ca4ae3514f0bf3867ce948a36d99a6411b8f4755f4cf9", size = 19342, upload-time = "2026-05-16T08:18:33.538Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/50/25/da1f0b4dd970e52bf5a36c204c107e11a0c6d3ed195eba0bfbc664c312b2/aiofile-3.9.0-py3-none-any.whl", hash = "sha256:ce2f6c1571538cbdfa0143b04e16b208ecb0e9cb4148e528af8a640ed51cc8aa", size = 19539, upload-time = "2024-10-08T10:39:32.955Z" }, + { url = "https://files.pythonhosted.org/packages/67/cd/0d76dfc5de72bde52f55f53e925c7d152d9c7906634ec1e0cbc7e8d4ad93/aiofile-3.11.1-py3-none-any.whl", hash = "sha256:ce77d14ac07f77bc2b757834a5c129321f3f705c474593deed5ab209079a52c9", size = 20446, upload-time = "2026-05-16T08:18:32.051Z" }, ] [[package]] @@ -345,11 +345,11 @@ wheels = [ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" 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, upload-time = "2025-03-12T01:42:48.764Z" } +sdist = { url = "https://files.pythonhosted.org/packages/33/c6/61a2d7b7572279226bb2e7f61d7a19ca7c90da0329c93fa0d560cbf288d8/aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64", size = 22591, upload-time = "2026-05-20T15:12:24.631Z" } wheels = [ - { 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" }, + { url = "https://files.pythonhosted.org/packages/5f/fc/a7bf5b6e4e617b45f90f2d9d2a68519c249c81dd4fc2658c7a2a61c4f4b7/aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4", size = 15062, upload-time = "2026-05-20T15:12:23.328Z" }, ] [[package]] @@ -439,16 +439,16 @@ wheels = [ [[package]] name = "aiologic" -version = "0.16.0" +version = "0.17.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "sniffio", marker = "python_full_version < '3.13'" }, + { name = "sniffio" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, - { name = "wrapt", marker = "python_full_version < '3.13'" }, + { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a8/13/50b91a3ea6b030d280d2654be97c48b6ed81753a50286ee43c646ba36d3c/aiologic-0.16.0.tar.gz", hash = "sha256:c267ccbd3ff417ec93e78d28d4d577ccca115d5797cdbd16785a551d9658858f", size = 225952, upload-time = "2025-11-27T23:48:41.195Z" } +sdist = { url = "https://files.pythonhosted.org/packages/53/a7/809482759f40079f4c4328c7318bf569ae25d457f5017aad30a1b9aafedc/aiologic-0.17.0.tar.gz", hash = "sha256:65aa058e858c94cd208badb188e7f00b54dcabb3ba85b34f794db98074d108b9", size = 251625, upload-time = "2026-06-14T12:24:35.367Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f6/27/206615942005471499f6fbc36621582e24d0686f33c74b2d018fcfd4fe67/aiologic-0.16.0-py3-none-any.whl", hash = "sha256:e00ce5f68c5607c864d26aec99c0a33a83bdf8237aa7312ffbb96805af67d8b6", size = 135193, upload-time = "2025-11-27T23:48:40.099Z" }, + { url = "https://files.pythonhosted.org/packages/9e/6b/5f75d6194b597ac32bbdbb7b524a28fb1fa98bd0ddcefce94b313a818cc0/aiologic-0.17.0-py3-none-any.whl", hash = "sha256:1bf4d3e4314df2bcb06a9e696417204e206ab50e10ec98d28d157e2e57634f74", size = 161084, upload-time = "2026-06-14T12:24:34.146Z" }, ] [[package]] @@ -484,7 +484,7 @@ wheels = [ [[package]] name = "anthropic" -version = "0.98.1" +version = "0.109.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -496,22 +496,22 @@ dependencies = [ { name = "sniffio" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/24/60/a9e4426dfe594e5eec8a9757d48e3d8dcf529a0a35a4fc8aefa352bd95fe/anthropic-0.98.1.tar.gz", hash = "sha256:62205edec42f5877df63d58be8e9443843d3e032215836e228fba1f59514a433", size = 725085, upload-time = "2026-05-04T21:40:39.496Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/b7/9a8e2f79011e89dd6eeb599c27332aed765dac9d6fbee3a55e68e4e3ec25/anthropic-0.109.2.tar.gz", hash = "sha256:d37db299597c7bc124b49b767ff135f1e6456b64af2b2fad4b63b2a1df333cf0", size = 927559, upload-time = "2026-06-15T17:30:25.024Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9b/6f/7f7f80f714e6de0784518f1999f71fd632076aefd3e22fe0ccd27ca9571f/anthropic-0.98.1-py3-none-any.whl", hash = "sha256:107ebf954415382fdcea6a94f9cf334a53199ad64794403590dc55366cefcc28", size = 699604, upload-time = "2026-05-04T21:40:41.311Z" }, + { url = "https://files.pythonhosted.org/packages/d1/f2/bee5de8a2699fc8a3cce34d61c7a2626a2c310ddde7ea5611327eb0ddbe9/anthropic-0.109.2-py3-none-any.whl", hash = "sha256:e0fb4ca5df0ed983248c9c6c3242adc81d9cfddb8725902da53698554117abac", size = 923800, upload-time = "2026-06-15T17:30:23.124Z" }, ] [[package]] name = "anyio" -version = "4.13.0" +version = "4.14.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "idna" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/19/14/2c5dd9f512b66549ae92767a9c7b330ae88e1932ca57876909410251fe13/anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc", size = 231622, upload-time = "2026-03-24T12:59:09.671Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/b5/001890774a9552aff22502b8da382593109ce0c95314abaebbb116567545/anyio-4.14.0.tar.gz", hash = "sha256:b47c1f9ccf73e67021df785332508f99379c68fa7d0684e8e3492cb1d4b23f89", size = 253586, upload-time = "2026-06-15T22:00:49.021Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708", size = 114353, upload-time = "2026-03-24T12:59:08.246Z" }, + { url = "https://files.pythonhosted.org/packages/ba/16/9826f089383c593cdfc4a6e5aca94d9e91ae1692c57af82c3b2aa5e810f7/anyio-4.14.0-py3-none-any.whl", hash = "sha256:dd9b7a2a9799ed6552fde617b2c5df02b7fdd7d88392fc48101e51bae46164d9", size = 123506, upload-time = "2026-06-15T22:00:47.595Z" }, ] [[package]] @@ -683,7 +683,7 @@ wheels = [ [[package]] name = "azure-functions" -version = "1.26.0b1" +version = "1.26.0b3" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version < '3.13'", @@ -691,14 +691,14 @@ resolution-markers = [ dependencies = [ { name = "werkzeug", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/30/a1/dcc891ab6595a9933b92559eeaac7a322b02efc3bc2b8c46bdefb96cba2b/azure_functions-1.26.0b1.tar.gz", hash = "sha256:f12d33858e91f84a03369fd3c35a9edf6d7ff2c33314e7de5f78e81cb3473e1a", size = 152503, upload-time = "2026-04-14T15:28:20.678Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e1/8d/fb56179e6e550e222446444c7d519c78d3c94760cc877d1062bd264ac744/azure_functions-1.26.0b3.tar.gz", hash = "sha256:e40684bed006f03473df8d87407bee4666e89f4805242481bf7ecca304909b67", size = 168636, upload-time = "2026-06-02T16:18:39.082Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/91/3d/702c510f5aec4331de1df3acbbc0bf4574a04209422e142d1430f8671478/azure_functions-1.26.0b1-py3-none-any.whl", hash = "sha256:4101cec4e129ac492b3d4658c9d4f22b04154c854fd1ef75c0faafdbb60c2796", size = 117544, upload-time = "2026-04-14T15:28:19.535Z" }, + { url = "https://files.pythonhosted.org/packages/da/88/82c1df422d82e279d17db33a4989f117f87d5b8076203918f3108dbad1b9/azure_functions-1.26.0b3-py3-none-any.whl", hash = "sha256:75454ff884fb422831c42fba0bf464c3535dc73433e13edadf7a7fa96853aeea", size = 124908, upload-time = "2026-06-02T16:18:37.658Z" }, ] [[package]] name = "azure-functions" -version = "2.2.0b2" +version = "2.2.0b5" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14'", @@ -707,9 +707,9 @@ resolution-markers = [ dependencies = [ { name = "werkzeug", marker = "python_full_version >= '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/77/b9/71c0ba96e1951ad41c677d93c22041b8f56ed121734c6210c57b235e6073/azure_functions-2.2.0b2.tar.gz", hash = "sha256:51097332654ede6be3ed53a7fb9512f5323f222940dcc966827962f70e7ea6af", size = 155316, upload-time = "2026-04-22T17:13:38.71Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b7/17/8385e0cf4dd0f919cd407093519ae8a2a903569707c426bdddbe338380de/azure_functions-2.2.0b5.tar.gz", hash = "sha256:693767f6ee067d9afdbd20a47b24a9cfd0ab74cacfd04f8b3764fd2c5a845e95", size = 168505, upload-time = "2026-06-01T19:11:48.673Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fe/3e/12391f538cf695dc3b824cef35e50794895bafabe886b167c84dd0b53354/azure_functions-2.2.0b2-py3-none-any.whl", hash = "sha256:22705ceddc50af72d551fba90cefe59453f2cc5f354340c1f61058be7784a8cd", size = 119590, upload-time = "2026-04-22T17:13:36.742Z" }, + { url = "https://files.pythonhosted.org/packages/e8/6a/565464f20e9ae6b497a09e386c6988b90c8cb8da8364a680993a68c49f4a/azure_functions-2.2.0b5-py3-none-any.whl", hash = "sha256:a1035ce6bb04ccde7016225a3411f2d5eed4ad5f5c2aa855c1189676866b4aa8", size = 124883, upload-time = "2026-06-01T19:11:47.339Z" }, ] [[package]] @@ -718,8 +718,8 @@ version = "1.5.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohttp" }, - { name = "azure-functions", version = "1.26.0b1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, - { name = "azure-functions", version = "2.2.0b2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, + { name = "azure-functions", version = "1.26.0b3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, + { name = "azure-functions", version = "2.2.0b5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, { name = "furl" }, { name = "opentelemetry-api" }, { name = "opentelemetry-sdk" }, @@ -786,7 +786,7 @@ wheels = [ [[package]] name = "azure-monitor-opentelemetry-exporter" -version = "1.0.0b52" +version = "1.0.0b53" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "azure-core" }, @@ -796,9 +796,9 @@ dependencies = [ { name = "opentelemetry-sdk" }, { name = "psutil" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7c/7e/bfc03436b88c48f5adc21a3ebbf4392b6b7fbbfe33ef3b1e88d07ba9f380/azure_monitor_opentelemetry_exporter-1.0.0b52.tar.gz", hash = "sha256:7eac679fca32dee9e426df65f2a538161db4514fc322fc66107f7826567d86e1", size = 326179, upload-time = "2026-05-11T22:47:02.687Z" } +sdist = { url = "https://files.pythonhosted.org/packages/05/64/875f13849fe2e3832ceda6a218fa5422a25e72c1b86623a8514f541a8c60/azure_monitor_opentelemetry_exporter-1.0.0b53.tar.gz", hash = "sha256:1274e9008909414a25c6287185a6c5a884209705b6e651a1ffddfbdab3b76e52", size = 335614, upload-time = "2026-06-08T15:54:22.683Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a4/e8/d13e6a74c98ecc3011bce9ab09fc2e75aec48ab46288f72be57c2fa21460/azure_monitor_opentelemetry_exporter-1.0.0b52-py2.py3-none-any.whl", hash = "sha256:a38c503e5e2cc0ec8a4bf336b23cce23488719f5361a45cdd01a514080f0e7fc", size = 244751, upload-time = "2026-05-11T22:47:04.304Z" }, + { url = "https://files.pythonhosted.org/packages/5f/dc/6dbff881ac739999bc31f166504ed31a2e0fef45cc59a3c1b2386a4b4abe/azure_monitor_opentelemetry_exporter-1.0.0b53-py2.py3-none-any.whl", hash = "sha256:e2faadaf203369f25cb96df2a75017bd7f11655b033200c5867151f8fb2a6ba7", size = 249226, upload-time = "2026-06-08T15:54:24.413Z" }, ] [[package]] @@ -881,11 +881,11 @@ wheels = [ [[package]] name = "cachetools" -version = "7.1.1" +version = "7.1.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ff/e2/85f227594656000ff4d8adadae91a21f536d4a84c6c716a86bd6685874be/cachetools-7.1.1.tar.gz", hash = "sha256:27bdf856d68fd3c71c26c01b5edc312124ed427524d1ddb31aa2b7746fe20d4b", size = 40202, upload-time = "2026-05-03T20:00:29.391Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f4/8b/0d3945a13955303b81272f759a0331e54c5c793da455e6f5706b89d2639c/cachetools-7.1.4.tar.gz", hash = "sha256:437f55a4e0c1b01a4f3077cc470e6991d47430970e36fbcb77e2be0df4fc1cd6", size = 40085, upload-time = "2026-05-21T22:40:43.376Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bf/0f/f897abe4ea0a8c408ae65c8c83bffab4936ad65d6032d4fb4cd35bbdc3ee/cachetools-7.1.1-py3-none-any.whl", hash = "sha256:0335cd7a0952d2b22327441fb0628139e234c565559eeb91a8a4ac7551c5353d", size = 16775, upload-time = "2026-05-03T20:00:27.857Z" }, + { url = "https://files.pythonhosted.org/packages/8c/7b/1fc1c09cc0756cf25861a3be10565915953876da48bb228fb9a672b20a42/cachetools-7.1.4-py3-none-any.whl", hash = "sha256:323dc4127934744db5b54eb4924482d7edafbf9554e820d1531c2e08c0e4ef54", size = 16761, upload-time = "2026-05-21T22:40:41.845Z" }, ] [[package]] @@ -911,11 +911,11 @@ wheels = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.6.17" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/25/ee/6caf7a40c36a1220410afe15a1cc64993a1f864871f698c0f93acb72842a/certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580", size = 137077, upload-time = "2026-04-22T11:26:11.191Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/c7/424b75da314c1045981bd9777432fad05a9e0c69daa4ed7e308bbaffe405/certifi-2026.6.17.tar.gz", hash = "sha256:024c88eeec92ca068db80f02b8b07c9cef7b9fe261d1d535abfd5abd6f6af432", size = 134594, upload-time = "2026-06-17T10:31:07.894Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/22/30/7cd8fdcdfbc5b869528b079bfb76dcdf6056b1a2097a662e5e8c04f42965/certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a", size = 135707, upload-time = "2026-04-22T11:26:09.372Z" }, + { url = "https://files.pythonhosted.org/packages/ef/2f/c5464532e965badff2f4c4c1a3a83f5697f0d7c407ed0cda44aaa99bb451/certifi-2026.6.17-py3-none-any.whl", hash = "sha256:2227dcbaafe0d2f59279d1762ddddc37783ed4354594f194ffc31d20f41fc3db", size = 133289, upload-time = "2026-06-17T10:31:06.348Z" }, ] [[package]] @@ -1059,14 +1059,14 @@ wheels = [ [[package]] name = "click" -version = "8.3.3" +version = "8.4.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/bb/63/f9e1ea081ce35720d8b92acde70daaedace594dc93b693c869e0d5910718/click-8.3.3.tar.gz", hash = "sha256:398329ad4837b2ff7cbe1dd166a4c0f8900c3ca3a218de04466f38f6497f18a2", size = 328061, upload-time = "2026-04-22T15:11:27.506Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9b/98/518d8e5081007684232226f475082b30087d0f585e8457db087298259f49/click-8.4.1.tar.gz", hash = "sha256:918b5633eddf6b41c32d4f454bf0de810065c74e3f7dbf8ee5452f8be88d3e96", size = 353007, upload-time = "2026-05-22T04:08:37.769Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ae/44/c1221527f6a71a01ec6fbad7fa78f1d50dfa02217385cf0fa3eec7087d59/click-8.3.3-py3-none-any.whl", hash = "sha256:a2bf429bb3033c89fa4936ffb35d5cb471e3719e1f3c8a7c3fff0b8314305613", size = 110502, upload-time = "2026-04-22T15:11:25.044Z" }, + { url = "https://files.pythonhosted.org/packages/c7/0d/67e5b4109ea4a837e80daa87c2c696711955e40449a97e8926672534def2/click-8.4.1-py3-none-any.whl", hash = "sha256:482be17c6991b8c19c5429a1e995d9b0efdbb63172824c41f99965dc0ade8ec2", size = 116639, upload-time = "2026-05-22T04:08:35.26Z" }, ] [[package]] @@ -1074,7 +1074,7 @@ name = "clr-loader" version = "0.2.10" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cffi", marker = "python_full_version < '3.14'" }, + { name = "cffi" }, ] sdist = { url = "https://files.pythonhosted.org/packages/18/24/c12faf3f61614b3131b5c98d3bf0d376b49c7feaa73edca559aeb2aee080/clr_loader-0.2.10.tar.gz", hash = "sha256:81f114afbc5005bafc5efe5af1341d400e22137e275b042a8979f3feb9fc9446", size = 83605, upload-time = "2026-01-03T23:13:06.984Z" } wheels = [ @@ -1092,55 +1092,52 @@ wheels = [ [[package]] name = "cryptography" -version = "48.0.0" +version = "49.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9f/a9/db8f313fdcd85d767d4973515e1db101f9c71f95fced83233de224673757/cryptography-48.0.0.tar.gz", hash = "sha256:5c3932f4436d1cccb036cb0eaef46e6e2db91035166f1ad6505c3c9d5a635920", size = 832984, upload-time = "2026-05-04T22:59:38.133Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/df/3d/01f6dd9190170a5a241e0e98c2d04be3664a9e6f5b9b872cde63aff1c3dd/cryptography-48.0.0-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:0c558d2cdffd8f4bbb30fc7134c74d2ca9a476f830bb053074498fbc86f41ed6", size = 8001587, upload-time = "2026-05-04T22:57:36.803Z" }, - { url = "https://files.pythonhosted.org/packages/b2/6e/e90527eef33f309beb811cf7c982c3aeffcce8e3edb178baa4ca3ae4a6fa/cryptography-48.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f5333311663ea94f75dd408665686aaf426563556bb5283554a3539177e03b8c", size = 4690433, upload-time = "2026-05-04T22:57:40.373Z" }, - { url = "https://files.pythonhosted.org/packages/90/04/673510ed51ddff56575f306cf1617d80411ee76831ccd3097599140efdfe/cryptography-48.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7995ef305d7165c3f11ae07f2517e5a4f1d5c18da1376a0a9ed496336b69e5f3", size = 4710620, upload-time = "2026-05-04T22:57:42.935Z" }, - { url = "https://files.pythonhosted.org/packages/14/d5/e9c4ef932c8d800490c34d8bd589d64a31d5890e27ec9e9ad532be893294/cryptography-48.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:40ba1f85eaa6959837b1d51c9767e230e14612eea4ef110ee8854ada22da1bf5", size = 4696283, upload-time = "2026-05-04T22:57:45.294Z" }, - { url = "https://files.pythonhosted.org/packages/0c/29/174b9dfb60b12d59ecfc6cfa04bc88c21b42a54f01b8aae09bb6e51e4c7f/cryptography-48.0.0-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:369a6348999f94bbd53435c894377b20ab95f25a9065c283570e70150d8abc3c", size = 5296573, upload-time = "2026-05-04T22:57:47.933Z" }, - { url = "https://files.pythonhosted.org/packages/95/38/0d29a6fd7d0d1373f0c0c88a04ba20e359b257753ac497564cd660fc1d55/cryptography-48.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a0e692c683f4df67815a2d258b324e66f4738bd7a96a218c826dce4f4bd05d8f", size = 4743677, upload-time = "2026-05-04T22:57:50.067Z" }, - { url = "https://files.pythonhosted.org/packages/30/be/eef653013d5c63b6a490529e0316f9ac14a37602965d4903efed1399f32b/cryptography-48.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:18349bbc56f4743c8b12dc32e2bccb2cf83ee8b69a3bba74ef8ae857e26b3d25", size = 4330808, upload-time = "2026-05-04T22:57:52.301Z" }, - { url = "https://files.pythonhosted.org/packages/84/9e/500463e87abb7a0a0f9f256ec21123ecde0a7b5541a15e840ea54551fd81/cryptography-48.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:7e8eac43dfca5c4cccc6dad9a80504436fca53bb9bc3100a2386d730fbe6b602", size = 4695941, upload-time = "2026-05-04T22:57:54.603Z" }, - { url = "https://files.pythonhosted.org/packages/e3/dc/7303087450c2ec9e7fbb750e17c2abfbc658f23cbd0e54009509b7cc4091/cryptography-48.0.0-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:9ccdac7d40688ecb5a3b4a604b8a88c8002e3442d6c60aead1db2a89a041560c", size = 5252579, upload-time = "2026-05-04T22:57:57.207Z" }, - { url = "https://files.pythonhosted.org/packages/d0/c0/7101d3b7215edcdc90c45da544961fd8ed2d6448f77577460fa75a8443f7/cryptography-48.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:bd72e68b06bb1e96913f97dd4901119bc17f39d4586a5adf2d3e47bc2b9d58b5", size = 4743326, upload-time = "2026-05-04T22:57:59.535Z" }, - { url = "https://files.pythonhosted.org/packages/ac/d8/5b833bad13016f562ab9d063d68199a4bd121d18458e439515601d3357ec/cryptography-48.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:59baa2cb386c4f0b9905bd6eb4c2a79a69a128408fd31d32ca4d7102d4156321", size = 4826672, upload-time = "2026-05-04T22:58:01.996Z" }, - { url = "https://files.pythonhosted.org/packages/98/e1/7074eb8bf3c135558c73fc2bcf0f5633f912e6fb87e868a55c454080ef09/cryptography-48.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9249e3cd978541d665967ac2cb2787fd6a62bddf1e75b3e347a594d7dacf4f74", size = 4972574, upload-time = "2026-05-04T22:58:03.968Z" }, - { url = "https://files.pythonhosted.org/packages/04/70/e5a1b41d325f797f39427aa44ef8baf0be500065ab6d8e10369d850d4a4f/cryptography-48.0.0-cp311-abi3-win32.whl", hash = "sha256:9c459db21422be75e2809370b829a87eb37f74cd785fc4aa9ea1e5f43b47cda4", size = 3294868, upload-time = "2026-05-04T22:58:06.467Z" }, - { url = "https://files.pythonhosted.org/packages/f4/ac/8ac51b4a5fc5932eb7ee5c517ba7dc8cd834f0048962b6b352f00f41ebf9/cryptography-48.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:5b012212e08b8dd5edc78ef54da83dd9892fd9105323b3993eff6bea65dc21d7", size = 3817107, upload-time = "2026-05-04T22:58:08.845Z" }, - { url = "https://files.pythonhosted.org/packages/6b/84/70e3feea9feea87fd7cbe77efb2712ae1e3e6edf10749dc6e95f4e60e455/cryptography-48.0.0-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:3cb07a3ed6431663cd321ea8a000a1314c74211f823e4177fefa2255e057d1ec", size = 7986556, upload-time = "2026-05-04T22:58:11.172Z" }, - { url = "https://files.pythonhosted.org/packages/89/6e/18e07a618bb5442ba10cf4df16e99c071365528aa570dfcb8c02e25a303b/cryptography-48.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8c7378637d7d88016fa6791c159f698b3d3eed28ebf844ac36b9dc04a14dae18", size = 4684776, upload-time = "2026-05-04T22:58:13.712Z" }, - { url = "https://files.pythonhosted.org/packages/be/6a/4ea3b4c6c6759794d5ee2103c304a5076dc4b19ae1f9fe47dba439e159e9/cryptography-48.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc90c0b39b2e3c65ef52c804b72e3c58f8a04ab2a1871272798e5f9572c17d20", size = 4698121, upload-time = "2026-05-04T22:58:16.448Z" }, - { url = "https://files.pythonhosted.org/packages/2f/59/6ff6ad6cae03bb887da2a5860b2c9805f8dac969ef01ce563336c49bd1d1/cryptography-48.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:76341972e1eff8b4bea859f09c0d3e64b96ce931b084f9b9b7db8ef364c30eff", size = 4690042, upload-time = "2026-05-04T22:58:18.544Z" }, - { url = "https://files.pythonhosted.org/packages/ca/b4/fc334ed8cfd705aca282fe4d8f5ae64a8e0f74932e9feecb344610cf6e4d/cryptography-48.0.0-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:55b7718303bf06a5753dcdccf2f3945cf18ad7bffde41b61226e4db31ab89a9c", size = 5282526, upload-time = "2026-05-04T22:58:20.75Z" }, - { url = "https://files.pythonhosted.org/packages/11/08/9f8c5386cc4cd90d8255c7cdd0f5baf459a08502a09de30dc51f553d38dc/cryptography-48.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:a64697c641c7b1b2178e573cbc31c7c6684cd56883a478d75143dbb7118036db", size = 4733116, upload-time = "2026-05-04T22:58:23.627Z" }, - { url = "https://files.pythonhosted.org/packages/b8/77/99307d7574045699f8805aa500fa0fb83422d115b5400a064ddd306d7750/cryptography-48.0.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:561215ea3879cb1cbbf272867e2efda62476f240fb58c64de6b393ae19246741", size = 4316030, upload-time = "2026-05-04T22:58:25.581Z" }, - { url = "https://files.pythonhosted.org/packages/fd/36/a608b98337af3cb2aff4818e406649d30572b7031918b04c87d979495348/cryptography-48.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:ad64688338ed4bc1a6618076ba75fd7194a5f1797ac60b47afe926285adb3166", size = 4689640, upload-time = "2026-05-04T22:58:27.747Z" }, - { url = "https://files.pythonhosted.org/packages/dd/a6/825010a291b4438aecc1f568bc428189fc1175515223632477c07dc0a6df/cryptography-48.0.0-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:906cbf0670286c6e0044156bc7d4af9cbb0ef6db9f73e52c3ec56ba6bdde5336", size = 5237657, upload-time = "2026-05-04T22:58:29.848Z" }, - { url = "https://files.pythonhosted.org/packages/b9/09/4e76a09b4caa29aad535ddc806f5d4c5d01885bd978bd984fbc6ca032cae/cryptography-48.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:ea8990436d914540a40ab24b6a77c0969695ed52f4a4874c5137ccf7045a7057", size = 4732362, upload-time = "2026-05-04T22:58:32.009Z" }, - { url = "https://files.pythonhosted.org/packages/18/78/444fa04a77d0cb95f417dda20d450e13c56ba8e5220fc892a1658f44f882/cryptography-48.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c18684a7f0cc9a3cb60328f496b8e3372def7c5d2df39ac267878b05565aaaae", size = 4819580, upload-time = "2026-05-04T22:58:34.254Z" }, - { url = "https://files.pythonhosted.org/packages/38/85/ea67067c70a1fd4be2c63d35eeed82658023021affccc7b17705f8527dd2/cryptography-48.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9be5aafa5736574f8f15f262adc81b2a9869e2cfe9014d52a44633905b40d52c", size = 4963283, upload-time = "2026-05-04T22:58:36.376Z" }, - { url = "https://files.pythonhosted.org/packages/75/54/cc6d0f3deac3e81c7f847e8a189a12b6cdd65059b43dad25d4316abd849a/cryptography-48.0.0-cp314-cp314t-win32.whl", hash = "sha256:c17dfe85494deaeddc5ce251aebd1d60bbe6afc8b62071bb0b469431a000124f", size = 3270954, upload-time = "2026-05-04T22:58:38.791Z" }, - { url = "https://files.pythonhosted.org/packages/49/67/cc947e288c0758a4e5473d1dcb743037ab7785541265a969240b8885441a/cryptography-48.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27241b1dc9962e056062a8eef1991d02c3a24569c95975bd2322a8a52c6e5e12", size = 3797313, upload-time = "2026-05-04T22:58:40.746Z" }, - { url = "https://files.pythonhosted.org/packages/f2/63/61d4a4e1c6b6bab6ce1e213cd36a24c415d90e76d78c5eb8577c5541d2e8/cryptography-48.0.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:58d00498e8933e4a194f3076aee1b4a97dfec1a6da444535755822fe5d8b0b86", size = 7983482, upload-time = "2026-05-04T22:58:43.769Z" }, - { url = "https://files.pythonhosted.org/packages/d5/ac/f5b5995b87770c693e2596559ffafe195b4033a57f14a82268a2842953f3/cryptography-48.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:614d0949f4790582d2cc25553abd09dd723025f0c0e7c67376a1d77196743d6e", size = 4683266, upload-time = "2026-05-04T22:58:46.064Z" }, - { url = "https://files.pythonhosted.org/packages/ec/c6/8b14f67e18338fbc4adb76f66c001f5c3610b3e2d1837f268f47a347dbbb/cryptography-48.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7ce4bfae76319a532a2dc68f82cc32f5676ee792a983187dac07183690e5c66f", size = 4696228, upload-time = "2026-05-04T22:58:48.22Z" }, - { url = "https://files.pythonhosted.org/packages/ea/73/f808fbae9514bd91b47875b003f13e284c8c6bdfd904b7944e803937eec1/cryptography-48.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:2eb992bbd4661238c5a397594c83f5b4dc2bc5b848c365c8f991b6780efcc5c7", size = 4689097, upload-time = "2026-05-04T22:58:50.9Z" }, - { url = "https://files.pythonhosted.org/packages/93/01/d86632d7d28db8ae83221995752eeb6639ffb374c2d22955648cf8d52797/cryptography-48.0.0-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:22a5cb272895dce158b2cacdfdc3debd299019659f42947dbdac6f32d68fe832", size = 5283582, upload-time = "2026-05-04T22:58:53.017Z" }, - { url = "https://files.pythonhosted.org/packages/02/e1/50edc7a50334807cc4791fc4a0ce7468b4a1416d9138eab358bfc9a3d70b/cryptography-48.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2b4d59804e8408e2fea7d1fbaf218e5ec984325221db76e6a241a9abd6cdd95c", size = 4730479, upload-time = "2026-05-04T22:58:55.611Z" }, - { url = "https://files.pythonhosted.org/packages/6f/af/99a582b1b1641ff5911ac559beb45097cf79efd4ead4657f578ef1af2d47/cryptography-48.0.0-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:984a20b0f62a26f48a3396c72e4bc34c66e356d356bf370053066b3b6d54634a", size = 4326481, upload-time = "2026-05-04T22:58:57.607Z" }, - { url = "https://files.pythonhosted.org/packages/90/ee/89aa26a06ef0a7d7611788ffd571a7c50e368cc6a4d5eef8b4884e866edb/cryptography-48.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:5a5ed8fde7a1d09376ca0b40e68cd59c69fe23b1f9768bd5824f54681626032a", size = 4688713, upload-time = "2026-05-04T22:59:00.077Z" }, - { url = "https://files.pythonhosted.org/packages/70/ba/bcb1b0bb7a33d4c7c0c4d4c7874b4a62ae4f56113a5f4baefa362dfb1f0f/cryptography-48.0.0-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:8cd666227ef7af430aa5914a9910e0ddd703e75f039cef0825cd0da71b6b711a", size = 5238165, upload-time = "2026-05-04T22:59:02.317Z" }, - { url = "https://files.pythonhosted.org/packages/c9/70/ca4003b1ce5ca3dc3186ada51908c8a9b9ff7d5cab83cc0d43ee14ec144f/cryptography-48.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:9071196d81abc88b3516ac8cdfad32e2b66dd4a5393a8e68a961e9161ddc6239", size = 4729947, upload-time = "2026-05-04T22:59:05.255Z" }, - { url = "https://files.pythonhosted.org/packages/44/a0/4ec7cf774207905aef1a8d11c3750d5a1db805eb380ee4e16df317870128/cryptography-48.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1e2d54c8be6152856a36f0882ab231e70f8ec7f14e93cf87db8a2ed056bf160c", size = 4822059, upload-time = "2026-05-04T22:59:07.802Z" }, - { url = "https://files.pythonhosted.org/packages/1e/75/a2e55f99c16fcac7b5d6c1eb19ad8e00799854d6be5ca845f9259eae1681/cryptography-48.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a5da777e32ffed6f85a7b2b3f7c5cbc88c146bfcd0a1d7baf5fcc6c52ee35dd4", size = 4960575, upload-time = "2026-05-04T22:59:09.851Z" }, - { url = "https://files.pythonhosted.org/packages/b8/23/6e6f32143ab5d8b36ca848a502c4bcd477ae75b9e1677e3530d669062578/cryptography-48.0.0-cp39-abi3-win32.whl", hash = "sha256:77a2ccbbe917f6710e05ba9adaa25fb5075620bf3ea6fb751997875aff4ae4bd", size = 3279117, upload-time = "2026-05-04T22:59:12.019Z" }, - { url = "https://files.pythonhosted.org/packages/9d/9a/0fea98a70cf1749d41d738836f6349d97945f7c89433a259a6c2642eefeb/cryptography-48.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:16cd65b9330583e4619939b3a3843eec1e6e789744bb01e7c7e2e62e33c239c8", size = 3792100, upload-time = "2026-05-04T22:59:14.884Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/1f/99/d1c90d6041656cc6ee229dc99cd67fd0cd5aec3c5f7d72fffc27cc750054/cryptography-49.0.0.tar.gz", hash = "sha256:f89660a348f4f78a92366240a61404e337586ef7f5909a2fef59ca88ef505493", size = 854345, upload-time = "2026-06-12T20:02:30.512Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/22/adf66990e63584a68dfb50c24f48a125c07b1699899381c8151e63ed458c/cryptography-49.0.0-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:966fe0e9c67490071f14c0d2b1cb2dfb3023c5ce39457343931415f08382f2db", size = 4032100, upload-time = "2026-06-12T20:02:32.143Z" }, + { url = "https://files.pythonhosted.org/packages/09/41/3797cfaf69cae04a13ee78ebd83f0678d9c02b4779d21ce24445326f1a69/cryptography-49.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:36d1709f992593689b45bda411498d62c6e365f2ca00b84657d4dadd24de16db", size = 4692978, upload-time = "2026-06-12T20:01:21.305Z" }, + { url = "https://files.pythonhosted.org/packages/e6/8b/43011f7ebe515a8aa20d61f290a326cd890c2e738e16e59eaff8d9c3a412/cryptography-49.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0e959b578856a3924bc0cbb710fc12c387b9412a951389f3ca61704a9e25f325", size = 4716422, upload-time = "2026-06-12T20:01:48.566Z" }, + { url = "https://files.pythonhosted.org/packages/4a/91/01ce7303a4579e6d3a6abef01bd322848e9ea7a219adcabc5048b9033571/cryptography-49.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:53ecee2e23f7169b6117e99fc8a944e5e50f79e69758a83b52a00cb98ab2b2d2", size = 4700503, upload-time = "2026-06-12T20:02:47.091Z" }, + { url = "https://files.pythonhosted.org/packages/62/99/a2c95cf8293f07491e9e27c20cc4dcd18176d944e674679adeb1d0173fd6/cryptography-49.0.0-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:2eda353d8a27bcbcaa4cbed18994a74ab4d19a2ca897db188ea269ab9b71419b", size = 5309779, upload-time = "2026-06-12T20:02:08.987Z" }, + { url = "https://files.pythonhosted.org/packages/20/2c/0622f20ff02b2ef32558733443805dc82fd4c275be01b2d19d14676f3a1b/cryptography-49.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2afe9051da7ae7bd5905da5a949280c7d2bb75682e188f650a9d0f2756b834c6", size = 4749683, upload-time = "2026-06-12T20:02:03.335Z" }, + { url = "https://files.pythonhosted.org/packages/a3/5b/c5246635d5fd3b64e0d45ae10e99fd32fe9676a79915ccfe5a61ba9af1a5/cryptography-49.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:0b82e28ee398a386f0807bba7884d30f25218855690f45115831bcce5d90822c", size = 4337874, upload-time = "2026-06-12T20:02:54.323Z" }, + { url = "https://files.pythonhosted.org/packages/6d/88/05563c7fe2e914e87d1a536d06fe83e66b4e1d95cb593e05aea375531da8/cryptography-49.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:ccac2bfebc306b862133e3bb71f3f6ee8bb525240089b2d952e4144b3a6d5da7", size = 4700283, upload-time = "2026-06-12T20:01:34.822Z" }, + { url = "https://files.pythonhosted.org/packages/c4/b6/d7696e4e890d6ae1469935164c9e5215c557671cb78d6e3f458ccceaa632/cryptography-49.0.0-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:d0527ce944105f257f605a827d6ebead966c752038b6e8656abb9c5edee6fc68", size = 5265844, upload-time = "2026-06-12T20:01:24.09Z" }, + { url = "https://files.pythonhosted.org/packages/a9/3c/f3ad17eecc1a57b0ba236dc01f90e783c51f4a2f35f64777cc4f47a184b2/cryptography-49.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:cbc77da8c523d5abd028635ba850a6966fcee2c82e2bf65a41d1d8afe0f98be9", size = 4749290, upload-time = "2026-06-12T20:01:30.848Z" }, + { url = "https://files.pythonhosted.org/packages/4f/01/339573cf1023163a400b0b5d16f6d507de413b9f60be6fd1b77feeaf6737/cryptography-49.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b87e65d263b3e5d3bb92a57e2a6638e2f31110fa7aa890c7b2dbba42248d0a3f", size = 4834612, upload-time = "2026-06-12T20:01:29.246Z" }, + { url = "https://files.pythonhosted.org/packages/71/fd/577302e213a1be9468f92d1afef66fcf1ef83d516819d9992ca547f592bd/cryptography-49.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:66ec79c3904820572d7e987abdf304281f141d37ad9a489b8e97066e7b9b6459", size = 4980804, upload-time = "2026-06-12T20:01:42.853Z" }, + { url = "https://files.pythonhosted.org/packages/1f/09/f42b1d190c5ba75f72062a387f8030d1d75f6ab035788f1d9c4b01de6525/cryptography-49.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:e5dfc1e64de5677cec922ffa8da89c546d0415bf6efdf081842e5d44c84e1f0e", size = 3810026, upload-time = "2026-06-12T20:02:39.262Z" }, + { url = "https://files.pythonhosted.org/packages/ec/9e/db72b3ae7fc9cfad53e630e56c6ae83b9b6ff0bf3718ffb8012d20b3aabf/cryptography-49.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:73a205dce83953d131a4aa1e0fd917a2fd1c5b1eef251e9d7152efefcbf5caf7", size = 4013892, upload-time = "2026-06-12T20:02:10.735Z" }, + { url = "https://files.pythonhosted.org/packages/86/12/c48a424f38db03027be9f7ed5c7dc5de9933dbee992865f98b13727a009d/cryptography-49.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:196ecd6a36e4e9aa10270393bb98d8df88fccee0bf1e5128b91ae4eb4375896d", size = 4678835, upload-time = "2026-06-12T20:02:48.743Z" }, + { url = "https://files.pythonhosted.org/packages/68/28/8a3ad4653662c93fc44dc4e5d8fd374c25c42e07b34bbfbadf49cf57a5a8/cryptography-49.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7abcee80084cda3f7691f3eb1ce480d8df49cec637b429aa35986c1de71738aa", size = 4697239, upload-time = "2026-06-12T20:02:56.03Z" }, + { url = "https://files.pythonhosted.org/packages/a8/b2/2193fc74f81aee4f9b62733133b73b5176718932ed8f2e4b03fa040480a6/cryptography-49.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:4ae387c9cb68ea569ca17e490d66d8142b81c3cc814bf179974b7d146e490bbb", size = 4685593, upload-time = "2026-06-12T20:02:50.666Z" }, + { url = "https://files.pythonhosted.org/packages/47/f1/1d3eaa243bfc5de4a187b22aa8c048b3e4980bfbe830ac46e6bac2e66947/cryptography-49.0.0-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:f37d847238971164fdbc68ade6f6574aecc9c0af714190e2083429ff68f4ce9d", size = 5289961, upload-time = "2026-06-12T20:01:46.468Z" }, + { url = "https://files.pythonhosted.org/packages/58/39/2d51306721330c486495853eda1c567880ff036de15a14c4b74f399934af/cryptography-49.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:c2bc30226390d60ea19d9f82b19db005fe0452154a23c1c410c12ea801e43561", size = 4731145, upload-time = "2026-06-12T20:02:16.832Z" }, + { url = "https://files.pythonhosted.org/packages/17/50/983e838c7fd0d87fd8c969bcdd328edaf5f756e38df5281637424c155873/cryptography-49.0.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:07cab27cc7b7e0fd28e5e26bb9eeedde5c135c868b46de4a27845abe94af6122", size = 4321719, upload-time = "2026-06-12T20:02:52.611Z" }, + { url = "https://files.pythonhosted.org/packages/a7/f5/8f571d7e27c55bce9f76f026143bcb1e040a4233149ecca0bea5fa5dd5f7/cryptography-49.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:b20133d204d2bb56ba047642199603876c872026ca53e79c35b83772ab2cc505", size = 4685209, upload-time = "2026-06-12T20:02:07.282Z" }, + { url = "https://files.pythonhosted.org/packages/e7/84/0e27016a6fc5a0886f797018b26aa42f40c09a82332bff77822a451deaaa/cryptography-49.0.0-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:b970c6da94d5bb18629db453d14f2a1300f6bf59b61e9b82377931ef95504866", size = 5246285, upload-time = "2026-06-12T20:01:32.439Z" }, + { url = "https://files.pythonhosted.org/packages/11/2d/5e1fb307cb5931881516b464c98774b3f2c36b5d4bb9a2830253cf553cad/cryptography-49.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:d8ecde755e2e91bf773fc94e8c9d730cd7f2007004cb492263a794ec3899a1c8", size = 4730441, upload-time = "2026-06-12T20:02:01.469Z" }, + { url = "https://files.pythonhosted.org/packages/e4/c0/bff5a02ee731d207d6a1ed51732549d8c53d2bc8da1d10ec6f2844201d68/cryptography-49.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e3fb64c420688e5319ae25113a354015abbd8dffbfbc41781a1ea66fc7622ac3", size = 4815869, upload-time = "2026-06-12T20:01:36.574Z" }, + { url = "https://files.pythonhosted.org/packages/b9/26/814681d14248d95d73d5c3eea0c39a94eb8302df966f670a2c60de90974b/cryptography-49.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:32703d93296f5c1f4b53349ad3a250c2cae0fdecd3a3dd5d47e616d8d616af27", size = 4960948, upload-time = "2026-06-12T20:02:18.688Z" }, + { url = "https://files.pythonhosted.org/packages/4c/fe/93ecac273d3738939d023612ad12cca9a3740a5345d69fda04134c43fd96/cryptography-49.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:33cd0565932807baddb67b96dbee92f2c374b5c89dee09fd74079aeb8c8dba61", size = 3799153, upload-time = "2026-06-12T20:01:39.059Z" }, + { url = "https://files.pythonhosted.org/packages/19/2a/5bb823f5bedcf80718cea7fbc95ec5515cca3769633c4b01a32be7f30e7c/cryptography-49.0.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:ec5e529fb80935c94fe7b729f9972b50e351a0e6b50aa294fd5cabb109fcc29a", size = 4025947, upload-time = "2026-06-12T20:01:25.745Z" }, + { url = "https://files.pythonhosted.org/packages/3d/df/40577043ca124e17012f408ddddaeb213b856336ac82ddb3bc915f39e29f/cryptography-49.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f78ff2c9ed8dc2d036b0f4d640e22522213d047c1b14e61205a7e55c80a494d4", size = 4692429, upload-time = "2026-06-12T20:01:53.628Z" }, + { url = "https://files.pythonhosted.org/packages/2c/99/2d13299eb3dd27b02dcfaafcc91d6b5cb3329f7cbd6d8f51921acd566c1a/cryptography-49.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:35b151772baff2c74cba7fa290ceaff4c3b11c0c881eb93eb5dbc05a7cfbba18", size = 4700968, upload-time = "2026-06-12T20:02:45.383Z" }, + { url = "https://files.pythonhosted.org/packages/a5/4d/9c0cd02f95e2602dd5e563da149ee0830abef3537be8b34dc56281ebe27a/cryptography-49.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:0f21641cf4b30fca7aee061ced0ec7ad7b073518088b7c9969a297c0ae796c69", size = 4697758, upload-time = "2026-06-12T20:01:41.13Z" }, + { url = "https://files.pythonhosted.org/packages/24/01/186c825898477d77e2324d5360fefe622ff1d8d1963ec0554e2cada8ec77/cryptography-49.0.0-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:9e82dcc8e56052715fb18b2429e3bca4823b1629136a2084fc45a9a5cecb9b64", size = 5298863, upload-time = "2026-06-12T20:02:24.579Z" }, + { url = "https://files.pythonhosted.org/packages/b8/7b/62cbbab75d0659865bf0273790031544a0b16c8072d258f9428dcd8190dc/cryptography-49.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:6f2debedf9ca60cf1d5bd466475638af5130f89965605cd818484d19987d3a21", size = 4735983, upload-time = "2026-06-12T20:01:50.14Z" }, + { url = "https://files.pythonhosted.org/packages/6c/72/3e798c064bc39e471008075d0f9bc9daf77a80879c092e4a8e170c585ed4/cryptography-49.0.0-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:8c25ceb16df5b9435f3f6a9829204985b0e0cbee3b48aacd432c7d2c850b44d9", size = 4334173, upload-time = "2026-06-12T20:01:44.743Z" }, + { url = "https://files.pythonhosted.org/packages/f0/ee/6fca21d1ac73e06f8bef71940abfd4d2f6472b4bca284d770f32bd4086f6/cryptography-49.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:28d8b15e6275f12c8a207dc309dfa957903c927d08d0cc937ee3f63f200693cc", size = 4697298, upload-time = "2026-06-12T20:02:20.918Z" }, + { url = "https://files.pythonhosted.org/packages/67/d0/a5fcd3515f0bae49a7b6d0413cc1bdccdcc1fc0047037a0d480642cdc5d6/cryptography-49.0.0-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:6fc361c34fb6aac015ce19435876635e5c6d21db31998b0920f675f131e043b8", size = 5254338, upload-time = "2026-06-12T20:02:22.737Z" }, + { url = "https://files.pythonhosted.org/packages/a0/84/84fe36f19caf857d61cb7fc9c63035a47ffabd84ea12d1d393148efa3615/cryptography-49.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:2400ef9c9e2299a25614eb1dea3db54a69b1349efd043bfac9c67630d136df36", size = 4735650, upload-time = "2026-06-12T20:02:41.389Z" }, + { url = "https://files.pythonhosted.org/packages/6c/a0/db537264e234f7273a73ec020873d6d6b39dfd8a53db78b550ca8320440e/cryptography-49.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:67e1d20ad9ef3a563c59ef22e7a8a0b8210bd26604369ea4a30a7c66aefe504e", size = 4834820, upload-time = "2026-06-12T20:01:51.847Z" }, + { url = "https://files.pythonhosted.org/packages/93/77/8df9eb486495979bccecd1062e2eaf435250e84437040295b57d09048b0b/cryptography-49.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:42b0684e0e40cf26122427802486f6d93aea593612603a94fbf260c7eb1e9c1b", size = 4967968, upload-time = "2026-06-12T20:02:12.524Z" }, + { url = "https://files.pythonhosted.org/packages/c2/e6/f60198ea8d9dfa15fff9ed4ca02ce362f6eadd9ba757dcc50634c4257b63/cryptography-49.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:026ac7423e6fa66872d3bf889be5974507da3944f866f704fa200eadacd00001", size = 3785547, upload-time = "2026-06-12T20:02:26.847Z" }, ] [[package]] @@ -1148,7 +1145,7 @@ name = "culsans" version = "0.11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "aiologic", marker = "python_full_version < '3.13'" }, + { name = "aiologic" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/d9/e3/49afa1bc180e0d28008ec6bcdf82a4072d1c7a41032b5b759b60814ca4b0/culsans-0.11.0.tar.gz", hash = "sha256:0b43d0d05dce6106293d114c86e3fb4bfc63088cfe8ff08ed3fe36891447fe33", size = 107546, upload-time = "2025-12-31T23:15:38.196Z" } @@ -1172,11 +1169,11 @@ wheels = [ [[package]] name = "distlib" -version = "0.4.0" +version = "0.4.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605, upload-time = "2025-07-17T16:52:00.465Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/02/bd72be9134d25ed783ecbbc38a539ffaefbf90c78418c7fb7229600dbac7/distlib-0.4.3.tar.gz", hash = "sha256:f152097224a0ae24be5a0f6bae1b9359af82133bce63f98a95f86cae1aede9ed", size = 615141, upload-time = "2026-06-12T08:04:52.847Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" }, + { url = "https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl", hash = "sha256:4b0ce306c966eb73bc3a7b6abad017c556dadd92c44701562cd528ac7fde4d5b", size = 470628, upload-time = "2026-06-12T08:04:50.506Z" }, ] [[package]] @@ -1208,7 +1205,7 @@ wheels = [ [[package]] name = "durabletask" -version = "1.4.0" +version = "1.5.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "asyncio" }, @@ -1216,22 +1213,22 @@ dependencies = [ { name = "packaging" }, { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/26/25/11d70b07723587a0b95fb57b5817627c9e605554b874697e5aeee3e5466d/durabletask-1.4.0.tar.gz", hash = "sha256:639138c10e2687a485ee94d218c27f8dc193376367dce9617f1ca2ec1cc8f021", size = 97252, upload-time = "2026-04-08T18:49:26.348Z" } +sdist = { url = "https://files.pythonhosted.org/packages/97/78/099bc761bec57fd8fdd906058bc7a9c1b8caf8a0bfff774c25162ef1a20c/durabletask-1.5.0.tar.gz", hash = "sha256:93a80f6fb04d8fdad90b0d69518d27a70947cceb5ae1c6fb6a286ff481d19c16", size = 112887, upload-time = "2026-06-05T22:36:51.031Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fe/3f/7250be7683aa6e9e89324db549e2b44cb6db7904cd315024933a23405e07/durabletask-1.4.0-py3-none-any.whl", hash = "sha256:75e11407bf24f045e32ef26b5e753f49f64fee822c8c9bfc5184a0911cb0969c", size = 107934, upload-time = "2026-04-08T18:49:24.856Z" }, + { url = "https://files.pythonhosted.org/packages/96/67/c0a33b3208ad862eef23e1ddcc452b3759af0d24e73b91f3c517c43d863d/durabletask-1.5.0-py3-none-any.whl", hash = "sha256:58371340fe37714bf662d9c9d05791e038203e6b2e7888e7a4b9927af0999711", size = 125164, upload-time = "2026-06-05T22:36:52.259Z" }, ] [[package]] name = "durabletask-azuremanaged" -version = "1.4.0" +version = "1.5.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "azure-identity" }, { name = "durabletask" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/13/a9/18501dc091867a9bb5a7d184c69f3fac14294f34dea2363aa9379eeeedc3/durabletask_azuremanaged-1.4.0.tar.gz", hash = "sha256:739cde74ecdacf732fa4a9a40c0afba5d3185c5e575a6883d303c5a112f2c34a", size = 5657, upload-time = "2026-04-08T19:22:27.732Z" } +sdist = { url = "https://files.pythonhosted.org/packages/89/53/653c4a4a120906cfff9c824583bd9398528657ba683bfe9d12daac064c94/durabletask_azuremanaged-1.5.0.tar.gz", hash = "sha256:5af9fabb67a555039087b5242d628587b5179a2cfa6609cfbc5b227a0e8669e6", size = 5944, upload-time = "2026-06-05T22:37:10.299Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1f/95/00ef2b2e0dd62fee6dc411aa1c4071ac55e54bcbd47a1384722e0ba54f42/durabletask_azuremanaged-1.4.0-py3-none-any.whl", hash = "sha256:80a0255afa7b61c01886d82dc22b75188b786f2454ea9f1a09dac10888a3c131", size = 7852, upload-time = "2026-04-08T19:22:26.624Z" }, + { url = "https://files.pythonhosted.org/packages/e1/22/b6b7173209f03b458fae9ae98d20b074bf0a1788286227489d9bd23df302/durabletask_azuremanaged-1.5.0-py3-none-any.whl", hash = "sha256:260249dc537010f01b12215f57072a2dbdf00892132aa37a543dcc66712502af", size = 8314, upload-time = "2026-06-05T22:37:11.247Z" }, ] [[package]] @@ -1261,7 +1258,7 @@ wheels = [ [[package]] name = "fastapi" -version = "0.136.1" +version = "0.137.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-doc" }, @@ -1270,9 +1267,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5d/45/c130091c2dfa061bbfe3150f2a5091ef1adf149f2a8d2ae769ecaf6e99a2/fastapi-0.136.1.tar.gz", hash = "sha256:7af665ad7acfa0a3baf8983d393b6b471b9da10ede59c60045f49fbc89a0fa7f", size = 397448, upload-time = "2026-04-23T16:49:44.046Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/29/cc5819dc24d3daa80cdaa1aec023bf8652a70dd7fd1c96b0b225c99a7690/fastapi-0.137.2.tar.gz", hash = "sha256:b9d893bebc97dcfbdcb1917e88a292d062844ea19445a5fa4f7eb28c4baea9e3", size = 410332, upload-time = "2026-06-18T06:58:24.434Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/ff/2e4eca3ade2c22fe1dea7043b8ee9dabe47753349eb1b56a202de8af6349/fastapi-0.136.1-py3-none-any.whl", hash = "sha256:a6e9d7eeada96c93a4d69cb03836b44fa34e2854accb7244a1ece36cd4781c3f", size = 117683, upload-time = "2026-04-23T16:49:42.437Z" }, + { url = "https://files.pythonhosted.org/packages/2f/ed/0c6b644e99fb5697d8bdcd36cdb47c52e77a63fc7a1514b1f03a6ecab955/fastapi-0.137.2-py3-none-any.whl", hash = "sha256:791d36261e916a98b25ac85ee591bc3db159394070f6d3d096d94fb378f60ce2", size = 122252, upload-time = "2026-06-18T06:58:26.074Z" }, ] [[package]] @@ -1310,11 +1307,11 @@ wheels = [ [[package]] name = "filelock" -version = "3.29.0" +version = "3.29.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b5/fe/997687a931ab51049acce6fa1f23e8f01216374ea81374ddee763c493db5/filelock-3.29.0.tar.gz", hash = "sha256:69974355e960702e789734cb4871f884ea6fe50bd8404051a3530bc07809cf90", size = 57571, upload-time = "2026-04-19T15:39:10.068Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e6/dc/be6cbe99670cd6e4ad387123647cb08e0c32975e223f82551e914c5568a6/filelock-3.29.4.tar.gz", hash = "sha256:10cdb3656fc44541cdf30652a93fb10ec6b05325620eb316bd26893e4201538a", size = 63028, upload-time = "2026-06-13T16:12:00.744Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-py3-none-any.whl", hash = "sha256:96f5f6344709aa1572bbf631c640e4ebeeb519e08da902c39a001882f30ac258", size = 39812, upload-time = "2026-04-19T15:39:08.752Z" }, + { url = "https://files.pythonhosted.org/packages/13/37/a065dc3bd6e49423a6532c642ca7378d3f467b1ef44c2800c937af7f9739/filelock-3.29.4-py3-none-any.whl", hash = "sha256:dac1648087d5115554850d113e7dd8c83ab2d38e3435dde2d4f163847e57b767", size = 42757, upload-time = "2026-06-13T16:11:59.582Z" }, ] [[package]] @@ -1421,7 +1418,7 @@ wheels = [ [[package]] name = "google-api-core" -version = "2.30.3" +version = "2.31.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "google-auth" }, @@ -1430,34 +1427,34 @@ dependencies = [ { name = "protobuf" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/16/ce/502a57fb0ec752026d24df1280b162294b22a0afb98a326084f9a979138b/google_api_core-2.30.3.tar.gz", hash = "sha256:e601a37f148585319b26db36e219df68c5d07b6382cff2d580e83404e44d641b", size = 177001, upload-time = "2026-04-10T00:41:28.035Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c6/22/155cadf1d49272a9cf48f3168c0f3874fa13397297e611a5ea00cd093880/google_api_core-2.31.0.tar.gz", hash = "sha256:2be84ee0f584c48e6bde1b36766e23348b361fb7e55e56135fc76ce1c397f9c2", size = 176492, upload-time = "2026-06-03T14:52:17.257Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/03/15/e56f351cf6ef1cfea58e6ac226a7318ed1deb2218c4b3cc9bd9e4b786c5a/google_api_core-2.30.3-py3-none-any.whl", hash = "sha256:a85761ba72c444dad5d611c2220633480b2b6be2521eca69cca2dbb3ffd6bfe8", size = 173274, upload-time = "2026-04-09T22:57:16.198Z" }, + { url = "https://files.pythonhosted.org/packages/86/40/9bdbb60b03a332bd45acb8703da08bbc27d991d35286b62e42acc86d243a/google_api_core-2.31.0-py3-none-any.whl", hash = "sha256:ef79fb3784c71cbac89cbd03301ba0c8fb8ad2aa95d7f9204dd9628f7adf59ab", size = 173102, upload-time = "2026-06-03T14:51:26.729Z" }, ] [[package]] name = "google-auth" -version = "2.50.0" +version = "2.55.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cryptography" }, { name = "pyasn1-modules" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5f/18/238d7021d151bdab868f23433817b027dd759135202f4dfce0670d1230ca/google_auth-2.50.0.tar.gz", hash = "sha256:f35eafb191195328e8ce10a7883970877e7aeb49c2bfaa54aa0e394316d353d0", size = 336523, upload-time = "2026-04-30T21:19:29.659Z" } +sdist = { url = "https://files.pythonhosted.org/packages/81/1c/70b23fc52b2bb3c70b379f3bd05c4a60ab3a873e30c6bd21c57e0154848a/google_auth-2.55.0.tar.gz", hash = "sha256:fcd3a130f575fa36403d38774af1c64a4fbfbca09215f0589d2372b5119697cb", size = 349379, upload-time = "2026-06-15T22:33:16.466Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/37/cf/4880c2137c14280b2f59975cdf12cc442bc0ae1f9ea473a26eaa0c146786/google_auth-2.50.0-py3-none-any.whl", hash = "sha256:04382175e28b94f49694977f0a792688b59a668def1499e9d8de996dc9ce5b15", size = 246495, upload-time = "2026-04-30T21:19:27.664Z" }, + { url = "https://files.pythonhosted.org/packages/44/71/c0321dc6d63d99946da45f7c06299b934e4f7f7da5c4f14d101bcb39adf1/google_auth-2.55.0-py3-none-any.whl", hash = "sha256:a17cef9dedf98c4ebae2fb0c48c8f75952c877cbc2efe09f329ef16c2783d88a", size = 252400, upload-time = "2026-06-15T22:33:14.992Z" }, ] [[package]] name = "googleapis-common-protos" -version = "1.74.0" +version = "1.75.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/20/18/a746c8344152d368a5aac738d4c857012f2c5d1fd2eac7e17b647a7861bd/googleapis_common_protos-1.74.0.tar.gz", hash = "sha256:57971e4eeeba6aad1163c1f0fc88543f965bb49129b8bb55b2b7b26ecab084f1", size = 151254, upload-time = "2026-04-02T21:23:26.679Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b5/c8/f439cffde755cffa462bfbb156278fa6f9d09119719af9814b858fd4f81f/googleapis_common_protos-1.75.0.tar.gz", hash = "sha256:53a062ff3c32552fbd62c11fe23768b78e4ddf0494d5e5fd97d3f4689c75fbbd", size = 151035, upload-time = "2026-05-07T08:04:49.423Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b6/b0/be5d3329badb9230b765de6eea66b73abd5944bdeb5afb3562ddcd80ae84/googleapis_common_protos-1.74.0-py3-none-any.whl", hash = "sha256:702216f78610bb510e3f12ac3cafd281b7ac45cc5d86e90ad87e4d301a3426b5", size = 300743, upload-time = "2026-04-02T21:22:49.108Z" }, + { url = "https://files.pythonhosted.org/packages/e7/c8/e2645aa8ed02fd4c7a2f59d68783b65b1f3cbdfe39a6308e156509d1fee8/googleapis_common_protos-1.75.0-py3-none-any.whl", hash = "sha256:961ed60399c457ceb0ee8f285a84c870aabc9c6a832b9d37bb281b5bebde43ed", size = 300631, upload-time = "2026-05-07T08:03:30.345Z" }, ] [[package]] @@ -1471,43 +1468,43 @@ wheels = [ [[package]] name = "grpcio" -version = "1.80.0" +version = "1.82.0rc1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b7/48/af6173dbca4454f4637a4678b67f52ca7e0c1ed7d5894d89d434fecede05/grpcio-1.80.0.tar.gz", hash = "sha256:29aca15edd0688c22ba01d7cc01cb000d72b2033f4a3c72a81a19b56fd143257", size = 12978905, upload-time = "2026-03-30T08:49:10.502Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5c/e8/a2b749265eb3415abc94f2e619bbd9e9707bebdda787e61c593004ec927a/grpcio-1.80.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:c624cc9f1008361014378c9d776de7182b11fe8b2e5a81bc69f23a295f2a1ad0", size = 6015616, upload-time = "2026-03-30T08:47:13.428Z" }, - { url = "https://files.pythonhosted.org/packages/3e/97/b1282161a15d699d1e90c360df18d19165a045ce1c343c7f313f5e8a0b77/grpcio-1.80.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:f49eddcac43c3bf350c0385366a58f36bed8cc2c0ec35ef7b74b49e56552c0c2", size = 12014204, upload-time = "2026-03-30T08:47:15.873Z" }, - { url = "https://files.pythonhosted.org/packages/6e/5e/d319c6e997b50c155ac5a8cb12f5173d5b42677510e886d250d50264949d/grpcio-1.80.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d334591df610ab94714048e0d5b4f3dd5ad1bee74dfec11eee344220077a79de", size = 6563866, upload-time = "2026-03-30T08:47:18.588Z" }, - { url = "https://files.pythonhosted.org/packages/ae/f6/fdd975a2cb4d78eb67769a7b3b3830970bfa2e919f1decf724ae4445f42c/grpcio-1.80.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:0cb517eb1d0d0aaf1d87af7cc5b801d686557c1d88b2619f5e31fab3c2315921", size = 7273060, upload-time = "2026-03-30T08:47:21.113Z" }, - { url = "https://files.pythonhosted.org/packages/db/f0/a3deb5feba60d9538a962913e37bd2e69a195f1c3376a3dd44fe0427e996/grpcio-1.80.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4e78c4ac0d97dc2e569b2f4bcbbb447491167cb358d1a389fc4af71ab6f70411", size = 6782121, upload-time = "2026-03-30T08:47:23.827Z" }, - { url = "https://files.pythonhosted.org/packages/ca/84/36c6dcfddc093e108141f757c407902a05085e0c328007cb090d56646cdf/grpcio-1.80.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2ed770b4c06984f3b47eb0517b1c69ad0b84ef3f40128f51448433be904634cd", size = 7383811, upload-time = "2026-03-30T08:47:26.517Z" }, - { url = "https://files.pythonhosted.org/packages/7c/ef/f3a77e3dc5b471a0ec86c564c98d6adfa3510d38f8ee99010410858d591e/grpcio-1.80.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:256507e2f524092f1473071a05e65a5b10d84b82e3ff24c5b571513cfaa61e2f", size = 8393860, upload-time = "2026-03-30T08:47:29.439Z" }, - { url = "https://files.pythonhosted.org/packages/9b/8d/9d4d27ed7f33d109c50d6b5ce578a9914aa68edab75d65869a17e630a8d1/grpcio-1.80.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9a6284a5d907c37db53350645567c522be314bac859a64a7a5ca63b77bb7958f", size = 7830132, upload-time = "2026-03-30T08:47:33.254Z" }, - { url = "https://files.pythonhosted.org/packages/14/e4/9990b41c6d7a44e1e9dee8ac11d7a9802ba1378b40d77468a7761d1ad288/grpcio-1.80.0-cp312-cp312-win32.whl", hash = "sha256:c71309cfce2f22be26aa4a847357c502db6c621f1a49825ae98aa0907595b193", size = 4140904, upload-time = "2026-03-30T08:47:35.319Z" }, - { url = "https://files.pythonhosted.org/packages/2f/2c/296f6138caca1f4b92a31ace4ae1b87dab692fc16a7a3417af3bb3c805bf/grpcio-1.80.0-cp312-cp312-win_amd64.whl", hash = "sha256:9fe648599c0e37594c4809d81a9e77bd138cc82eb8baa71b6a86af65426723ff", size = 4880944, upload-time = "2026-03-30T08:47:37.831Z" }, - { url = "https://files.pythonhosted.org/packages/2f/3a/7c3c25789e3f069e581dc342e03613c5b1cb012c4e8c7d9d5cf960a75856/grpcio-1.80.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:e9e408fc016dffd20661f0126c53d8a31c2821b5c13c5d67a0f5ed5de93319ad", size = 6017243, upload-time = "2026-03-30T08:47:40.075Z" }, - { url = "https://files.pythonhosted.org/packages/04/19/21a9806eb8240e174fd1ab0cd5b9aa948bb0e05c2f2f55f9d5d7405e6d08/grpcio-1.80.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:92d787312e613754d4d8b9ca6d3297e69994a7912a32fa38c4c4e01c272974b0", size = 12010840, upload-time = "2026-03-30T08:47:43.11Z" }, - { url = "https://files.pythonhosted.org/packages/18/3a/23347d35f76f639e807fb7a36fad3068aed100996849a33809591f26eca6/grpcio-1.80.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8ac393b58aa16991a2f1144ec578084d544038c12242da3a215966b512904d0f", size = 6567644, upload-time = "2026-03-30T08:47:46.806Z" }, - { url = "https://files.pythonhosted.org/packages/ff/40/96e07ecb604a6a67ae6ab151e3e35b132875d98bc68ec65f3e5ab3e781d7/grpcio-1.80.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:68e5851ac4b9afe07e7f84483803ad167852570d65326b34d54ca560bfa53fb6", size = 7277830, upload-time = "2026-03-30T08:47:49.643Z" }, - { url = "https://files.pythonhosted.org/packages/9b/e2/da1506ecea1f34a5e365964644b35edef53803052b763ca214ba3870c856/grpcio-1.80.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:873ff5d17d68992ef6605330127425d2fc4e77e612fa3c3e0ed4e668685e3140", size = 6783216, upload-time = "2026-03-30T08:47:52.817Z" }, - { url = "https://files.pythonhosted.org/packages/44/83/3b20ff58d0c3b7f6caaa3af9a4174d4023701df40a3f39f7f1c8e7c48f9d/grpcio-1.80.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2bea16af2750fd0a899bf1abd9022244418b55d1f37da2202249ba4ba673838d", size = 7385866, upload-time = "2026-03-30T08:47:55.687Z" }, - { url = "https://files.pythonhosted.org/packages/47/45/55c507599c5520416de5eefecc927d6a0d7af55e91cfffb2e410607e5744/grpcio-1.80.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba0db34f7e1d803a878284cd70e4c63cb6ae2510ba51937bf8f45ba997cefcf7", size = 8391602, upload-time = "2026-03-30T08:47:58.303Z" }, - { url = "https://files.pythonhosted.org/packages/10/bb/dd06f4c24c01db9cf11341b547d0a016b2c90ed7dbbb086a5710df7dd1d7/grpcio-1.80.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8eb613f02d34721f1acf3626dfdb3545bd3c8505b0e52bf8b5710a28d02e8aa7", size = 7826752, upload-time = "2026-03-30T08:48:01.311Z" }, - { url = "https://files.pythonhosted.org/packages/f9/1e/9d67992ba23371fd63d4527096eb8c6b76d74d52b500df992a3343fd7251/grpcio-1.80.0-cp313-cp313-win32.whl", hash = "sha256:93b6f823810720912fd131f561f91f5fed0fda372b6b7028a2681b8194d5d294", size = 4142310, upload-time = "2026-03-30T08:48:04.594Z" }, - { url = "https://files.pythonhosted.org/packages/cf/e6/283326a27da9e2c3038bc93eeea36fb118ce0b2d03922a9cda6688f53c5b/grpcio-1.80.0-cp313-cp313-win_amd64.whl", hash = "sha256:e172cf795a3ba5246d3529e4d34c53db70e888fa582a8ffebd2e6e48bc0cba50", size = 4882833, upload-time = "2026-03-30T08:48:07.363Z" }, - { url = "https://files.pythonhosted.org/packages/c5/6d/e65307ce20f5a09244ba9e9d8476e99fb039de7154f37fb85f26978b59c3/grpcio-1.80.0-cp314-cp314-linux_armv7l.whl", hash = "sha256:3d4147a97c8344d065d01bbf8b6acec2cf86fb0400d40696c8bdad34a64ffc0e", size = 6017376, upload-time = "2026-03-30T08:48:10.005Z" }, - { url = "https://files.pythonhosted.org/packages/69/10/9cef5d9650c72625a699c549940f0abb3c4bfdb5ed45a5ce431f92f31806/grpcio-1.80.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:d8e11f167935b3eb089ac9038e1a063e6d7dbe995c0bb4a661e614583352e76f", size = 12018133, upload-time = "2026-03-30T08:48:12.927Z" }, - { url = "https://files.pythonhosted.org/packages/04/82/983aabaad82ba26113caceeb9091706a0696b25da004fe3defb5b346e15b/grpcio-1.80.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f14b618fc30de822681ee986cfdcc2d9327229dc4c98aed16896761cacd468b9", size = 6574748, upload-time = "2026-03-30T08:48:16.386Z" }, - { url = "https://files.pythonhosted.org/packages/07/d7/031666ef155aa0bf399ed7e19439656c38bbd143779ae0861b038ce82abd/grpcio-1.80.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:4ed39fbdcf9b87370f6e8df4e39ca7b38b3e5e9d1b0013c7b6be9639d6578d14", size = 7277711, upload-time = "2026-03-30T08:48:19.627Z" }, - { url = "https://files.pythonhosted.org/packages/e8/43/f437a78f7f4f1d311804189e8f11fb311a01049b2e08557c1068d470cb2e/grpcio-1.80.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2dcc70e9f0ba987526e8e8603a610fb4f460e42899e74e7a518bf3c68fe1bf05", size = 6785372, upload-time = "2026-03-30T08:48:22.373Z" }, - { url = "https://files.pythonhosted.org/packages/93/3d/f6558e9c6296cb4227faa5c43c54a34c68d32654b829f53288313d16a86e/grpcio-1.80.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:448c884b668b868562b1bda833c5fce6272d26e1926ec46747cda05741d302c1", size = 7395268, upload-time = "2026-03-30T08:48:25.638Z" }, - { url = "https://files.pythonhosted.org/packages/06/21/0fdd77e84720b08843c371a2efa6f2e19dbebf56adc72df73d891f5506f0/grpcio-1.80.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a1dc80fe55685b4a543555e6eef975303b36c8db1023b1599b094b92aa77965f", size = 8392000, upload-time = "2026-03-30T08:48:28.974Z" }, - { url = "https://files.pythonhosted.org/packages/f5/68/67f4947ed55d2e69f2cc199ab9fd85e0a0034d813bbeef84df6d2ba4d4b7/grpcio-1.80.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:31b9ac4ad1aa28ffee5503821fafd09e4da0a261ce1c1281c6c8da0423c83b6e", size = 7828477, upload-time = "2026-03-30T08:48:32.054Z" }, - { url = "https://files.pythonhosted.org/packages/44/b6/8d4096691b2e385e8271911a0de4f35f0a6c7d05aff7098e296c3de86939/grpcio-1.80.0-cp314-cp314-win32.whl", hash = "sha256:367ce30ba67d05e0592470428f0ec1c31714cab9ef19b8f2e37be1f4c7d32fae", size = 4218563, upload-time = "2026-03-30T08:48:34.538Z" }, - { url = "https://files.pythonhosted.org/packages/e5/8c/bbe6baf2557262834f2070cf668515fa308b2d38a4bbf771f8f7872a7036/grpcio-1.80.0-cp314-cp314-win_amd64.whl", hash = "sha256:3b01e1f5464c583d2f567b2e46ff0d516ef979978f72091fd81f5ab7fa6e2e7f", size = 5019457, upload-time = "2026-03-30T08:48:37.308Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/70/6f/4320167963523abb65d7731ec1f5988ca4369bef11f1a3ff261fa0ae2124/grpcio-1.82.0rc1.tar.gz", hash = "sha256:bb8ff2c5b564823d0d1f1cfad8926a5c5226344f611ddca915452a9839d99277", size = 13194883, upload-time = "2026-06-17T04:01:25.518Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4c/fb/9a898553730680cd039d38df4c29340dc965f986141ea1350580c71c9cf0/grpcio-1.82.0rc1-cp312-cp312-linux_armv7l.whl", hash = "sha256:ab5eddfa69b9c00c9b9d4b920beefed108f1d1ecc7d4c432034c19af333eb6cf", size = 6144666, upload-time = "2026-06-17T03:59:59.164Z" }, + { url = "https://files.pythonhosted.org/packages/77/68/70614957671cfc56873212e53453234acd7d868a0ee9a9375cd666d04b08/grpcio-1.82.0rc1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:66ab43e0f2d24af3710bc20b8c6fffb0a75603549acbc52951114ee04b9de518", size = 11952602, upload-time = "2026-06-17T04:00:01.821Z" }, + { url = "https://files.pythonhosted.org/packages/bd/63/a557baa37f6b399820e72b55b3b027b75b2cce8a3525107da8c725981614/grpcio-1.82.0rc1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6f07018eeb61f2c4350bb46a67a32d50991c063373e7c249d80bc29fcf899e81", size = 6710554, upload-time = "2026-06-17T04:00:04.878Z" }, + { url = "https://files.pythonhosted.org/packages/56/8e/22a4bae98c5bbe3332a52ecb5186b219a978b0a2e18f34d36d3af6dad9c7/grpcio-1.82.0rc1-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:0f85e745cd372f248f9cd7b62fe7b25b0b39dc8c990eec43d47817cf3045eb7f", size = 7450507, upload-time = "2026-06-17T04:00:07.191Z" }, + { url = "https://files.pythonhosted.org/packages/1f/17/d4702335b0c6d8dfd97f5f5388356356cace9bddd95e5ed8daf5457ba531/grpcio-1.82.0rc1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:25d60cc203c492097e73844a7a7b49f8f37ffba8796ff0fdb561cc72df9cb80d", size = 6886906, upload-time = "2026-06-17T04:00:09.738Z" }, + { url = "https://files.pythonhosted.org/packages/8f/83/2b77cbb2ac5af963278878a6b61f68e18bcbc1a216ec282da7deeabd26e2/grpcio-1.82.0rc1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:acc4586e269a1ee9d1858ff46d8350be6d98af480d7b4ca0586031f35cb2fc72", size = 7501500, upload-time = "2026-06-17T04:00:12.07Z" }, + { url = "https://files.pythonhosted.org/packages/c8/16/0ee65d3c05121cc9587672d32250df458305a988a2a940ed28ba5a3bc487/grpcio-1.82.0rc1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9bf115ad35c21a833c010be15be66e0730db69d7cdaa23f0c31570df58939fa2", size = 8537067, upload-time = "2026-06-17T04:00:14.855Z" }, + { url = "https://files.pythonhosted.org/packages/94/99/c0ab7d8f17650aa957ad5a2dc43889018cf8fda32e53d5976415b0a019f0/grpcio-1.82.0rc1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:60e3b011aa1a7ffd414c2a57f2cd6e86fc73f3d2e54e14cc2e2ab224284c95b5", size = 7914009, upload-time = "2026-06-17T04:00:18.273Z" }, + { url = "https://files.pythonhosted.org/packages/22/2c/72c887f9cbb625d2adca69848f54ef1b41458eb97afdcd51000c8d9ef67e/grpcio-1.82.0rc1-cp312-cp312-win32.whl", hash = "sha256:c6ec64409c8d010b6d982d14f9458aacf00f29cfb1b74164ba35a09d08d9985d", size = 4240963, upload-time = "2026-06-17T04:00:20.621Z" }, + { url = "https://files.pythonhosted.org/packages/f2/c0/31dd136de4951557715b9b18e08200baea3ec2db4edce1209c69467949d6/grpcio-1.82.0rc1-cp312-cp312-win_amd64.whl", hash = "sha256:867c057a00695d7dce0408dfe8af848995d7f604b68599aaf8b3aa28ae061317", size = 5001620, upload-time = "2026-06-17T04:00:23.493Z" }, + { url = "https://files.pythonhosted.org/packages/ac/d4/e749e36c0d957fcd717b45dafb13baaac13eecfcc5860684020a739632cf/grpcio-1.82.0rc1-cp313-cp313-linux_armv7l.whl", hash = "sha256:4d01821d674f000419c4f024d9ee4b078a69f3800ad921824341617e652289ed", size = 6146209, upload-time = "2026-06-17T04:00:26.403Z" }, + { url = "https://files.pythonhosted.org/packages/ad/57/02b55bf3025ed4382d44697be0ae85326963615a0d2c1afeb17dd94f0414/grpcio-1.82.0rc1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:0f568b4cc39b2f62c81d19522f7eb9f445854864ed92569753c829d6f07298bb", size = 11949296, upload-time = "2026-06-17T04:00:29.258Z" }, + { url = "https://files.pythonhosted.org/packages/b8/12/aa919596defb1476f86be967682c2d35f7ec28243d5df90274747b251cda/grpcio-1.82.0rc1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:503461041476e8ad20b0ec133d6c2d6d7a3a572d8247e4d55d0d82db1a83adb0", size = 6714824, upload-time = "2026-06-17T04:00:32.391Z" }, + { url = "https://files.pythonhosted.org/packages/f3/e8/186174f75c98f421f7228e1d5d484a1245e5eb14d914805ebc29d7a84729/grpcio-1.82.0rc1-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:22e92d5c014274fdd40e851c9d0c0ff560c2c8993f69150aa8f7badcece1c7d8", size = 7455067, upload-time = "2026-06-17T04:00:35.174Z" }, + { url = "https://files.pythonhosted.org/packages/6e/b5/a78296611b9f32866a1c4734c7a23f52c690796a90a3b0650781e5dc65d0/grpcio-1.82.0rc1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:07de1d51c1d21012fdbf55b4881b39088fefccf073eb0c9e196e68d15ef99ceb", size = 6888683, upload-time = "2026-06-17T04:00:37.701Z" }, + { url = "https://files.pythonhosted.org/packages/20/7e/ec0b970e199ea85be088316db36baded6388fc7fdfb1670548caa29888a7/grpcio-1.82.0rc1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cc946230ddd72e69545b12a10b6700159c00ca10cd1292247796244a77aa21db", size = 7505400, upload-time = "2026-06-17T04:00:40.417Z" }, + { url = "https://files.pythonhosted.org/packages/45/0f/cf0c8425292e5ca7ca25d6b6380a723699b7c17bed166dde451cbd6ad9d7/grpcio-1.82.0rc1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:dba6765b4d71b821e2b2b1f7bc8bcfcc7d9ea46036482c17b370c207a96727c7", size = 8535513, upload-time = "2026-06-17T04:00:43.603Z" }, + { url = "https://files.pythonhosted.org/packages/7f/6e/8cdf689eac595b3a637121c04d117d05cf678c81438e9ec417b768d7985d/grpcio-1.82.0rc1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f85aa36c71b95d80a6d8301499f9e32fcad961bc8e9f9410c30ab140fccf368f", size = 7910806, upload-time = "2026-06-17T04:00:46.898Z" }, + { url = "https://files.pythonhosted.org/packages/11/b3/a182581364e7ebc7e5bbcb16ad69faf6058d3eca4b8264584a5c5fa4469b/grpcio-1.82.0rc1-cp313-cp313-win32.whl", hash = "sha256:df1cabd2e881c8d972774ea2cc8009ffde482231fe915a086e9847980e1dd429", size = 4240319, upload-time = "2026-06-17T04:00:49.856Z" }, + { url = "https://files.pythonhosted.org/packages/c2/45/d2da1afc20929559eb9071d40d41877b4001ad867437bd2b71c32c37b96f/grpcio-1.82.0rc1-cp313-cp313-win_amd64.whl", hash = "sha256:84897a4d0349bc738a6414080ba7dd4588d2df84c19d95d75009bb2bb67f847a", size = 5001025, upload-time = "2026-06-17T04:00:52.433Z" }, + { url = "https://files.pythonhosted.org/packages/f0/44/03acb103cfba93c6dd07d99fcfe546a88def23ad063104e36292d7ef8e77/grpcio-1.82.0rc1-cp314-cp314-linux_armv7l.whl", hash = "sha256:3fd91e483e65a277a3db7800b980691efebbe0582e6cd43ef14c502e804de33c", size = 6146965, upload-time = "2026-06-17T04:00:55.027Z" }, + { url = "https://files.pythonhosted.org/packages/4e/c1/8492645d7915024dcf542d03ded03302110e747b798abf7e68bcdde9a39a/grpcio-1.82.0rc1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:dbe71d2ff02fa096e6069a34e561561eec54f3bf9f193591264676371a48919b", size = 11955303, upload-time = "2026-06-17T04:00:58.081Z" }, + { url = "https://files.pythonhosted.org/packages/d6/99/39f0c51fc2625f8f91f1a8a2da1fcbe052c3cb692a0a7a0f35a798b2449e/grpcio-1.82.0rc1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:da5193e684228e6fc886f59afd723f20b8ddae4fd99bfca48a912a1626ce5349", size = 6723217, upload-time = "2026-06-17T04:01:01.334Z" }, + { url = "https://files.pythonhosted.org/packages/74/86/a5463de87685a24369205a9005d08b8203217231fd0e8d7b2a3030b1e702/grpcio-1.82.0rc1-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:030026c57ea908e1a7b012ecab6dedbc856dac632d12a3977ddbc8dde6610efa", size = 7454594, upload-time = "2026-06-17T04:01:05.178Z" }, + { url = "https://files.pythonhosted.org/packages/0c/38/7d420a87148e60af4335403812dccd663fc37825d64c419b30fef9359a88/grpcio-1.82.0rc1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8f82fc5c61a1eaa07f0d19a1632530cf9b45aa92d00521a8e397016a08a95a32", size = 6889641, upload-time = "2026-06-17T04:01:07.702Z" }, + { url = "https://files.pythonhosted.org/packages/60/66/60c5a5f379a5fe2a9546719ca376ffdd3b0e0531ca2fb0003272fcd45bd2/grpcio-1.82.0rc1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:cc5aaff87fd5a077fcb007d3e869d2fa17585ccf0db11047829818347cb41876", size = 7514334, upload-time = "2026-06-17T04:01:10.472Z" }, + { url = "https://files.pythonhosted.org/packages/e1/fb/a797d851315b776c76c7d7044a1d78e3b9fdfb3ce4934703339cbef21440/grpcio-1.82.0rc1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:4b4609c64d173e5f63c177bba0a1c4ca421b72e5d14dc287f3ea79eb3f0fc47e", size = 8536358, upload-time = "2026-06-17T04:01:13.335Z" }, + { url = "https://files.pythonhosted.org/packages/74/4b/6435cc50532675183e6a2be9b96d2b0168a73ba455e3fda1ff5d14fcaa85/grpcio-1.82.0rc1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:eb20fc64e5efd19e0e9b2f03e77e348229ade5e1beaced35929c9c02f58ac135", size = 7912667, upload-time = "2026-06-17T04:01:16.773Z" }, + { url = "https://files.pythonhosted.org/packages/d8/97/153558ddba64dc3d9cda2866b0df21f395f9bd16eb6df05722b36beeb208/grpcio-1.82.0rc1-cp314-cp314-win32.whl", hash = "sha256:8d895ca6cf862f1b830658d607d01d6fe15d2d4c426e0a1e4195ce0cd4601405", size = 4321944, upload-time = "2026-06-17T04:01:19.297Z" }, + { url = "https://files.pythonhosted.org/packages/d4/c0/18250da493ddf95d84da6945e60b6702c1b2d0d37251ffb3456cb538f30a/grpcio-1.82.0rc1-cp314-cp314-win_amd64.whl", hash = "sha256:892c08a292241e38a740de509f2b0189f2940ffaa599d55ae4d4fc5ba4f1386a", size = 5141156, upload-time = "2026-06-17T04:01:22.201Z" }, ] [[package]] @@ -1556,31 +1553,38 @@ wheels = [ [[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, upload-time = "2025-10-10T03:55:08.559Z" } -wheels = [ - { 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" }, +version = "0.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/e5/d471fcb0e14523fe1c3f4ba58ca52480e7bd70ad7109a3846bc75892f7fb/httptools-0.8.0.tar.gz", hash = "sha256:6b2a32f18d97e16e90827d7a819ffa8dbd8cc245fc4e1fa9d1095b54ef4bd999", size = 271342, upload-time = "2026-05-25T22:17:48.841Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/88/1d21a36da8f5cb0fa49eafd4b169eba5608d57e75bbcf61845cbc6243216/httptools-0.8.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:880490234c10f70a9830743097e8958d6e4b9f5a0ffc24515023afeef984054d", size = 208247, upload-time = "2026-05-25T22:17:07.843Z" }, + { url = "https://files.pythonhosted.org/packages/a5/42/cc4feea2945cb3051038f090c9b36bd5b8a9d7f5a894a506a8983e33fd1c/httptools-0.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5931891fb7b441b8a3853cf1b85c82c903defce084dd5f6771ca46e31bf862c5", size = 113064, upload-time = "2026-05-25T22:17:09.136Z" }, + { url = "https://files.pythonhosted.org/packages/e3/a6/febbb8b8db0f58b38e44ad6cb946e6a255ae49b55f2e8543408fb7501ccd/httptools-0.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b15fc622b0f869d19207c4089a501d9bcc63ca5e071ffdd2f03f922df882dcb2", size = 523851, upload-time = "2026-05-25T22:17:10.106Z" }, + { url = "https://files.pythonhosted.org/packages/b7/e4/f90a0df0b83beff265b7e3b65f2a4cefd95792d4be0ac3e16049f2acd3c2/httptools-0.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:425f83884fd6343828d8c565f046cb72b6d19063f6924093e11bcd8e1548cd09", size = 518842, upload-time = "2026-05-25T22:17:11.218Z" }, + { url = "https://files.pythonhosted.org/packages/9e/2d/0c9ac76dd2c893841fbf6498d6acec4f2442e1b7067f6e3e316a80e494e8/httptools-0.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ef7c3c97f4311c7be57e2986629df89d49cb434dbff78eafcd48c2bff986b15a", size = 501238, upload-time = "2026-05-25T22:17:12.728Z" }, + { url = "https://files.pythonhosted.org/packages/ca/42/906adc91ae3a5fa9c59c0a2f21c139725bd7e5b41ae6acd485cd14123ebf/httptools-0.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a1afd7c9fbff0d9f5d489c4ce2768bd09c84a46ddefc7161e6aa82ae35c85745", size = 509567, upload-time = "2026-05-25T22:17:13.842Z" }, + { url = "https://files.pythonhosted.org/packages/05/0b/4240efeb672751ee5b9b380cb0e3fdc050bc05f68adc7a8aefc4fcd9a69a/httptools-0.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:cd96f29b4bab1d42fa6e3d008711c75e0f79e94e06827330160e3a304227f150", size = 90918, upload-time = "2026-05-25T22:17:15.155Z" }, + { url = "https://files.pythonhosted.org/packages/5e/e5/8cfcabc5546e8022f168be28bcdaa128a240a0befdd03b59d558b4f18bd6/httptools-0.8.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:614ceea8ea606848bece2338ac03b3ce5324bcb4be8dc7d377ed708012fa4db8", size = 205148, upload-time = "2026-05-25T22:17:16.333Z" }, + { url = "https://files.pythonhosted.org/packages/2a/0e/0fb14848c19a686c8062ff9067c1a48793e3224b47bc5b201535b6036fce/httptools-0.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2d689918c15a013c65ef52d9fd495d766893ab831a2c8d89f2ac5940a5df847c", size = 111368, upload-time = "2026-05-25T22:17:17.586Z" }, + { url = "https://files.pythonhosted.org/packages/2e/1b/46f1cecf06b9bbde8e4b8c88034ac7908989e5ff7a3a388ef38392949c1f/httptools-0.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:eb3028cca2fc0a6d720e52ef61d8ebb62fcbfeb1de56874546d858d3f25a26b7", size = 486447, upload-time = "2026-05-25T22:17:18.564Z" }, + { url = "https://files.pythonhosted.org/packages/77/00/258bfc0837221f81d9725c45f9b948a6a6b2994a147a4fb66e85100c668f/httptools-0.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:88bdd940f2b5d487b4d032c6afa5489a7dc4694410d43de3c38c4fb3af0dc45d", size = 482448, upload-time = "2026-05-25T22:17:19.912Z" }, + { url = "https://files.pythonhosted.org/packages/04/ab/d1cef3b5523f4d272a70f42a776c3169a2dddfe3a54de4b2ce4a36341528/httptools-0.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6a43c9dd399758ccc0531acb0a3c4a6c299ee893ee9400e9c893b7bdcfae0681", size = 464460, upload-time = "2026-05-25T22:17:20.882Z" }, + { url = "https://files.pythonhosted.org/packages/ce/48/5d1d072442277bb2b3434e0e60690b8e8c23840ef7de8b6ea54040a536d3/httptools-0.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0770728beb05094c809b98e814edff5fef69d26ad7d21185f2f6d5884a0ba683", size = 471312, upload-time = "2026-05-25T22:17:22.085Z" }, + { url = "https://files.pythonhosted.org/packages/0d/66/b96623b27e51a68199ef4efdda0613cced9233fe3062ac74e50749c5ad37/httptools-0.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:7685df791fad561384bfb139e77fde27a1ffd93134e016f95a0db424ffbf77b1", size = 90117, upload-time = "2026-05-25T22:17:23.074Z" }, + { url = "https://files.pythonhosted.org/packages/1a/12/fa3fbf5f9517b273edea2dc982aa82a8c634091e67c590792b729017bc6f/httptools-0.8.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:de242a49b5d18e0a8776e654e9f6bf6d89f3875a5c35b425a0e7ce940feb3fd6", size = 206183, upload-time = "2026-05-25T22:17:24.004Z" }, + { url = "https://files.pythonhosted.org/packages/30/fc/5e7c4cb443370f2090a3aba0453a07384d29ff66b7435bb90e77e1037599/httptools-0.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:159e9ab5f701ccd42e555a12f1ad8ff69702910fc1c996cf2bb66e5fcb7a231b", size = 112079, upload-time = "2026-05-25T22:17:25.216Z" }, + { url = "https://files.pythonhosted.org/packages/ba/53/771bd891eb0f236f32145d6a1775777ec85745f3cc983a1f23d1a3b8ddfe/httptools-0.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c4a9f1707e4823d54dfec6c33fa3697d302aed536ed352a7ebb5a061ddb869d0", size = 481596, upload-time = "2026-05-25T22:17:26.186Z" }, + { url = "https://files.pythonhosted.org/packages/62/42/94e15bc68ce3d423243c45d7f1b0c7561f13844f97dc52ae23182fb65628/httptools-0.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d76ad7b951387e3632c8716a9bb03ac5b45c5f16119aa409db0459520887944e", size = 480865, upload-time = "2026-05-25T22:17:27.542Z" }, + { url = "https://files.pythonhosted.org/packages/1c/7c/fe2980fc03723272e30f135b62360b075f513dfe7cc73aef36c7f04012bd/httptools-0.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a3b7387147361c3fd47a0bde763c5c91b5b4cd4dc9989b8ece84ff436c99843b", size = 463189, upload-time = "2026-05-25T22:17:28.546Z" }, + { url = "https://files.pythonhosted.org/packages/15/1b/47fc5fff68acd1bfa20b4734059c9a06cadb88119dcd5258b5b0d21d91c8/httptools-0.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f256d6ce930c52ca1cb2a960b7da03548c454e7d28b06059ad41bfe789036ce0", size = 466610, upload-time = "2026-05-25T22:17:29.816Z" }, + { url = "https://files.pythonhosted.org/packages/60/bd/07b13c93ffd9bec9546e0d43f8e19378dd696dbd278511406bc07371ef1f/httptools-0.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:19d1ee275bb59ba2643ba9a3a1e51cc0c788caf2b8df506368e03f56fdd08527", size = 92705, upload-time = "2026-05-25T22:17:31.133Z" }, + { url = "https://files.pythonhosted.org/packages/fd/c4/121648f68ce066d7bd762d6b6d97e620847642d38d54f3d90ff11d947629/httptools-0.8.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:de1ed58a974e75d56560acc7e7fed01a454994429456f65209789992e41f2568", size = 215023, upload-time = "2026-05-25T22:17:32.401Z" }, + { url = "https://files.pythonhosted.org/packages/b9/b0/312a062ae741ae3e8baa8c8bf20be81b2e67337b259ab4349bebc7b6142e/httptools-0.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e93c227b595c6926c1acee96891dd9da4be338cfbe82e5cd3bb9d8dd7dc4ac0b", size = 117405, upload-time = "2026-05-25T22:17:33.742Z" }, + { url = "https://files.pythonhosted.org/packages/fc/37/fccd705f795386bb05bf413012fecff2a33e5aa8c2f069096de3e9fd8702/httptools-0.8.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2a021c3a8e65cc125390d72f59b968afca3bdcaff25bd67965e0a055a14946ca", size = 558497, upload-time = "2026-05-25T22:17:34.732Z" }, + { url = "https://files.pythonhosted.org/packages/bd/39/f172e8003576de35f5ba77ff417cf0e34429d35dc014deef15afa337a72c/httptools-0.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:48774d39cbb70e2b1f71f88852a3087ae1d3a1eb80482bb48c13067ab080c14f", size = 571585, upload-time = "2026-05-25T22:17:35.813Z" }, + { url = "https://files.pythonhosted.org/packages/3e/b9/f5564760af99f3dbbf3f9104dc00e5da27e96cf433c6bdcf77617f70bf3f/httptools-0.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:88eead8ec8680a9f146c655bc88445a325bd7921cfd8194c7337e9467282427d", size = 543297, upload-time = "2026-05-25T22:17:37.08Z" }, + { url = "https://files.pythonhosted.org/packages/99/67/8d9f2c313618e161b82f3873188e7196126da1d6e29688df40eb3997c77a/httptools-0.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2c032fa028f46871ec7e1fc59fc15e8023eab3e6bbe6ece786a1611719a5d081", size = 539535, upload-time = "2026-05-25T22:17:38.032Z" }, + { url = "https://files.pythonhosted.org/packages/48/63/b906c01e53f50d432c0defe43ce52764a111dc1bdd028bafbeb54dcfd008/httptools-0.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:384c17174464c8e873398b7af24f0b1f44d992c820328413951a625323155d77", size = 108209, upload-time = "2026-05-25T22:17:39.473Z" }, ] [[package]] @@ -1632,11 +1636,11 @@ wheels = [ [[package]] name = "idna" -version = "3.13" +version = "3.18" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ce/cc/762dfb036166873f0059f3b7de4565e1b5bc3d6f28a414c13da27e442f99/idna-3.13.tar.gz", hash = "sha256:585ea8fe5d69b9181ec1afba340451fba6ba764af97026f92a91d4eef164a242", size = 194210, upload-time = "2026-04-22T16:42:42.314Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/63/9496c57188a2ee585e0f1db071d75089a11e98aa86eb99d9d7618fc1edce/idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848", size = 196711, upload-time = "2026-06-02T14:34:07.794Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/13/ad7d7ca3808a898b4612b6fe93cde56b53f3034dcde235acb1f0e1df24c6/idna-3.13-py3-none-any.whl", hash = "sha256:892ea0cde124a99ce773decba204c5552b69c3c67ffd5f232eb7696135bc8bb3", size = 68629, upload-time = "2026-04-22T16:42:40.909Z" }, + { url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455, upload-time = "2026-06-02T14:34:06.319Z" }, ] [[package]] @@ -1692,14 +1696,14 @@ wheels = [ [[package]] name = "jaraco-functools" -version = "4.4.0" +version = "4.5.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "more-itertools" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0f/27/056e0638a86749374d6f57d0b0db39f29509cce9313cf91bdc0ac4d91084/jaraco_functools-4.4.0.tar.gz", hash = "sha256:da21933b0417b89515562656547a77b4931f98176eb173644c0d35032a33d6bb", size = 19943, upload-time = "2025-12-21T09:29:43.6Z" } +sdist = { url = "https://files.pythonhosted.org/packages/36/cf/ea4ef2920830dea3f5ab2ea4da6fb67724e6dca80ee2553788c3607243d0/jaraco_functools-4.5.0.tar.gz", hash = "sha256:3bb5665ea4a020cf78a7040e89154c77edadb3ca74f366479669c5999aa70b03", size = 20272, upload-time = "2026-05-15T21:34:10.025Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fd/c4/813bb09f0985cb21e959f21f2464169eca882656849adf727ac7bb7e1767/jaraco_functools-4.4.0-py3-none-any.whl", hash = "sha256:9eec1e36f45c818d9bf307c8948eb03b2b56cd44087b3cdc989abca1f20b9176", size = 10481, upload-time = "2025-12-21T09:29:42.27Z" }, + { url = "https://files.pythonhosted.org/packages/96/9a/982e48afcffcd727a9144506720ffd4224b6b7e355c98641866f38b7c043/jaraco_functools-4.5.0-py3-none-any.whl", hash = "sha256:79ce39246eddbde4b3a03b77ea5f0f7878dc669b166a66cf3fa8e266aa3fa2f4", size = 10594, upload-time = "2026-05-15T21:34:08.595Z" }, ] [[package]] @@ -1725,86 +1729,86 @@ wheels = [ [[package]] name = "jiter" -version = "0.14.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6e/c1/0cddc6eb17d4c53a99840953f95dd3accdc5cfc7a337b0e9b26476276be9/jiter-0.14.0.tar.gz", hash = "sha256:e8a39e66dac7153cf3f964a12aad515afa8d74938ec5cc0018adcdae5367c79e", size = 165725, upload-time = "2026-04-10T14:28:42.01Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/68/7390a418f10897da93b158f2d5a8bd0bcd73a0f9ec3bb36917085bb759ef/jiter-0.14.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:2fb2ce3a7bc331256dfb14cefc34832366bb28a9aca81deaf43bbf2a5659e607", size = 316295, upload-time = "2026-04-10T14:26:24.887Z" }, - { url = "https://files.pythonhosted.org/packages/60/a0/5854ac00ff63551c52c6c89534ec6aba4b93474e7924d64e860b1c94165b/jiter-0.14.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5252a7ca23785cef5d02d4ece6077a1b556a410c591b379f82091c3001e14844", size = 315898, upload-time = "2026-04-10T14:26:26.601Z" }, - { url = "https://files.pythonhosted.org/packages/41/a1/4f44832650a16b18e8391f1bf1d6ca4909bc738351826bcc198bba4357f4/jiter-0.14.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c409578cbd77c338975670ada777add4efd53379667edf0aceea730cabede6fb", size = 343730, upload-time = "2026-04-10T14:26:28.326Z" }, - { url = "https://files.pythonhosted.org/packages/48/64/a329e9d469f86307203594b1707e11ae51c3348d03bfd514a5f997870012/jiter-0.14.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7ede4331a1899d604463369c730dbb961ffdc5312bc7f16c41c2896415b1304a", size = 370102, upload-time = "2026-04-10T14:26:30.089Z" }, - { url = "https://files.pythonhosted.org/packages/94/c1/5e3dfc59635aa4d4c7bd20a820ac1d09b8ed851568356802cf1c08edb3cf/jiter-0.14.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:92cd8b6025981a041f5310430310b55b25ca593972c16407af8837d3d7d2ca01", size = 461335, upload-time = "2026-04-10T14:26:31.911Z" }, - { url = "https://files.pythonhosted.org/packages/e3/1b/dd157009dbc058f7b00108f545ccb72a2d56461395c4fc7b9cfdccb00af4/jiter-0.14.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:351bf6eda4e3a7ceb876377840c702e9a3e4ecc4624dbfb2d6463c67ae52637d", size = 378536, upload-time = "2026-04-10T14:26:33.595Z" }, - { url = "https://files.pythonhosted.org/packages/91/78/256013667b7c10b8834f8e6e54cd3e562d4c6e34227a1596addccc05e38c/jiter-0.14.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1dcfbeb93d9ecd9ca128bbf8910120367777973fa193fb9a39c31237d8df165", size = 353859, upload-time = "2026-04-10T14:26:35.098Z" }, - { url = "https://files.pythonhosted.org/packages/de/d9/137d65ade9093a409fe80955ce60b12bb753722c986467aeda47faf450ad/jiter-0.14.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:ae039aaef8de3f8157ecc1fdd4d85043ac4f57538c245a0afaecb8321ec951c3", size = 357626, upload-time = "2026-04-10T14:26:36.685Z" }, - { url = "https://files.pythonhosted.org/packages/2e/48/76750835b87029342727c1a268bea8878ab988caf81ee4e7b880900eeb5a/jiter-0.14.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7d9d51eb96c82a9652933bd769fe6de66877d6eb2b2440e281f2938c51b5643e", size = 393172, upload-time = "2026-04-10T14:26:38.097Z" }, - { url = "https://files.pythonhosted.org/packages/a6/60/456c4e81d5c8045279aefe60e9e483be08793828800a4e64add8fdde7f2a/jiter-0.14.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d824ca4148b705970bf4e120924a212fdfca9859a73e42bd7889a63a4ea6bb98", size = 520300, upload-time = "2026-04-10T14:26:39.532Z" }, - { url = "https://files.pythonhosted.org/packages/a8/9f/2020e0984c235f678dced38fe4eec3058cf528e6af36ebf969b410305941/jiter-0.14.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ff3a6465b3a0f54b1a430f45c3c0ba7d61ceb45cbc3e33f9e1a7f638d690baf3", size = 553059, upload-time = "2026-04-10T14:26:40.991Z" }, - { url = "https://files.pythonhosted.org/packages/ef/32/e2d298e1a22a4bbe6062136d1c7192db7dba003a6975e51d9a9eecabc4c2/jiter-0.14.0-cp312-cp312-win32.whl", hash = "sha256:5dec7c0a3e98d2a3f8a2e67382d0d7c3ac60c69103a4b271da889b4e8bb1e129", size = 206030, upload-time = "2026-04-10T14:26:42.517Z" }, - { url = "https://files.pythonhosted.org/packages/36/ac/96369141b3d8a4a8e4590e983085efe1c436f35c0cda940dd76d942e3e40/jiter-0.14.0-cp312-cp312-win_amd64.whl", hash = "sha256:fc7e37b4b8bc7e80a63ad6cfa5fc11fab27dbfea4cc4ae644b1ab3f273dc348f", size = 201603, upload-time = "2026-04-10T14:26:44.328Z" }, - { url = "https://files.pythonhosted.org/packages/01/c3/75d847f264647017d7e3052bbcc8b1e24b95fa139c320c5f5066fa7a0bdd/jiter-0.14.0-cp312-cp312-win_arm64.whl", hash = "sha256:ee4a72f12847ef29b072aee9ad5474041ab2924106bdca9fcf5d7d965853e057", size = 191525, upload-time = "2026-04-10T14:26:46Z" }, - { url = "https://files.pythonhosted.org/packages/97/2a/09f70020898507a89279659a1afe3364d57fc1b2c89949081975d135f6f5/jiter-0.14.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:af72f204cf4d44258e5b4c1745130ac45ddab0e71a06333b01de660ab4187a94", size = 315502, upload-time = "2026-04-10T14:26:47.697Z" }, - { url = "https://files.pythonhosted.org/packages/d6/be/080c96a45cd74f9fce5db4fd68510b88087fb37ffe2541ff73c12db92535/jiter-0.14.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4b77da71f6e819be5fbcec11a453fde5b1d0267ef6ed487e2a392fd8e14e4e3a", size = 314870, upload-time = "2026-04-10T14:26:49.149Z" }, - { url = "https://files.pythonhosted.org/packages/7d/5e/2d0fee155826a968a832cc32438de5e2a193292c8721ca70d0b53e58245b/jiter-0.14.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f4ea612fe8b84b8b04e51d0e78029ecf3466348e25973f953de6e6a59aa4c1", size = 343406, upload-time = "2026-04-10T14:26:50.762Z" }, - { url = "https://files.pythonhosted.org/packages/70/af/bf9ee0d3a4f8dc0d679fc1337f874fe60cdbf841ebbb304b374e1c9aaceb/jiter-0.14.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:62fe2451f8fcc0240261e6a4df18ecbcd58327857e61e625b2393ea3b468aac9", size = 369415, upload-time = "2026-04-10T14:26:52.188Z" }, - { url = "https://files.pythonhosted.org/packages/0f/83/8e8561eadba31f4d3948a5b712fb0447ec71c3560b57a855449e7b8ddc98/jiter-0.14.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6112f26f5afc75bcb475787d29da3aa92f9d09c7858f632f4be6ffe607be82e9", size = 461456, upload-time = "2026-04-10T14:26:53.611Z" }, - { url = "https://files.pythonhosted.org/packages/f6/c9/c5299e826a5fe6108d172b344033f61c69b1bb979dd8d9ddd4278a160971/jiter-0.14.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:215a6cb8fb7dc702aa35d475cc00ddc7f970e5c0b1417fb4b4ac5d82fa2a29db", size = 378488, upload-time = "2026-04-10T14:26:55.211Z" }, - { url = "https://files.pythonhosted.org/packages/5d/37/c16d9d15c0a471b8644b1abe3c82668092a707d9bedcf076f24ff2e380cd/jiter-0.14.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc4ab96a30fb3cb2c7e0cd33f7616c8860da5f5674438988a54ac717caccdbaa", size = 353242, upload-time = "2026-04-10T14:26:56.705Z" }, - { url = "https://files.pythonhosted.org/packages/58/ea/8050cb0dc654e728e1bfacbc0c640772f2181af5dedd13ae70145743a439/jiter-0.14.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:3a99c1387b1f2928f799a9de899193484d66206a50e98233b6b088a7f0c1edb2", size = 356823, upload-time = "2026-04-10T14:26:58.281Z" }, - { url = "https://files.pythonhosted.org/packages/b0/3b/cf71506d270e5f84d97326bf220e47aed9b95e9a4a060758fb07772170ab/jiter-0.14.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ab18d11074485438695f8d34a1b6da61db9754248f96d51341956607a8f39985", size = 392564, upload-time = "2026-04-10T14:27:00.018Z" }, - { url = "https://files.pythonhosted.org/packages/b0/cc/8c6c74a3efb5bd671bfd14f51e8a73375464ca914b1551bc3b40e26ac2c9/jiter-0.14.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:801028dcfc26ac0895e4964cbc0fd62c73be9fd4a7d7b1aaf6e5790033a719b7", size = 520322, upload-time = "2026-04-10T14:27:01.664Z" }, - { url = "https://files.pythonhosted.org/packages/41/24/68d7b883ec959884ddf00d019b2e0e82ba81b167e1253684fa90519ce33c/jiter-0.14.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ad425b087aafb4a1c7e1e98a279200743b9aaf30c3e0ba723aec93f061bd9bc8", size = 552619, upload-time = "2026-04-10T14:27:03.316Z" }, - { url = "https://files.pythonhosted.org/packages/b6/89/b1a0985223bbf3150ff9e8f46f98fc9360c1de94f48abe271bbe1b465682/jiter-0.14.0-cp313-cp313-win32.whl", hash = "sha256:882bcb9b334318e233950b8be366fe5f92c86b66a7e449e76975dfd6d776a01f", size = 205699, upload-time = "2026-04-10T14:27:04.662Z" }, - { url = "https://files.pythonhosted.org/packages/4c/19/3f339a5a7f14a11730e67f6be34f9d5105751d547b615ef593fa122a5ded/jiter-0.14.0-cp313-cp313-win_amd64.whl", hash = "sha256:9b8c571a5dba09b98bd3462b5a53f27209a5cbbe85670391692ede71974e979f", size = 201323, upload-time = "2026-04-10T14:27:06.139Z" }, - { url = "https://files.pythonhosted.org/packages/50/56/752dd89c84be0e022a8ea3720bcfa0a8431db79a962578544812ce061739/jiter-0.14.0-cp313-cp313-win_arm64.whl", hash = "sha256:34f19dcc35cb1abe7c369b3756babf8c7f04595c0807a848df8f26ef8298ef92", size = 191099, upload-time = "2026-04-10T14:27:07.564Z" }, - { url = "https://files.pythonhosted.org/packages/91/28/292916f354f25a1fe8cf2c918d1415c699a4a659ae00be0430e1c5d9ffea/jiter-0.14.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e89bcd7d426a75bb4952c696b267075790d854a07aad4c9894551a82c5b574ab", size = 320880, upload-time = "2026-04-10T14:27:09.326Z" }, - { url = "https://files.pythonhosted.org/packages/ad/c7/b002a7d8b8957ac3d469bd59c18ef4b1595a5216ae0de639a287b9816023/jiter-0.14.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b25beaa0d4447ea8c7ae0c18c688905d34840d7d0b937f2f7bdd52162c98a40", size = 346563, upload-time = "2026-04-10T14:27:11.287Z" }, - { url = "https://files.pythonhosted.org/packages/f9/3b/f8d07580d8706021d255a6356b8fab13ee4c869412995550ce6ed4ddf97d/jiter-0.14.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:651a8758dd413c51e3b7f6557cdc6921faf70b14106f45f969f091f5cda990ea", size = 357928, upload-time = "2026-04-10T14:27:12.729Z" }, - { url = "https://files.pythonhosted.org/packages/47/5b/ac1a974da29e35507230383110ffec59998b290a8732585d04e19a9eb5ba/jiter-0.14.0-cp313-cp313t-win_amd64.whl", hash = "sha256:e1a7eead856a5038a8d291f1447176ab0b525c77a279a058121b5fccee257f6f", size = 203519, upload-time = "2026-04-10T14:27:14.125Z" }, - { url = "https://files.pythonhosted.org/packages/96/6d/9fc8433d667d2454271378a79747d8c76c10b51b482b454e6190e511f244/jiter-0.14.0-cp313-cp313t-win_arm64.whl", hash = "sha256:2e692633a12cda97e352fdcd1c4acc971b1c28707e1e33aeef782b0cbf051975", size = 190113, upload-time = "2026-04-10T14:27:16.638Z" }, - { url = "https://files.pythonhosted.org/packages/4f/1e/354ed92461b165bd581f9ef5150971a572c873ec3b68a916d5aa91da3cc2/jiter-0.14.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:6f396837fc7577871ca8c12edaf239ed9ccef3bbe39904ae9b8b63ce0a48b140", size = 315277, upload-time = "2026-04-10T14:27:18.109Z" }, - { url = "https://files.pythonhosted.org/packages/a6/95/8c7c7028aa8636ac21b7a55faef3e34215e6ed0cbf5ae58258427f621aa3/jiter-0.14.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a4d50ea3d8ba4176f79754333bd35f1bbcd28e91adc13eb9b7ca91bc52a6cef9", size = 315923, upload-time = "2026-04-10T14:27:19.603Z" }, - { url = "https://files.pythonhosted.org/packages/47/40/e2a852a44c4a089f2681a16611b7ce113224a80fd8504c46d78491b47220/jiter-0.14.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce17f8a050447d1b4153bda4fb7d26e6a9e74eb4f4a41913f30934c5075bf615", size = 344943, upload-time = "2026-04-10T14:27:21.262Z" }, - { url = "https://files.pythonhosted.org/packages/fc/1f/670f92adee1e9895eac41e8a4d623b6da68c4d46249d8b556b60b63f949e/jiter-0.14.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f4f1c4b125e1652aefbc2e2c1617b60a160ab789d180e3d423c41439e5f32850", size = 369725, upload-time = "2026-04-10T14:27:22.766Z" }, - { url = "https://files.pythonhosted.org/packages/01/2f/541c9ba567d05de1c4874a0f8f8c5e3fd78e2b874266623da9a775cf46e0/jiter-0.14.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be808176a6a3a14321d18c603f2d40741858a7c4fc982f83232842689fe86dd9", size = 461210, upload-time = "2026-04-10T14:27:24.315Z" }, - { url = "https://files.pythonhosted.org/packages/ce/a9/c31cbec09627e0d5de7aeaec7690dba03e090caa808fefd8133137cf45bc/jiter-0.14.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:26679d58ba816f88c3849306dd58cb863a90a1cf352cdd4ef67e30ccf8a77994", size = 380002, upload-time = "2026-04-10T14:27:26.155Z" }, - { url = "https://files.pythonhosted.org/packages/50/02/3c05c1666c41904a2f607475a73e7a4763d1cbde2d18229c4f85b22dc253/jiter-0.14.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80381f5a19af8fa9aef743f080e34f6b25ebd89656475f8cf0470ec6157052aa", size = 354678, upload-time = "2026-04-10T14:27:27.701Z" }, - { url = "https://files.pythonhosted.org/packages/7d/97/e15b33545c2b13518f560d695f974b9891b311641bdcf178d63177e8801e/jiter-0.14.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:004df5fdb8ecbd6d99f3227df18ba1a259254c4359736a2e6f036c944e02d7c5", size = 358920, upload-time = "2026-04-10T14:27:29.256Z" }, - { url = "https://files.pythonhosted.org/packages/ad/d2/8b1461def6b96ba44530df20d07ef7a1c7da22f3f9bf1727e2d611077bf1/jiter-0.14.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cff5708f7ed0fa098f2b53446c6fa74c48469118e5cd7497b4f1cd569ab06928", size = 394512, upload-time = "2026-04-10T14:27:31.344Z" }, - { url = "https://files.pythonhosted.org/packages/e3/88/837566dd6ed6e452e8d3205355afd484ce44b2533edfa4ed73a298ea893e/jiter-0.14.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:2492e5f06c36a976d25c7cc347a60e26d5470178d44cde1b9b75e60b4e519f28", size = 521120, upload-time = "2026-04-10T14:27:33.299Z" }, - { url = "https://files.pythonhosted.org/packages/89/6b/b00b45c4d1b4c031777fe161d620b755b5b02cdade1e316dcb46e4471d63/jiter-0.14.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:7609cfbe3a03d37bfdbf5052012d5a879e72b83168a363deae7b3a26564d57de", size = 553668, upload-time = "2026-04-10T14:27:34.868Z" }, - { url = "https://files.pythonhosted.org/packages/ad/d8/6fe5b42011d19397433d345716eac16728ac241862a2aac9c91923c7509a/jiter-0.14.0-cp314-cp314-win32.whl", hash = "sha256:7282342d32e357543565286b6450378c3cd402eea333fc1ebe146f1fabb306fc", size = 207001, upload-time = "2026-04-10T14:27:36.455Z" }, - { url = "https://files.pythonhosted.org/packages/e5/43/5c2e08da1efad5e410f0eaaabeadd954812612c33fbbd8fd5328b489139d/jiter-0.14.0-cp314-cp314-win_amd64.whl", hash = "sha256:bd77945f38866a448e73b0b7637366afa814d4617790ecd88a18ca74377e6c02", size = 202187, upload-time = "2026-04-10T14:27:38Z" }, - { url = "https://files.pythonhosted.org/packages/aa/1f/6e39ac0b4cdfa23e606af5b245df5f9adaa76f35e0c5096790da430ca506/jiter-0.14.0-cp314-cp314-win_arm64.whl", hash = "sha256:f2d4c61da0821ee42e0cdf5489da60a6d074306313a377c2b35af464955a3611", size = 192257, upload-time = "2026-04-10T14:27:39.504Z" }, - { url = "https://files.pythonhosted.org/packages/05/57/7dbc0ffbbb5176a27e3518716608aa464aee2e2887dc938f0b900a120449/jiter-0.14.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1bf7ff85517dd2f20a5750081d2b75083c1b269cf75afc7511bdf1f9548beb3b", size = 323441, upload-time = "2026-04-10T14:27:41.039Z" }, - { url = "https://files.pythonhosted.org/packages/83/6e/7b3314398d8983f06b557aa21b670511ec72d3b79a68ee5e4d9bff972286/jiter-0.14.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c8ef8791c3e78d6c6b157c6d360fbb5c715bebb8113bc6a9303c5caff012754a", size = 348109, upload-time = "2026-04-10T14:27:42.552Z" }, - { url = "https://files.pythonhosted.org/packages/ae/4f/8dc674bcd7db6dba566de73c08c763c337058baff1dbeb34567045b27cdc/jiter-0.14.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e74663b8b10da1fe0f4e4703fd7980d24ad17174b6bb35d8498d6e3ebce2ae6a", size = 368328, upload-time = "2026-04-10T14:27:44.574Z" }, - { url = "https://files.pythonhosted.org/packages/3b/5f/188e09a1f20906f98bbdec44ed820e19f4e8eb8aff88b9d1a5a497587ff3/jiter-0.14.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1aca29ba52913f78362ec9c2da62f22cdc4c3083313403f90c15460979b84d9b", size = 463301, upload-time = "2026-04-10T14:27:46.717Z" }, - { url = "https://files.pythonhosted.org/packages/ac/f0/19046ef965ed8f349e8554775bb12ff4352f443fbe12b95d31f575891256/jiter-0.14.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8b39b7d87a952b79949af5fef44d2544e58c21a28da7f1bae3ef166455c61746", size = 378891, upload-time = "2026-04-10T14:27:48.32Z" }, - { url = "https://files.pythonhosted.org/packages/c4/c3/da43bd8431ee175695777ee78cf0e93eacbb47393ff493f18c45231b427d/jiter-0.14.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78d918a68b26e9fab068c2b5453577ef04943ab2807b9a6275df2a812599a310", size = 360749, upload-time = "2026-04-10T14:27:49.88Z" }, - { url = "https://files.pythonhosted.org/packages/72/26/e054771be889707c6161dbdec9c23d33a9ec70945395d70f07cfea1e9a6f/jiter-0.14.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:b08997c35aee1201c1a5361466a8fb9162d03ae7bf6568df70b6c859f1e654a4", size = 358526, upload-time = "2026-04-10T14:27:51.504Z" }, - { url = "https://files.pythonhosted.org/packages/c3/0f/7bea65ea2a6d91f2bf989ff11a18136644392bf2b0497a1fa50934c30a9c/jiter-0.14.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:260bf7ca20704d58d41f669e5e9fe7fe2fa72901a6b324e79056f5d52e9c9be2", size = 393926, upload-time = "2026-04-10T14:27:53.368Z" }, - { url = "https://files.pythonhosted.org/packages/3c/a1/b1ff7d70deef61ac0b7c6c2f12d2ace950cdeecb4fdc94500a0926802857/jiter-0.14.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:37826e3df29e60f30a382f9294348d0238ef127f4b5d7f5f8da78b5b9e050560", size = 521052, upload-time = "2026-04-10T14:27:55.058Z" }, - { url = "https://files.pythonhosted.org/packages/0b/7b/3b0649983cbaf15eda26a414b5b1982e910c67bd6f7b1b490f3cfc76896a/jiter-0.14.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:645be49c46f2900937ba0eaf871ad5183c96858c0af74b6becc7f4e367e36e06", size = 553716, upload-time = "2026-04-10T14:27:57.269Z" }, - { url = "https://files.pythonhosted.org/packages/97/f8/33d78c83bd93ae0c0af05293a6660f88a1977caef39a6d72a84afab94ce0/jiter-0.14.0-cp314-cp314t-win32.whl", hash = "sha256:2f7877ed45118de283786178eceaf877110abacd04fde31efff3940ae9672674", size = 207957, upload-time = "2026-04-10T14:27:59.285Z" }, - { url = "https://files.pythonhosted.org/packages/d6/ac/2b760516c03e2227826d1f7025d89bf6bf6357a28fe75c2a2800873c50bf/jiter-0.14.0-cp314-cp314t-win_amd64.whl", hash = "sha256:14c0cb10337c49f5eafe8e7364daca5e29a020ea03580b8f8e6c597fed4e1588", size = 204690, upload-time = "2026-04-10T14:28:00.962Z" }, - { url = "https://files.pythonhosted.org/packages/dc/2e/a44c20c58aeed0355f2d326969a181696aeb551a25195f47563908a815be/jiter-0.14.0-cp314-cp314t-win_arm64.whl", hash = "sha256:5419d4aa2024961da9fe12a9cfe7484996735dca99e8e090b5c88595ef1951ff", size = 191338, upload-time = "2026-04-10T14:28:02.853Z" }, - { url = "https://files.pythonhosted.org/packages/21/42/9042c3f3019de4adcb8c16591c325ec7255beea9fcd33a42a43f3b0b1000/jiter-0.14.0-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:fbd9e482663ca9d005d051330e4d2d8150bb208a209409c10f7e7dfdf7c49da9", size = 308810, upload-time = "2026-04-10T14:28:34.673Z" }, - { url = "https://files.pythonhosted.org/packages/60/cf/a7e19b308bd86bb04776803b1f01a5f9a287a4c55205f4708827ee487fbf/jiter-0.14.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:33a20d838b91ef376b3a56896d5b04e725c7df5bc4864cc6569cf046a8d73b6d", size = 308443, upload-time = "2026-04-10T14:28:36.658Z" }, - { url = "https://files.pythonhosted.org/packages/ca/44/e26ede3f0caeff93f222559cb0cc4ca68579f07d009d7b6010c5b586f9b1/jiter-0.14.0-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:432c4db5255d86a259efde91e55cb4c8d18c0521d844c9e2e7efcce3899fb016", size = 343039, upload-time = "2026-04-10T14:28:38.356Z" }, - { url = "https://files.pythonhosted.org/packages/da/e9/1f9ada30cef7b05e74bb06f52127e7a724976c225f46adb65c37b1dadfb6/jiter-0.14.0-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67f00d94b281174144d6532a04b66a12cb866cbdc47c3af3bfe2973677f9861a", size = 349613, upload-time = "2026-04-10T14:28:40.066Z" }, +version = "0.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/66/b5/55f06bb281d92fb3cc86d14e1def2bd908bb77693183e7cb1f5a3c388b0c/jiter-0.15.0.tar.gz", hash = "sha256:4251acc80e2b7c9b7b8823456ea0fceeb0734dac2df7636d3c711b38476b5a76", size = 166640, upload-time = "2026-05-19T10:09:48.361Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/53/4f6bddbcde3c71e56d0aa1337ec95950f3d27dd4153e25aadf0feac71751/jiter-0.15.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:0e90a1c315a0226ec822d973817967f9223b7701546c8c2a7913e7ab0926294d", size = 308793, upload-time = "2026-05-19T10:07:35.25Z" }, + { url = "https://files.pythonhosted.org/packages/01/84/c01099b59a285a1ebba64ae93f62bfa036675340fd1b0045ae65890a0442/jiter-0.15.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8c9004af7c8d67cce7f1aae1026fb55607f4aa600710d08ede3a3ce4aeefe7e0", size = 309570, upload-time = "2026-05-19T10:07:36.919Z" }, + { url = "https://files.pythonhosted.org/packages/58/64/8fb7f9d45bb98190355454cd04dad8d8f27223d6bd52f83af07f637168a6/jiter-0.15.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c210f8b35dc6f30aafd4b4365ca89b9d1189f21ab49b8e68fa6322a847aef138", size = 336783, upload-time = "2026-05-19T10:07:38.694Z" }, + { url = "https://files.pythonhosted.org/packages/c3/b6/f5739011d009b3a30f6a53c5240979030ba29ae46a8c67e3a15759f7c37d/jiter-0.15.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f30bae8bc1c2d613e28e5af3e8cceb09b742f1c8a8a5f839fb67afaffc03b61", size = 363555, upload-time = "2026-05-19T10:07:40.832Z" }, + { url = "https://files.pythonhosted.org/packages/e5/12/98a9d9f766665e8a3b6252454e17cb0c464606a28cf2fa09399b003345fa/jiter-0.15.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c60e71b6d10cfc284c9bf36bd885e8d44c46f688ce50aa91b5edd90181dea687", size = 452255, upload-time = "2026-05-19T10:07:42.62Z" }, + { url = "https://files.pythonhosted.org/packages/e8/d5/60f972840f79c5e7544fce567c56f1e4e50468f996baba3e78d823dd62a6/jiter-0.15.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ab068bce62a45aa3e7367eceaffb5dde60b7eb853be8dece45132e3d0ff4879", size = 373559, upload-time = "2026-05-19T10:07:44.201Z" }, + { url = "https://files.pythonhosted.org/packages/ee/cf/d46ef1234ba335aabc2f013210db8e0821a22f5e644a2e9449df199ecc23/jiter-0.15.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa248c9eb220197d363f688818dac2fd4b2f0cd7d843ca7105d652034823427d", size = 346055, upload-time = "2026-05-19T10:07:46.005Z" }, + { url = "https://files.pythonhosted.org/packages/f0/63/4d2749d8d54d230bad9b3a6b0d00cc28c6ff6b2fdffc26a8ccf76cc5a974/jiter-0.15.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:2a77aadd57cac1682e4401a72724d2796d89a4ba129b1a5812aa94ee480826eb", size = 351406, upload-time = "2026-05-19T10:07:47.855Z" }, + { url = "https://files.pythonhosted.org/packages/d9/b9/9965b990035d8773328e0a8c8b457a87bf2b19f6c4126d9d99296be5d16a/jiter-0.15.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2ae901f3a55bfafdde31d289590fa25e3245735a2b1e8c7cc15871710a002871", size = 389357, upload-time = "2026-05-19T10:07:49.665Z" }, + { url = "https://files.pythonhosted.org/packages/2d/55/9ddf903deda1413e87fed792f416b7123daee5b8efbad6a202a7421c36a5/jiter-0.15.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f0b271b462769543716f92d3a4f90527df6ef5ed05ee95ec4137f513e21e1b77", size = 517263, upload-time = "2026-05-19T10:07:51.537Z" }, + { url = "https://files.pythonhosted.org/packages/e8/76/a0c40ad064d3a20a4fde231e35d56e9a01ce82164278180e82d5daf85469/jiter-0.15.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2fb6a5d26af81fc0f00f9360a891e05cf755e149bba391c4d563adc54812973d", size = 548646, upload-time = "2026-05-19T10:07:53.196Z" }, + { url = "https://files.pythonhosted.org/packages/23/4f/eca9b954942916ba2f453891b8593ab444cd872396fe66a3936616f236f3/jiter-0.15.0-cp312-cp312-win32.whl", hash = "sha256:c2f6bb8b5216ab9e7873bc08b5d7bef2b8abbb578a3069bf1cd14a45d71d771d", size = 206427, upload-time = "2026-05-19T10:07:55.307Z" }, + { url = "https://files.pythonhosted.org/packages/95/bf/8ead82a87495149542748e828d153fd232a512a22c83b02c4815c1a9c7d8/jiter-0.15.0-cp312-cp312-win_amd64.whl", hash = "sha256:40b2c7e92c44a84d748d21706c68dc6ff8161d80b59c99d774721a0d2317d7c7", size = 197300, upload-time = "2026-05-19T10:07:56.651Z" }, + { url = "https://files.pythonhosted.org/packages/f4/e4/9b8a78fb2d894471bc344e37f1949bdd784bd914d031dba0ba3a40c71dd7/jiter-0.15.0-cp312-cp312-win_arm64.whl", hash = "sha256:cc0bc345cf2df9d1c00ac443f50d543c1ccfa8b0422cb85b1ab70d681c0b255b", size = 192702, upload-time = "2026-05-19T10:07:58.307Z" }, + { url = "https://files.pythonhosted.org/packages/e5/f4/f708c900ecee41b2025ef8413d5351e5649eb2125c506f6720cc69b06f5c/jiter-0.15.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1c11465f97e2abf45a014b83b730222f8f1c5335e802c7055a67d50de6f1f4e3", size = 307829, upload-time = "2026-05-19T10:07:59.704Z" }, + { url = "https://files.pythonhosted.org/packages/86/59/db537c0949e83668c38481d426b9f2fd5ab758c4ee53a811dd0a510626a0/jiter-0.15.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d1e7b1776f0797956c509e123d0952d10d293a9492dea9f288ab9570ec01d1a5", size = 308445, upload-time = "2026-05-19T10:08:01.184Z" }, + { url = "https://files.pythonhosted.org/packages/37/38/ea0e13b18c30ef951da0d47d39e7fa9edb82a93a62990ffbd7cea9b622d4/jiter-0.15.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:351a341c2105aa430b7047e30f1bf7975f6313b00165d3fc07be2edaf741f279", size = 336181, upload-time = "2026-05-19T10:08:02.688Z" }, + { url = "https://files.pythonhosted.org/packages/58/fc/2303901b16c4ba05865588990a420c0b4156270b44379c20931544a1d962/jiter-0.15.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4ab395feec8d249ec4044e228e98a7033f043426a265df439dc3698823f0a4e4", size = 362985, upload-time = "2026-05-19T10:08:04.394Z" }, + { url = "https://files.pythonhosted.org/packages/5b/6f/11bace093c52e7d4d26c8e606ccd7ae8c972189622469ec0d9e28161e28b/jiter-0.15.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a2a438005b6f22d0273413484d6094d7c2c5d10ec1b3a3bf128e0d1d3ba53258", size = 453292, upload-time = "2026-05-19T10:08:05.967Z" }, + { url = "https://files.pythonhosted.org/packages/22/db/987f2f086ca4d7a6582eb4ccd513f9b26b42d9e4243a087609a3137a8fc7/jiter-0.15.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f18f85e4218d1b40f000f42a92239a7a61a902cd42c65e6c360dbd17dcb20894", size = 373501, upload-time = "2026-05-19T10:08:07.857Z" }, + { url = "https://files.pythonhosted.org/packages/8f/7c/89fbcabb2739b7a5b8dc959a1b6c5761f6484f5fed3486854b3c789bb1de/jiter-0.15.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1aa62e277fc1cbd80e6deacae6f4d983b41b3d7728e0645c5d741a6149bba45", size = 344683, upload-time = "2026-05-19T10:08:09.431Z" }, + { url = "https://files.pythonhosted.org/packages/30/6f/6cca7692e7dddfec6d8d76c54dc97f2af2a41df4ac0674b999df1f09a5f3/jiter-0.15.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:6550fa135c7deb8ead6af49ed7ff648532ea8334a1447fe34a36315ef79c5c29", size = 350892, upload-time = "2026-05-19T10:08:11.352Z" }, + { url = "https://files.pythonhosted.org/packages/39/14/0338d6190cb8e6d22e677ab1d4eabd4117f67cca70c54cd04b82ff64e068/jiter-0.15.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:066f8f33f18b2419cd8213b2436fa7fbc9c499f315971cfa3ce1f9820c001b1b", size = 388723, upload-time = "2026-05-19T10:08:12.912Z" }, + { url = "https://files.pythonhosted.org/packages/90/31/cc19f4a1bdb6afb09ce6a2f2615aa8d44d994eba0d8e6105ed1af920e736/jiter-0.15.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:75e8a04e91432dde9f1838373cf93d23726c79d3e908d319acf0e796f85592e7", size = 516648, upload-time = "2026-05-19T10:08:14.808Z" }, + { url = "https://files.pythonhosted.org/packages/49/9f/833c541512cd091b63c10c0381973dfe11bc7a503a818c16384417e0c81e/jiter-0.15.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:a97261f1fccb8e50ecd2890a96e46efdc3f57c80a197324c6777827231eca712", size = 547382, upload-time = "2026-05-19T10:08:16.927Z" }, + { url = "https://files.pythonhosted.org/packages/d2/11/e7b70e91f90bc4477e8eee9e8a5f7cf3cb41b4525d6394dc98a714eb8f7f/jiter-0.15.0-cp313-cp313-win32.whl", hash = "sha256:c77496cb10bd7549690fbbab3e5ec05857b83e49276f4a9423a766ddd2afcd4c", size = 205845, upload-time = "2026-05-19T10:08:18.401Z" }, + { url = "https://files.pythonhosted.org/packages/4b/23/5c20d9ad6f02c493e4023e5d2d09e1c1f15fe2753c9102c544aff068a88e/jiter-0.15.0-cp313-cp313-win_amd64.whl", hash = "sha256:b15741f501469009ae0ae90b7147958a664a7dede40aa7ff174a8a4645f546d0", size = 196842, upload-time = "2026-05-19T10:08:20.131Z" }, + { url = "https://files.pythonhosted.org/packages/6b/11/1eb400ef248e8c925fd883fbe325daf5e42cd1b0d308539dd332bd4f7ffc/jiter-0.15.0-cp313-cp313-win_arm64.whl", hash = "sha256:5d6a60072b44c3c2b797a7ddcbcbbf2b34ea3cfd4721580fbfd2a09d9d9b84ba", size = 192212, upload-time = "2026-05-19T10:08:21.807Z" }, + { url = "https://files.pythonhosted.org/packages/8a/60/2fd8d7c79da8acf9b7b277c7616847773779356b92acfc9bb158452174da/jiter-0.15.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ef1fd24d9413f6209e00d3d5a453e67acfe004a25cc6c8e8484faed4311ab9e8", size = 315065, upload-time = "2026-05-19T10:08:23.218Z" }, + { url = "https://files.pythonhosted.org/packages/46/f4/008fb7d65e8ac2abf00811651a661e025c4ba80bbc6f378450384ddd3aed/jiter-0.15.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:144f8e72cb53dab146347b91cceac01f5481237f2b93b4a339a1ee8f8878b67c", size = 339444, upload-time = "2026-05-19T10:08:24.701Z" }, + { url = "https://files.pythonhosted.org/packages/00/55/90b0c7b9c6896c0f2a591dd36d36b71d22e09674bfef178fa03ba3f81499/jiter-0.15.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:553fcac2ef2cb990877f9fc0833b8b629a3e6a5670b6b5fd58219b41a653ddc4", size = 347779, upload-time = "2026-05-19T10:08:26.408Z" }, + { url = "https://files.pythonhosted.org/packages/51/6b/69666cec5000fd57734c118437394516c749ae8dbeea9fb66d6fef9c4775/jiter-0.15.0-cp313-cp313t-win_amd64.whl", hash = "sha256:774f93f65031856bf14ad9f59bdcab8b8cad501e5ceabd51ba3525f76937a25b", size = 200395, upload-time = "2026-05-19T10:08:28.055Z" }, + { url = "https://files.pythonhosted.org/packages/39/04/a6aa62cd27e8149b0d28df5561f10f6cceaf7935a9ccf3f1c5a05f9a0cd8/jiter-0.15.0-cp313-cp313t-win_arm64.whl", hash = "sha256:f1e1754960f38ec40613a07e5e372df67acb3b890fb383b6fb3de3e49ddbf3c7", size = 190516, upload-time = "2026-05-19T10:08:29.35Z" }, + { url = "https://files.pythonhosted.org/packages/eb/d2/079f350ebf7859d081de30aa890f9e3be68516f754f3ba32366ffff4dcee/jiter-0.15.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:ac0d9ddea4350974be7a221fc25895f251a8fee748c889bdced2141c0fec1a49", size = 308884, upload-time = "2026-05-19T10:08:31.667Z" }, + { url = "https://files.pythonhosted.org/packages/04/4e/a2c30a7f69b48c03b20935d647479106fe932f6e63f75faf53937197e05d/jiter-0.15.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:01a8222cf05ab1128e239421156c207949808acaaea2bdfd33130ae666786e86", size = 310028, upload-time = "2026-05-19T10:08:33.304Z" }, + { url = "https://files.pythonhosted.org/packages/40/90/2e7cdfd3cf8ca967be38c48f5cf474d79f089efaf559a40f15984a77ae69/jiter-0.15.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:182226cbc930c9fab81bc2e41a4da672f89539906dadb05e75670ac07b94f71f", size = 337485, upload-time = "2026-05-19T10:08:35.259Z" }, + { url = "https://files.pythonhosted.org/packages/9b/11/15a1aa28b120b8ee5b4f1fb894c125046225f09847738bd64233d3b84883/jiter-0.15.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:71683c38c825452999b5717fcae07ea708e8c93003e808be4319c1b02e3d176e", size = 364223, upload-time = "2026-05-19T10:08:36.694Z" }, + { url = "https://files.pythonhosted.org/packages/b7/25/f442e8af5f3d0dcf47b39e83a0efd9ee45ea946aa6d04625dc3181eae3b6/jiter-0.15.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:30f2218e6a9e5c18bc10fe6d41ac189c442c88eacf11bad9f28ef95a9bef00e6", size = 456387, upload-time = "2026-05-19T10:08:38.143Z" }, + { url = "https://files.pythonhosted.org/packages/da/f4/37f2d2c9f64f49af7da652ed7532bb5a2372e588e6927c3fdd76f911db65/jiter-0.15.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5157de9f76eb4bc5ea74a1219366a25f945ad305641d74e04f59c54087091aa9", size = 374461, upload-time = "2026-05-19T10:08:39.869Z" }, + { url = "https://files.pythonhosted.org/packages/60/28/edcfbbbf0cb15436f36664a8908a0df47ab9006298d4cd937dc08ea932d6/jiter-0.15.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90c5db5527c221249a876160663ab891ace358c17f7b9c93ec1478b7f0550e5c", size = 345924, upload-time = "2026-05-19T10:08:41.668Z" }, + { url = "https://files.pythonhosted.org/packages/47/13/89fba6398dab7f202b7278c4b4aac122399d2c0183971c4a57a3b7088df5/jiter-0.15.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:3e4540b8e74e4268811ac05db226a6a128ff572e7e0ce3f1163b693cadb184cd", size = 352283, upload-time = "2026-05-19T10:08:43.091Z" }, + { url = "https://files.pythonhosted.org/packages/1b/da/0f6af8cef2c565a1ab44d970f268c43ccaa72707386ea6388e6fe2b6cd26/jiter-0.15.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:62ebd14e47e9aed9df4472afcb2663668ce4d74891cd54f86bf6e44029d6dc89", size = 389985, upload-time = "2026-05-19T10:08:44.915Z" }, + { url = "https://files.pythonhosted.org/packages/a1/ec/b9cb7d6d29e24ee14910266157d2a279d7a8f60ee0df7fa840882976ba64/jiter-0.15.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0be6f5ad41a809f303f416d17cec92a7a725902fb9b4f3de3d19362ac0ef8554", size = 517695, upload-time = "2026-05-19T10:08:46.486Z" }, + { url = "https://files.pythonhosted.org/packages/64/5e/6d1bda880723aae0ad86b4b763f044362448efe31e3e819635d41cb03451/jiter-0.15.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:813dfbb17d65328bf86e5f0905dd277ba2265d3ca20556e86c0c7035b7182e5a", size = 548868, upload-time = "2026-05-19T10:08:48.026Z" }, + { url = "https://files.pythonhosted.org/packages/0c/72/7de501cf38dcacaf35098796f3a50e0f2e338baba18a58946c618544b809/jiter-0.15.0-cp314-cp314-win32.whl", hash = "sha256:50e51156192722a9c58db112837d3f8ef96fb3c5ecc14e95f409134b08b158ec", size = 206380, upload-time = "2026-05-19T10:08:49.738Z" }, + { url = "https://files.pythonhosted.org/packages/1e/a9/e19addf4b0c1bdce52c6da12351e6bc42c340c45e7c09e2158e46d293ccc/jiter-0.15.0-cp314-cp314-win_amd64.whl", hash = "sha256:30ce1a5d16b5641dc935d50ef775af6a0871e3d14ab05d6fc54dff371b78e558", size = 197687, upload-time = "2026-05-19T10:08:51.088Z" }, + { url = "https://files.pythonhosted.org/packages/f2/c9/776b1db01db25fc6c1d58d1979a37b0a9fe787e5f5b1d062d2eaacb77923/jiter-0.15.0-cp314-cp314-win_arm64.whl", hash = "sha256:510c8b3c17a0ed9ac69850c0438dada3c9b82d9c4d589fcb62002a5a9cf3a866", size = 192571, upload-time = "2026-05-19T10:08:52.451Z" }, + { url = "https://files.pythonhosted.org/packages/a0/f6/45bb4670bacf300fd2c7abadbfb3af376e5f1b6ae75fd9bc069891d15870/jiter-0.15.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7553333dd0930c104a5a0db8df72bf7219fe663d731383b576bb6ed6351c984d", size = 317151, upload-time = "2026-05-19T10:08:53.867Z" }, + { url = "https://files.pythonhosted.org/packages/d7/68/ed635ad5acd7b73e454283083bbb7c8205ad10e88b0d9d7d793b09fe8226/jiter-0.15.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2143ab06181d2b029eedcb6af3cebe95f11bbac62441781860f98ee9330a6a6", size = 341243, upload-time = "2026-05-19T10:08:55.383Z" }, + { url = "https://files.pythonhosted.org/packages/5d/db/3ff4176b817b8ea33879e71e13d8bc2b0d481a7ed3fe9e080f333d415c16/jiter-0.15.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6eac374c5c975709b69c10f09afd199df74150172156ad10c8d4fd785b7da995", size = 363629, upload-time = "2026-05-19T10:08:56.928Z" }, + { url = "https://files.pythonhosted.org/packages/ab/24/5f8270e0ba9c883582f96f722f8a0b58015c7ce1f8c6d4571cf394e99b6b/jiter-0.15.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b3b3b775e33d3bfaec9899edc526ae97b0da0bf9d071a46124ba419149a414f8", size = 456198, upload-time = "2026-05-19T10:08:58.618Z" }, + { url = "https://files.pythonhosted.org/packages/45/5b/76fc02b0b5c54c3d18c60653156e2f76fde1816f9b4722db68d6ee2c897e/jiter-0.15.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3071db3346334beae1360b46da4606da57bf3528c167b3c38533afaf9f2c5", size = 373710, upload-time = "2026-05-19T10:09:00.151Z" }, + { url = "https://files.pythonhosted.org/packages/c4/52/4310821b0ea9277994d3e1f49fc6a4b34e4800caebacb2c0af81da59a454/jiter-0.15.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c6694a173ecabc12eb60efbc0b474464ead1951ff65cd8b1e72100715c64512b", size = 349901, upload-time = "2026-05-19T10:09:01.621Z" }, + { url = "https://files.pythonhosted.org/packages/93/fe/67648c35b3594fba8854ac64cc8a826d8bcd18324bbdb53d77697c60b6ef/jiter-0.15.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:a254e10b593624d230c365b6d616b22ca0ad65e63a16e6631c2b3466022e6ba8", size = 352438, upload-time = "2026-05-19T10:09:03.216Z" }, + { url = "https://files.pythonhosted.org/packages/cb/28/0a1879d07ad6b3e025a2750027363452ced93c2d16d1c9d4b153ffd51c91/jiter-0.15.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d8d2955167274e15d79a7a020afdd9b39c990eb80b2d89fca695d92dcfdd38ec", size = 388152, upload-time = "2026-05-19T10:09:04.741Z" }, + { url = "https://files.pythonhosted.org/packages/c1/78/46c6f6b56ba85c90021f4afd72ed42f691f8f84daacb5fe27277070e3858/jiter-0.15.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:acf4ee4d1fc55917239fe72972fb292dd773055d05eb040d36f4326e02cc2c0e", size = 517707, upload-time = "2026-05-19T10:09:06.231Z" }, + { url = "https://files.pythonhosted.org/packages/ca/cb/720662d4c88fcad606e826fef5424365527ba43ce4868a479aed8f8c507e/jiter-0.15.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:e7196e56f1cd69af1dbb07dff02dcfb260a50b45a82d409d92a06fedb32473b5", size = 548241, upload-time = "2026-05-19T10:09:08.093Z" }, + { url = "https://files.pythonhosted.org/packages/60/e3/935b8034fd143f21125c87d51404a9e0e1449186a494405721ff5d1d695e/jiter-0.15.0-cp314-cp314t-win32.whl", hash = "sha256:7f6163c0f10b055245f814dcc59f4818da60dfe72f3e72ab89fc24b6bd5e9c52", size = 207950, upload-time = "2026-05-19T10:09:09.616Z" }, + { url = "https://files.pythonhosted.org/packages/93/59/984fd9ece895953dad3e0880a650e766f5a2da2c5514f0eafdaaabbeb5f9/jiter-0.15.0-cp314-cp314t-win_amd64.whl", hash = "sha256:980c256edb05b78a111b99c4de3b1d32e31634b867fd1fc2cf726e7b7bba9854", size = 200055, upload-time = "2026-05-19T10:09:11.367Z" }, + { url = "https://files.pythonhosted.org/packages/0e/a4/cf8d779feb133a27a2e3bc833bccb9e13aa332cdf820497ebf72c10ce8c3/jiter-0.15.0-cp314-cp314t-win_arm64.whl", hash = "sha256:66b1880df2d01e206e8339769d1c7c1753bcb653efd6289e203f6f24ebada0c0", size = 191244, upload-time = "2026-05-19T10:09:12.74Z" }, + { url = "https://files.pythonhosted.org/packages/73/38/505941b2b092fd5bbbd60a52a880db1173f1690ae6751bed3af1c9ddcb4e/jiter-0.15.0-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:631f13a3d04e97d4e083993b10f4b99530e3a10d953e2eb5e196b7dc7f812ce0", size = 303769, upload-time = "2026-05-19T10:09:42.203Z" }, + { url = "https://files.pythonhosted.org/packages/e7/95/a06692b29e77473f286e1ec1f426d3ca44d7b5843be8ad21d7a5f3fcdcc0/jiter-0.15.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:b6c0ffae686c39bf3737be60793783267628783ea42545632c10b291105aee45", size = 305128, upload-time = "2026-05-19T10:09:43.657Z" }, + { url = "https://files.pythonhosted.org/packages/23/85/7270d7ad41d6061a25b950c6bf91d638bd9aacb113200a8c8d57a055fd67/jiter-0.15.0-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d54fb5b31dea401a41af3f8a7d2512e9b6a6a005491e6166c7e4ffab9639a9c", size = 340459, upload-time = "2026-05-19T10:09:45.452Z" }, + { url = "https://files.pythonhosted.org/packages/c8/8d/302cb2057b7513327b4d575cff6b1d066ee6431a5357fc3f8867cd684406/jiter-0.15.0-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54d5d6090cdc1b7c9e780dfb04949a990adb1e301a2fc0bbcee7de4638d33f9a", size = 344469, upload-time = "2026-05-19T10:09:46.864Z" }, ] [[package]] name = "joserfc" -version = "1.6.4" +version = "1.7.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cryptography" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/de/c6/de8fdbdfa75c8ca04fead38a82d573df8a82906e984c349d58665f459558/joserfc-1.6.4.tar.gz", hash = "sha256:34ce5f499bfcc5e9ad4cc75077f9278ab3227b71da9aaf28f9ab705f8a560d3c", size = 231866, upload-time = "2026-04-13T13:15:40.632Z" } +sdist = { url = "https://files.pythonhosted.org/packages/44/90/25cb27518750218e4f850be63d8bbb2343efaad1c01c3571aaa4b3c33bd7/joserfc-1.7.1.tar.gz", hash = "sha256:77d0b76514879c68c6f433bc5b7357a4ab72008ff1e33d8379fd11d72bd8ca81", size = 233181, upload-time = "2026-06-08T07:21:33.412Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b6/f7/210b27752e972edb36d239315b08d3eb6b14824cc4a590da2337d195260b/joserfc-1.6.4-py3-none-any.whl", hash = "sha256:3e4a22b509b41908989237a045e25c8308d5fd47ab96bdae2dd8057c6451003a", size = 70464, upload-time = "2026-04-13T13:15:39.259Z" }, + { url = "https://files.pythonhosted.org/packages/b3/00/fa62404c3e347f946faa13aa21085205f9cc06ad17671e37f81a51662ae8/joserfc-1.7.1-py3-none-any.whl", hash = "sha256:b3e3d655612e2e1ef67b2600f2f420e12e537b020208fab1761fad647319c164", size = 70423, upload-time = "2026-06-08T07:21:32.001Z" }, ] [[package]] @@ -1851,16 +1855,17 @@ wheels = [ [[package]] name = "jsonschema-path" -version = "0.4.6" +version = "0.5.0" source = { registry = "https://pypi.org/simple" } dependencies = [ + { name = "attrs" }, { name = "pathable" }, { name = "pyyaml" }, { name = "referencing" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/01/86/cfee6dd25843bec0760f456599a4f7e7e40221a934b9229fda0662c859bc/jsonschema_path-0.4.6.tar.gz", hash = "sha256:c89eb635f4d497c9ac328eeff359c489755838806a7d033510a692e9576f5c4b", size = 15302, upload-time = "2026-04-27T18:57:08.412Z" } +sdist = { url = "https://files.pythonhosted.org/packages/39/79/cd02a4df6d9270efdc7d3feefe6edd730b0820c39eeaa107a2faee8322d5/jsonschema_path-0.5.0.tar.gz", hash = "sha256:493b156ba895c97602655b620a8456caa2ce08c1aa389f5a7addec065e6e855c", size = 19597, upload-time = "2026-05-19T20:45:00.971Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6c/43/3d3065c05a04bb550c143bfbb8e4fd7022cd327e1082bf257bac74923783/jsonschema_path-0.4.6-py3-none-any.whl", hash = "sha256:451354b5311fa955c3144e6e4e255388c751c0121c5570ec5bb9291dd42d08c9", size = 19565, upload-time = "2026-04-27T18:57:06.792Z" }, + { url = "https://files.pythonhosted.org/packages/04/2c/9e69d73c4297508be9e3b64a970ea3971b3eb8db64ffc5802d40bd25981f/jsonschema_path-0.5.0-py3-none-any.whl", hash = "sha256:2790a070bc7abb08ea3dbe4d340ece4efadf639223001f020c7503229ba068e2", size = 24077, upload-time = "2026-05-19T20:44:59.225Z" }, ] [[package]] @@ -1903,14 +1908,14 @@ wheels = [ [[package]] name = "markdown-it-py" -version = "4.0.0" +version = "4.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mdurl" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" } +sdist = { url = "https://files.pythonhosted.org/packages/06/ff/7841249c247aa650a76b9ee4bbaeae59370dc8bfd2f6c01f3630c35eb134/markdown_it_py-4.2.0.tar.gz", hash = "sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49", size = 82454, upload-time = "2026-05-07T12:08:28.36Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" }, + { url = "https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl", hash = "sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a", size = 91687, upload-time = "2026-05-07T12:08:27.182Z" }, ] [[package]] @@ -2017,7 +2022,7 @@ wheels = [ [[package]] name = "mem0ai" -version = "2.0.1" +version = "2.0.7" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "openai" }, @@ -2028,38 +2033,38 @@ dependencies = [ { name = "qdrant-client" }, { name = "sqlalchemy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ef/03/3dc535b98310912e4f10083acdbbca2c5e2dfccb3921230a460464f9f4d0/mem0ai-2.0.1.tar.gz", hash = "sha256:070dbc3f1f332c8908379b42a81ab3a96ab169f2f9fa537e6ac719df02478f9c", size = 211820, upload-time = "2026-04-25T17:39:06.744Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ad/e8/813101298d681510a000ae5ddb4a994d78a2e20165fc4acf5fa482630502/mem0ai-2.0.7.tar.gz", hash = "sha256:c254c5b851f9bd95dc1f136a2f14f10a980afa607bff381d775b9846442c44ca", size = 229204, upload-time = "2026-06-17T16:26:19.253Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/96/e6153262f1464f4d412208732fea31496d9983ade155dd2c5c5492f8f8a4/mem0ai-2.0.1-py3-none-any.whl", hash = "sha256:63da5f50ad0c2514e27c2f380ef03f2ceea47c97873096ddfd997785b58043ec", size = 299461, upload-time = "2026-04-25T17:39:04.143Z" }, + { url = "https://files.pythonhosted.org/packages/a4/db/b4492c154e7b7bbde5d763ad53675a1735095681a4c1890f0017880d8819/mem0ai-2.0.7-py3-none-any.whl", hash = "sha256:02bfef7932297d77a8a13ddb4f2232a781a8f827004026586c947b7e3ae0ff24", size = 319881, upload-time = "2026-06-17T16:26:17.709Z" }, ] [[package]] name = "microsoft-agents-activity" -version = "0.10.0.dev4" +version = "1.1.0.dev7" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/33/9c/d43e35476c65aed92b8edfa6a1b4b4fb199cc0b4b1aa2baf5e64599dc888/microsoft_agents_activity-0.10.0.dev4.tar.gz", hash = "sha256:5172ef444e7143592488667145697f7c852179330aeeb91caf1b5ac720a8ab23", size = 62600, upload-time = "2026-05-02T09:10:03.04Z" } +sdist = { url = "https://files.pythonhosted.org/packages/96/19/776fa3a7f63470fe2ee3641ffa810ecf2fe15580f155114d59446cba0b76/microsoft_agents_activity-1.1.0.dev7.tar.gz", hash = "sha256:55ced3bc4f3df634de125723323f29e99cbf44481cac2d1e103d516cbc6c2e3a", size = 62799, upload-time = "2026-06-16T09:13:54.072Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/09/07/9e0e6a71508b42c5c60c46c68136ce5d7c4f48eb1b06b788693450651675/microsoft_agents_activity-0.10.0.dev4-py3-none-any.whl", hash = "sha256:133301f41e4e186d59b4c9de21bcd87731ad2dbdd45adcde5820bb6939fe748b", size = 134838, upload-time = "2026-05-02T09:10:14.273Z" }, + { url = "https://files.pythonhosted.org/packages/cb/ac/dcd98767a80e7040b087eefcef6724435d0e5bada7bc36e57056cfb6713e/microsoft_agents_activity-1.1.0.dev7-py3-none-any.whl", hash = "sha256:de4c76ea207113c85cafcdf6e0d4429342cdc17176bac3285280eb5b4164d031", size = 135004, upload-time = "2026-06-16T09:14:05.141Z" }, ] [[package]] name = "microsoft-agents-copilotstudio-client" -version = "0.10.0.dev4" +version = "1.1.0.dev7" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "microsoft-agents-hosting-core" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/14/1b/ac3291d1e0a2a36b6b155e583a816f26623c8201c01b6c208b911badd5ab/microsoft_agents_copilotstudio_client-0.10.0.dev4.tar.gz", hash = "sha256:141300abde93463d071b3544c946766e9a97159b36cca826ed484bff408da984", size = 27276, upload-time = "2026-05-02T09:10:05.527Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d4/0b/4ca87e9a390f0016981898964d289ff788dfcdd819a7d7f3e0da71284aab/microsoft_agents_copilotstudio_client-1.1.0.dev7.tar.gz", hash = "sha256:acfa7d0aa0034ead271306d6ee93619927f081b6e82ca0c5de54ec7c39895957", size = 27470, upload-time = "2026-06-16T09:13:56.262Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/91/48/6fdb7cbf84fb084adcb1749996be3b83bc6fc50b1c1e53feb895c293d22c/microsoft_agents_copilotstudio_client-0.10.0.dev4-py3-none-any.whl", hash = "sha256:f275aa20da3ddf466980eebac3486c1e2299d5400303264bf4b7421e0cdf6e7f", size = 23813, upload-time = "2026-05-02T09:10:16.791Z" }, + { url = "https://files.pythonhosted.org/packages/7e/bb/4aa60f78e9af97b2982501d67b936be1141c1f457c5fbee68d9c32368516/microsoft_agents_copilotstudio_client-1.1.0.dev7-py3-none-any.whl", hash = "sha256:d9f78523cf60d835c905dad708851463fa8408986a80480b46a9eb77960c931f", size = 23819, upload-time = "2026-06-16T09:14:07.405Z" }, ] [[package]] name = "microsoft-agents-hosting-core" -version = "0.10.0.dev4" +version = "1.1.0.dev7" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "azure-core" }, @@ -2070,9 +2075,9 @@ dependencies = [ { name = "pyjwt" }, { name = "python-dotenv" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/11/b4/4a412794e6e2885f15a9b0ad9c59a3e062fbbe7851daced6b62cdfec546c/microsoft_agents_hosting_core-0.10.0.dev4.tar.gz", hash = "sha256:0e7689fb05d7b2fe7757e5953c83bbe242506f1ad6a5f224d8c7e4f94f598e90", size = 119529, upload-time = "2026-05-02T09:10:07.806Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/9f/1ef6f4134a3136fb746dafbe1b293b15e3e4ffc5b200c8ba7930a77ebc80/microsoft_agents_hosting_core-1.1.0.dev7.tar.gz", hash = "sha256:add663fa8d23faf57f7d3bba9c3d699f075969eb958934d5b3c170b6c79d8a11", size = 123424, upload-time = "2026-06-16T09:13:58.152Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/63/eb/7bc9ed8a60f8bade4ca08d050598e68a65fe7850ea454047c0ff622c9749/microsoft_agents_hosting_core-0.10.0.dev4-py3-none-any.whl", hash = "sha256:a449bfd3498c67150eed33faf0f6e2cb1ba5cf011de2c3459e6e079b85896366", size = 182042, upload-time = "2026-05-02T09:10:18.866Z" }, + { url = "https://files.pythonhosted.org/packages/61/e5/a38313f7edaddd5b156e825efd0c9eeb4ab660d40c30365effd4c0ed545a/microsoft_agents_hosting_core-1.1.0.dev7-py3-none-any.whl", hash = "sha256:f17316fdd9260655a40faf80c66e22f444dff831f15aa91df4027b031b1d2224", size = 186993, upload-time = "2026-06-16T09:14:09.426Z" }, ] [[package]] @@ -2113,25 +2118,25 @@ wheels = [ [[package]] name = "more-itertools" -version = "11.0.2" +version = "11.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/f7/139d22fef48ac78127d18e01d80cf1be40236ae489769d17f35c3d425293/more_itertools-11.0.2.tar.gz", hash = "sha256:392a9e1e362cbc106a2457d37cabf9b36e5e12efd4ebff1654630e76597df804", size = 144659, upload-time = "2026-04-09T15:01:33.297Z" } +sdist = { url = "https://files.pythonhosted.org/packages/de/1d/f4da6f02cdffe04d6362210b807146a26044c88d839208aec273bb0d9184/more_itertools-11.1.0.tar.gz", hash = "sha256:48e8f4d9e7e5878571ecf6f2b4e57634f93cd474cc8cfbd2376f2d11b396e30d", size = 145772, upload-time = "2026-05-22T14:14:29.909Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/98/6af411189d9413534c3eb691182bff1f5c6d44ed2f93f2edfe52a1bbceb8/more_itertools-11.0.2-py3-none-any.whl", hash = "sha256:6e35b35f818b01f691643c6c611bc0902f2e92b46c18fffa77ae1e7c46e912e4", size = 71939, upload-time = "2026-04-09T15:01:32.21Z" }, + { url = "https://files.pythonhosted.org/packages/e8/3d/1087453384dbde46a8c7f9356eead2c58be8a7bf156bca40243377c85715/more_itertools-11.1.0-py3-none-any.whl", hash = "sha256:4b65538ae22f6fed0ce4874efd317463a7489796a0939fa66824dd542125a192", size = 72226, upload-time = "2026-05-22T14:14:28.824Z" }, ] [[package]] name = "msal" -version = "1.36.0" +version = "1.38.0rc1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cryptography" }, { name = "pyjwt", extra = ["crypto"] }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/de/cb/b02b0f748ac668922364ccb3c3bff5b71628a05f5adfec2ba2a5c3031483/msal-1.36.0.tar.gz", hash = "sha256:3f6a4af2b036b476a4215111c4297b4e6e236ed186cd804faefba23e4990978b", size = 174217, upload-time = "2026-04-09T10:20:33.525Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b1/ba/afc0474f72674e1a19155afe7b6b3a8b12359d2aee458e216f4dd8030f5b/msal-1.38.0rc1.tar.gz", hash = "sha256:c0160b98217f84705339189d0fa5099cdec0ffa5986e3d2053f450007a2f1a89", size = 182430, upload-time = "2026-06-05T15:20:47.287Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/d3/414d1f0a5f6f4fe5313c2b002c54e78a3332970feb3f5fed14237aa17064/msal-1.36.0-py3-none-any.whl", hash = "sha256:36ecac30e2ff4322d956029aabce3c82301c29f0acb1ad89b94edcabb0e58ec4", size = 121547, upload-time = "2026-04-09T10:20:32.336Z" }, + { url = "https://files.pythonhosted.org/packages/d9/9f/873565789342901574aa85d228c6ed6761addf9f5a4829217e7f94671700/msal-1.38.0rc1-py3-none-any.whl", hash = "sha256:4c3528a473d856f725c8f9b302c364c576e21b5cf43a581273c2682d973c4da3", size = 123766, upload-time = "2026-06-05T15:20:48.981Z" }, ] [[package]] @@ -2272,63 +2277,53 @@ wheels = [ [[package]] name = "numpy" -version = "2.4.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d7/9f/b8cef5bffa569759033adda9481211426f12f53299629b410340795c2514/numpy-2.4.4.tar.gz", hash = "sha256:2d390634c5182175533585cc89f3608a4682ccb173cc9bb940b2881c8d6f8fa0", size = 20731587, upload-time = "2026-03-29T13:22:01.298Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/28/05/32396bec30fb2263770ee910142f49c1476d08e8ad41abf8403806b520ce/numpy-2.4.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:15716cfef24d3a9762e3acdf87e27f58dc823d1348f765bbea6bef8c639bfa1b", size = 16689272, upload-time = "2026-03-29T13:18:49.223Z" }, - { url = "https://files.pythonhosted.org/packages/c5/f3/a983d28637bfcd763a9c7aafdb6d5c0ebf3d487d1e1459ffdb57e2f01117/numpy-2.4.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:23cbfd4c17357c81021f21540da84ee282b9c8fba38a03b7b9d09ba6b951421e", size = 14699573, upload-time = "2026-03-29T13:18:52.629Z" }, - { url = "https://files.pythonhosted.org/packages/9b/fd/e5ecca1e78c05106d98028114f5c00d3eddb41207686b2b7de3e477b0e22/numpy-2.4.4-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:8b3b60bb7cba2c8c81837661c488637eee696f59a877788a396d33150c35d842", size = 5204782, upload-time = "2026-03-29T13:18:55.579Z" }, - { url = "https://files.pythonhosted.org/packages/de/2f/702a4594413c1a8632092beae8aba00f1d67947389369b3777aed783fdca/numpy-2.4.4-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:e4a010c27ff6f210ff4c6ef34394cd61470d01014439b192ec22552ee867f2a8", size = 6552038, upload-time = "2026-03-29T13:18:57.769Z" }, - { url = "https://files.pythonhosted.org/packages/7f/37/eed308a8f56cba4d1fdf467a4fc67ef4ff4bf1c888f5fc980481890104b1/numpy-2.4.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f9e75681b59ddaa5e659898085ae0eaea229d054f2ac0c7e563a62205a700121", size = 15670666, upload-time = "2026-03-29T13:19:00.341Z" }, - { url = "https://files.pythonhosted.org/packages/0a/0d/0e3ecece05b7a7e87ab9fb587855548da437a061326fff64a223b6dcb78a/numpy-2.4.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:81f4a14bee47aec54f883e0cad2d73986640c1590eb9bfaaba7ad17394481e6e", size = 16645480, upload-time = "2026-03-29T13:19:03.63Z" }, - { url = "https://files.pythonhosted.org/packages/34/49/f2312c154b82a286758ee2f1743336d50651f8b5195db18cdb63675ff649/numpy-2.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:62d6b0f03b694173f9fcb1fb317f7222fd0b0b103e784c6549f5e53a27718c44", size = 17020036, upload-time = "2026-03-29T13:19:07.428Z" }, - { url = "https://files.pythonhosted.org/packages/7b/e9/736d17bd77f1b0ec4f9901aaec129c00d59f5d84d5e79bba540ef12c2330/numpy-2.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fbc356aae7adf9e6336d336b9c8111d390a05df88f1805573ebb0807bd06fd1d", size = 18368643, upload-time = "2026-03-29T13:19:10.775Z" }, - { url = "https://files.pythonhosted.org/packages/63/f6/d417977c5f519b17c8a5c3bc9e8304b0908b0e21136fe43bf628a1343914/numpy-2.4.4-cp312-cp312-win32.whl", hash = "sha256:0d35aea54ad1d420c812bfa0385c71cd7cc5bcf7c65fed95fc2cd02fe8c79827", size = 5961117, upload-time = "2026-03-29T13:19:13.464Z" }, - { url = "https://files.pythonhosted.org/packages/2d/5b/e1deebf88ff431b01b7406ca3583ab2bbb90972bbe1c568732e49c844f7e/numpy-2.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:b5f0362dc928a6ecd9db58868fca5e48485205e3855957bdedea308f8672ea4a", size = 12320584, upload-time = "2026-03-29T13:19:16.155Z" }, - { url = "https://files.pythonhosted.org/packages/58/89/e4e856ac82a68c3ed64486a544977d0e7bdd18b8da75b78a577ca31c4395/numpy-2.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:846300f379b5b12cc769334464656bc882e0735d27d9726568bc932fdc49d5ec", size = 10221450, upload-time = "2026-03-29T13:19:18.994Z" }, - { url = "https://files.pythonhosted.org/packages/14/1d/d0a583ce4fefcc3308806a749a536c201ed6b5ad6e1322e227ee4848979d/numpy-2.4.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:08f2e31ed5e6f04b118e49821397f12767934cfdd12a1ce86a058f91e004ee50", size = 16684933, upload-time = "2026-03-29T13:19:22.47Z" }, - { url = "https://files.pythonhosted.org/packages/c1/62/2b7a48fbb745d344742c0277f01286dead15f3f68e4f359fbfcf7b48f70f/numpy-2.4.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e823b8b6edc81e747526f70f71a9c0a07ac4e7ad13020aa736bb7c9d67196115", size = 14694532, upload-time = "2026-03-29T13:19:25.581Z" }, - { url = "https://files.pythonhosted.org/packages/e5/87/499737bfba066b4a3bebff24a8f1c5b2dee410b209bc6668c9be692580f0/numpy-2.4.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:4a19d9dba1a76618dd86b164d608566f393f8ec6ac7c44f0cc879011c45e65af", size = 5199661, upload-time = "2026-03-29T13:19:28.31Z" }, - { url = "https://files.pythonhosted.org/packages/cd/da/464d551604320d1491bc345efed99b4b7034143a85787aab78d5691d5a0e/numpy-2.4.4-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:d2a8490669bfe99a233298348acc2d824d496dee0e66e31b66a6022c2ad74a5c", size = 6547539, upload-time = "2026-03-29T13:19:30.97Z" }, - { url = "https://files.pythonhosted.org/packages/7d/90/8d23e3b0dafd024bf31bdec225b3bb5c2dbfa6912f8a53b8659f21216cbf/numpy-2.4.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:45dbed2ab436a9e826e302fcdcbe9133f9b0006e5af7168afb8963a6520da103", size = 15668806, upload-time = "2026-03-29T13:19:33.887Z" }, - { url = "https://files.pythonhosted.org/packages/d1/73/a9d864e42a01896bb5974475438f16086be9ba1f0d19d0bb7a07427c4a8b/numpy-2.4.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c901b15172510173f5cb310eae652908340f8dede90fff9e3bf6c0d8dfd92f83", size = 16632682, upload-time = "2026-03-29T13:19:37.336Z" }, - { url = "https://files.pythonhosted.org/packages/34/fb/14570d65c3bde4e202a031210475ae9cde9b7686a2e7dc97ee67d2833b35/numpy-2.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:99d838547ace2c4aace6c4f76e879ddfe02bb58a80c1549928477862b7a6d6ed", size = 17019810, upload-time = "2026-03-29T13:19:40.963Z" }, - { url = "https://files.pythonhosted.org/packages/8a/77/2ba9d87081fd41f6d640c83f26fb7351e536b7ce6dd9061b6af5904e8e46/numpy-2.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0aec54fd785890ecca25a6003fd9a5aed47ad607bbac5cd64f836ad8666f4959", size = 18357394, upload-time = "2026-03-29T13:19:44.859Z" }, - { url = "https://files.pythonhosted.org/packages/a2/23/52666c9a41708b0853fa3b1a12c90da38c507a3074883823126d4e9d5b30/numpy-2.4.4-cp313-cp313-win32.whl", hash = "sha256:07077278157d02f65c43b1b26a3886bce886f95d20aabd11f87932750dfb14ed", size = 5959556, upload-time = "2026-03-29T13:19:47.661Z" }, - { url = "https://files.pythonhosted.org/packages/57/fb/48649b4971cde70d817cf97a2a2fdc0b4d8308569f1dd2f2611959d2e0cf/numpy-2.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:5c70f1cc1c4efbe316a572e2d8b9b9cc44e89b95f79ca3331553fbb63716e2bf", size = 12317311, upload-time = "2026-03-29T13:19:50.67Z" }, - { url = "https://files.pythonhosted.org/packages/ba/d8/11490cddd564eb4de97b4579ef6bfe6a736cc07e94c1598590ae25415e01/numpy-2.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:ef4059d6e5152fa1a39f888e344c73fdc926e1b2dd58c771d67b0acfbf2aa67d", size = 10222060, upload-time = "2026-03-29T13:19:54.229Z" }, - { url = "https://files.pythonhosted.org/packages/99/5d/dab4339177a905aad3e2221c915b35202f1ec30d750dd2e5e9d9a72b804b/numpy-2.4.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4bbc7f303d125971f60ec0aaad5e12c62d0d2c925f0ab1273debd0e4ba37aba5", size = 14822302, upload-time = "2026-03-29T13:19:57.585Z" }, - { url = "https://files.pythonhosted.org/packages/eb/e4/0564a65e7d3d97562ed6f9b0fd0fb0a6f559ee444092f105938b50043876/numpy-2.4.4-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:4d6d57903571f86180eb98f8f0c839fa9ebbfb031356d87f1361be91e433f5b7", size = 5327407, upload-time = "2026-03-29T13:20:00.601Z" }, - { url = "https://files.pythonhosted.org/packages/29/8d/35a3a6ce5ad371afa58b4700f1c820f8f279948cca32524e0a695b0ded83/numpy-2.4.4-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:4636de7fd195197b7535f231b5de9e4b36d2c440b6e566d2e4e4746e6af0ca93", size = 6647631, upload-time = "2026-03-29T13:20:02.855Z" }, - { url = "https://files.pythonhosted.org/packages/f4/da/477731acbd5a58a946c736edfdabb2ac5b34c3d08d1ba1a7b437fa0884df/numpy-2.4.4-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ad2e2ef14e0b04e544ea2fa0a36463f847f113d314aa02e5b402fdf910ef309e", size = 15727691, upload-time = "2026-03-29T13:20:06.004Z" }, - { url = "https://files.pythonhosted.org/packages/e6/db/338535d9b152beabeb511579598418ba0212ce77cf9718edd70262cc4370/numpy-2.4.4-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a285b3b96f951841799528cd1f4f01cd70e7e0204b4abebac9463eecfcf2a40", size = 16681241, upload-time = "2026-03-29T13:20:09.417Z" }, - { url = "https://files.pythonhosted.org/packages/e2/a9/ad248e8f58beb7a0219b413c9c7d8151c5d285f7f946c3e26695bdbbe2df/numpy-2.4.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:f8474c4241bc18b750be2abea9d7a9ec84f46ef861dbacf86a4f6e043401f79e", size = 17085767, upload-time = "2026-03-29T13:20:13.126Z" }, - { url = "https://files.pythonhosted.org/packages/b5/1a/3b88ccd3694681356f70da841630e4725a7264d6a885c8d442a697e1146b/numpy-2.4.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4e874c976154687c1f71715b034739b45c7711bec81db01914770373d125e392", size = 18403169, upload-time = "2026-03-29T13:20:17.096Z" }, - { url = "https://files.pythonhosted.org/packages/c2/c9/fcfd5d0639222c6eac7f304829b04892ef51c96a75d479214d77e3ce6e33/numpy-2.4.4-cp313-cp313t-win32.whl", hash = "sha256:9c585a1790d5436a5374bac930dad6ed244c046ed91b2b2a3634eb2971d21008", size = 6083477, upload-time = "2026-03-29T13:20:20.195Z" }, - { url = "https://files.pythonhosted.org/packages/d5/e3/3938a61d1c538aaec8ed6fd6323f57b0c2d2d2219512434c5c878db76553/numpy-2.4.4-cp313-cp313t-win_amd64.whl", hash = "sha256:93e15038125dc1e5345d9b5b68aa7f996ec33b98118d18c6ca0d0b7d6198b7e8", size = 12457487, upload-time = "2026-03-29T13:20:22.946Z" }, - { url = "https://files.pythonhosted.org/packages/97/6a/7e345032cc60501721ef94e0e30b60f6b0bd601f9174ebd36389a2b86d40/numpy-2.4.4-cp313-cp313t-win_arm64.whl", hash = "sha256:0dfd3f9d3adbe2920b68b5cd3d51444e13a10792ec7154cd0a2f6e74d4ab3233", size = 10292002, upload-time = "2026-03-29T13:20:25.909Z" }, - { url = "https://files.pythonhosted.org/packages/6e/06/c54062f85f673dd5c04cbe2f14c3acb8c8b95e3384869bb8cc9bff8cb9df/numpy-2.4.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f169b9a863d34f5d11b8698ead99febeaa17a13ca044961aa8e2662a6c7766a0", size = 16684353, upload-time = "2026-03-29T13:20:29.504Z" }, - { url = "https://files.pythonhosted.org/packages/4c/39/8a320264a84404c74cc7e79715de85d6130fa07a0898f67fb5cd5bd79908/numpy-2.4.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2483e4584a1cb3092da4470b38866634bafb223cbcd551ee047633fd2584599a", size = 14704914, upload-time = "2026-03-29T13:20:33.547Z" }, - { url = "https://files.pythonhosted.org/packages/91/fb/287076b2614e1d1044235f50f03748f31fa287e3dbe6abeb35cdfa351eca/numpy-2.4.4-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:2d19e6e2095506d1736b7d80595e0f252d76b89f5e715c35e06e937679ea7d7a", size = 5210005, upload-time = "2026-03-29T13:20:36.45Z" }, - { url = "https://files.pythonhosted.org/packages/63/eb/fcc338595309910de6ecabfcef2419a9ce24399680bfb149421fa2df1280/numpy-2.4.4-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:6a246d5914aa1c820c9443ddcee9c02bec3e203b0c080349533fae17727dfd1b", size = 6544974, upload-time = "2026-03-29T13:20:39.014Z" }, - { url = "https://files.pythonhosted.org/packages/44/5d/e7e9044032a716cdfaa3fba27a8e874bf1c5f1912a1ddd4ed071bf8a14a6/numpy-2.4.4-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:989824e9faf85f96ec9c7761cd8d29c531ad857bfa1daa930cba85baaecf1a9a", size = 15684591, upload-time = "2026-03-29T13:20:42.146Z" }, - { url = "https://files.pythonhosted.org/packages/98/7c/21252050676612625449b4807d6b695b9ce8a7c9e1c197ee6216c8a65c7c/numpy-2.4.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:27a8d92cd10f1382a67d7cf4db7ce18341b66438bdd9f691d7b0e48d104c2a9d", size = 16637700, upload-time = "2026-03-29T13:20:46.204Z" }, - { url = "https://files.pythonhosted.org/packages/b1/29/56d2bbef9465db24ef25393383d761a1af4f446a1df9b8cded4fe3a5a5d7/numpy-2.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e44319a2953c738205bf3354537979eaa3998ed673395b964c1176083dd46252", size = 17035781, upload-time = "2026-03-29T13:20:50.242Z" }, - { url = "https://files.pythonhosted.org/packages/e3/2b/a35a6d7589d21f44cea7d0a98de5ddcbb3d421b2622a5c96b1edf18707c3/numpy-2.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e892aff75639bbef0d2a2cfd55535510df26ff92f63c92cd84ef8d4ba5a5557f", size = 18362959, upload-time = "2026-03-29T13:20:54.019Z" }, - { url = "https://files.pythonhosted.org/packages/64/c9/d52ec581f2390e0f5f85cbfd80fb83d965fc15e9f0e1aec2195faa142cde/numpy-2.4.4-cp314-cp314-win32.whl", hash = "sha256:1378871da56ca8943c2ba674530924bb8ca40cd228358a3b5f302ad60cf875fc", size = 6008768, upload-time = "2026-03-29T13:20:56.912Z" }, - { url = "https://files.pythonhosted.org/packages/fa/22/4cc31a62a6c7b74a8730e31a4274c5dc80e005751e277a2ce38e675e4923/numpy-2.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:715d1c092715954784bc79e1174fc2a90093dc4dc84ea15eb14dad8abdcdeb74", size = 12449181, upload-time = "2026-03-29T13:20:59.548Z" }, - { url = "https://files.pythonhosted.org/packages/70/2e/14cda6f4d8e396c612d1bf97f22958e92148801d7e4f110cabebdc0eef4b/numpy-2.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:2c194dd721e54ecad9ad387c1d35e63dce5c4450c6dc7dd5611283dda239aabb", size = 10496035, upload-time = "2026-03-29T13:21:02.524Z" }, - { url = "https://files.pythonhosted.org/packages/b1/e8/8fed8c8d848d7ecea092dc3469643f9d10bc3a134a815a3b033da1d2039b/numpy-2.4.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2aa0613a5177c264ff5921051a5719d20095ea586ca88cc802c5c218d1c67d3e", size = 14824958, upload-time = "2026-03-29T13:21:05.671Z" }, - { url = "https://files.pythonhosted.org/packages/05/1a/d8007a5138c179c2bf33ef44503e83d70434d2642877ee8fbb230e7c0548/numpy-2.4.4-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:42c16925aa5a02362f986765f9ebabf20de75cdefdca827d14315c568dcab113", size = 5330020, upload-time = "2026-03-29T13:21:08.635Z" }, - { url = "https://files.pythonhosted.org/packages/99/64/ffb99ac6ae93faf117bcbd5c7ba48a7f45364a33e8e458545d3633615dda/numpy-2.4.4-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:874f200b2a981c647340f841730fc3a2b54c9d940566a3c4149099591e2c4c3d", size = 6650758, upload-time = "2026-03-29T13:21:10.949Z" }, - { url = "https://files.pythonhosted.org/packages/6e/6e/795cc078b78a384052e73b2f6281ff7a700e9bf53bcce2ee579d4f6dd879/numpy-2.4.4-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c9b39d38a9bd2ae1becd7eac1303d031c5c110ad31f2b319c6e7d98b135c934d", size = 15729948, upload-time = "2026-03-29T13:21:14.047Z" }, - { url = "https://files.pythonhosted.org/packages/5f/86/2acbda8cc2af5f3d7bfc791192863b9e3e19674da7b5e533fded124d1299/numpy-2.4.4-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b268594bccac7d7cf5844c7732e3f20c50921d94e36d7ec9b79e9857694b1b2f", size = 16679325, upload-time = "2026-03-29T13:21:17.561Z" }, - { url = "https://files.pythonhosted.org/packages/bc/59/cafd83018f4aa55e0ac6fa92aa066c0a1877b77a615ceff1711c260ffae8/numpy-2.4.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ac6b31e35612a26483e20750126d30d0941f949426974cace8e6b5c58a3657b0", size = 17084883, upload-time = "2026-03-29T13:21:21.106Z" }, - { url = "https://files.pythonhosted.org/packages/f0/85/a42548db84e65ece46ab2caea3d3f78b416a47af387fcbb47ec28e660dc2/numpy-2.4.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8e3ed142f2728df44263aaf5fb1f5b0b99f4070c553a0d7f033be65338329150", size = 18403474, upload-time = "2026-03-29T13:21:24.828Z" }, - { url = "https://files.pythonhosted.org/packages/ed/ad/483d9e262f4b831000062e5d8a45e342166ec8aaa1195264982bca267e62/numpy-2.4.4-cp314-cp314t-win32.whl", hash = "sha256:dddbbd259598d7240b18c9d87c56a9d2fb3b02fe266f49a7c101532e78c1d871", size = 6155500, upload-time = "2026-03-29T13:21:28.205Z" }, - { url = "https://files.pythonhosted.org/packages/c7/03/2fc4e14c7bd4ff2964b74ba90ecb8552540b6315f201df70f137faa5c589/numpy-2.4.4-cp314-cp314t-win_amd64.whl", hash = "sha256:a7164afb23be6e37ad90b2f10426149fd75aee07ca55653d2aa41e66c4ef697e", size = 12637755, upload-time = "2026-03-29T13:21:31.107Z" }, - { url = "https://files.pythonhosted.org/packages/58/78/548fb8e07b1a341746bfbecb32f2c268470f45fa028aacdbd10d9bc73aab/numpy-2.4.4-cp314-cp314t-win_arm64.whl", hash = "sha256:ba203255017337d39f89bdd58417f03c4426f12beed0440cfd933cb15f8669c7", size = 10566643, upload-time = "2026-03-29T13:21:34.339Z" }, +version = "2.5.0rc1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d3/e1/f2fe8f6113a657a1da83b358b16755e528a602c6976a43724a88f42b482a/numpy-2.5.0rc1.tar.gz", hash = "sha256:4de3e0d48e58ef53e32c756bddea2722b9dde7ec25f96afc5fe7ece620cc4cb9", size = 20610894, upload-time = "2026-06-02T12:58:09.126Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/d5/39a7bac854c1c228d62e48535f431b6901159a1e054b39d13da363fe5fd4/numpy-2.5.0rc1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:dc5f26f2155b2a64938adec26cd4652660e34588612cb08fe5e65860e638235a", size = 16790758, upload-time = "2026-06-02T12:56:22.836Z" }, + { url = "https://files.pythonhosted.org/packages/da/a2/ede0d2a76e84f0a35796ecd3a2c8f9973b35bdc947ca3a395f3c7a72a157/numpy-2.5.0rc1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:859c145cf7ebe885a0c579a80bccfed9b35ceb6bbc23131149b118919764f407", size = 14807438, upload-time = "2026-06-02T12:56:26.229Z" }, + { url = "https://files.pythonhosted.org/packages/c3/9a/c5b3472279f4c363c3919830a8005073d4cd6fb73816f1f9f8f5345405a6/numpy-2.5.0rc1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9cca573cd3610227420f3045a90bb5fd414f536043be16d8e46c8229830d244d", size = 5313467, upload-time = "2026-06-02T12:56:28.336Z" }, + { url = "https://files.pythonhosted.org/packages/ae/50/aecf9566e752f8ee0c16367932ef061fdacdfcc02b87e13ecefa6a163a1b/numpy-2.5.0rc1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:5f75ef317f45dae8090124630254b842b3cc10eea7db4f7f1a688f37cd285d2f", size = 6650465, upload-time = "2026-06-02T12:56:30.116Z" }, + { url = "https://files.pythonhosted.org/packages/94/d7/dad93b29f68ccc2310f3a071212eb2a60bd05b42f2bd50a3b807dcfa0323/numpy-2.5.0rc1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1586c886dfff6d814541ddedfa1317c4133ad2f171b89c8578b08f1d103a57c", size = 15153866, upload-time = "2026-06-02T12:56:32.355Z" }, + { url = "https://files.pythonhosted.org/packages/9b/ea/0c9ebeceb3ab32585946799eb2ec86ff55d6b39d310e814c44855bd1d07f/numpy-2.5.0rc1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f1763247152f2c929cf21767dfa36b2d3d91c304218e0751b79849859d595f50", size = 16661583, upload-time = "2026-06-02T12:56:34.798Z" }, + { url = "https://files.pythonhosted.org/packages/cd/74/a2d32f679cbfe23c7202613c73a654be1db4cee933bc332ab835ad3de1ef/numpy-2.5.0rc1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ddfeee2c7a587838c578fb999aba1523fac7cea2802ec24b25c8eb8fec2214ac", size = 16513414, upload-time = "2026-06-02T12:56:37.596Z" }, + { url = "https://files.pythonhosted.org/packages/d0/c7/935e9164ad43852ade3b8cee0340dc20fe461f1682b9071cc4e2b76acff6/numpy-2.5.0rc1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5417326ce746c188be90e817b3471e97f11616ae2ac7d190aa3c128971b9b93a", size = 18417787, upload-time = "2026-06-02T12:56:40.074Z" }, + { url = "https://files.pythonhosted.org/packages/12/1a/bb52a27340e606d0b78e17bae36c11bf59c070f758889b2cacea984b58e0/numpy-2.5.0rc1-cp312-cp312-win32.whl", hash = "sha256:99cf48d26b3341361d3d70fc7490af1499de41d2dba28a721a835acc83cb2d87", size = 6053661, upload-time = "2026-06-02T12:56:42.511Z" }, + { url = "https://files.pythonhosted.org/packages/47/70/c75cd23d7b49821dec7f0c5ae29e858d3f686f83b0b4ecfdc4c7a39db80a/numpy-2.5.0rc1-cp312-cp312-win_amd64.whl", hash = "sha256:e4afd8387b6617e61ecb0ceb60080c342366767b046fcc5c80516f19442f269b", size = 12416233, upload-time = "2026-06-02T12:56:44.605Z" }, + { url = "https://files.pythonhosted.org/packages/c3/6c/c0ab9acddf4a4ca6fe77236938a9b85ad5227efaf628fc6e39b750276146/numpy-2.5.0rc1-cp312-cp312-win_arm64.whl", hash = "sha256:c072a4e5cad53345ab6ee29ca5b1a44df3cbc38991d066af71e6246583294e32", size = 10334234, upload-time = "2026-06-02T12:56:46.728Z" }, + { url = "https://files.pythonhosted.org/packages/f9/2c/6735b782a68595ba9cd6f453d5cef8e637b6ee89af3555e8074e5084f188/numpy-2.5.0rc1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:342294d39fbaaf93d33c8950d6b4fb873034e5eabbb67676190dd79c07ba10af", size = 16785923, upload-time = "2026-06-02T12:56:49.319Z" }, + { url = "https://files.pythonhosted.org/packages/00/24/1864bcfea4adb5fbffdb8063ae7993f537cb168fda1cfadcec3eb1afc940/numpy-2.5.0rc1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d3b9d87c617fbd05b9afb38cacd56a9f60e2d3c0dd96a7278028a07a1362e948", size = 14802138, upload-time = "2026-06-02T12:56:51.792Z" }, + { url = "https://files.pythonhosted.org/packages/fc/f5/c15966ab370698ca66c8a64ac86172cbba3b62e9355f51c5b7f4c11a5840/numpy-2.5.0rc1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:6b29138613d32227696eaad490d136cc114c2b3742cd379509d69cb97c013939", size = 5308122, upload-time = "2026-06-02T12:56:54.318Z" }, + { url = "https://files.pythonhosted.org/packages/63/46/ab26a2d69fe6babd2883c1d4eead5dfba869525b239e3152c02ea7a92cc7/numpy-2.5.0rc1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:8954bbb79e6ebc60e218b97168603a02475745b557ee0749f36da131e553fffd", size = 6645557, upload-time = "2026-06-02T12:56:55.908Z" }, + { url = "https://files.pythonhosted.org/packages/96/a8/e6265c8deacae8445a591186baa507da578eebbb8799a64230119fbdc18a/numpy-2.5.0rc1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a8bdefbd3b2c8ec3e060f0aeccbd7db4d10658de34fa9c161304a876d4b7a57f", size = 15149536, upload-time = "2026-06-02T12:56:58.154Z" }, + { url = "https://files.pythonhosted.org/packages/f8/cb/38e09c2d8d8af874ae9650d51ba74fb65af5e016e10154454e4f585699d7/numpy-2.5.0rc1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7a4c5bccef100aad3ca67f0c82fec6c9c41d93f45af082765a49248af9e4aa4c", size = 16648175, upload-time = "2026-06-02T12:57:00.909Z" }, + { url = "https://files.pythonhosted.org/packages/18/b4/74ad66aab93cf2e45b01ffc00b552d988d2d3c7978f3b6f90415adf4529f/numpy-2.5.0rc1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:fe949230df0a1c10e75e4ddbd0b382f1773c04ff8e4b988b43a3386b80885dd5", size = 16510274, upload-time = "2026-06-02T12:57:03.32Z" }, + { url = "https://files.pythonhosted.org/packages/b0/c6/9025469bac3f2e11e327058e94e872b08d3e8eb22f9985ae20a05f46edbc/numpy-2.5.0rc1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9352b974bded4f5e82dd94c028207bdc26c5051c9aa76508215c8430a9f98047", size = 18406033, upload-time = "2026-06-02T12:57:05.872Z" }, + { url = "https://files.pythonhosted.org/packages/e1/c8/e19a496ae871db9627f16c52ab7ba5393a2404dccefcc4c295c60b593da6/numpy-2.5.0rc1-cp313-cp313-win32.whl", hash = "sha256:79316ee5595e364c589a7d4b910006a818a2f8455e13ca1b544144d3b26b5c16", size = 6051769, upload-time = "2026-06-02T12:57:08.151Z" }, + { url = "https://files.pythonhosted.org/packages/31/e6/899d0ae5ca1c12af50c84a251b3a9807326ac6f2549289d3318649b60f1c/numpy-2.5.0rc1-cp313-cp313-win_amd64.whl", hash = "sha256:009be5174f1d8ebcc2f246eb1606c3e695a864f7d79821f3be5d7e2758213753", size = 12411388, upload-time = "2026-06-02T12:57:10.115Z" }, + { url = "https://files.pythonhosted.org/packages/db/31/aad462df2fda6948de95329dc1cb1d7f45651e51407937266f132b6a3e98/numpy-2.5.0rc1-cp313-cp313-win_arm64.whl", hash = "sha256:cc5330a6613229cc2511dd36578a0eb853e0aa6c248cf8d0a880bd775b8000e3", size = 10334033, upload-time = "2026-06-02T12:57:12.785Z" }, + { url = "https://files.pythonhosted.org/packages/d7/26/defbaa994ef5a2626a5c0d22a4d6e7c9f53c4699a3a534f03abbaa1cb1c0/numpy-2.5.0rc1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d672d8b3414f94d170dd4903b594c44bec4ae5ac1fcf37fe61e7cb0eb94f510d", size = 16784532, upload-time = "2026-06-02T12:57:15.2Z" }, + { url = "https://files.pythonhosted.org/packages/77/93/cac22c6d06e9a7220a1c167dd6cae45658460d8843f7b38c58673d9c4896/numpy-2.5.0rc1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a123a7f1ee049548835eea5af9e10f31ee1b334215b45b3fcc4a6f9240faa9e4", size = 14813009, upload-time = "2026-06-02T12:57:17.78Z" }, + { url = "https://files.pythonhosted.org/packages/56/69/2f4fa0a9deb91de9d4b5384e19b396a4bbfb9de38a30134a07e5e59aa8f5/numpy-2.5.0rc1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:2461850c9e41a1abd79fa8538b52bc7a828a8f5565398d3f9a7bf2da4e891fdd", size = 5319042, upload-time = "2026-06-02T12:57:20.088Z" }, + { url = "https://files.pythonhosted.org/packages/f0/11/3448e6923d5e342e08d3929878472791d2e04ed4b348959a994d6b4f936b/numpy-2.5.0rc1-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:f6c70078f019426adda42de7ed2369b7631ed41ee2cec7064b2f90db23ac6507", size = 6642057, upload-time = "2026-06-02T12:57:22.272Z" }, + { url = "https://files.pythonhosted.org/packages/70/5b/c2885366e1e4c68f4c5683bedb4caf7f868a80b813269ae4e157b30b9b53/numpy-2.5.0rc1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:00a7bcd1be5d066f7ff834706f2ffb6aee7ebbb544236b5293e5c702a5dc22b7", size = 15167044, upload-time = "2026-06-02T12:57:24.398Z" }, + { url = "https://files.pythonhosted.org/packages/08/ff/8b877f5e097518319cc6c9dbb78c0dfed378109cf719b94dfa0a39d4d386/numpy-2.5.0rc1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:799814f0986b8fd3275b125b8efb1f066b3a9d1454d753e70648f38fb488bf0d", size = 16652160, upload-time = "2026-06-02T12:57:27.068Z" }, + { url = "https://files.pythonhosted.org/packages/c7/d2/c8997aa8d92f599afec8b5c9d8e5f11a847cb894eee98a91cdde00b694f1/numpy-2.5.0rc1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4642001be298cc2b47d567feb499d4aa9a9b88dca795d6f147ceb16ebd685ff0", size = 16523955, upload-time = "2026-06-02T12:57:29.865Z" }, + { url = "https://files.pythonhosted.org/packages/2f/5f/07de8d32301763b19a88fc8b63b0175971de3879537d0f501433bd736067/numpy-2.5.0rc1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:19592531defb5237022cd60120b4aea1639065cd05cdf0bde731a2ed96eff6ab", size = 18410190, upload-time = "2026-06-02T12:57:32.767Z" }, + { url = "https://files.pythonhosted.org/packages/d2/0c/ed1012f7dc187de21f21b634161f9661c702c713973aaf339463009d4495/numpy-2.5.0rc1-cp314-cp314-win32.whl", hash = "sha256:dacc0758f3b01f15fbc7a0e57d868d5ba55d19fc2fd19a979b8f8d00cb5e13d4", size = 6104044, upload-time = "2026-06-02T12:57:35.423Z" }, + { url = "https://files.pythonhosted.org/packages/b6/04/e24e16f1dde491350c64e82a37317da5f8f264d129ed4f9b5f117d4a4dfc/numpy-2.5.0rc1-cp314-cp314-win_amd64.whl", hash = "sha256:6089d453e0e6828f918636677fbceca9a82674b32f1fb327f9b1ee494ea36688", size = 12547661, upload-time = "2026-06-02T12:57:37.571Z" }, + { url = "https://files.pythonhosted.org/packages/a4/d3/0b9bb44def2b4dbd6c962e30db3048a77c1ef3e8a3c1d9d57e95102b125b/numpy-2.5.0rc1-cp314-cp314-win_arm64.whl", hash = "sha256:185b5534a5e7d1d63742fefa30fe023f6372c2dc501a7944ab751598e3d1e0e5", size = 10611115, upload-time = "2026-06-02T12:57:39.982Z" }, + { url = "https://files.pythonhosted.org/packages/32/85/2c2d9798cf5ece4a2ea2fdfa6446e829c7038924885432643e26cd539d58/numpy-2.5.0rc1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9b042e0c0e565785f7a949238c6f133b79b81798916764802c4fe6e42ec44152", size = 14929474, upload-time = "2026-06-02T12:57:42.506Z" }, + { url = "https://files.pythonhosted.org/packages/3e/7c/63e362acb28a1829897d6ebddf5ff0b574296c33ba8c1ae8afe5b688c546/numpy-2.5.0rc1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:d6f5f960cf81af1bab473ae447b9bc0eeddfe75daa5c6d40666a06c64c85b935", size = 5435431, upload-time = "2026-06-02T12:57:44.77Z" }, + { url = "https://files.pythonhosted.org/packages/3d/65/a50dd4c4b95bafe57cd578d2c80ef28e6f9b961c138eff6482147cf7e2c0/numpy-2.5.0rc1-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:09d9c6728008dcfe9b5fafb78000a1349176d095e25abae7828026eb8922a3ab", size = 6745844, upload-time = "2026-06-02T12:57:46.746Z" }, + { url = "https://files.pythonhosted.org/packages/18/9c/e1e6dde63fa7b7df6bebd97c5f2ec91e918c58a264423338ae1ce2fa06dd/numpy-2.5.0rc1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9a9e7396a878d06b74c3114753091178981cc1ed40de0afe924bed6961e5d53b", size = 15212366, upload-time = "2026-06-02T12:57:48.877Z" }, + { url = "https://files.pythonhosted.org/packages/85/20/f11412d706cc3622a55a4a5d04bcc6ac1a77c935cb5771c5629a6304b799/numpy-2.5.0rc1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b13412a84ce0b894c63148a68ab743b7f405f696dad6a7e72d9534f535137bd8", size = 16688617, upload-time = "2026-06-02T12:57:51.68Z" }, + { url = "https://files.pythonhosted.org/packages/01/c7/4002b397c8ff3fc1c0109ddb9f5b9cc0c10a16f1627e96c63d8a8fba83d4/numpy-2.5.0rc1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5b32736a68a67c0935a678e92a798209f3acbf857306cfe1a807142cb7a6c7c9", size = 16577833, upload-time = "2026-06-02T12:57:54.23Z" }, + { url = "https://files.pythonhosted.org/packages/14/3e/3316e3b6db7505df3b8f66e74d5e102be4b9297a5661ba0f95df74991e96/numpy-2.5.0rc1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:28865f569d8c0caf71a3de85c95fd1f021aedb732bdd21bb1e37ed3a17644672", size = 18451445, upload-time = "2026-06-02T12:57:56.986Z" }, + { url = "https://files.pythonhosted.org/packages/60/27/dca162784210d5046a0e83128614d07e42e0196c240e32679b55d65cb3a0/numpy-2.5.0rc1-cp314-cp314t-win32.whl", hash = "sha256:a06ddccbcc74b3bdc63a8f781af8792b4dcb2bec9a39c0ff3c630372e638a1c7", size = 6251446, upload-time = "2026-06-02T12:57:59.788Z" }, + { url = "https://files.pythonhosted.org/packages/60/b1/bb7e49625673e0398d3ec6d1a7a7a46ba044569c8b6373d58d9931d1b823/numpy-2.5.0rc1-cp314-cp314t-win_amd64.whl", hash = "sha256:6a9405558adc50738a254e68c786467f7ab74a523a2689c51fb003863b8289c7", size = 12732397, upload-time = "2026-06-02T12:58:01.809Z" }, + { url = "https://files.pythonhosted.org/packages/53/dd/bec9b508b770988aa3218de36847f31b0bab94699e83d9d3d401379718df/numpy-2.5.0rc1-cp314-cp314t-win_arm64.whl", hash = "sha256:e2857303f8c8bfe897addc98099dd166a192fe847dc5147a5d7e49625143edcf", size = 10680555, upload-time = "2026-06-02T12:58:06.407Z" }, ] [[package]] @@ -2374,7 +2369,7 @@ wheels = [ [[package]] name = "openai-agents" -version = "0.15.1" +version = "0.17.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "griffelib" }, @@ -2386,14 +2381,14 @@ dependencies = [ { name = "typing-extensions" }, { name = "websockets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1d/52/125891e56b67ec78bef08d91dc0a8d39457088cd0f59bf8e74a37e5e591c/openai_agents-0.15.1.tar.gz", hash = "sha256:78c3f1226e1d6d34dd7566e211c8345e996629d1704335153d1728995a3a7775", size = 5319915, upload-time = "2026-05-02T02:20:53.631Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fd/16/b79c1849125eb6d19cae98c21ff35caa2e55b5ec8d7a02b354b711917ef7/openai_agents-0.17.3.tar.gz", hash = "sha256:63b6dda6bd4fb51169e2a2cbd5d187a4e5ce823bbd15f965c8ed1d3b89072eec", size = 5406135, upload-time = "2026-05-19T01:28:15.971Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/7b/69a33975b3610300219e1d25185c46280814cdb87e69a779acf0c9b9166a/openai_agents-0.15.1-py3-none-any.whl", hash = "sha256:2d304a5dcb919bc4fa1de5c7c9c93a4353a8a79d87d582d6170929390b7b3cef", size = 818627, upload-time = "2026-05-02T02:20:50.932Z" }, + { url = "https://files.pythonhosted.org/packages/80/ec/775a14cfd5f12f4ffe458c7ac9527831093c72e8c1aef682898fc6394106/openai_agents-0.17.3-py3-none-any.whl", hash = "sha256:a048bb0752d40913d18bccf6562f56260b603bb57c972597b6da58f60123f4bd", size = 841541, upload-time = "2026-05-19T01:28:13.334Z" }, ] [[package]] name = "openai-chatkit" -version = "1.6.3" +version = "1.6.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jinja2" }, @@ -2402,9 +2397,9 @@ dependencies = [ { name = "pydantic" }, { name = "uvicorn" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/05/46/b15fd77f7df12a2cabd8475de6226ce04d1cec7b283b21e8f0f52edc63a7/openai_chatkit-1.6.3.tar.gz", hash = "sha256:f16e347f39c376a78dddb5ceaf5398a4bb700c0145bfa7cb899d65135972956e", size = 61822, upload-time = "2026-03-04T19:30:19.564Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ef/07/c4b4ea034f34f25e73cf1a872deb349e3acab6c929a5531d547a9a994890/openai_chatkit-1.6.5.tar.gz", hash = "sha256:903e9702bf26cd8a2b23d4e7b199b657bee4379758e0ca11ebaee09362d2889e", size = 65057, upload-time = "2026-05-19T05:05:14.954Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/52/5e/e06a4bec431083c282dea5729b0947b940900a4014216835182048078877/openai_chatkit-1.6.3-py3-none-any.whl", hash = "sha256:642ecdf810eda3619964f316e393f252741130a5500dc3a357d501f8657b3941", size = 42578, upload-time = "2026-03-04T19:30:18.314Z" }, + { url = "https://files.pythonhosted.org/packages/aa/00/64b0faae946885e9bf52a8ea77d8bd01c80f6d0be967a30c1d4e73fc2ce1/openai_chatkit-1.6.5-py3-none-any.whl", hash = "sha256:9e16d3bdc6c15fec900801591dc5bb8ba9446447d3db2247dc8120518f1ca3c0", size = 44094, upload-time = "2026-05-19T05:05:13.654Z" }, ] [[package]] @@ -2708,20 +2703,20 @@ wheels = [ [[package]] name = "pathable" -version = "0.5.0" +version = "0.6.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/55/b748445cb4ea6b125626f15379be7c96d1035d4fa3e8fee362fa92298abf/pathable-0.5.0.tar.gz", hash = "sha256:d81938348a1cacb525e7c75166270644782c0fb9c8cecc16be033e71427e0ef1", size = 16655, upload-time = "2026-02-20T08:47:00.748Z" } +sdist = { url = "https://files.pythonhosted.org/packages/66/f3/5a20387de9bcd0607871bfc2198ee0e15836da7baa4592ccd7f24c27c986/pathable-0.6.0.tar.gz", hash = "sha256:6404b8b82aef5ff0fd478934137128b99b12212ba35afdde5525ca4f8388ea58", size = 18970, upload-time = "2026-05-19T18:15:11.911Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/52/96/5a770e5c461462575474468e5af931cff9de036e7c2b4fea23c1c58d2cbe/pathable-0.5.0-py3-none-any.whl", hash = "sha256:646e3d09491a6351a0c82632a09c02cdf70a252e73196b36d8a15ba0a114f0a6", size = 16867, upload-time = "2026-02-20T08:46:59.536Z" }, + { url = "https://files.pythonhosted.org/packages/a2/e8/6d75ffd9784bce2e93d1ae4415649427e39a53bb172d4672b2b59c6f0a7b/pathable-0.6.0-py3-none-any.whl", hash = "sha256:82c4ca6c98c502ad12e0d4e9779b6210afee93c38990988c8c5d1b49bdcdf566", size = 18983, upload-time = "2026-05-19T18:15:10.728Z" }, ] [[package]] name = "platformdirs" -version = "4.9.6" +version = "4.10.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9f/4a/0883b8e3802965322523f0b200ecf33d31f10991d0401162f4b23c698b42/platformdirs-4.9.6.tar.gz", hash = "sha256:3bfa75b0ad0db84096ae777218481852c0ebc6c727b3168c1b9e0118e458cf0a", size = 29400, upload-time = "2026-04-09T00:04:10.812Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/47/e4501f49c178ae1d9f4a75073fda4204f52647993f075a9db4d14930e0c5/platformdirs-4.10.0.tar.gz", hash = "sha256:31e761a6a0ca04faf7353ea759bdba55652be214725111e5aac52dfa29d4bef7", size = 31224, upload-time = "2026-05-28T03:32:53.587Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/75/a6/a0a304dc33b49145b21f4808d763822111e67d1c3a32b524a1baf947b6e1/platformdirs-4.9.6-py3-none-any.whl", hash = "sha256:e61adb1d5e5cb3441b4b7710bea7e4c12250ca49439228cc1021c00dcfac0917", size = 21348, upload-time = "2026-04-09T00:04:09.463Z" }, + { url = "https://files.pythonhosted.org/packages/81/e6/cd9575ac904136b3cbf7aa7ee819ef86eedb7274e46f230e94ea4342e729/platformdirs-4.10.0-py3-none-any.whl", hash = "sha256:fb516cdb12eb0d857d0cd85a7c57cea4d060bee4578d6cf5a14dfdf8cbf8784a", size = 22743, upload-time = "2026-05-28T03:32:52.175Z" }, ] [[package]] @@ -2747,7 +2742,7 @@ wheels = [ [[package]] name = "posthog" -version = "7.14.0" +version = "7.19.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "backoff" }, @@ -2755,9 +2750,9 @@ dependencies = [ { name = "requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c4/0f/0e6578feaf0d4e670bc517b6da09ec147a65421c44e0cd687eba12f08743/posthog-7.14.0.tar.gz", hash = "sha256:3be5e513f07e4ee5119f98b0458cb640739b49cef7c96c3e18b1d65076b18239", size = 205083, upload-time = "2026-05-01T20:41:37.971Z" } +sdist = { url = "https://files.pythonhosted.org/packages/19/24/c4880740bb875c05da8cb4cb70fcb4d0e572748bae30347f73de97217851/posthog-7.19.2.tar.gz", hash = "sha256:c39d464b5b5930bf930329ba2593a778d44ecf8724f2c3b473209e3e68ef6cad", size = 239161, upload-time = "2026-06-17T14:07:10.098Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b3/c2/2dc3e08e481f45c0215da4325ccc9b5f368dcee504779d2f169ab567c766/posthog-7.14.0-py3-none-any.whl", hash = "sha256:76db6e3158e2c11ec9bbcf32a673efec4acc8078965d92e2d3055555220ee546", size = 240187, upload-time = "2026-05-01T20:41:36.022Z" }, + { url = "https://files.pythonhosted.org/packages/3a/55/71d3e8ad027e4e4dca6dd2a21e233b2fe6de569822f17bb7e14860879903/posthog-7.19.2-py3-none-any.whl", hash = "sha256:1a8da398b684e328f67f4305ddd89d2b190285c34a34d954934cdb9fb4857996", size = 279371, upload-time = "2026-06-17T14:07:08.441Z" }, ] [[package]] @@ -2765,8 +2760,8 @@ name = "powerfx" version = "0.0.34" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cffi", marker = "python_full_version < '3.14'" }, - { name = "pythonnet", marker = "python_full_version < '3.14'" }, + { name = "cffi" }, + { name = "pythonnet" }, ] sdist = { url = "https://files.pythonhosted.org/packages/9f/fb/6c4bf87e0c74ca1c563921ce89ca1c5785b7576bca932f7255cdf81082a7/powerfx-0.0.34.tar.gz", hash = "sha256:956992e7afd272657ed16d80f4cad24ec95d9e4a79fb9dfa4a068a09e136af32", size = 3237555, upload-time = "2025-12-22T15:50:59.682Z" } wheels = [ @@ -2864,98 +2859,108 @@ dev = [ [[package]] name = "propcache" -version = "0.4.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9e/da/e9fc233cf63743258bff22b3dfa7ea5baef7b5bc324af47a0ad89b8ffc6f/propcache-0.4.1.tar.gz", hash = "sha256:f48107a8c637e80362555f37ecf49abe20370e557cc4ab374f04ec4423c97c3d", size = 46442, upload-time = "2025-10-08T19:49:02.291Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a2/0f/f17b1b2b221d5ca28b4b876e8bb046ac40466513960646bda8e1853cdfa2/propcache-0.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e153e9cd40cc8945138822807139367f256f89c6810c2634a4f6902b52d3b4e2", size = 80061, upload-time = "2025-10-08T19:46:46.075Z" }, - { url = "https://files.pythonhosted.org/packages/76/47/8ccf75935f51448ba9a16a71b783eb7ef6b9ee60f5d14c7f8a8a79fbeed7/propcache-0.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cd547953428f7abb73c5ad82cbb32109566204260d98e41e5dfdc682eb7f8403", size = 46037, upload-time = "2025-10-08T19:46:47.23Z" }, - { url = "https://files.pythonhosted.org/packages/0a/b6/5c9a0e42df4d00bfb4a3cbbe5cf9f54260300c88a0e9af1f47ca5ce17ac0/propcache-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f048da1b4f243fc44f205dfd320933a951b8d89e0afd4c7cacc762a8b9165207", size = 47324, upload-time = "2025-10-08T19:46:48.384Z" }, - { url = "https://files.pythonhosted.org/packages/9e/d3/6c7ee328b39a81ee877c962469f1e795f9db87f925251efeb0545e0020d0/propcache-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ec17c65562a827bba85e3872ead335f95405ea1674860d96483a02f5c698fa72", size = 225505, upload-time = "2025-10-08T19:46:50.055Z" }, - { url = "https://files.pythonhosted.org/packages/01/5d/1c53f4563490b1d06a684742cc6076ef944bc6457df6051b7d1a877c057b/propcache-0.4.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:405aac25c6394ef275dee4c709be43745d36674b223ba4eb7144bf4d691b7367", size = 230242, upload-time = "2025-10-08T19:46:51.815Z" }, - { url = "https://files.pythonhosted.org/packages/20/e1/ce4620633b0e2422207c3cb774a0ee61cac13abc6217763a7b9e2e3f4a12/propcache-0.4.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0013cb6f8dde4b2a2f66903b8ba740bdfe378c943c4377a200551ceb27f379e4", size = 238474, upload-time = "2025-10-08T19:46:53.208Z" }, - { url = "https://files.pythonhosted.org/packages/46/4b/3aae6835b8e5f44ea6a68348ad90f78134047b503765087be2f9912140ea/propcache-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15932ab57837c3368b024473a525e25d316d8353016e7cc0e5ba9eb343fbb1cf", size = 221575, upload-time = "2025-10-08T19:46:54.511Z" }, - { url = "https://files.pythonhosted.org/packages/6e/a5/8a5e8678bcc9d3a1a15b9a29165640d64762d424a16af543f00629c87338/propcache-0.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:031dce78b9dc099f4c29785d9cf5577a3faf9ebf74ecbd3c856a7b92768c3df3", size = 216736, upload-time = "2025-10-08T19:46:56.212Z" }, - { url = "https://files.pythonhosted.org/packages/f1/63/b7b215eddeac83ca1c6b934f89d09a625aa9ee4ba158338854c87210cc36/propcache-0.4.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ab08df6c9a035bee56e31af99be621526bd237bea9f32def431c656b29e41778", size = 213019, upload-time = "2025-10-08T19:46:57.595Z" }, - { url = "https://files.pythonhosted.org/packages/57/74/f580099a58c8af587cac7ba19ee7cb418506342fbbe2d4a4401661cca886/propcache-0.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4d7af63f9f93fe593afbf104c21b3b15868efb2c21d07d8732c0c4287e66b6a6", size = 220376, upload-time = "2025-10-08T19:46:59.067Z" }, - { url = "https://files.pythonhosted.org/packages/c4/ee/542f1313aff7eaf19c2bb758c5d0560d2683dac001a1c96d0774af799843/propcache-0.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cfc27c945f422e8b5071b6e93169679e4eb5bf73bbcbf1ba3ae3a83d2f78ebd9", size = 226988, upload-time = "2025-10-08T19:47:00.544Z" }, - { url = "https://files.pythonhosted.org/packages/8f/18/9c6b015dd9c6930f6ce2229e1f02fb35298b847f2087ea2b436a5bfa7287/propcache-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:35c3277624a080cc6ec6f847cbbbb5b49affa3598c4535a0a4682a697aaa5c75", size = 215615, upload-time = "2025-10-08T19:47:01.968Z" }, - { url = "https://files.pythonhosted.org/packages/80/9e/e7b85720b98c45a45e1fca6a177024934dc9bc5f4d5dd04207f216fc33ed/propcache-0.4.1-cp312-cp312-win32.whl", hash = "sha256:671538c2262dadb5ba6395e26c1731e1d52534bfe9ae56d0b5573ce539266aa8", size = 38066, upload-time = "2025-10-08T19:47:03.503Z" }, - { url = "https://files.pythonhosted.org/packages/54/09/d19cff2a5aaac632ec8fc03737b223597b1e347416934c1b3a7df079784c/propcache-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:cb2d222e72399fcf5890d1d5cc1060857b9b236adff2792ff48ca2dfd46c81db", size = 41655, upload-time = "2025-10-08T19:47:04.973Z" }, - { url = "https://files.pythonhosted.org/packages/68/ab/6b5c191bb5de08036a8c697b265d4ca76148efb10fa162f14af14fb5f076/propcache-0.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:204483131fb222bdaaeeea9f9e6c6ed0cac32731f75dfc1d4a567fc1926477c1", size = 37789, upload-time = "2025-10-08T19:47:06.077Z" }, - { url = "https://files.pythonhosted.org/packages/bf/df/6d9c1b6ac12b003837dde8a10231a7344512186e87b36e855bef32241942/propcache-0.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:43eedf29202c08550aac1d14e0ee619b0430aaef78f85864c1a892294fbc28cf", size = 77750, upload-time = "2025-10-08T19:47:07.648Z" }, - { url = "https://files.pythonhosted.org/packages/8b/e8/677a0025e8a2acf07d3418a2e7ba529c9c33caf09d3c1f25513023c1db56/propcache-0.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d62cdfcfd89ccb8de04e0eda998535c406bf5e060ffd56be6c586cbcc05b3311", size = 44780, upload-time = "2025-10-08T19:47:08.851Z" }, - { url = "https://files.pythonhosted.org/packages/89/a4/92380f7ca60f99ebae761936bc48a72a639e8a47b29050615eef757cb2a7/propcache-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cae65ad55793da34db5f54e4029b89d3b9b9490d8abe1b4c7ab5d4b8ec7ebf74", size = 46308, upload-time = "2025-10-08T19:47:09.982Z" }, - { url = "https://files.pythonhosted.org/packages/2d/48/c5ac64dee5262044348d1d78a5f85dd1a57464a60d30daee946699963eb3/propcache-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:333ddb9031d2704a301ee3e506dc46b1fe5f294ec198ed6435ad5b6a085facfe", size = 208182, upload-time = "2025-10-08T19:47:11.319Z" }, - { url = "https://files.pythonhosted.org/packages/c6/0c/cd762dd011a9287389a6a3eb43aa30207bde253610cca06824aeabfe9653/propcache-0.4.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fd0858c20f078a32cf55f7e81473d96dcf3b93fd2ccdb3d40fdf54b8573df3af", size = 211215, upload-time = "2025-10-08T19:47:13.146Z" }, - { url = "https://files.pythonhosted.org/packages/30/3e/49861e90233ba36890ae0ca4c660e95df565b2cd15d4a68556ab5865974e/propcache-0.4.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:678ae89ebc632c5c204c794f8dab2837c5f159aeb59e6ed0539500400577298c", size = 218112, upload-time = "2025-10-08T19:47:14.913Z" }, - { url = "https://files.pythonhosted.org/packages/f1/8b/544bc867e24e1bd48f3118cecd3b05c694e160a168478fa28770f22fd094/propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d472aeb4fbf9865e0c6d622d7f4d54a4e101a89715d8904282bb5f9a2f476c3f", size = 204442, upload-time = "2025-10-08T19:47:16.277Z" }, - { url = "https://files.pythonhosted.org/packages/50/a6/4282772fd016a76d3e5c0df58380a5ea64900afd836cec2c2f662d1b9bb3/propcache-0.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4d3df5fa7e36b3225954fba85589da77a0fe6a53e3976de39caf04a0db4c36f1", size = 199398, upload-time = "2025-10-08T19:47:17.962Z" }, - { url = "https://files.pythonhosted.org/packages/3e/ec/d8a7cd406ee1ddb705db2139f8a10a8a427100347bd698e7014351c7af09/propcache-0.4.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ee17f18d2498f2673e432faaa71698032b0127ebf23ae5974eeaf806c279df24", size = 196920, upload-time = "2025-10-08T19:47:19.355Z" }, - { url = "https://files.pythonhosted.org/packages/f6/6c/f38ab64af3764f431e359f8baf9e0a21013e24329e8b85d2da32e8ed07ca/propcache-0.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:580e97762b950f993ae618e167e7be9256b8353c2dcd8b99ec100eb50f5286aa", size = 203748, upload-time = "2025-10-08T19:47:21.338Z" }, - { url = "https://files.pythonhosted.org/packages/d6/e3/fa846bd70f6534d647886621388f0a265254d30e3ce47e5c8e6e27dbf153/propcache-0.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:501d20b891688eb8e7aa903021f0b72d5a55db40ffaab27edefd1027caaafa61", size = 205877, upload-time = "2025-10-08T19:47:23.059Z" }, - { url = "https://files.pythonhosted.org/packages/e2/39/8163fc6f3133fea7b5f2827e8eba2029a0277ab2c5beee6c1db7b10fc23d/propcache-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a0bd56e5b100aef69bd8562b74b46254e7c8812918d3baa700c8a8009b0af66", size = 199437, upload-time = "2025-10-08T19:47:24.445Z" }, - { url = "https://files.pythonhosted.org/packages/93/89/caa9089970ca49c7c01662bd0eeedfe85494e863e8043565aeb6472ce8fe/propcache-0.4.1-cp313-cp313-win32.whl", hash = "sha256:bcc9aaa5d80322bc2fb24bb7accb4a30f81e90ab8d6ba187aec0744bc302ad81", size = 37586, upload-time = "2025-10-08T19:47:25.736Z" }, - { url = "https://files.pythonhosted.org/packages/f5/ab/f76ec3c3627c883215b5c8080debb4394ef5a7a29be811f786415fc1e6fd/propcache-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:381914df18634f5494334d201e98245c0596067504b9372d8cf93f4bb23e025e", size = 40790, upload-time = "2025-10-08T19:47:26.847Z" }, - { url = "https://files.pythonhosted.org/packages/59/1b/e71ae98235f8e2ba5004d8cb19765a74877abf189bc53fc0c80d799e56c3/propcache-0.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:8873eb4460fd55333ea49b7d189749ecf6e55bf85080f11b1c4530ed3034cba1", size = 37158, upload-time = "2025-10-08T19:47:27.961Z" }, - { url = "https://files.pythonhosted.org/packages/83/ce/a31bbdfc24ee0dcbba458c8175ed26089cf109a55bbe7b7640ed2470cfe9/propcache-0.4.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:92d1935ee1f8d7442da9c0c4fa7ac20d07e94064184811b685f5c4fada64553b", size = 81451, upload-time = "2025-10-08T19:47:29.445Z" }, - { url = "https://files.pythonhosted.org/packages/25/9c/442a45a470a68456e710d96cacd3573ef26a1d0a60067e6a7d5e655621ed/propcache-0.4.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:473c61b39e1460d386479b9b2f337da492042447c9b685f28be4f74d3529e566", size = 46374, upload-time = "2025-10-08T19:47:30.579Z" }, - { url = "https://files.pythonhosted.org/packages/f4/bf/b1d5e21dbc3b2e889ea4327044fb16312a736d97640fb8b6aa3f9c7b3b65/propcache-0.4.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c0ef0aaafc66fbd87842a3fe3902fd889825646bc21149eafe47be6072725835", size = 48396, upload-time = "2025-10-08T19:47:31.79Z" }, - { url = "https://files.pythonhosted.org/packages/f4/04/5b4c54a103d480e978d3c8a76073502b18db0c4bc17ab91b3cb5092ad949/propcache-0.4.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f95393b4d66bfae908c3ca8d169d5f79cd65636ae15b5e7a4f6e67af675adb0e", size = 275950, upload-time = "2025-10-08T19:47:33.481Z" }, - { url = "https://files.pythonhosted.org/packages/b4/c1/86f846827fb969c4b78b0af79bba1d1ea2156492e1b83dea8b8a6ae27395/propcache-0.4.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c07fda85708bc48578467e85099645167a955ba093be0a2dcba962195676e859", size = 273856, upload-time = "2025-10-08T19:47:34.906Z" }, - { url = "https://files.pythonhosted.org/packages/36/1d/fc272a63c8d3bbad6878c336c7a7dea15e8f2d23a544bda43205dfa83ada/propcache-0.4.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:af223b406d6d000830c6f65f1e6431783fc3f713ba3e6cc8c024d5ee96170a4b", size = 280420, upload-time = "2025-10-08T19:47:36.338Z" }, - { url = "https://files.pythonhosted.org/packages/07/0c/01f2219d39f7e53d52e5173bcb09c976609ba30209912a0680adfb8c593a/propcache-0.4.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a78372c932c90ee474559c5ddfffd718238e8673c340dc21fe45c5b8b54559a0", size = 263254, upload-time = "2025-10-08T19:47:37.692Z" }, - { url = "https://files.pythonhosted.org/packages/2d/18/cd28081658ce597898f0c4d174d4d0f3c5b6d4dc27ffafeef835c95eb359/propcache-0.4.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:564d9f0d4d9509e1a870c920a89b2fec951b44bf5ba7d537a9e7c1ccec2c18af", size = 261205, upload-time = "2025-10-08T19:47:39.659Z" }, - { url = "https://files.pythonhosted.org/packages/7a/71/1f9e22eb8b8316701c2a19fa1f388c8a3185082607da8e406a803c9b954e/propcache-0.4.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:17612831fda0138059cc5546f4d12a2aacfb9e47068c06af35c400ba58ba7393", size = 247873, upload-time = "2025-10-08T19:47:41.084Z" }, - { url = "https://files.pythonhosted.org/packages/4a/65/3d4b61f36af2b4eddba9def857959f1016a51066b4f1ce348e0cf7881f58/propcache-0.4.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:41a89040cb10bd345b3c1a873b2bf36413d48da1def52f268a055f7398514874", size = 262739, upload-time = "2025-10-08T19:47:42.51Z" }, - { url = "https://files.pythonhosted.org/packages/2a/42/26746ab087faa77c1c68079b228810436ccd9a5ce9ac85e2b7307195fd06/propcache-0.4.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e35b88984e7fa64aacecea39236cee32dd9bd8c55f57ba8a75cf2399553f9bd7", size = 263514, upload-time = "2025-10-08T19:47:43.927Z" }, - { url = "https://files.pythonhosted.org/packages/94/13/630690fe201f5502d2403dd3cfd451ed8858fe3c738ee88d095ad2ff407b/propcache-0.4.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f8b465489f927b0df505cbe26ffbeed4d6d8a2bbc61ce90eb074ff129ef0ab1", size = 257781, upload-time = "2025-10-08T19:47:45.448Z" }, - { url = "https://files.pythonhosted.org/packages/92/f7/1d4ec5841505f423469efbfc381d64b7b467438cd5a4bbcbb063f3b73d27/propcache-0.4.1-cp313-cp313t-win32.whl", hash = "sha256:2ad890caa1d928c7c2965b48f3a3815c853180831d0e5503d35cf00c472f4717", size = 41396, upload-time = "2025-10-08T19:47:47.202Z" }, - { url = "https://files.pythonhosted.org/packages/48/f0/615c30622316496d2cbbc29f5985f7777d3ada70f23370608c1d3e081c1f/propcache-0.4.1-cp313-cp313t-win_amd64.whl", hash = "sha256:f7ee0e597f495cf415bcbd3da3caa3bd7e816b74d0d52b8145954c5e6fd3ff37", size = 44897, upload-time = "2025-10-08T19:47:48.336Z" }, - { url = "https://files.pythonhosted.org/packages/fd/ca/6002e46eccbe0e33dcd4069ef32f7f1c9e243736e07adca37ae8c4830ec3/propcache-0.4.1-cp313-cp313t-win_arm64.whl", hash = "sha256:929d7cbe1f01bb7baffb33dc14eb5691c95831450a26354cd210a8155170c93a", size = 39789, upload-time = "2025-10-08T19:47:49.876Z" }, - { url = "https://files.pythonhosted.org/packages/8e/5c/bca52d654a896f831b8256683457ceddd490ec18d9ec50e97dfd8fc726a8/propcache-0.4.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3f7124c9d820ba5548d431afb4632301acf965db49e666aa21c305cbe8c6de12", size = 78152, upload-time = "2025-10-08T19:47:51.051Z" }, - { url = "https://files.pythonhosted.org/packages/65/9b/03b04e7d82a5f54fb16113d839f5ea1ede58a61e90edf515f6577c66fa8f/propcache-0.4.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c0d4b719b7da33599dfe3b22d3db1ef789210a0597bc650b7cee9c77c2be8c5c", size = 44869, upload-time = "2025-10-08T19:47:52.594Z" }, - { url = "https://files.pythonhosted.org/packages/b2/fa/89a8ef0468d5833a23fff277b143d0573897cf75bd56670a6d28126c7d68/propcache-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9f302f4783709a78240ebc311b793f123328716a60911d667e0c036bc5dcbded", size = 46596, upload-time = "2025-10-08T19:47:54.073Z" }, - { url = "https://files.pythonhosted.org/packages/86/bd/47816020d337f4a746edc42fe8d53669965138f39ee117414c7d7a340cfe/propcache-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c80ee5802e3fb9ea37938e7eecc307fb984837091d5fd262bb37238b1ae97641", size = 206981, upload-time = "2025-10-08T19:47:55.715Z" }, - { url = "https://files.pythonhosted.org/packages/df/f6/c5fa1357cc9748510ee55f37173eb31bfde6d94e98ccd9e6f033f2fc06e1/propcache-0.4.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ed5a841e8bb29a55fb8159ed526b26adc5bdd7e8bd7bf793ce647cb08656cdf4", size = 211490, upload-time = "2025-10-08T19:47:57.499Z" }, - { url = "https://files.pythonhosted.org/packages/80/1e/e5889652a7c4a3846683401a48f0f2e5083ce0ec1a8a5221d8058fbd1adf/propcache-0.4.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:55c72fd6ea2da4c318e74ffdf93c4fe4e926051133657459131a95c846d16d44", size = 215371, upload-time = "2025-10-08T19:47:59.317Z" }, - { url = "https://files.pythonhosted.org/packages/b2/f2/889ad4b2408f72fe1a4f6a19491177b30ea7bf1a0fd5f17050ca08cfc882/propcache-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8326e144341460402713f91df60ade3c999d601e7eb5ff8f6f7862d54de0610d", size = 201424, upload-time = "2025-10-08T19:48:00.67Z" }, - { url = "https://files.pythonhosted.org/packages/27/73/033d63069b57b0812c8bd19f311faebeceb6ba31b8f32b73432d12a0b826/propcache-0.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:060b16ae65bc098da7f6d25bf359f1f31f688384858204fe5d652979e0015e5b", size = 197566, upload-time = "2025-10-08T19:48:02.604Z" }, - { url = "https://files.pythonhosted.org/packages/dc/89/ce24f3dc182630b4e07aa6d15f0ff4b14ed4b9955fae95a0b54c58d66c05/propcache-0.4.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:89eb3fa9524f7bec9de6e83cf3faed9d79bffa560672c118a96a171a6f55831e", size = 193130, upload-time = "2025-10-08T19:48:04.499Z" }, - { url = "https://files.pythonhosted.org/packages/a9/24/ef0d5fd1a811fb5c609278d0209c9f10c35f20581fcc16f818da959fc5b4/propcache-0.4.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:dee69d7015dc235f526fe80a9c90d65eb0039103fe565776250881731f06349f", size = 202625, upload-time = "2025-10-08T19:48:06.213Z" }, - { url = "https://files.pythonhosted.org/packages/f5/02/98ec20ff5546f68d673df2f7a69e8c0d076b5abd05ca882dc7ee3a83653d/propcache-0.4.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5558992a00dfd54ccbc64a32726a3357ec93825a418a401f5cc67df0ac5d9e49", size = 204209, upload-time = "2025-10-08T19:48:08.432Z" }, - { url = "https://files.pythonhosted.org/packages/a0/87/492694f76759b15f0467a2a93ab68d32859672b646aa8a04ce4864e7932d/propcache-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c9b822a577f560fbd9554812526831712c1436d2c046cedee4c3796d3543b144", size = 197797, upload-time = "2025-10-08T19:48:09.968Z" }, - { url = "https://files.pythonhosted.org/packages/ee/36/66367de3575db1d2d3f3d177432bd14ee577a39d3f5d1b3d5df8afe3b6e2/propcache-0.4.1-cp314-cp314-win32.whl", hash = "sha256:ab4c29b49d560fe48b696cdcb127dd36e0bc2472548f3bf56cc5cb3da2b2984f", size = 38140, upload-time = "2025-10-08T19:48:11.232Z" }, - { url = "https://files.pythonhosted.org/packages/0c/2a/a758b47de253636e1b8aef181c0b4f4f204bf0dd964914fb2af90a95b49b/propcache-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:5a103c3eb905fcea0ab98be99c3a9a5ab2de60228aa5aceedc614c0281cf6153", size = 41257, upload-time = "2025-10-08T19:48:12.707Z" }, - { url = "https://files.pythonhosted.org/packages/34/5e/63bd5896c3fec12edcbd6f12508d4890d23c265df28c74b175e1ef9f4f3b/propcache-0.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:74c1fb26515153e482e00177a1ad654721bf9207da8a494a0c05e797ad27b992", size = 38097, upload-time = "2025-10-08T19:48:13.923Z" }, - { url = "https://files.pythonhosted.org/packages/99/85/9ff785d787ccf9bbb3f3106f79884a130951436f58392000231b4c737c80/propcache-0.4.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:824e908bce90fb2743bd6b59db36eb4f45cd350a39637c9f73b1c1ea66f5b75f", size = 81455, upload-time = "2025-10-08T19:48:15.16Z" }, - { url = "https://files.pythonhosted.org/packages/90/85/2431c10c8e7ddb1445c1f7c4b54d886e8ad20e3c6307e7218f05922cad67/propcache-0.4.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c2b5e7db5328427c57c8e8831abda175421b709672f6cfc3d630c3b7e2146393", size = 46372, upload-time = "2025-10-08T19:48:16.424Z" }, - { url = "https://files.pythonhosted.org/packages/01/20/b0972d902472da9bcb683fa595099911f4d2e86e5683bcc45de60dd05dc3/propcache-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6f6ff873ed40292cd4969ef5310179afd5db59fdf055897e282485043fc80ad0", size = 48411, upload-time = "2025-10-08T19:48:17.577Z" }, - { url = "https://files.pythonhosted.org/packages/e2/e3/7dc89f4f21e8f99bad3d5ddb3a3389afcf9da4ac69e3deb2dcdc96e74169/propcache-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49a2dc67c154db2c1463013594c458881a069fcf98940e61a0569016a583020a", size = 275712, upload-time = "2025-10-08T19:48:18.901Z" }, - { url = "https://files.pythonhosted.org/packages/20/67/89800c8352489b21a8047c773067644e3897f02ecbbd610f4d46b7f08612/propcache-0.4.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:005f08e6a0529984491e37d8dbc3dd86f84bd78a8ceb5fa9a021f4c48d4984be", size = 273557, upload-time = "2025-10-08T19:48:20.762Z" }, - { url = "https://files.pythonhosted.org/packages/e2/a1/b52b055c766a54ce6d9c16d9aca0cad8059acd9637cdf8aa0222f4a026ef/propcache-0.4.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5c3310452e0d31390da9035c348633b43d7e7feb2e37be252be6da45abd1abcc", size = 280015, upload-time = "2025-10-08T19:48:22.592Z" }, - { url = "https://files.pythonhosted.org/packages/48/c8/33cee30bd890672c63743049f3c9e4be087e6780906bfc3ec58528be59c1/propcache-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c3c70630930447f9ef1caac7728c8ad1c56bc5015338b20fed0d08ea2480b3a", size = 262880, upload-time = "2025-10-08T19:48:23.947Z" }, - { url = "https://files.pythonhosted.org/packages/0c/b1/8f08a143b204b418285c88b83d00edbd61afbc2c6415ffafc8905da7038b/propcache-0.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8e57061305815dfc910a3634dcf584f08168a8836e6999983569f51a8544cd89", size = 260938, upload-time = "2025-10-08T19:48:25.656Z" }, - { url = "https://files.pythonhosted.org/packages/cf/12/96e4664c82ca2f31e1c8dff86afb867348979eb78d3cb8546a680287a1e9/propcache-0.4.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:521a463429ef54143092c11a77e04056dd00636f72e8c45b70aaa3140d639726", size = 247641, upload-time = "2025-10-08T19:48:27.207Z" }, - { url = "https://files.pythonhosted.org/packages/18/ed/e7a9cfca28133386ba52278136d42209d3125db08d0a6395f0cba0c0285c/propcache-0.4.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:120c964da3fdc75e3731aa392527136d4ad35868cc556fd09bb6d09172d9a367", size = 262510, upload-time = "2025-10-08T19:48:28.65Z" }, - { url = "https://files.pythonhosted.org/packages/f5/76/16d8bf65e8845dd62b4e2b57444ab81f07f40caa5652b8969b87ddcf2ef6/propcache-0.4.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:d8f353eb14ee3441ee844ade4277d560cdd68288838673273b978e3d6d2c8f36", size = 263161, upload-time = "2025-10-08T19:48:30.133Z" }, - { url = "https://files.pythonhosted.org/packages/e7/70/c99e9edb5d91d5ad8a49fa3c1e8285ba64f1476782fed10ab251ff413ba1/propcache-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ab2943be7c652f09638800905ee1bab2c544e537edb57d527997a24c13dc1455", size = 257393, upload-time = "2025-10-08T19:48:31.567Z" }, - { url = "https://files.pythonhosted.org/packages/08/02/87b25304249a35c0915d236575bc3574a323f60b47939a2262b77632a3ee/propcache-0.4.1-cp314-cp314t-win32.whl", hash = "sha256:05674a162469f31358c30bcaa8883cb7829fa3110bf9c0991fe27d7896c42d85", size = 42546, upload-time = "2025-10-08T19:48:32.872Z" }, - { url = "https://files.pythonhosted.org/packages/cb/ef/3c6ecf8b317aa982f309835e8f96987466123c6e596646d4e6a1dfcd080f/propcache-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:990f6b3e2a27d683cb7602ed6c86f15ee6b43b1194736f9baaeb93d0016633b1", size = 46259, upload-time = "2025-10-08T19:48:34.226Z" }, - { url = "https://files.pythonhosted.org/packages/c4/2d/346e946d4951f37eca1e4f55be0f0174c52cd70720f84029b02f296f4a38/propcache-0.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ecef2343af4cc68e05131e45024ba34f6095821988a9d0a02aa7c73fcc448aa9", size = 40428, upload-time = "2025-10-08T19:48:35.441Z" }, - { url = "https://files.pythonhosted.org/packages/5b/5a/bc7b4a4ef808fa59a816c17b20c4bef6884daebbdf627ff2a161da67da19/propcache-0.4.1-py3-none-any.whl", hash = "sha256:af2a6052aeb6cf17d3e46ee169099044fd8224cbaf75c76a2ef596e8163e2237", size = 13305, upload-time = "2025-10-08T19:49:00.792Z" }, +version = "0.5.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/44/c87281c333769159c50594f22610f77398a47ccbfbbf23074e744e86f87c/propcache-0.5.2.tar.gz", hash = "sha256:01c4fc7480cd0598bb4b57022df55b9ca296da7fc5a8760bd8451a7e63a7d427", size = 50208, upload-time = "2026-05-08T21:02:12.199Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4a/cb/e27bc2b2737a0bb49962b275efa051e8f1c35a936df7d5139b6b658b7dc9/propcache-0.5.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:806719138ecd720339a12410fb9614ac9b2b2d3a5fdf8235d56981c36f4039ba", size = 95887, upload-time = "2026-05-08T21:00:11.277Z" }, + { url = "https://files.pythonhosted.org/packages/e6/13/b8ae04c59392f8d11c6cd9fb4011d1dc7c86b81225c770280300e259ffe1/propcache-0.5.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:db2b80ea58eab4f86b2beec3cc8b39e8ff9276ac20e96b7cce43c8ae84cd6b5a", size = 54654, upload-time = "2026-05-08T21:00:12.604Z" }, + { url = "https://files.pythonhosted.org/packages/2c/7d/49777a3e20b55863d4794384a38acd460c04157b0a00f8602b0d508b8431/propcache-0.5.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e5cbfac9f61484f7e9f3597775500cd3ebe8274e9b050c38f9525c77c97520bf", size = 55190, upload-time = "2026-05-08T21:00:13.935Z" }, + { url = "https://files.pythonhosted.org/packages/44/c7/085d0cd63062e84044e3f05797749c3f8e3938ff3aeb0eb2f69d43fafc91/propcache-0.5.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5dbc581d2814337da56222fab8dc5f161cd798a434e49bac27930aaef798e144", size = 59995, upload-time = "2026-05-08T21:00:15.526Z" }, + { url = "https://files.pythonhosted.org/packages/9c/42/32cf8e3009e92b2645cf1e944f701e8ea4e924dffde1ee26db860bcbf7e4/propcache-0.5.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:857187f381f88c8e2fa2fe56ab94879d011b883d5a2ee5a1b60a8cd2a06846d9", size = 63422, upload-time = "2026-05-08T21:00:16.824Z" }, + { url = "https://files.pythonhosted.org/packages/9e/1b/f112433f99fc979431b87a39ef169e3f8df070d99a72792c56d6937ac48b/propcache-0.5.2-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:178b4a2cdaac1818e2bf1c5a99b94383fa73ea5382e032a48dec07dc5668dc42", size = 64342, upload-time = "2026-05-08T21:00:18.362Z" }, + { url = "https://files.pythonhosted.org/packages/14/15/5574111ae50dd6e879456888c0eadd4c5a869959775854e18e18a6b345f3/propcache-0.5.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f328175a2cde1f0ff2c4ed8ce968b9dcfb55f3a7153f39e2957ed994da13476", size = 61639, upload-time = "2026-05-08T21:00:19.692Z" }, + { url = "https://files.pythonhosted.org/packages/cc/da/4d775080b1490c0ae604acda868bd71aabe3a89ed16f2aa4339eb8a283e7/propcache-0.5.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5671d09a36b06d0fd4a3da0fccbcae360e9b1570924171a15e9e0997f0249fba", size = 61588, upload-time = "2026-05-08T21:00:21.155Z" }, + { url = "https://files.pythonhosted.org/packages/04/ac/f076982cbe2195ee9cf32de5a1e46951d9fb399fc207f390562dd0fd8fb2/propcache-0.5.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:80168e2ebe4d3ec6599d10ad8f520304ae1cad9b6c5a95372aef1b66b7bfb53a", size = 60029, upload-time = "2026-05-08T21:00:22.713Z" }, + { url = "https://files.pythonhosted.org/packages/70/60/189be62e0dd898dce3b331e1b8c7a543cd3a405ac0c81fe8ee8a9d5d77e1/propcache-0.5.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:45f11346f884bc47444f6e6647131055844134c3175b629f84952e2b5cd62b64", size = 56774, upload-time = "2026-05-08T21:00:24.001Z" }, + { url = "https://files.pythonhosted.org/packages/ea/9e/93377b9c7939c1ffae98f878dee955efadfd638078bc86dbc21f9d52f651/propcache-0.5.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8e778ebd44ef4f66ed60a0416b06b489687db264a9c0b3620362f26489492913", size = 63532, upload-time = "2026-05-08T21:00:25.545Z" }, + { url = "https://files.pythonhosted.org/packages/14/f9/590ef6cfb9b8028d516d287812ece32bb0bc5f11fbb9c8bf6b2e6313fec8/propcache-0.5.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:c0cb9ed24c8964e172768d455a38254c2dd8a552905729ce006cad3d3dda59b1", size = 61592, upload-time = "2026-05-08T21:00:27.186Z" }, + { url = "https://files.pythonhosted.org/packages/b4/5e/70958b3034c297a630bba2f17ca7abc2d5f39a803ad7e370ab79d1ecd022/propcache-0.5.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:1d1ad32d9d4355e2be65574fd0bfd3677e7066b009cd5b9b2dee8aa6a6393b33", size = 64788, upload-time = "2026-05-08T21:00:28.8Z" }, + { url = "https://files.pythonhosted.org/packages/12/fd/77fe5936d8c3086ca9048f7f415f122ed82e53884a9ec193646b42deef06/propcache-0.5.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c80f4ba3e8f00189165999a742ee526ebeccedf6c3f7beb0c7df821e9772435a", size = 62514, upload-time = "2026-05-08T21:00:30.098Z" }, + { url = "https://files.pythonhosted.org/packages/cf/74/66bd798b5b3be70aa1b391f5cc9d6a0a5532d7fd3b19ec0b213e72e6ad9d/propcache-0.5.2-cp312-cp312-win32.whl", hash = "sha256:8c7972d8f193740d9175f0998ab38717e6cd322d5935c5b0fef8c0d323fd9031", size = 39018, upload-time = "2026-05-08T21:00:31.622Z" }, + { url = "https://files.pythonhosted.org/packages/61/7c/5c0d34aa3024694d6dcb9271cdbdd08c4e47c1c0ad95ec7e7bc74cdea145/propcache-0.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:d9ee8826a7d47863a08ac44e1a5f611a462eefc3a194b492da242128bec75b42", size = 42322, upload-time = "2026-05-08T21:00:32.918Z" }, + { url = "https://files.pythonhosted.org/packages/4d/91/875812f1a3feb20ceba818ef39fbe4d92f1081e04ac815c822496d0d038b/propcache-0.5.2-cp312-cp312-win_arm64.whl", hash = "sha256:2800a4a8ead6b28cccd1ec54b59346f0def7922ee1c7598e8499c733cfbb7c84", size = 38172, upload-time = "2026-05-08T21:00:35.124Z" }, + { url = "https://files.pythonhosted.org/packages/c5/09/f049e45385503fe67db75a6b6186a7b9f0c3930366dc960522c312a825b1/propcache-0.5.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:099aaf4b4d1a02265b92a977edf00b5c4f63b3b17ac6de39b0d637c9cac0188a", size = 94457, upload-time = "2026-05-08T21:00:36.355Z" }, + { url = "https://files.pythonhosted.org/packages/6b/65/83d1d05655baf63113731bd5a1008435e14f8d1e5a06cbe4ec5b23ad7a31/propcache-0.5.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:68ce1c44c7a813a7f71ea04315a8c7b330b63db99d059a797a4651bb6f69f117", size = 53835, upload-time = "2026-05-08T21:00:38.072Z" }, + { url = "https://files.pythonhosted.org/packages/a9/12/a6ba6482bb5ea3260c000c9b20881c95fa11c6b30173715668259f844ed7/propcache-0.5.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fc299c129490f55f254cd90be0deca4764e36e9a7c08b4aa588479a3bbed3098", size = 54545, upload-time = "2026-05-08T21:00:39.319Z" }, + { url = "https://files.pythonhosted.org/packages/a9/19/7fa086f5764c59ec8a8e157cd93aa8497acc00aba9dcdec56bfffb32602d/propcache-0.5.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a6ae2198be502c10f09b2516e7b5d019816924bc3183a43ce792a7bd6625e6f4", size = 59886, upload-time = "2026-05-08T21:00:40.621Z" }, + { url = "https://files.pythonhosted.org/packages/a1/e4/5d7663dc8235956c8f5281698a3af1d351d8820341ddd890f59d9a9127f2/propcache-0.5.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6041d31504dc1779d700e1edcfb08eea334b357620b06681a4eabb57a74e574e", size = 63261, upload-time = "2026-05-08T21:00:41.775Z" }, + { url = "https://files.pythonhosted.org/packages/4a/4a/15a03adee24d6350da4292caeac44c34c033d2afe5e87eb370f38854560f/propcache-0.5.2-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f7eabc04151c78a9f4d5bbb5f1faf571e4defeb4b585e0fe95b60ff2dbe4d3d7", size = 64184, upload-time = "2026-05-08T21:00:43.018Z" }, + { url = "https://files.pythonhosted.org/packages/8b/c6/979176efdaa3d239e36d503d5af63a0a773b36662ed8f52e5b6a6d9fd40e/propcache-0.5.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4db0ba63d693afd40d249bd93f842b5f144f8fcbb83de05660373bcf30517b1d", size = 61534, upload-time = "2026-05-08T21:00:44.507Z" }, + { url = "https://files.pythonhosted.org/packages/c8/22/63e8cd1bae4c2d2be6493b6b7d10566ddafad88137cfbc99964a1119853c/propcache-0.5.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1dbcf7675229b35d31abb6547d8ebc8c27a830ac3f9a794edff6254873ec7c0a", size = 61500, upload-time = "2026-05-08T21:00:45.796Z" }, + { url = "https://files.pythonhosted.org/packages/60/5a/28e5d9acbac1cc9ccb67045e8c1b943aa8d79fdf39c93bd73cacd68008ea/propcache-0.5.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d310c013aad2c72f1c3f2f8dd3279d460a858c551f97aeb8c63e4693cca7b4d2", size = 59994, upload-time = "2026-05-08T21:00:47.093Z" }, + { url = "https://files.pythonhosted.org/packages/f3/40/db650677f554a95b9c01a7c9d93d629e93a15562f5deb4573c9ee136fed2/propcache-0.5.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:06187263ddad280d05b4d8a8b3bb7d164cbebd469236544a42e6d9b28ac6a4fa", size = 56884, upload-time = "2026-05-08T21:00:48.376Z" }, + { url = "https://files.pythonhosted.org/packages/80/45/70b39b89516ff8b96bf732fa6fded8cef20f293cb1508690101c3c07ec51/propcache-0.5.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3115559b8effafd63b142ea5ed53d63a16ea6469cbc63dce4ee194b42db5d853", size = 63464, upload-time = "2026-05-08T21:00:49.954Z" }, + { url = "https://files.pythonhosted.org/packages/f9/e2/fa59d3a89eac5534293124af4f1d0d0ada091ce4a0ab4610ce03fd2bdd8d/propcache-0.5.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c60462af8e6dc30c35407c7237ea908d777b22862bbee27bc4699c0d8bcdc45a", size = 61588, upload-time = "2026-05-08T21:00:51.281Z" }, + { url = "https://files.pythonhosted.org/packages/0b/97/efb547a55c4bc7381cfb202d6a2239ac621045277bc1ea5dfd3a7f0516c0/propcache-0.5.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:40314bca9ac559716fe374094fc81c11dcc34b64fd6c585360f5775690505704", size = 64667, upload-time = "2026-05-08T21:00:52.602Z" }, + { url = "https://files.pythonhosted.org/packages/92/56/f5c7d9b4b7595d5127da38974d791b2153f3d1eae6c674af3583ace92ad3/propcache-0.5.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:cfa21e036ce1e1db2be04ba3b85d2df1bb1702fa01932d984c5464c665228ff4", size = 62463, upload-time = "2026-05-08T21:00:54.303Z" }, + { url = "https://files.pythonhosted.org/packages/bd/3b/484a3a65fc9f9f60c41dcd17b428bace5389544e2c680994534a20755066/propcache-0.5.2-cp313-cp313-win32.whl", hash = "sha256:f156a3529f38063b6dbaf356e15602a7f95f8055b1295a438433a6386f10463d", size = 38621, upload-time = "2026-05-08T21:00:55.808Z" }, + { url = "https://files.pythonhosted.org/packages/1c/fd/3f0f10dba4dabad3bf53102be007abf55481067952bde0fdddff439e7c61/propcache-0.5.2-cp313-cp313-win_amd64.whl", hash = "sha256:dfed59d0a5aeb01e242e66ff0300bc4a265a7c05f612d30016f0b60b1017d757", size = 41649, upload-time = "2026-05-08T21:00:57.061Z" }, + { url = "https://files.pythonhosted.org/packages/90/ec/6ce619cc32bb500a482f811f9cd509368b4e58e638d13f2c68f370d6b475/propcache-0.5.2-cp313-cp313-win_arm64.whl", hash = "sha256:ba338430e87ceb9c8f0cf754de38a9860560261e56c00376debd628698a7364f", size = 37636, upload-time = "2026-05-08T21:00:58.646Z" }, + { url = "https://files.pythonhosted.org/packages/1b/82/c1d268bbbf2ef981c5bf0fbbe746db617c66e3bcefe431a1aa8943fbe23a/propcache-0.5.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a592f5f3da71c8691c788c13cb6734b6d17663d2e1cb8caddf0673d01ef8847d", size = 98872, upload-time = "2026-05-08T21:00:59.889Z" }, + { url = "https://files.pythonhosted.org/packages/f4/d4/52c871e73e864e6b34c0e2d58ac1ec5ccd149497ddc7ad2137ae98323a35/propcache-0.5.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6a997d0489e9668a384fcfd5061b857aa5361de73191cac204d04b889cfbbafa", size = 56257, upload-time = "2026-05-08T21:01:01.195Z" }, + { url = "https://files.pythonhosted.org/packages/67/f0/9b90ca2a210b3d09bcfcd96ecd0f55545c091535abce2a45de2775cfd357/propcache-0.5.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:10734b5484ea113152ee25a91dccedf81631791805d2c9ccb054958e51842c94", size = 56696, upload-time = "2026-05-08T21:01:02.941Z" }, + { url = "https://files.pythonhosted.org/packages/9d/0e/6e9d4ba07c8e56e21ddec1e75f12148142b21ca83a51871babce095334f4/propcache-0.5.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cafca7e56c12bb02ae16d283742bef25a61122e9dab2b5b3f2ccbe589ce32164", size = 62378, upload-time = "2026-05-08T21:01:04.475Z" }, + { url = "https://files.pythonhosted.org/packages/65/19/c10badaa463dde8a27ce884f8ee2ec37e6035b7c9f5ff0c8f74f06f08dac/propcache-0.5.2-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f064f8d2b59177878b7615df1735cd8fe3462ed6be8c7b217d17a276489c2b7f", size = 65283, upload-time = "2026-05-08T21:01:05.959Z" }, + { url = "https://files.pythonhosted.org/packages/b0/b6/93bea99ca80e19cef6512a8580e5b7857bbe09422d9daa7fd4ef5723306c/propcache-0.5.2-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f78abfa8dfc32376fd1aacf597b2f2fbbe0ea751419aee718af5d4f82537ef8c", size = 66616, upload-time = "2026-05-08T21:01:07.228Z" }, + { url = "https://files.pythonhosted.org/packages/83/e4/5c7462e50625f051f37fb38b8224f7639f667184bbd34424ec83819bb1b7/propcache-0.5.2-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f7467da8a9822bf1a55336f877340c5bcbd3c482afc43a99771169f74a26dedc", size = 63773, upload-time = "2026-05-08T21:01:08.514Z" }, + { url = "https://files.pythonhosted.org/packages/ca/b6/99238894047b13c823be25027e736626cd414a52a5e30d2c3347c2733529/propcache-0.5.2-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a6ddc6ac9e25de626c1f129c1b467d7ecd33ce2237d3fd0c4e429feef0a7ee1f", size = 63664, upload-time = "2026-05-08T21:01:09.874Z" }, + { url = "https://files.pythonhosted.org/packages/85/1e/a3a1a63116a2b8edb415a8bb9a6f0c34bd03830b1e18e8ce2904e1dc1cf4/propcache-0.5.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2f22cbbac9e26a8e864c0985ff1268d5d939d53d9d9411a9824279097e03a2cb", size = 62643, upload-time = "2026-05-08T21:01:11.132Z" }, + { url = "https://files.pythonhosted.org/packages/e4/03/893cf147de2fc6543c5eaa07ad833170e7e2a2385725bbebe8c0503723bb/propcache-0.5.2-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:fc76378c62a0f04d0cd82fbb1a2cd2d7e28fcb40d5873f28a6c44e388aaa2751", size = 59595, upload-time = "2026-05-08T21:01:12.387Z" }, + { url = "https://files.pythonhosted.org/packages/86/3b/04c1a2e12c57766568ba75ba72b3bf2042818d4c1425fab6fc07155c7cff/propcache-0.5.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:acd2c8edba48e31e58a363b8cf4e5c7db3b04b3f9e371f601df30d9b0d244836", size = 65711, upload-time = "2026-05-08T21:01:13.676Z" }, + { url = "https://files.pythonhosted.org/packages/1c/34/80f8d0099f8d6bacc4de1624c85672681c8cd1149ca2da0e38fd120b817f/propcache-0.5.2-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:452b5065457eb9991ec5eb38ff41d6cd4c991c9ac7c531c4d5849ae473a9a13f", size = 64247, upload-time = "2026-05-08T21:01:14.936Z" }, + { url = "https://files.pythonhosted.org/packages/f3/1a/8b08f3a5f1037e9e370c55883ceeeee0f6dd0416fb2d2d67b8bfc91f2a79/propcache-0.5.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:3430bb2bfe1331885c427745a751e774ee679fd4344f80b97bf879815fe8fa55", size = 67102, upload-time = "2026-05-08T21:01:16.281Z" }, + { url = "https://files.pythonhosted.org/packages/34/68/8bdb7bb7756d76e005490649d10e4a8369e610c74d619f71e1aedf889e9c/propcache-0.5.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:cef6cea3922890dd6c9654971001fa797b526c16ab5e1e46c05fd6f877be7568", size = 64964, upload-time = "2026-05-08T21:01:17.57Z" }, + { url = "https://files.pythonhosted.org/packages/0a/aa/50fb0b5d3968b61a510926ff8b8465f1d6e976b3ab74496d7a4b9fc42515/propcache-0.5.2-cp313-cp313t-win32.whl", hash = "sha256:72d61e16dd78228b58c5d47be830ff3da7e5f139abdf0aef9d86cde1c5cf2191", size = 42546, upload-time = "2026-05-08T21:01:18.946Z" }, + { url = "https://files.pythonhosted.org/packages/ae/4c/0ddbae64321bd4a95bcbfc19307238016b5b1fee645c84626c8d539e5b74/propcache-0.5.2-cp313-cp313t-win_amd64.whl", hash = "sha256:0958834041a0166d343b8d2cedcd8bcbaeb4fdbe0cf08320c5379f143c3be6e7", size = 46330, upload-time = "2026-05-08T21:01:20.162Z" }, + { url = "https://files.pythonhosted.org/packages/00/d9/9cddc8efb78d8af264c5ec9f6d10b62f57c515feda8d321595f56010fb23/propcache-0.5.2-cp313-cp313t-win_arm64.whl", hash = "sha256:6de8bd93ddde9b992cf2b2e0d796d501a19026b5b9fd87356d7d0779531a8d96", size = 40521, upload-time = "2026-05-08T21:01:21.399Z" }, + { url = "https://files.pythonhosted.org/packages/e2/ea/23ee535d90ce8bcc465a3028eb3cc0ce3bd1005f4bb27710b30587de798d/propcache-0.5.2-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:46088abff4cba581dea21ae0467a480526cb25aa5f3c269e909f800328bc3999", size = 94662, upload-time = "2026-05-08T21:01:22.683Z" }, + { url = "https://files.pythonhosted.org/packages/b5/06/c5a52f419b5d8972f8d46a7577476090d8e3263ff589ce40b5ca4968d5be/propcache-0.5.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fc88b26f08d634f7bc819a7852e5214f5802641ab8d9fd5326892292eee1993e", size = 53928, upload-time = "2026-05-08T21:01:23.986Z" }, + { url = "https://files.pythonhosted.org/packages/63/b1/4260d67d6bd85e58a66b72d54ce15d5de789b6f3870cc6bedf8ff9667401/propcache-0.5.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:97797ebb098e670a2f92dd66f32897e30d7615b14e7f59711de23e30a9072539", size = 54650, upload-time = "2026-05-08T21:01:25.305Z" }, + { url = "https://files.pythonhosted.org/packages/70/06/2f46c318e3307cd7a6a7481def374ce838c0fe20084b39dd54b0879d0e99/propcache-0.5.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba57fffe4ac99c5d30076161b5866336d97600769bad35cc68f7774b15298a4e", size = 59912, upload-time = "2026-05-08T21:01:26.545Z" }, + { url = "https://files.pythonhosted.org/packages/4c/29/fe1aebec2ce57ab985a9c382bded1124431f85078113aa222c5d278430d4/propcache-0.5.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:583c19759d9eec1e5b69e2fbef36a7d9c326041be9746cb822d335c8cedc2979", size = 63300, upload-time = "2026-05-08T21:01:27.937Z" }, + { url = "https://files.pythonhosted.org/packages/b4/18/2334b26768b6c82be8c69e83671b767d5ef426aa09b0cba6c2ea47816774/propcache-0.5.2-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d0326e2e5e1f3163fa306c834e48e8d490e5fae607a097a40c0648109b47ba80", size = 64208, upload-time = "2026-05-08T21:01:29.484Z" }, + { url = "https://files.pythonhosted.org/packages/2b/76/7f1bfd6afff4c5e38e36a3c6d68eb5f4b7311ea80baf693db78d95b603c4/propcache-0.5.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e00820e192c8dbebcafb383ebbf99030895f09905e7a0eb2e0340a0bcc2bc825", size = 61633, upload-time = "2026-05-08T21:01:31.068Z" }, + { url = "https://files.pythonhosted.org/packages/c4/46/b3ff8aba2b4953a3e50de2cf72f1b5748b8eca93b15f3dc2c84339084c09/propcache-0.5.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c66afea89b1e43725731d2004732a046fe6fe955d51f952c3e95a7314a284a39", size = 61724, upload-time = "2026-05-08T21:01:32.374Z" }, + { url = "https://files.pythonhosted.org/packages/c5/01/814cfcafbcff954f94c01cf30e097ddc88a076b5440fbcf4570753437d40/propcache-0.5.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d4dc37dec6c6cdad0b57881a5658fd14fbf53e333b1a86cf86559f190e1d9ec4", size = 60069, upload-time = "2026-05-08T21:01:33.67Z" }, + { url = "https://files.pythonhosted.org/packages/da/68/5c6f7622d510cc666a300687e06fd060c1a43361c0c9b20d284f06d8096a/propcache-0.5.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:5570dbcc97571c15f68068e529c92715a12f8d54030e272d264b377e22bd17a5", size = 57099, upload-time = "2026-05-08T21:01:34.915Z" }, + { url = "https://files.pythonhosted.org/packages/55/27/9cb0b4c679124085327957d42521c99dba04c88c90c3e55a6f0b633ebccc/propcache-0.5.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f814362777a9f841adddb200ecdf8f5cb1e5a3c4b7a86378edbd6ccb26edd702", size = 63391, upload-time = "2026-05-08T21:01:36.231Z" }, + { url = "https://files.pythonhosted.org/packages/f0/9d/7258aaa5bdf60fc6f27591eef6fe52768cb0beda7140be477c8b12c9794a/propcache-0.5.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:196913dea116aeb5a2ba95af4ddcb7ea85559ae07d8eee8751688310d09168c3", size = 61626, upload-time = "2026-05-08T21:01:37.545Z" }, + { url = "https://files.pythonhosted.org/packages/8e/0d/41c602003e8a9b16fe1e7eadf62c7bfba9d5474370b24200bf48b315f45f/propcache-0.5.2-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:6e7b8719005dd1175be4ab1cd25e9b98659a5e0347331506ec6760d2773a7fb5", size = 64781, upload-time = "2026-05-08T21:01:38.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/f3/38e66b1856e9bd079deea015bc4a55f7767c0e4db2f7dcf69e7e680ba4ce/propcache-0.5.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:51f96d685ab16e88cab128cd37a52c5da540809c8b879fa047731bfcb4ad35a4", size = 62570, upload-time = "2026-05-08T21:01:40.415Z" }, + { url = "https://files.pythonhosted.org/packages/95/ca/bbfe9b910ce57dde8bb4876b4520fc02a4e89497c10de26be936758a3aaa/propcache-0.5.2-cp314-cp314-win32.whl", hash = "sha256:cc6fc3cc62e8501d3ed62894425040d2728ecddb1ed072737a5c70bd537aa9f0", size = 39436, upload-time = "2026-05-08T21:01:41.654Z" }, + { url = "https://files.pythonhosted.org/packages/61/d2/45c9defbaa1ea297035d9d4cce9e8f80daafbf19319c6007f157c6256ea9/propcache-0.5.2-cp314-cp314-win_amd64.whl", hash = "sha256:81e3a30b0bb60caa22033dd0f8a3618d1d67356212514f62c57db75cb0ef410c", size = 42373, upload-time = "2026-05-08T21:01:43.041Z" }, + { url = "https://files.pythonhosted.org/packages/44/68/9ea5103f41d5217d7d6ec24db90018e23aebec070c3f9a6e54d12b841fd8/propcache-0.5.2-cp314-cp314-win_arm64.whl", hash = "sha256:0d2c9bf8528f135dbb805ce027567e09164f7efa51a2be07458a2c0420f292d0", size = 38554, upload-time = "2026-05-08T21:01:44.336Z" }, + { url = "https://files.pythonhosted.org/packages/8a/81/fadf555f42d3b762eea8a53950b0489fdc0aa9da5f8ed9e10ce0a4e01b48/propcache-0.5.2-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:4bc8ff1feffc6a61c7002ffe84634c41b822e104990ae009f44a0834430070bb", size = 99395, upload-time = "2026-05-08T21:01:45.883Z" }, + { url = "https://files.pythonhosted.org/packages/f5/c9/c61e134a686949cf7971af3a390148b1156f7be81c73bc0cd12c873e2d48/propcache-0.5.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:79aa3ff0a9b566633b642fa9caf7e21ed1c13d6feca718187873f199e1514078", size = 56653, upload-time = "2026-05-08T21:01:47.307Z" }, + { url = "https://files.pythonhosted.org/packages/cb/73/daf935ea7048ddd7ec8eec5345b4a40b619d2d178b3c0a0900796bc3c794/propcache-0.5.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1b31822f4474c4036bae62de9402710051d431a606d6a0f907fec79935a071aa", size = 56914, upload-time = "2026-05-08T21:01:48.573Z" }, + { url = "https://files.pythonhosted.org/packages/79/9f/aba959b435ea18617edd7cf0a7ad0b9c574b8fc7e3d2cd55fb59cb255d33/propcache-0.5.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:13fef48778b5a2a756523fdb781326b028ca75e32858b04f2cdd19f394564917", size = 62567, upload-time = "2026-05-08T21:01:49.903Z" }, + { url = "https://files.pythonhosted.org/packages/6c/a1/859942de9a791ff42f6141736f5b37749b8f53e65edfa49638c67dd67e6a/propcache-0.5.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8b73ab70f1a3351fbc71f663b3e645af6dd0329100c353081cf69c37433fc6fe", size = 65542, upload-time = "2026-05-08T21:01:51.204Z" }, + { url = "https://files.pythonhosted.org/packages/b5/61/315bc0fd6c0fc7f80a528b8afd209e5fc4a875ea79571b91b8f50f442907/propcache-0.5.2-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5538d2c13d93e4698af7e092b57bc7298fd35d1d58e656ae18f23ee0d0378e03", size = 66845, upload-time = "2026-05-08T21:01:52.539Z" }, + { url = "https://files.pythonhosted.org/packages/47/f7/9f8122e3132e8e354ac41975ef8f1099be7d5a16bc7ae562734e993665c0/propcache-0.5.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cd645f03898405cabe694fb8bc35241e3a9c332ec85627584fe3de201452b335", size = 63985, upload-time = "2026-05-08T21:01:53.847Z" }, + { url = "https://files.pythonhosted.org/packages/c8/54/c317819ec157cbf6f35df9df9657a6f82daf34d5faf15948b2f639c2192e/propcache-0.5.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a473b3440261e0c60706e732b2ed2f517857344fc21bf48fdfe211e2d98eb285", size = 63999, upload-time = "2026-05-08T21:01:55.179Z" }, + { url = "https://files.pythonhosted.org/packages/5a/56/387e3f7dfce0a9233df41fb888aa1c30222cb4bbbf09537c02dd9bd85fe2/propcache-0.5.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7afa37062e6650640e932e4cc9297d81f9f42d9944029cc386b8247dea4da837", size = 62779, upload-time = "2026-05-08T21:01:57.489Z" }, + { url = "https://files.pythonhosted.org/packages/a1/9c/596784cb5824ed61ee960d3f8655a3f0993e107c6e98ab6c818b7fb92ccb/propcache-0.5.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:8a90efd5777e996e42d568db9ac740b944d691e565cbfd31b2f7832f9184b2b8", size = 59796, upload-time = "2026-05-08T21:01:58.736Z" }, + { url = "https://files.pythonhosted.org/packages/c2/3d/1a6cfa1726a48542c1e8784a0761421476a5b68e09b7f36bf95eb954aaba/propcache-0.5.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:f19bb891234d72535764d703bfed1153cc34f4214d5bd7150aee1eec9e8f4366", size = 66023, upload-time = "2026-05-08T21:02:00.228Z" }, + { url = "https://files.pythonhosted.org/packages/e4/0e/05fd6990369477076e4e280bcb970de760fddf0161a46e988bc95f7940ec/propcache-0.5.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:32775082acd2d807ee3db715c7770d38767b817870acfa08c29e057f3c4d5b56", size = 64448, upload-time = "2026-05-08T21:02:01.888Z" }, + { url = "https://files.pythonhosted.org/packages/cd/86/5f8da315a4309c62c10c0b2516b17492d5d3bbe1bb862b96604db67e2a37/propcache-0.5.2-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:9282fb1a3bccd038da9f768b927b24a0c753e466c086b7c4f3c6982851eefb2d", size = 67329, upload-time = "2026-05-08T21:02:03.484Z" }, + { url = "https://files.pythonhosted.org/packages/da/d3/3368efe79ab21f0cdf86ef49895811c9cc933131d4cde1f28a624e22e712/propcache-0.5.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cc49723e2f60d6b32a0f0b08a3fd6d13203c07f1cd9566cfce0f12a917c967a2", size = 65172, upload-time = "2026-05-08T21:02:04.745Z" }, + { url = "https://files.pythonhosted.org/packages/d5/07/127e8b0bacfb325396196f9d976a22453049b89b9b2b08477cc3145faa44/propcache-0.5.2-cp314-cp314t-win32.whl", hash = "sha256:2d7aa89ebca5acc98cba9d1472d976e394782f587bad6661003602a619fd1821", size = 43813, upload-time = "2026-05-08T21:02:06.025Z" }, + { url = "https://files.pythonhosted.org/packages/88/fb/46dad6c0ae49ed230ab1b16c890c2b6314e2403e6c412976f4a72d64a527/propcache-0.5.2-cp314-cp314t-win_amd64.whl", hash = "sha256:d447bb0b3054be5818458fbb171208b1d9ff11eba14e18ca18b90cbb45767370", size = 47764, upload-time = "2026-05-08T21:02:07.353Z" }, + { url = "https://files.pythonhosted.org/packages/e7/c4/a47d0a63aa309d10d59ede6e9d4cff03a344a79d1f0f4cd0cd74997b53e0/propcache-0.5.2-cp314-cp314t-win_arm64.whl", hash = "sha256:fe67a3d11cd9b4efabfa45c3d00ffba2b26811442a73a581a94b67c2b5faccf6", size = 41140, upload-time = "2026-05-08T21:02:09.065Z" }, + { url = "https://files.pythonhosted.org/packages/3a/ed/1cdcab6ba3d6ab7feca11fc14f0eeea80755bb53ef4e892079f31b10a25f/propcache-0.5.2-py3-none-any.whl", hash = "sha256:be1ddfcbb376e3de5d2e2db1d58d6d67463e6b4f9f040c000de8e300295465fe", size = 14036, upload-time = "2026-05-08T21:02:10.673Z" }, ] [[package]] name = "proto-plus" -version = "1.27.2" +version = "1.28.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/81/0d/94dfe80193e79d55258345901acd2917523d56e8381bc4dee7fd38e3868a/proto_plus-1.27.2.tar.gz", hash = "sha256:b2adde53adadf75737c44d3dcb0104fde65250dfc83ad59168b4aa3e574b6a24", size = 57204, upload-time = "2026-03-26T22:18:57.174Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/56/e647b0c675392d2da368da7b6f158f7368b18542fd6f7d7400a2f39de000/proto_plus-1.28.0.tar.gz", hash = "sha256:38e5696342835b08fc116f30a25665b29531cda9d5d5643e9b81fc312385abd9", size = 57221, upload-time = "2026-05-07T08:04:50.811Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/84/f3/1fba73eeffafc998a25d59703b63f8be4fe8a5cb12eaff7386a0ba0f7125/proto_plus-1.27.2-py3-none-any.whl", hash = "sha256:6432f75893d3b9e70b9c412f1d2f03f65b11fb164b793d14ae2ca01821d22718", size = 50450, upload-time = "2026-03-26T22:13:42.927Z" }, + { url = "https://files.pythonhosted.org/packages/7c/20/b122d4626976acb81132036d2ad1bb35a1a8775fceb837ec30964622516a/proto_plus-1.28.0-py3-none-any.whl", hash = "sha256:a630604310899e73c59ec302e5765c058d412b2f090b9c79c8822589f14955b8", size = 50410, upload-time = "2026-05-07T08:03:31.962Z" }, ] [[package]] @@ -3003,15 +3008,15 @@ wheels = [ [[package]] name = "py-key-value-aio" -version = "0.4.4" +version = "0.4.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "beartype" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/04/3c/0397c072a38d4bc580994b42e0c90c5f44f679303489e4376289534735e5/py_key_value_aio-0.4.4.tar.gz", hash = "sha256:e3012e6243ed7cc09bb05457bd4d03b1ba5c2b1ca8700096b3927db79ffbbe55", size = 92300, upload-time = "2026-02-16T21:21:43.245Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fb/e2/d689d922894a7ecde73b6daeaf9b13dab5aae06fe6aaaf7514722644d382/py_key_value_aio-0.4.5.tar.gz", hash = "sha256:c6563a2c6abe5da5e20f4f9e875c2a9b425a2244a54fadbf46cf140a9eea45d7", size = 107547, upload-time = "2026-05-27T16:37:08.107Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/32/69/f1b537ee70b7def42d63124a539ed3026a11a3ffc3086947a1ca6e861868/py_key_value_aio-0.4.4-py3-none-any.whl", hash = "sha256:18e17564ecae61b987f909fc2cd41ee2012c84b4b1dcb8c055cf8b4bc1bf3f5d", size = 152291, upload-time = "2026-02-16T21:21:44.241Z" }, + { url = "https://files.pythonhosted.org/packages/f6/95/b8ba862968712caa12a19666175334fa979e1f198b896a430adb3bacfe87/py_key_value_aio-0.4.5-py3-none-any.whl", hash = "sha256:ab862adbcb8c72547d1c57821f22cbbb71ab86509039c96f36e914e0336c8dd7", size = 170005, upload-time = "2026-05-27T16:37:06.629Z" }, ] [package.optional-dependencies] @@ -3058,7 +3063,7 @@ wheels = [ [[package]] name = "pydantic" -version = "2.13.3" +version = "2.14.0a1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-types" }, @@ -3066,9 +3071,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d9/e4/40d09941a2cebcb20609b86a559817d5b9291c49dd6f8c87e5feffbe703a/pydantic-2.13.3.tar.gz", hash = "sha256:af09e9d1d09f4e7fe37145c1f577e1d61ceb9a41924bf0094a36506285d0a84d", size = 844068, upload-time = "2026-04-20T14:46:43.632Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cb/71/0ae6d4e0a84faf0cd050033416509c44a19dc6dbec5bfdd3e1318cb1feb4/pydantic-2.14.0a1.tar.gz", hash = "sha256:2c3a5627d48f59725564c41b582328a29fa8d9b1108128ef6710463a0136d4fd", size = 844232, upload-time = "2026-05-22T13:23:43.109Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/0a/fd7d723f8f8153418fb40cf9c940e82004fce7e987026b08a68a36dd3fe7/pydantic-2.13.3-py3-none-any.whl", hash = "sha256:6db14ac8dfc9a1e57f87ea2c0de670c251240f43cb0c30a5130e9720dc612927", size = 471981, upload-time = "2026-04-20T14:46:41.402Z" }, + { url = "https://files.pythonhosted.org/packages/91/4e/15d81754895da3f256273432da60fcb7c885177fefaf495c5b4364fadca5/pydantic-2.14.0a1-py3-none-any.whl", hash = "sha256:61a1ea8d65df95b681c1fab9cd7d01b2472837f798df53dc6d0f41f0c217b061", size = 470439, upload-time = "2026-05-22T13:23:40.57Z" }, ] [package.optional-dependencies] @@ -3078,91 +3083,92 @@ email = [ [[package]] name = "pydantic-core" -version = "2.46.3" +version = "2.47.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2a/ef/f7abb56c49382a246fd2ce9c799691e3c3e7175ec74b14d99e798bcddb1a/pydantic_core-2.46.3.tar.gz", hash = "sha256:41c178f65b8c29807239d47e6050262eb6bf84eb695e41101e62e38df4a5bc2c", size = 471412, upload-time = "2026-04-20T14:40:56.672Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4b/cb/5b47425556ecc1f3fe18ed2a0083188aa46e1dd812b06e406475b3a5d536/pydantic_core-2.46.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:b11b59b3eee90a80a36701ddb4576d9ae31f93f05cb9e277ceaa09e6bf074a67", size = 2101946, upload-time = "2026-04-20T14:40:52.581Z" }, - { url = "https://files.pythonhosted.org/packages/a1/4f/2fb62c2267cae99b815bbf4a7b9283812c88ca3153ef29f7707200f1d4e5/pydantic_core-2.46.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:af8653713055ea18a3abc1537fe2ebc42f5b0bbb768d1eb79fd74eb47c0ac089", size = 1951612, upload-time = "2026-04-20T14:42:42.996Z" }, - { url = "https://files.pythonhosted.org/packages/50/6e/b7348fd30d6556d132cddd5bd79f37f96f2601fe0608afac4f5fb01ec0b3/pydantic_core-2.46.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:75a519dab6d63c514f3a81053e5266c549679e4aa88f6ec57f2b7b854aceb1b0", size = 1977027, upload-time = "2026-04-20T14:42:02.001Z" }, - { url = "https://files.pythonhosted.org/packages/82/11/31d60ee2b45540d3fb0b29302a393dbc01cd771c473f5b5147bcd353e593/pydantic_core-2.46.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a6cd87cb1575b1ad05ba98894c5b5c96411ef678fa2f6ed2576607095b8d9789", size = 2063008, upload-time = "2026-04-20T14:44:17.952Z" }, - { url = "https://files.pythonhosted.org/packages/8a/db/3a9d1957181b59258f44a2300ab0f0be9d1e12d662a4f57bb31250455c52/pydantic_core-2.46.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f80a55484b8d843c8ada81ebf70a682f3f00a3d40e378c06cf17ecb44d280d7d", size = 2233082, upload-time = "2026-04-20T14:40:57.934Z" }, - { url = "https://files.pythonhosted.org/packages/9c/e1/3277c38792aeb5cfb18c2f0c5785a221d9ff4e149abbe1184d53d5f72273/pydantic_core-2.46.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3861f1731b90c50a3266316b9044f5c9b405eecb8e299b0a7120596334e4fe9c", size = 2304615, upload-time = "2026-04-20T14:42:12.584Z" }, - { url = "https://files.pythonhosted.org/packages/5e/d5/e3d9717c9eba10855325650afd2a9cba8e607321697f18953af9d562da2f/pydantic_core-2.46.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb528e295ed31570ac3dcc9bfdd6e0150bc11ce6168ac87a8082055cf1a67395", size = 2094380, upload-time = "2026-04-20T14:43:05.522Z" }, - { url = "https://files.pythonhosted.org/packages/a1/20/abac35dedcbfd66c6f0b03e4e3564511771d6c9b7ede10a362d03e110d9b/pydantic_core-2.46.3-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:367508faa4973b992b271ba1494acaab36eb7e8739d1e47be5035fb1ea225396", size = 2135429, upload-time = "2026-04-20T14:41:55.549Z" }, - { url = "https://files.pythonhosted.org/packages/6c/a5/41bfd1df69afad71b5cf0535055bccc73022715ad362edbc124bc1e021d7/pydantic_core-2.46.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5ad3c826fe523e4becf4fe39baa44286cff85ef137c729a2c5e269afbfd0905d", size = 2174582, upload-time = "2026-04-20T14:41:45.96Z" }, - { url = "https://files.pythonhosted.org/packages/79/65/38d86ea056b29b2b10734eb23329b7a7672ca604df4f2b6e9c02d4ee22fe/pydantic_core-2.46.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ec638c5d194ef8af27db69f16c954a09797c0dc25015ad6123eb2c73a4d271ca", size = 2187533, upload-time = "2026-04-20T14:40:55.367Z" }, - { url = "https://files.pythonhosted.org/packages/b6/55/a1129141678a2026badc539ad1dee0a71d06f54c2f06a4bd68c030ac781b/pydantic_core-2.46.3-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:28ed528c45446062ee66edb1d33df5d88828ae167de76e773a3c7f64bd14e976", size = 2332985, upload-time = "2026-04-20T14:44:13.05Z" }, - { url = "https://files.pythonhosted.org/packages/d7/60/cb26f4077719f709e54819f4e8e1d43f4091f94e285eb6bd21e1190a7b7c/pydantic_core-2.46.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aed19d0c783886d5bd86d80ae5030006b45e28464218747dcf83dabfdd092c7b", size = 2373670, upload-time = "2026-04-20T14:41:53.421Z" }, - { url = "https://files.pythonhosted.org/packages/6b/7e/c3f21882bdf1d8d086876f81b5e296206c69c6082551d776895de7801fa0/pydantic_core-2.46.3-cp312-cp312-win32.whl", hash = "sha256:06d5d8820cbbdb4147578c1fe7ffcd5b83f34508cb9f9ab76e807be7db6ff0a4", size = 1966722, upload-time = "2026-04-20T14:44:30.588Z" }, - { url = "https://files.pythonhosted.org/packages/57/be/6b5e757b859013ebfbd7adba02f23b428f37c86dcbf78b5bb0b4ffd36e99/pydantic_core-2.46.3-cp312-cp312-win_amd64.whl", hash = "sha256:c3212fda0ee959c1dd04c60b601ec31097aaa893573a3a1abd0a47bcac2968c1", size = 2072970, upload-time = "2026-04-20T14:42:54.248Z" }, - { url = "https://files.pythonhosted.org/packages/bf/f8/a989b21cc75e9a32d24192ef700eea606521221a89faa40c919ce884f2b1/pydantic_core-2.46.3-cp312-cp312-win_arm64.whl", hash = "sha256:f1f8338dd7a7f31761f1f1a3c47503a9a3b34eea3c8b01fa6ee96408affb5e72", size = 2035963, upload-time = "2026-04-20T14:44:20.4Z" }, - { url = "https://files.pythonhosted.org/packages/9b/3c/9b5e8eb9821936d065439c3b0fb1490ffa64163bfe7e1595985a47896073/pydantic_core-2.46.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:12bc98de041458b80c86c56b24df1d23832f3e166cbaff011f25d187f5c62c37", size = 2102109, upload-time = "2026-04-20T14:41:24.219Z" }, - { url = "https://files.pythonhosted.org/packages/91/97/1c41d1f5a19f241d8069f1e249853bcce378cdb76eec8ab636d7bc426280/pydantic_core-2.46.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:85348b8f89d2c3508b65b16c3c33a4da22b8215138d8b996912bb1532868885f", size = 1951820, upload-time = "2026-04-20T14:42:14.236Z" }, - { url = "https://files.pythonhosted.org/packages/30/b4/d03a7ae14571bc2b6b3c7b122441154720619afe9a336fa3a95434df5e2f/pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1105677a6df914b1fb71a81b96c8cce7726857e1717d86001f29be06a25ee6f8", size = 1977785, upload-time = "2026-04-20T14:42:31.648Z" }, - { url = "https://files.pythonhosted.org/packages/ae/0c/4086f808834b59e3c8f1aa26df8f4b6d998cdcf354a143d18ef41529d1fe/pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:87082cd65669a33adeba5470769e9704c7cf026cc30afb9cc77fd865578ebaad", size = 2062761, upload-time = "2026-04-20T14:40:37.093Z" }, - { url = "https://files.pythonhosted.org/packages/fa/71/a649be5a5064c2df0db06e0a512c2281134ed2fcc981f52a657936a7527c/pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60e5f66e12c4f5212d08522963380eaaeac5ebd795826cfd19b2dfb0c7a52b9c", size = 2232989, upload-time = "2026-04-20T14:42:59.254Z" }, - { url = "https://files.pythonhosted.org/packages/a2/84/7756e75763e810b3a710f4724441d1ecc5883b94aacb07ca71c5fb5cfb69/pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b6cdf19bf84128d5e7c37e8a73a0c5c10d51103a650ac585d42dd6ae233f2b7f", size = 2303975, upload-time = "2026-04-20T14:41:32.287Z" }, - { url = "https://files.pythonhosted.org/packages/6c/35/68a762e0c1e31f35fa0dac733cbd9f5b118042853698de9509c8e5bf128b/pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:031bb17f4885a43773c8c763089499f242aee2ea85cf17154168775dccdecf35", size = 2095325, upload-time = "2026-04-20T14:42:47.685Z" }, - { url = "https://files.pythonhosted.org/packages/77/bf/1bf8c9a8e91836c926eae5e3e51dce009bf495a60ca56060689d3df3f340/pydantic_core-2.46.3-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:bcf2a8b2982a6673693eae7348ef3d8cf3979c1d63b54fca7c397a635cc68687", size = 2133368, upload-time = "2026-04-20T14:41:22.766Z" }, - { url = "https://files.pythonhosted.org/packages/e5/50/87d818d6bab915984995157ceb2380f5aac4e563dddbed6b56f0ed057aba/pydantic_core-2.46.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28e8cf2f52d72ced402a137145923a762cbb5081e48b34312f7a0c8f55928ec3", size = 2173908, upload-time = "2026-04-20T14:42:52.044Z" }, - { url = "https://files.pythonhosted.org/packages/91/88/a311fb306d0bd6185db41fa14ae888fb81d0baf648a761ae760d30819d33/pydantic_core-2.46.3-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:17eaface65d9fc5abb940003020309c1bf7a211f5f608d7870297c367e6f9022", size = 2186422, upload-time = "2026-04-20T14:43:29.55Z" }, - { url = "https://files.pythonhosted.org/packages/8f/79/28fd0d81508525ab2054fef7c77a638c8b5b0afcbbaeee493cf7c3fef7e1/pydantic_core-2.46.3-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:93fd339f23408a07e98950a89644f92c54d8729719a40b30c0a30bb9ebc55d23", size = 2332709, upload-time = "2026-04-20T14:42:16.134Z" }, - { url = "https://files.pythonhosted.org/packages/b3/21/795bf5fe5c0f379308b8ef19c50dedab2e7711dbc8d0c2acf08f1c7daa05/pydantic_core-2.46.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:23cbdb3aaa74dfe0837975dbf69b469753bbde8eacace524519ffdb6b6e89eb7", size = 2372428, upload-time = "2026-04-20T14:41:10.974Z" }, - { url = "https://files.pythonhosted.org/packages/45/b3/ed14c659cbe7605e3ef063077680a64680aec81eb1a04763a05190d49b7f/pydantic_core-2.46.3-cp313-cp313-win32.whl", hash = "sha256:610eda2e3838f401105e6326ca304f5da1e15393ae25dacae5c5c63f2c275b13", size = 1965601, upload-time = "2026-04-20T14:41:42.128Z" }, - { url = "https://files.pythonhosted.org/packages/ef/bb/adb70d9a762ddd002d723fbf1bd492244d37da41e3af7b74ad212609027e/pydantic_core-2.46.3-cp313-cp313-win_amd64.whl", hash = "sha256:68cc7866ed863db34351294187f9b729964c371ba33e31c26f478471c52e1ed0", size = 2071517, upload-time = "2026-04-20T14:43:36.096Z" }, - { url = "https://files.pythonhosted.org/packages/52/eb/66faefabebfe68bd7788339c9c9127231e680b11906368c67ce112fdb47f/pydantic_core-2.46.3-cp313-cp313-win_arm64.whl", hash = "sha256:f64b5537ac62b231572879cd08ec05600308636a5d63bcbdb15063a466977bec", size = 2035802, upload-time = "2026-04-20T14:43:38.507Z" }, - { url = "https://files.pythonhosted.org/packages/7f/db/a7bcb4940183fda36022cd18ba8dd12f2dff40740ec7b58ce7457befa416/pydantic_core-2.46.3-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:afa3aa644f74e290cdede48a7b0bee37d1c35e71b05105f6b340d484af536d9b", size = 2097614, upload-time = "2026-04-20T14:44:38.374Z" }, - { url = "https://files.pythonhosted.org/packages/24/35/e4066358a22e3e99519db370494c7528f5a2aa1367370e80e27e20283543/pydantic_core-2.46.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ced3310e51aa425f7f77da8bbbb5212616655bedbe82c70944320bc1dbe5e018", size = 1951896, upload-time = "2026-04-20T14:40:53.996Z" }, - { url = "https://files.pythonhosted.org/packages/87/92/37cf4049d1636996e4b888c05a501f40a43ff218983a551d57f9d5e14f0d/pydantic_core-2.46.3-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e29908922ce9da1a30b4da490bd1d3d82c01dcfdf864d2a74aacee674d0bfa34", size = 1979314, upload-time = "2026-04-20T14:41:49.446Z" }, - { url = "https://files.pythonhosted.org/packages/d8/36/9ff4d676dfbdfb2d591cf43f3d90ded01e15b1404fd101180ed2d62a2fd3/pydantic_core-2.46.3-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0c9ff69140423eea8ed2d5477df3ba037f671f5e897d206d921bc9fdc39613e7", size = 2056133, upload-time = "2026-04-20T14:42:23.574Z" }, - { url = "https://files.pythonhosted.org/packages/bc/f0/405b442a4d7ba855b06eec8b2bf9c617d43b8432d099dfdc7bf999293495/pydantic_core-2.46.3-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b675ab0a0d5b1c8fdb81195dc5bcefea3f3c240871cdd7ff9a2de8aa50772eb2", size = 2228726, upload-time = "2026-04-20T14:44:22.816Z" }, - { url = "https://files.pythonhosted.org/packages/e7/f8/65cd92dd5a0bd89ba277a98ecbfaf6fc36bbd3300973c7a4b826d6ab1391/pydantic_core-2.46.3-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0087084960f209a9a4af50ecd1fb063d9ad3658c07bb81a7a53f452dacbfb2ba", size = 2301214, upload-time = "2026-04-20T14:44:48.792Z" }, - { url = "https://files.pythonhosted.org/packages/fd/86/ef96a4c6e79e7a2d0410826a68fbc0eccc0fd44aa733be199d5fcac3bb87/pydantic_core-2.46.3-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed42e6cc8e1b0e2b9b96e2276bad70ae625d10d6d524aed0c93de974ae029f9f", size = 2099927, upload-time = "2026-04-20T14:41:40.196Z" }, - { url = "https://files.pythonhosted.org/packages/6d/53/269caf30e0096e0a8a8f929d1982a27b3879872cca2d917d17c2f9fdf4fe/pydantic_core-2.46.3-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:f1771ce258afb3e4201e67d154edbbae712a76a6081079fe247c2f53c6322c22", size = 2128789, upload-time = "2026-04-20T14:41:15.868Z" }, - { url = "https://files.pythonhosted.org/packages/00/b0/1a6d9b6a587e118482910c244a1c5acf4d192604174132efd12bf0ac486f/pydantic_core-2.46.3-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a7610b6a5242a6c736d8ad47fd5fff87fcfe8f833b281b1c409c3d6835d9227f", size = 2173815, upload-time = "2026-04-20T14:44:25.152Z" }, - { url = "https://files.pythonhosted.org/packages/87/56/e7e00d4041a7e62b5a40815590114db3b535bf3ca0bf4dca9f16cef25246/pydantic_core-2.46.3-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:ff5e7783bcc5476e1db448bf268f11cb257b1c276d3e89f00b5727be86dd0127", size = 2181608, upload-time = "2026-04-20T14:41:28.933Z" }, - { url = "https://files.pythonhosted.org/packages/e8/22/4bd23c3d41f7c185d60808a1de83c76cf5aeabf792f6c636a55c3b1ec7f9/pydantic_core-2.46.3-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:9d2e32edcc143bc01e95300671915d9ca052d4f745aa0a49c48d4803f8a85f2c", size = 2326968, upload-time = "2026-04-20T14:42:03.962Z" }, - { url = "https://files.pythonhosted.org/packages/24/ac/66cd45129e3915e5ade3b292cb3bc7fd537f58f8f8dbdaba6170f7cabb74/pydantic_core-2.46.3-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:6e42d83d1c6b87fa56b521479cff237e626a292f3b31b6345c15a99121b454c1", size = 2369842, upload-time = "2026-04-20T14:41:35.52Z" }, - { url = "https://files.pythonhosted.org/packages/a2/51/dd4248abb84113615473aa20d5545b7c4cd73c8644003b5259686f93996c/pydantic_core-2.46.3-cp314-cp314-win32.whl", hash = "sha256:07bc6d2a28c3adb4f7c6ae46aa4f2d2929af127f587ed44057af50bf1ce0f505", size = 1959661, upload-time = "2026-04-20T14:41:00.042Z" }, - { url = "https://files.pythonhosted.org/packages/20/eb/59980e5f1ae54a3b86372bd9f0fa373ea2d402e8cdcd3459334430f91e91/pydantic_core-2.46.3-cp314-cp314-win_amd64.whl", hash = "sha256:8940562319bc621da30714617e6a7eaa6b98c84e8c685bcdc02d7ed5e7c7c44e", size = 2071686, upload-time = "2026-04-20T14:43:16.471Z" }, - { url = "https://files.pythonhosted.org/packages/8c/db/1cf77e5247047dfee34bc01fa9bca134854f528c8eb053e144298893d370/pydantic_core-2.46.3-cp314-cp314-win_arm64.whl", hash = "sha256:5dcbbcf4d22210ced8f837c96db941bdb078f419543472aca5d9a0bb7cddc7df", size = 2026907, upload-time = "2026-04-20T14:43:31.732Z" }, - { url = "https://files.pythonhosted.org/packages/57/c0/b3df9f6a543276eadba0a48487b082ca1f201745329d97dbfa287034a230/pydantic_core-2.46.3-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:d0fe3dce1e836e418f912c1ad91c73357d03e556a4d286f441bf34fed2dbeecf", size = 2095047, upload-time = "2026-04-20T14:42:37.982Z" }, - { url = "https://files.pythonhosted.org/packages/66/57/886a938073b97556c168fd99e1a7305bb363cd30a6d2c76086bf0587b32a/pydantic_core-2.46.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9ce92e58abc722dac1bf835a6798a60b294e48eb0e625ec9fd994b932ac5feee", size = 1934329, upload-time = "2026-04-20T14:43:49.655Z" }, - { url = "https://files.pythonhosted.org/packages/0b/7c/b42eaa5c34b13b07ecb51da21761297a9b8eb43044c864a035999998f328/pydantic_core-2.46.3-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a03e6467f0f5ab796a486146d1b887b2dc5e5f9b3288898c1b1c3ad974e53e4a", size = 1974847, upload-time = "2026-04-20T14:42:10.737Z" }, - { url = "https://files.pythonhosted.org/packages/e6/9b/92b42db6543e7de4f99ae977101a2967b63122d4b6cf7773812da2d7d5b5/pydantic_core-2.46.3-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2798b6ba041b9d70acfb9071a2ea13c8456dd1e6a5555798e41ba7b0790e329c", size = 2041742, upload-time = "2026-04-20T14:40:44.262Z" }, - { url = "https://files.pythonhosted.org/packages/0f/19/46fbe1efabb5aa2834b43b9454e70f9a83ad9c338c1291e48bdc4fecf167/pydantic_core-2.46.3-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9be3e221bdc6d69abf294dcf7aff6af19c31a5cdcc8f0aa3b14be29df4bd03b1", size = 2236235, upload-time = "2026-04-20T14:41:27.307Z" }, - { url = "https://files.pythonhosted.org/packages/77/da/b3f95bc009ad60ec53120f5d16c6faa8cabdbe8a20d83849a1f2b8728148/pydantic_core-2.46.3-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f13936129ce841f2a5ddf6f126fea3c43cd128807b5a59588c37cf10178c2e64", size = 2282633, upload-time = "2026-04-20T14:44:33.271Z" }, - { url = "https://files.pythonhosted.org/packages/cc/6e/401336117722e28f32fb8220df676769d28ebdf08f2f4469646d404c43a3/pydantic_core-2.46.3-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28b5f2ef03416facccb1c6ef744c69793175fd27e44ef15669201601cf423acb", size = 2109679, upload-time = "2026-04-20T14:44:41.065Z" }, - { url = "https://files.pythonhosted.org/packages/fc/53/b289f9bc8756a32fe718c46f55afaeaf8d489ee18d1a1e7be1db73f42cc4/pydantic_core-2.46.3-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:830d1247d77ad23852314f069e9d7ddafeec5f684baf9d7e7065ed46a049c4e6", size = 2108342, upload-time = "2026-04-20T14:42:50.144Z" }, - { url = "https://files.pythonhosted.org/packages/10/5b/8292fc7c1f9111f1b2b7c1b0dcf1179edcd014fc3ea4517499f50b829d71/pydantic_core-2.46.3-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0793c90c1a3c74966e7975eaef3ed30ebdff3260a0f815a62a22adc17e4c01c", size = 2157208, upload-time = "2026-04-20T14:42:08.133Z" }, - { url = "https://files.pythonhosted.org/packages/2b/9e/f80044e9ec07580f057a89fc131f78dda7a58751ddf52bbe05eaf31db50f/pydantic_core-2.46.3-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:d2d0aead851b66f5245ec0c4fb2612ef457f8bbafefdf65a2bf9d6bac6140f47", size = 2167237, upload-time = "2026-04-20T14:42:25.412Z" }, - { url = "https://files.pythonhosted.org/packages/f8/84/6781a1b037f3b96be9227edbd1101f6d3946746056231bf4ac48cdff1a8d/pydantic_core-2.46.3-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:2f40e4246676beb31c5ce77c38a55ca4e465c6b38d11ea1bd935420568e0b1ab", size = 2312540, upload-time = "2026-04-20T14:40:40.313Z" }, - { url = "https://files.pythonhosted.org/packages/3e/db/19c0839feeb728e7df03255581f198dfdf1c2aeb1e174a8420b63c5252e5/pydantic_core-2.46.3-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:cf489cf8986c543939aeee17a09c04d6ffb43bfef8ca16fcbcc5cfdcbed24dba", size = 2369556, upload-time = "2026-04-20T14:41:09.427Z" }, - { url = "https://files.pythonhosted.org/packages/e0/15/3228774cb7cd45f5f721ddf1b2242747f4eb834d0c491f0c02d606f09fed/pydantic_core-2.46.3-cp314-cp314t-win32.whl", hash = "sha256:ffe0883b56cfc05798bf994164d2b2ff03efe2d22022a2bb080f3b626176dd56", size = 1949756, upload-time = "2026-04-20T14:41:25.717Z" }, - { url = "https://files.pythonhosted.org/packages/b8/2a/c79cf53fd91e5a87e30d481809f52f9a60dd221e39de66455cf04deaad37/pydantic_core-2.46.3-cp314-cp314t-win_amd64.whl", hash = "sha256:706d9d0ce9cf4593d07270d8e9f53b161f90c57d315aeec4fb4fd7a8b10240d8", size = 2051305, upload-time = "2026-04-20T14:43:18.627Z" }, - { url = "https://files.pythonhosted.org/packages/0b/db/d8182a7f1d9343a032265aae186eb063fe26ca4c40f256b21e8da4498e89/pydantic_core-2.46.3-cp314-cp314t-win_arm64.whl", hash = "sha256:77706aeb41df6a76568434701e0917da10692da28cb69d5fb6919ce5fdb07374", size = 2026310, upload-time = "2026-04-20T14:41:01.778Z" }, - { url = "https://files.pythonhosted.org/packages/34/42/f426db557e8ab2791bc7562052299944a118655496fbff99914e564c0a94/pydantic_core-2.46.3-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:b12dd51f1187c2eb489af8e20f880362db98e954b54ab792fa5d92e8bcc6b803", size = 2091877, upload-time = "2026-04-20T14:43:27.091Z" }, - { url = "https://files.pythonhosted.org/packages/5c/4f/86a832a9d14df58e663bfdf4627dc00d3317c2bd583c4fb23390b0f04b8e/pydantic_core-2.46.3-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:f00a0961b125f1a47af7bcc17f00782e12f4cd056f83416006b30111d941dfa3", size = 1932428, upload-time = "2026-04-20T14:40:45.781Z" }, - { url = "https://files.pythonhosted.org/packages/11/1a/fe857968954d93fb78e0d4b6df5c988c74c4aaa67181c60be7cfe327c0ca/pydantic_core-2.46.3-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57697d7c056aca4bbb680200f96563e841a6386ac1129370a0102592f4dddff5", size = 1997550, upload-time = "2026-04-20T14:44:02.425Z" }, - { url = "https://files.pythonhosted.org/packages/17/eb/9d89ad2d9b0ba8cd65393d434471621b98912abb10fbe1df08e480ba57b5/pydantic_core-2.46.3-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd35aa21299def8db7ef4fe5c4ff862941a9a158ca7b63d61e66fe67d30416b4", size = 2137657, upload-time = "2026-04-20T14:42:45.149Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/a7/74/319859f70c733f341df03823c8ca27ce9003faaac3ffa3110f3af1c8641a/pydantic_core-2.47.0.tar.gz", hash = "sha256:422c1797a7864b2a9a996435aba92fe571fb80190f67a31edbc1ac040c7b51fe", size = 476601, upload-time = "2026-05-22T13:19:00.229Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/19/23/2daeff7346433ad9a3567852dd7a716329f9a7952dcf1ee74b166271bdc7/pydantic_core-2.47.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:70a7aeba54854f5d97da65cb1a61f000f53df3704cab41cd81d65ab127ddd031", size = 2108216, upload-time = "2026-05-22T13:19:31.898Z" }, + { url = "https://files.pythonhosted.org/packages/6c/70/2989cb5112b892b7dc13af570ff57d0f383f770fc88bbb644262df1b3017/pydantic_core-2.47.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c0e97329e38228f57fe1f2d91ba0ef39cc75cc1a84fe6ef58942d2fc6cb406bf", size = 1951502, upload-time = "2026-05-22T13:19:54.702Z" }, + { url = "https://files.pythonhosted.org/packages/fe/a6/2417d8b1856cf7579a21e2679f02417b910e4f29120303e2c45137525072/pydantic_core-2.47.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:faf83e50714837af72f13e9369c50377552a4a74049d4477bed51c7e5822d94b", size = 1975590, upload-time = "2026-05-22T13:17:26.586Z" }, + { url = "https://files.pythonhosted.org/packages/fc/71/0cd0813355b385bef8fa9a719982e0b44847afedc043b988c9f7c5d02ca9/pydantic_core-2.47.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f50abe60347bf8afe2b2f58db86cf3ac6e418eec7ffa01d9dc90ba29fc64f243", size = 2055885, upload-time = "2026-05-22T13:20:42.378Z" }, + { url = "https://files.pythonhosted.org/packages/a3/33/5687c6dcf8ecca106c95b2a49f04a633320ab49961da6e57682db37b4482/pydantic_core-2.47.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f13b18e7dec056336f29ee77dea3cc5db0271d6215cac7249cc5c61b0a49d293", size = 2235292, upload-time = "2026-05-22T13:19:17.723Z" }, + { url = "https://files.pythonhosted.org/packages/36/98/08e81f1a0f0aa82cfa1a4d07b66eb116740fd6c82ba4baa8f96d4b377132/pydantic_core-2.47.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3aaabbdbbbb8dff33fa053ffb2c980f39dec745fc03592f50e1e010449129841", size = 2308945, upload-time = "2026-05-22T13:18:38.113Z" }, + { url = "https://files.pythonhosted.org/packages/04/f1/6e35bdbdb79f96e630265297452d709b2ebd5facfbd83d3eb702eee24939/pydantic_core-2.47.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d2907ee6d15cf26787bfbeb4c42e18e52f358086eab91baea961a0d909248d6", size = 2093861, upload-time = "2026-05-22T13:20:26.271Z" }, + { url = "https://files.pythonhosted.org/packages/1f/c4/6a48e5da6a567bcf6d3ab113d2d476e1b93e3be941b4c486918f1c9d6714/pydantic_core-2.47.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:c413761260967f4dbb51135e1b49f30a1c29e15bf371fbb39754ed6475739545", size = 2124948, upload-time = "2026-05-22T13:18:15.728Z" }, + { url = "https://files.pythonhosted.org/packages/94/a1/e356c98dfc6bd5e6e166f2c832865a62941370b17a7199d35bc5999fecc8/pydantic_core-2.47.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2bb527ac6e5a9721023b24615e1a55c01f47c60007f08b7d2afb89ff9c7a0e22", size = 2182065, upload-time = "2026-05-22T13:19:07.986Z" }, + { url = "https://files.pythonhosted.org/packages/63/aa/6e99aac0a891b05cc522cc464aebeec2bb877fa4744be641a013ebc1785a/pydantic_core-2.47.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b3abf7fd5e6abe483a63413f9cd26b7c93c20780e19c8556434c7279f6b2f10c", size = 2185830, upload-time = "2026-05-22T13:18:34.551Z" }, + { url = "https://files.pythonhosted.org/packages/99/68/c6c5c1ad40b4d58f11d6e754e9c3990a2a3cfff20a9cf5c1f0a855a791b2/pydantic_core-2.47.0-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:9cefe43d17a5a273d71697c084d3787defa7f578cd5fab4cef4c66d13c9e44b2", size = 2327330, upload-time = "2026-05-22T13:19:48.203Z" }, + { url = "https://files.pythonhosted.org/packages/e0/f8/064747538297e385a1f197f35551d1bf6093e88c5ce9b4e15c035f0a1cfe/pydantic_core-2.47.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:38a8b5371b7938e4f6c31060dbd51d3b3229aef7c43eaac2eb8b153e43c3f189", size = 2366505, upload-time = "2026-05-22T13:20:11.895Z" }, + { url = "https://files.pythonhosted.org/packages/d8/d5/2d8715ef982c0b1875e42f4248b48439133a2781f0be77f0e4dbc84d147d/pydantic_core-2.47.0-cp312-cp312-win32.whl", hash = "sha256:b87e95e644df2a36bd631dd0d6e097aa73d19a55adf7b1724ebdeece3d9c76b7", size = 1957410, upload-time = "2026-05-22T13:17:38.025Z" }, + { url = "https://files.pythonhosted.org/packages/0e/ea/9d908a80798db41c7c38926259ed74d015869f0afe4be6ed56f71793a084/pydantic_core-2.47.0-cp312-cp312-win_amd64.whl", hash = "sha256:a0078c5695322050ccedbc86eafaf3e2548439782c51d99e575de0e31b9fe4f4", size = 2072354, upload-time = "2026-05-22T13:17:42.779Z" }, + { url = "https://files.pythonhosted.org/packages/fe/78/5549cba47c662ec7f05d79527474786e5ff7f08ef3e65a409a8a2ab6013e/pydantic_core-2.47.0-cp312-cp312-win_arm64.whl", hash = "sha256:7eedc31996e9eba3bdfbbc380805ac6d765c889b7e93b17cd00ecb0200fd6dca", size = 2035452, upload-time = "2026-05-22T13:18:18.75Z" }, + { url = "https://files.pythonhosted.org/packages/7a/91/810cabda42f7fdd13b349702694e1140f6071fd42d1e542d606d5ad0c2d4/pydantic_core-2.47.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:263560ece98bffbbc0a8047ce60b8a278c859db6a2a4e30d9454b02891045eca", size = 2108548, upload-time = "2026-05-22T13:18:09.878Z" }, + { url = "https://files.pythonhosted.org/packages/fa/87/f444a6d63bcbdff3c45291df842941ed56c568f45dd3742f25afb567b5a2/pydantic_core-2.47.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a7fdaff39a66bf66e9037da482575513d2f20bfb02ea9d9222b5cb3b902fc695", size = 1951421, upload-time = "2026-05-22T13:20:32.973Z" }, + { url = "https://files.pythonhosted.org/packages/f5/09/c90dff5407c11e59023161c84df19dd5ed93966a23b2f08cd21d45812ab9/pydantic_core-2.47.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b3e6c8ee5ce8c270bfae09763ae4bbbccfe81090c97d670a621fb86cb1ef6042", size = 1976554, upload-time = "2026-05-22T13:19:04.085Z" }, + { url = "https://files.pythonhosted.org/packages/95/cf/55fbe9f1b396f91005cd1cb7acf2bcf380a1f1b57e261ac78bcdc0ff42f3/pydantic_core-2.47.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e38cdae682cfec4b3816722dccf6376ca59049726d57dca83c2fe7cc13665589", size = 2055005, upload-time = "2026-05-22T13:18:12.809Z" }, + { url = "https://files.pythonhosted.org/packages/1b/c6/4f7cbaa62582e95c080cdd45fb5d4350ff05dac87ea32930c75dcb427bf1/pydantic_core-2.47.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fc3193ff0b7e2e168f84c6185e70475738c191f3154e0af8f897cd0f8f9a489", size = 2235489, upload-time = "2026-05-22T13:19:13.994Z" }, + { url = "https://files.pythonhosted.org/packages/31/75/16913c82ffd194c537222b3d0e8a05c11681e551add67308ec4af6023724/pydantic_core-2.47.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57ff41672a615f38af528ee904602be51c653248354e5db8e9252668abe91e68", size = 2308360, upload-time = "2026-05-22T13:18:39.894Z" }, + { url = "https://files.pythonhosted.org/packages/05/9f/b24bb1b764fc360adace5df806a81fd62ef1662df2973891e487c3fd5a2c/pydantic_core-2.47.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:473b9a2b2a1f0dd55cbb32d2b902f93babe7f141a0bb48fb4d3d4d2b3e93e9a0", size = 2092549, upload-time = "2026-05-22T13:17:45.38Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d3/0268ea5972b91178250c0a573bb54c17f697665cd2aa35623087a3589fc6/pydantic_core-2.47.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:195f9c4ac43a7b2a044a7b86631c3352abdb820bed2823ea29f98f779255f459", size = 2123849, upload-time = "2026-05-22T13:18:03.817Z" }, + { url = "https://files.pythonhosted.org/packages/8f/92/26b2147738a89f78925775bb4b34ae53f981ce880ee77ee2c2ccbc49466a/pydantic_core-2.47.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6c1cd10d39ef1ff8bcd68b6865bee9c434631ac0608d402fe86e678851c2e2a5", size = 2181733, upload-time = "2026-05-22T13:17:35.308Z" }, + { url = "https://files.pythonhosted.org/packages/d6/fa/8a858dde4784c7245b7216ab9a71518cb1a898a83ffb5c0509181789de19/pydantic_core-2.47.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7cbe66352fe2b39511d49150e5b52159429cd21f5633a3e801dd2c43829dcdca", size = 2184065, upload-time = "2026-05-22T13:18:11.369Z" }, + { url = "https://files.pythonhosted.org/packages/69/20/ea165877f965622e021f04d63330f9ceb55ede0aefa862863e06a9a610cb/pydantic_core-2.47.0-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:e35192a1d53e55d510d8bb1023c988c7cdae6d94539074971741b2a7656e49a1", size = 2326881, upload-time = "2026-05-22T13:18:41.477Z" }, + { url = "https://files.pythonhosted.org/packages/7b/d6/ad22a6f0941be5a5478e6c378d0ec3c7641be96d7a86a3ea1f9e9830a269/pydantic_core-2.47.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:81de54576de2e20baec76cc5afae2820f9049e6fdc4f357bac3391da02d0ba97", size = 2365574, upload-time = "2026-05-22T13:20:02.947Z" }, + { url = "https://files.pythonhosted.org/packages/e2/4e/eb5e3429dd29311e75ab89313cde09b709b0637fd88838d2cb4a98760b02/pydantic_core-2.47.0-cp313-cp313-win32.whl", hash = "sha256:05da6647bdfd3888936ac10aa39b239d659f3c93dff281af0fc5943eb55629dd", size = 1955451, upload-time = "2026-05-22T13:17:31.178Z" }, + { url = "https://files.pythonhosted.org/packages/15/12/ec107c12aa8729c766285d4aa6caa5f5addf8b2ef6cfd35c455a3ca5c66e/pydantic_core-2.47.0-cp313-cp313-win_amd64.whl", hash = "sha256:021220e0a03b66112737ee1fc49759340ce8fafb8d9ade1b7fb366b06033fa45", size = 2071285, upload-time = "2026-05-22T13:19:50.585Z" }, + { url = "https://files.pythonhosted.org/packages/f4/02/b003a55acfeb35823925d1cd502b80e3fe6c5d477865c36815fbefdf49ac/pydantic_core-2.47.0-cp313-cp313-win_arm64.whl", hash = "sha256:55156ee2f6f561ea4e25ab55f84bd70b9c9ed2546a834cb2b038fe10225aaa37", size = 2034804, upload-time = "2026-05-22T13:18:32.64Z" }, + { url = "https://files.pythonhosted.org/packages/ef/46/a4675c783822e229bf04e290ae28cf5a057b00a46039498743b066e0fefd/pydantic_core-2.47.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:6e37a6974fbd8fa7cae12285a76970d50b3689ffd6ed7c7fdd176ba81dd22d0e", size = 2104537, upload-time = "2026-05-22T13:18:05.312Z" }, + { url = "https://files.pythonhosted.org/packages/67/bb/f6d6d1dd8362d616b227b55af8bc2d1b7d4ea842facb7c50a82298ba3b9d/pydantic_core-2.47.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2433b8524785cc117e602233bc574879bc8d87f09523edeec51665d5c46cf42d", size = 1951581, upload-time = "2026-05-22T13:18:14.244Z" }, + { url = "https://files.pythonhosted.org/packages/3f/ae/7d01ed8658ccf8beae8a3fcf2cd023c3ba0ab0b19d3f456845a709cece94/pydantic_core-2.47.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8f9f2be064c8bf1189f46f7062fd42765d94f59cfb7db7ef8db19563192110a", size = 1978461, upload-time = "2026-05-22T13:19:15.829Z" }, + { url = "https://files.pythonhosted.org/packages/80/e5/dd38ccbd1e68f1c3fd7f8b413a8b32db11bd16d5999c3dc3aed4e54290df/pydantic_core-2.47.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1f0a9659a2eb161573418e3138f616101ba21bbd2ff04916dca7b6712155e015", size = 2049444, upload-time = "2026-05-22T13:19:06.258Z" }, + { url = "https://files.pythonhosted.org/packages/7d/63/0cb998f3f4ea4255ab6d891e2537d4566cd31630df0d406ce45b00306729/pydantic_core-2.47.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2995074b99242aa28991e0120a3c881babc139e08750a05b7ea7d140644e091d", size = 2231772, upload-time = "2026-05-22T13:20:05.22Z" }, + { url = "https://files.pythonhosted.org/packages/3f/20/2cacf5c4a1bdda1356351df38f343c6cf13af6194e6e0ee0f7967395793f/pydantic_core-2.47.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c5b224dc04c3ff9b08c24419464eb7f6ad7a1049e12284a00bf80df82bd15fdb", size = 2305322, upload-time = "2026-05-22T13:20:14.552Z" }, + { url = "https://files.pythonhosted.org/packages/e3/8f/b7e09086bd48b1ef3f74227ed98907496924a622c7f9315cf661946233c6/pydantic_core-2.47.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:264361b7236d4374fef6342908f87d084a0d58a2f8d0811e99f714309cb0ba7e", size = 2097748, upload-time = "2026-05-22T13:20:21.248Z" }, + { url = "https://files.pythonhosted.org/packages/bd/27/2927049e93f6fbbaa838b2bb656dbd63c6d6fcaab9909d632fd93573ebd9/pydantic_core-2.47.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:53368beaf693f6302a6e33bdefe950857534a04d282811421bd20176d0fb5636", size = 2122971, upload-time = "2026-05-22T13:19:37.977Z" }, + { url = "https://files.pythonhosted.org/packages/f2/3b/80b58a0c7f4339f024ad5c5b6316bef895536abf75c45dc85f0c83ae1a92/pydantic_core-2.47.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5d2177b44ba7d9d86850f865f362feeaac6a2ed8517a9b505b97ff0b7fdbd7dd", size = 2181287, upload-time = "2026-05-22T13:18:20.601Z" }, + { url = "https://files.pythonhosted.org/packages/5d/0a/dfcb9575603aaf113d2cae1fb622570046a210276eba2267764b3f83f4f2/pydantic_core-2.47.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:fb57a6b538ec7a01b937986bc093aec530fb056135b6bc9cfdd0bf8460c25bc2", size = 2177842, upload-time = "2026-05-22T13:17:55.981Z" }, + { url = "https://files.pythonhosted.org/packages/57/c2/31a198c8180b417d18410e7640c505b39c51ac291aca34f71ac37093f4c4/pydantic_core-2.47.0-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:18c9c7c3a18e9bdbf1215d913f6bd00e17595dc92949817935cb87a3cf5f1697", size = 2321802, upload-time = "2026-05-22T13:17:54.495Z" }, + { url = "https://files.pythonhosted.org/packages/11/2d/052d872b61f88d2f71b3d881c08abfbe33cb9d64bd988712bb4fb973001c/pydantic_core-2.47.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:2f72a382886ca85bb1247303b9134cf9978c9d454de62e710a1ebcd9d2131927", size = 2363870, upload-time = "2026-05-22T13:18:45.053Z" }, + { url = "https://files.pythonhosted.org/packages/be/60/c8c985d4081dafaa88fe6ac6a41ebe2b82d289c391b2bcbd3508f585e7a9/pydantic_core-2.47.0-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:cdf4dc2cdd0eacad1bd81c4d25422b4c25b206acae095d2d64e5d5cb7facc6b3", size = 1369335, upload-time = "2026-05-22T13:17:51.719Z" }, + { url = "https://files.pythonhosted.org/packages/42/70/30dfcc18dc5552f7235ae2ffd7b699a1ba0d38ebfcdb97f371c792d61601/pydantic_core-2.47.0-cp314-cp314-win32.whl", hash = "sha256:1e859dd5e06e9807080e14995db131649a77c61131cc464a7fe492a69ce82488", size = 1952107, upload-time = "2026-05-22T13:20:24.07Z" }, + { url = "https://files.pythonhosted.org/packages/fa/49/f5c517ba99b9e1036ead16cedd0fc189dbcbf900fb0b85b746b6a0b9b389/pydantic_core-2.47.0-cp314-cp314-win_amd64.whl", hash = "sha256:234ecade0e358caa1ea516c218b3f61e61e30532cad1a8bb12f2487325838548", size = 2071388, upload-time = "2026-05-22T13:18:46.74Z" }, + { url = "https://files.pythonhosted.org/packages/eb/6c/6b95e00ed7a3c7f78cb8544bc34cb29412c05512af507fc4466912576d50/pydantic_core-2.47.0-cp314-cp314-win_arm64.whl", hash = "sha256:58158d0111e86893bc35aacabe509f951ed303cddf8cdba43533190bde317914", size = 2026046, upload-time = "2026-05-22T13:18:22.112Z" }, + { url = "https://files.pythonhosted.org/packages/22/2c/fa67f96f381ba3c83e8cd75a6eb3c538e123d8d02e19d80383bff01ffda0/pydantic_core-2.47.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:24017fd3befd4d7cc4a8c71f4a1e9a44d29fdc91723c5446b0e795ab808adee7", size = 2102407, upload-time = "2026-05-22T13:17:46.882Z" }, + { url = "https://files.pythonhosted.org/packages/9b/e8/99abe6c33db3dda235a2095eae5fdd03266857abd9be1c50ed97a59587c3/pydantic_core-2.47.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:dfa0820888cc4549fcce7e6bd8affa7d75198d885ccd0bb0760def4bb8461862", size = 1931963, upload-time = "2026-05-22T13:18:23.776Z" }, + { url = "https://files.pythonhosted.org/packages/5c/dd/96d1884f65737f793e64b6b8745ee793e3cb4735e086ead0a40e5349294e/pydantic_core-2.47.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1d507f331756f7066cde7c9f35ed78fa78223a54369dbc8a34d6da7a5074fa1", size = 1973492, upload-time = "2026-05-22T13:17:22.128Z" }, + { url = "https://files.pythonhosted.org/packages/07/8c/0504f091c75b229cd07d61b45464d7c5291c3ecf2e7d847f215fe81cf5e5/pydantic_core-2.47.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5c4a885fb50c05903bc703a00830616e680304fdcdd90fc9535a52e72debe712", size = 2035548, upload-time = "2026-05-22T13:17:24.958Z" }, + { url = "https://files.pythonhosted.org/packages/0f/bf/e9f69c55ef6c945d29e4afb306f62a15354bfd09f1ee25d9aecd4c6cbb82/pydantic_core-2.47.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7b107cbd764bf68f12a57c7aa5846d868bc7463490a1ac2d0f19bffef624c5a9", size = 2238356, upload-time = "2026-05-22T13:19:10.309Z" }, + { url = "https://files.pythonhosted.org/packages/b3/1d/dfbd214487033093f5946c1fd5915ae5dc6b278efe764397b5e2ef8092a8/pydantic_core-2.47.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0b5f46412c8226d0c8f1f423c324c75afe342e4b854836933579fb484f68598c", size = 2284799, upload-time = "2026-05-22T13:17:27.959Z" }, + { url = "https://files.pythonhosted.org/packages/f1/ec/f80488fa26a6b27bd0d1267f35b0fce3bfeec23ca97021085a94ccd3adc3/pydantic_core-2.47.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90cbf7e35d597503cbdb5cd85409cbb75f377290bc7e8e37cc5dfe4f5cc66cf8", size = 2107895, upload-time = "2026-05-22T13:18:27.282Z" }, + { url = "https://files.pythonhosted.org/packages/9b/1d/67ac210745f63a982b145d8f281dc76bd347d98052792f087224a178da54/pydantic_core-2.47.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:04ee7ba7172cb4484af51b2890f19069d35773698abc8c6ebb651f52fbf41134", size = 2102797, upload-time = "2026-05-22T13:19:40.139Z" }, + { url = "https://files.pythonhosted.org/packages/6c/75/f3b9bd4d88fcaad9a5abf2ec6969f37a70e34f40a5f8d2d78077f168e83d/pydantic_core-2.47.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2a7acb3e120ddba94372b8146e62ea3a0bce203180e34641c817c87f995c91e0", size = 2160123, upload-time = "2026-05-22T13:19:22.354Z" }, + { url = "https://files.pythonhosted.org/packages/13/1b/e94ac13bd17c341e86c4b67deb47b63718208fe413034fdb01e7a4bf1dd8/pydantic_core-2.47.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:1a2f7ceb58013d167d8c96f10ec9b3a137018c819ba356f68ff1cb74302fd22e", size = 2164386, upload-time = "2026-05-22T13:18:48.372Z" }, + { url = "https://files.pythonhosted.org/packages/36/e5/862ff195402f5b08db9ad4d9ebe7cbed993f51528aab7edcb62531442556/pydantic_core-2.47.0-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:482b097637fec5037eb13fe9f9d5fe47e568a5b451d686bc2b854076cc0b50ca", size = 2303767, upload-time = "2026-05-22T13:18:50.211Z" }, + { url = "https://files.pythonhosted.org/packages/c2/05/c8ebbb97cf44686fc9cc64a6bb86ac72a8871426a9f61417e395d77c4894/pydantic_core-2.47.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:a8c7f5fad73eb404f4b84c75f3d9d3865b748ded248b7366341db6e516fc502b", size = 2361148, upload-time = "2026-05-22T13:18:52.631Z" }, + { url = "https://files.pythonhosted.org/packages/c5/8b/4695ec6ce81eda7cc822383de993c5b2c289cfc84d3f032a30c55eb238fc/pydantic_core-2.47.0-cp314-cp314t-win32.whl", hash = "sha256:f343c39928097175acf2f7d0cba5c00b0f62265d88a173a4ce264266ac849bd9", size = 1939588, upload-time = "2026-05-22T13:19:42.151Z" }, + { url = "https://files.pythonhosted.org/packages/96/4d/bc2e188ab648b3ab284e80a0340c5d5b9723c22572c6e36ba39eb300e718/pydantic_core-2.47.0-cp314-cp314t-win_amd64.whl", hash = "sha256:52d40e074da44e42b2425aedebd513e405a31807036ef597175117a9b01743a6", size = 2049697, upload-time = "2026-05-22T13:18:08.244Z" }, + { url = "https://files.pythonhosted.org/packages/6f/46/ca1e6813e029993e57340c6a37740450c51919e3809472f79060f4d64cf0/pydantic_core-2.47.0-cp314-cp314t-win_arm64.whl", hash = "sha256:25cac08c9735e61e5c0b9f7a85c438661fc0e9da226afdfa984c3da6c5942a5a", size = 2025906, upload-time = "2026-05-22T13:17:50.322Z" }, + { url = "https://files.pythonhosted.org/packages/4e/21/81cc7e3acbf7d4eed41e9585e81b5840aaf6ddbe65974a20946038d6516b/pydantic_core-2.47.0-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:d648515c65f8871ceca6a0446be32fb1a0aae414db3907ec0df6cc2380dd0c04", size = 2098016, upload-time = "2026-05-22T13:18:56.902Z" }, + { url = "https://files.pythonhosted.org/packages/4f/6e/c98e869e9f754f18088ad9b55887972d08606e7bf81dba088779e927eb65/pydantic_core-2.47.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:3292813ac8ed17671a5b1ac8256b6e98b96c29c1c6d061c35899e2f6e0444cac", size = 1931175, upload-time = "2026-05-22T13:20:07.566Z" }, + { url = "https://files.pythonhosted.org/packages/5d/65/ce0d773f4b51dc332764ba1bd585c81f62d5c92231a66d152aff16be8dfa/pydantic_core-2.47.0-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf3cfcea028f41a9b42a47f4a53d72ca4274d04e3ee525bd58ca89ea8c5e1910", size = 1995356, upload-time = "2026-05-22T13:19:44.143Z" }, + { url = "https://files.pythonhosted.org/packages/19/2c/6e63683a77d342c93bad67bb0762ca8fe1d8418acc8617d70ccdcc5ec5ff/pydantic_core-2.47.0-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:52afdd8640d59890539060e91c8a494cf96b463e63b2ba499cc5c23abcb5e96b", size = 2144893, upload-time = "2026-05-22T13:17:59.008Z" }, ] [[package]] name = "pydantic-settings" -version = "2.14.0" +version = "2.14.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic" }, { name = "python-dotenv" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/42/98/c8345dccdc31de4228c039a98f6467a941e39558da41c1744fbe29fa5666/pydantic_settings-2.14.0.tar.gz", hash = "sha256:24285fd4b0e0c06507dd9fdfd331ee23794305352aaec8fc4eb92d4047aeb67d", size = 235709, upload-time = "2026-04-20T13:37:40.293Z" } +sdist = { url = "https://files.pythonhosted.org/packages/07/60/1d1e59c9c90d54591469ada7d268251f71c24bdb765f1a8a832cee8c6653/pydantic_settings-2.14.1.tar.gz", hash = "sha256:e874d3bec7e787b0c9958277956ed9b4dd5de6a80e162188fdaff7c5e26fd5fa", size = 235551, upload-time = "2026-05-08T13:40:06.542Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/01/dd/bebff3040138f00ae8a102d426b27349b9a49acc310fcae7f92112d867e3/pydantic_settings-2.14.0-py3-none-any.whl", hash = "sha256:fc8d5d692eb7092e43c8647c1c35a3ecd00e040fcf02ed86f4cb5458ca62182e", size = 60940, upload-time = "2026-04-20T13:37:38.586Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8d/f1af3832f5e6eb13ba94ee809e72b8ecb5eef226d27ee0bef7d963d943c7/pydantic_settings-2.14.1-py3-none-any.whl", hash = "sha256:6e3c7edfd8277687cdc598f56e5cff0e9bfff0910a3749deaa8d4401c3a2b9de", size = 60964, upload-time = "2026-05-08T13:40:04.958Z" }, ] [[package]] @@ -3176,11 +3182,11 @@ wheels = [ [[package]] name = "pyjwt" -version = "2.12.1" +version = "2.13.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c2/27/a3b6e5bf6ff856d2509292e95c8f57f0df7017cf5394921fc4e4ef40308a/pyjwt-2.12.1.tar.gz", hash = "sha256:c74a7a2adf861c04d002db713dd85f84beb242228e671280bf709d765b03672b", size = 102564, upload-time = "2026-03-13T19:27:37.25Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3b/81/58d0ac84e1ef3a3843791d6954d94c0b33d526c75eeb1efbce9d0a4c4077/pyjwt-2.13.0.tar.gz", hash = "sha256:41571c89ca91598c79e8ef18a2d07367d4810fbbd6f637794879baf1b7703423", size = 107515, upload-time = "2026-05-21T19:54:36.618Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/7a/8dd906bd22e79e47397a61742927f6747fe93242ef86645ee9092e610244/pyjwt-2.12.1-py3-none-any.whl", hash = "sha256:28ca37c070cad8ba8cd9790cd940535d40274d22f80ab87f3ac6a713e6e8454c", size = 29726, upload-time = "2026-03-13T19:27:35.677Z" }, + { url = "https://files.pythonhosted.org/packages/a3/5e/ecf12fdb62546d64385c158514e9b2b671f7832108ef2ecd2020ce0af2d1/pyjwt-2.13.0-py3-none-any.whl", hash = "sha256:66adcc2aff09b3f1bbd95fc1e1577df8ac8723c978552fd43304c8a290ac5728", size = 31274, upload-time = "2026-05-21T19:54:35.362Z" }, ] [package.optional-dependencies] @@ -3278,15 +3284,15 @@ wheels = [ [[package]] name = "python-discovery" -version = "1.2.2" +version = "1.4.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "filelock" }, { name = "platformdirs" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/de/ef/3bae0e537cfe91e8431efcba4434463d2c5a65f5a89edd47c6cf2f03c55f/python_discovery-1.2.2.tar.gz", hash = "sha256:876e9c57139eb757cb5878cbdd9ae5379e5d96266c99ef731119e04fffe533bb", size = 58872, upload-time = "2026-04-07T17:28:49.249Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/1a/cbbaf13b730abb0a16b964d984e19f2fe520c21a4dc664051359a3f5a9e7/python_discovery-1.4.2.tar.gz", hash = "sha256:8f3746c4b4968d22afbb97d36e1a0e5b66e6c0f297290f2e95f05b9b8bf18690", size = 70277, upload-time = "2026-06-11T16:10:42.383Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d8/db/795879cc3ddfe338599bddea6388cc5100b088db0a4caf6e6c1af1c27e04/python_discovery-1.2.2-py3-none-any.whl", hash = "sha256:e1ae95d9af875e78f15e19aed0c6137ab1bb49c200f21f5061786490c9585c7a", size = 31894, upload-time = "2026-04-07T17:28:48.09Z" }, + { url = "https://files.pythonhosted.org/packages/1a/82/a70006589557f267f15bd384c0642ad49f0d97b690c3a05b166b9dcbad3b/python_discovery-1.4.2-py3-none-any.whl", hash = "sha256:475803f53b7b2ed6e490e27373f9d8340f7d2eebf9acdaf645d7d714c97bb500", size = 33886, upload-time = "2026-06-11T16:10:41.192Z" }, ] [[package]] @@ -3300,11 +3306,11 @@ wheels = [ [[package]] name = "python-multipart" -version = "0.0.27" +version = "0.0.32" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/69/9b/f23807317a113dc36e74e75eb265a02dd1a4d9082abc3c1064acd22997c4/python_multipart-0.0.27.tar.gz", hash = "sha256:9870a6a8c5a20a5bf4f07c017bd1489006ff8836cff097b6933355ee2b49b602", size = 44043, upload-time = "2026-04-27T10:51:26.649Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5b/42/55c32bb9b12693c092ad250a0e82edb5b31ddeda6eb772de5f308b3804ad/python_multipart-0.0.32.tar.gz", hash = "sha256:be54b7f3fa167bb83e4fcd936b887b708f4e57fe75911c02aebf53efaf8d938e", size = 46881, upload-time = "2026-06-04T16:18:58.647Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/99/78/4126abcbdbd3c559d43e0db7f7b9173fc6befe45d39a2856cc0b8ec2a5a6/python_multipart-0.0.27-py3-none-any.whl", hash = "sha256:6fccfad17a27334bd0193681b369f476eda3409f17381a2d65aa7df3f7275645", size = 29254, upload-time = "2026-04-27T10:51:24.997Z" }, + { url = "https://files.pythonhosted.org/packages/e1/04/e8135ebd1ad02c56ec633277529b2602ff99ff634be76cdba5744cf554fd/python_multipart-0.0.32-py3-none-any.whl", hash = "sha256:ff6d3f776f16878c894e52e107296ffc890e913c611b1a4ec6c44e2821fe2e23", size = 30042, upload-time = "2026-06-04T16:18:57.319Z" }, ] [[package]] @@ -3321,7 +3327,7 @@ name = "pythonnet" version = "3.0.5" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "clr-loader", marker = "python_full_version < '3.14'" }, + { name = "clr-loader" }, ] sdist = { url = "https://files.pythonhosted.org/packages/9a/d6/1afd75edd932306ae9bd2c2d961d603dc2b52fcec51b04afea464f1f6646/pythonnet-3.0.5.tar.gz", hash = "sha256:48e43ca463941b3608b32b4e236db92d8d40db4c58a75ace902985f76dac21cf", size = 239212, upload-time = "2024-12-13T08:30:44.393Z" } wheels = [ @@ -3339,18 +3345,21 @@ wheels = [ [[package]] name = "pywin32" -version = "311" +version = "312" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e7/ab/01ea1943d4eba0f850c3c61e78e8dd59757ff815ff3ccd0a84de5f541f42/pywin32-311-cp312-cp312-win32.whl", hash = "sha256:750ec6e621af2b948540032557b10a2d43b0cee2ae9758c54154d711cc852d31", size = 8706543, upload-time = "2025-07-14T20:13:20.765Z" }, - { url = "https://files.pythonhosted.org/packages/d1/a8/a0e8d07d4d051ec7502cd58b291ec98dcc0c3fff027caad0470b72cfcc2f/pywin32-311-cp312-cp312-win_amd64.whl", hash = "sha256:b8c095edad5c211ff31c05223658e71bf7116daa0ecf3ad85f3201ea3190d067", size = 9495040, upload-time = "2025-07-14T20:13:22.543Z" }, - { url = "https://files.pythonhosted.org/packages/ba/3a/2ae996277b4b50f17d61f0603efd8253cb2d79cc7ae159468007b586396d/pywin32-311-cp312-cp312-win_arm64.whl", hash = "sha256:e286f46a9a39c4a18b319c28f59b61de793654af2f395c102b4f819e584b5852", size = 8710102, upload-time = "2025-07-14T20:13:24.682Z" }, - { url = "https://files.pythonhosted.org/packages/a5/be/3fd5de0979fcb3994bfee0d65ed8ca9506a8a1260651b86174f6a86f52b3/pywin32-311-cp313-cp313-win32.whl", hash = "sha256:f95ba5a847cba10dd8c4d8fefa9f2a6cf283b8b88ed6178fa8a6c1ab16054d0d", size = 8705700, upload-time = "2025-07-14T20:13:26.471Z" }, - { url = "https://files.pythonhosted.org/packages/e3/28/e0a1909523c6890208295a29e05c2adb2126364e289826c0a8bc7297bd5c/pywin32-311-cp313-cp313-win_amd64.whl", hash = "sha256:718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d", size = 9494700, upload-time = "2025-07-14T20:13:28.243Z" }, - { url = "https://files.pythonhosted.org/packages/04/bf/90339ac0f55726dce7d794e6d79a18a91265bdf3aa70b6b9ca52f35e022a/pywin32-311-cp313-cp313-win_arm64.whl", hash = "sha256:7b4075d959648406202d92a2310cb990fea19b535c7f4a78d3f5e10b926eeb8a", size = 8709318, upload-time = "2025-07-14T20:13:30.348Z" }, - { url = "https://files.pythonhosted.org/packages/c9/31/097f2e132c4f16d99a22bfb777e0fd88bd8e1c634304e102f313af69ace5/pywin32-311-cp314-cp314-win32.whl", hash = "sha256:b7a2c10b93f8986666d0c803ee19b5990885872a7de910fc460f9b0c2fbf92ee", size = 8840714, upload-time = "2025-07-14T20:13:32.449Z" }, - { url = "https://files.pythonhosted.org/packages/90/4b/07c77d8ba0e01349358082713400435347df8426208171ce297da32c313d/pywin32-311-cp314-cp314-win_amd64.whl", hash = "sha256:3aca44c046bd2ed8c90de9cb8427f581c479e594e99b5c0bb19b29c10fd6cb87", size = 9656800, upload-time = "2025-07-14T20:13:34.312Z" }, - { url = "https://files.pythonhosted.org/packages/c0/d2/21af5c535501a7233e734b8af901574572da66fcc254cb35d0609c9080dd/pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42", size = 8932540, upload-time = "2025-07-14T20:13:36.379Z" }, + { url = "https://files.pythonhosted.org/packages/83/ff/32aa7d2ed0ab12b323aaa64f9b75e6ad4f8fd09f9ccfc28c79414d46838d/pywin32-312-cp312-cp312-win32.whl", hash = "sha256:dab4f65ac9c4e48400a2a0530c46c3c579cd5905ecd11b80692373915269208b", size = 6371877, upload-time = "2026-06-04T07:49:28.836Z" }, + { url = "https://files.pythonhosted.org/packages/03/d9/77040d3b43df3f3be32ea289433d660d2727f5ba327bc73be835127d9d60/pywin32-312-cp312-cp312-win_amd64.whl", hash = "sha256:b457f6d628a47e8a7346ce22acb7e1a46a4a78b52e1d17e1af56871bd19a93bc", size = 6914841, upload-time = "2026-06-04T07:49:31.85Z" }, + { url = "https://files.pythonhosted.org/packages/e3/cc/7b1ec671775756020a0ee7f4feeaf3c568f0ab86bd3900088cf986937a92/pywin32-312-cp312-cp312-win_arm64.whl", hash = "sha256:6017c58e12f6809fbb0555b75df144c2922a9ffd18e4b9b5afa863b6c1a9d950", size = 6727901, upload-time = "2026-06-04T07:49:34.244Z" }, + { url = "https://files.pythonhosted.org/packages/2d/41/12fbfd7f36ed2146d8bc9de96c2741296bf0d490b98508496cff322e274c/pywin32-312-cp313-cp313-win32.whl", hash = "sha256:7a27df850933d16a8eabfbaeb73d52b273e2da667f80d70b01a89d1f6828d02c", size = 6370184, upload-time = "2026-06-04T07:49:36.253Z" }, + { url = "https://files.pythonhosted.org/packages/ba/db/36a78e3403099d31d9746d13fdcde5accc43c1155f375a34d15983a479a7/pywin32-312-cp313-cp313-win_amd64.whl", hash = "sha256:c53e878d15a1c44788082bfe712a905433473aa38f86375b7cf8b45e3acbaaf9", size = 6914298, upload-time = "2026-06-04T07:49:38.876Z" }, + { url = "https://files.pythonhosted.org/packages/84/37/c1697194092b76de9ed47ca124323f02c57ffc8a45c06f88a3d5acaf01eb/pywin32-312-cp313-cp313-win_arm64.whl", hash = "sha256:59aba5d5940842075343a5ddc6b11f1cdf0d1567fe745290359dfbcc7c2eb831", size = 6727640, upload-time = "2026-06-04T07:49:41.083Z" }, + { url = "https://files.pythonhosted.org/packages/fc/2b/1f3cded5822fd49c02f40544cbb5f58c7cfd6b1694869fd476cb6170ee97/pywin32-312-cp314-cp314-win32.whl", hash = "sha256:a77a90fbb6881238d2ca9c6fd797b25817f3768fe78d214a90137ff055a75f5b", size = 6468928, upload-time = "2026-06-04T07:49:43.188Z" }, + { url = "https://files.pythonhosted.org/packages/21/82/3bf86d2e2808902013132e1ce905a7da0da53790f3836c64bf44d55e24f3/pywin32-312-cp314-cp314-win_amd64.whl", hash = "sha256:a4dd3a848290ef724347b19f301045831d8e802fa4464f491b98b1e0a081432e", size = 7024157, upload-time = "2026-06-04T07:49:45.34Z" }, + { url = "https://files.pythonhosted.org/packages/a4/0e/73f6d6800b4f27655abd9e9f6aaeaefcddb2b946e4674efa2bab184a7f7b/pywin32-312-cp314-cp314-win_arm64.whl", hash = "sha256:9fce94568364e0155e6dfb781ac5d95903be8baf28670632beab1b523f300daa", size = 6839598, upload-time = "2026-06-04T07:49:47.613Z" }, + { url = "https://files.pythonhosted.org/packages/eb/61/caa39686032d2ebdd04ff0ab5cbe163126c0066d98e00c9018646e42393b/pywin32-312-cp315-cp315-win32.whl", hash = "sha256:5c1fbe4a937a73ae9297384a3da38518cbc694c68ad8a809b2e19acd350f03ed", size = 6471159, upload-time = "2026-06-04T07:49:50.035Z" }, + { url = "https://files.pythonhosted.org/packages/0f/cd/7e1de64a4a6f69c04214169657ccab0d93a670ea50e35eb8f489d7378249/pywin32-312-cp315-cp315-win_amd64.whl", hash = "sha256:c2f03a0f73f804a13c2735b99392b0cd426bb4f2c4d0178e5ac966a0f21618d5", size = 7025293, upload-time = "2026-06-04T07:49:54.857Z" }, + { url = "https://files.pythonhosted.org/packages/23/ed/4532e9388e65fa16b46776ef47ad631a64eda1631884488af707666350ed/pywin32-312-cp315-cp315-win_arm64.whl", hash = "sha256:a8597d28f267b39074aef51fa593530082b39cbe5a074226096857b1fed2dfb9", size = 6840337, upload-time = "2026-06-04T07:49:57.531Z" }, ] [[package]] @@ -3410,7 +3419,7 @@ wheels = [ [[package]] name = "qdrant-client" -version = "1.17.1" +version = "1.18.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "grpcio" }, @@ -3421,23 +3430,23 @@ dependencies = [ { name = "pydantic" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/30/dd/f8a8261b83946af3cd65943c93c4f83e044f01184e8525404989d22a81a5/qdrant_client-1.17.1.tar.gz", hash = "sha256:22f990bbd63485ed97ba551a4c498181fcb723f71dcab5d6e4e43fe1050a2bc0", size = 344979, upload-time = "2026-03-13T17:13:44.678Z" } +sdist = { url = "https://files.pythonhosted.org/packages/65/45/5b1bdd15a3c7730eefb9c113600829e20d689b82b5a23f9e07d107094004/qdrant_client-1.18.0.tar.gz", hash = "sha256:52e8ece1a7d40519801bf0b70713bfa0f6b7ae28c7275bbe0b0286fbed7f6db4", size = 352580, upload-time = "2026-05-11T14:12:38.702Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/68/69/77d1a971c4b933e8c79403e99bcbb790463da5e48333cc4fd5d412c63c98/qdrant_client-1.17.1-py3-none-any.whl", hash = "sha256:6cda4064adfeaf211c751f3fbc00edbbdb499850918c7aff4855a9a759d56cbd", size = 389947, upload-time = "2026-03-13T17:13:43.156Z" }, + { url = "https://files.pythonhosted.org/packages/d6/10/c437bd2ac41ef30d3019063e6ce537dc111e9214473b337ee88f7fa6359a/qdrant_client-1.18.0-py3-none-any.whl", hash = "sha256:093aa8cf8a420ee3ad2a68b007e1378d7992b2600e0b53c193fc172674f659cd", size = 398126, upload-time = "2026-05-11T14:12:36.998Z" }, ] [[package]] name = "redis" -version = "7.4.0" +version = "7.4.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7b/7f/3759b1d0d72b7c92f0d70ffd9dc962b7b7b5ee74e135f9d7d8ab06b8a318/redis-7.4.0.tar.gz", hash = "sha256:64a6ea7bf567ad43c964d2c30d82853f8df927c5c9017766c55a1d1ed95d18ad", size = 4943913, upload-time = "2026-03-24T09:14:37.53Z" } +sdist = { url = "https://files.pythonhosted.org/packages/51/93/05e7d4a65285066a74f48697f9b9cde5cfce71398033d69ed83c3d98f5c9/redis-7.4.1.tar.gz", hash = "sha256:1a1df5067062cf7cbe677994e391f8ee0840f499d370f1a71266e0dd3aa9308e", size = 4945742, upload-time = "2026-06-05T09:10:06.703Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/74/3a/95deec7db1eb53979973ebd156f3369a72732208d1391cd2e5d127062a32/redis-7.4.0-py3-none-any.whl", hash = "sha256:a9c74a5c893a5ef8455a5adb793a31bb70feb821c86eccb62eebef5a19c429ec", size = 409772, upload-time = "2026-03-24T09:14:35.968Z" }, + { url = "https://files.pythonhosted.org/packages/4a/2e/2677f3f93dae0497e7e33b6637302e7f3744efc553f34231183e32584885/redis-7.4.1-py3-none-any.whl", hash = "sha256:1fa4647af1c5e93a2c685aa248ee44cce092691146d41390518dabe9a99839b0", size = 410171, upload-time = "2026-06-05T09:10:05.128Z" }, ] [[package]] name = "redisvl" -version = "0.18.1" +version = "0.20.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jsonpath-ng" }, @@ -3449,9 +3458,9 @@ dependencies = [ { name = "redis" }, { name = "tenacity" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c3/db/7d89a51e3571d3e0dba285a8b784ead25f0653c05a8f4f29515fb62e2e2a/redisvl-0.18.1.tar.gz", hash = "sha256:2beaf7dfe7c6412fe116759823f5e1eba5cdf8719b6b5c482cf0e62f8abeeb6b", size = 885584, upload-time = "2026-04-30T16:25:55.96Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/4c/2b4845f3e47ffcb7d220d83d3ec84aa044426119e71e6fefbd9c581ce7f8/redisvl-0.20.1.tar.gz", hash = "sha256:58c316e5cba75669a9fedfb783a2c02cbb93b20d11026291b80c2b78c82a2a45", size = 1110436, upload-time = "2026-06-10T13:57:50.627Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/35/04/18ba1697feb5fe0a4638a95de05193e9c3ed15912bd02c20dcfb8063d28c/redisvl-0.18.1-py3-none-any.whl", hash = "sha256:e6dd9c1fa4d8dd2b27f04bd86545ba55a2c712b99e75e90ec031ef498932d67a", size = 225554, upload-time = "2026-04-30T16:25:54.181Z" }, + { url = "https://files.pythonhosted.org/packages/99/88/8b0830f796c7abf65c6aaa075a9b45abfeae1d1ba13c432dd0e0768a1185/redisvl-0.20.1-py3-none-any.whl", hash = "sha256:b89f3fe412ee2167d475d2303db1563acee5156e4e89515d92d83759696b77be", size = 320529, upload-time = "2026-06-10T13:57:49.279Z" }, ] [[package]] @@ -3470,7 +3479,7 @@ wheels = [ [[package]] name = "requests" -version = "2.34.0.dev1" +version = "2.34.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, @@ -3478,9 +3487,9 @@ dependencies = [ { name = "idna" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f5/37/b3032e92a7712e988c92df2ed408d6aec5b00838e6c06009ae695433915b/requests-2.34.0.dev1.tar.gz", hash = "sha256:319ba4e42f1031737a08f3efc695c7dc436f22efb8d02630ca3a99cf23f752cd", size = 141686, upload-time = "2026-05-03T20:21:41.747Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ac/c3/e2a2b89f2d3e2179abd6d00ebd70bff6273f37fb3e0cc209f48b39d00cbf/requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed", size = 142856, upload-time = "2026-05-14T19:25:27.735Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0c/53/ddb8b8fa96367976cf52bb0610ffd529bd7d2795b2e4c1724724d071718c/requests-2.34.0.dev1-py3-none-any.whl", hash = "sha256:c8749aeb3c4b204f80fd288f7507378c9afe66a3f189fb43fd77ea33e74d7564", size = 73077, upload-time = "2026-05-03T20:21:40.509Z" }, + { url = "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075, upload-time = "2026-05-14T19:25:26.443Z" }, ] [[package]] @@ -3496,27 +3505,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3b/5d/63d4ae3b9daea098d5d6f5da83984853c1bbacd5dc826764b249fe119d24/requests_oauthlib-2.0.0-py2.py3-none-any.whl", hash = "sha256:7dd8a5c40426b779b0868c404bdef9768deccf22749cde15852df527e6269b36", size = 24179, upload-time = "2024-03-22T20:32:28.055Z" }, ] -[[package]] -name = "requests-toolbelt" -version = "1.0.0" -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, 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, 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, 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, upload-time = "2022-01-10T00:52:29.594Z" }, -] - [[package]] name = "rich" version = "15.0.0" @@ -3532,83 +3520,112 @@ wheels = [ [[package]] name = "rpds-py" -version = "0.30.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/20/af/3f2f423103f1113b36230496629986e0ef7e199d2aa8392452b484b38ced/rpds_py-0.30.0.tar.gz", hash = "sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84", size = 69469, upload-time = "2025-11-30T20:24:38.837Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/03/e7/98a2f4ac921d82f33e03f3835f5bf3a4a40aa1bfdc57975e74a97b2b4bdd/rpds_py-0.30.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a161f20d9a43006833cd7068375a94d035714d73a172b681d8881820600abfad", size = 375086, upload-time = "2025-11-30T20:22:17.93Z" }, - { url = "https://files.pythonhosted.org/packages/4d/a1/bca7fd3d452b272e13335db8d6b0b3ecde0f90ad6f16f3328c6fb150c889/rpds_py-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6abc8880d9d036ecaafe709079969f56e876fcf107f7a8e9920ba6d5a3878d05", size = 359053, upload-time = "2025-11-30T20:22:19.297Z" }, - { url = "https://files.pythonhosted.org/packages/65/1c/ae157e83a6357eceff62ba7e52113e3ec4834a84cfe07fa4b0757a7d105f/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca28829ae5f5d569bb62a79512c842a03a12576375d5ece7d2cadf8abe96ec28", size = 390763, upload-time = "2025-11-30T20:22:21.661Z" }, - { url = "https://files.pythonhosted.org/packages/d4/36/eb2eb8515e2ad24c0bd43c3ee9cd74c33f7ca6430755ccdb240fd3144c44/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1010ed9524c73b94d15919ca4d41d8780980e1765babf85f9a2f90d247153dd", size = 408951, upload-time = "2025-11-30T20:22:23.408Z" }, - { url = "https://files.pythonhosted.org/packages/d6/65/ad8dc1784a331fabbd740ef6f71ce2198c7ed0890dab595adb9ea2d775a1/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8d1736cfb49381ba528cd5baa46f82fdc65c06e843dab24dd70b63d09121b3f", size = 514622, upload-time = "2025-11-30T20:22:25.16Z" }, - { url = "https://files.pythonhosted.org/packages/63/8e/0cfa7ae158e15e143fe03993b5bcd743a59f541f5952e1546b1ac1b5fd45/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d948b135c4693daff7bc2dcfc4ec57237a29bd37e60c2fabf5aff2bbacf3e2f1", size = 414492, upload-time = "2025-11-30T20:22:26.505Z" }, - { url = "https://files.pythonhosted.org/packages/60/1b/6f8f29f3f995c7ffdde46a626ddccd7c63aefc0efae881dc13b6e5d5bb16/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47f236970bccb2233267d89173d3ad2703cd36a0e2a6e92d0560d333871a3d23", size = 394080, upload-time = "2025-11-30T20:22:27.934Z" }, - { url = "https://files.pythonhosted.org/packages/6d/d5/a266341051a7a3ca2f4b750a3aa4abc986378431fc2da508c5034d081b70/rpds_py-0.30.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:2e6ecb5a5bcacf59c3f912155044479af1d0b6681280048b338b28e364aca1f6", size = 408680, upload-time = "2025-11-30T20:22:29.341Z" }, - { url = "https://files.pythonhosted.org/packages/10/3b/71b725851df9ab7a7a4e33cf36d241933da66040d195a84781f49c50490c/rpds_py-0.30.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a8fa71a2e078c527c3e9dc9fc5a98c9db40bcc8a92b4e8858e36d329f8684b51", size = 423589, upload-time = "2025-11-30T20:22:31.469Z" }, - { url = "https://files.pythonhosted.org/packages/00/2b/e59e58c544dc9bd8bd8384ecdb8ea91f6727f0e37a7131baeff8d6f51661/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73c67f2db7bc334e518d097c6d1e6fed021bbc9b7d678d6cc433478365d1d5f5", size = 573289, upload-time = "2025-11-30T20:22:32.997Z" }, - { url = "https://files.pythonhosted.org/packages/da/3e/a18e6f5b460893172a7d6a680e86d3b6bc87a54c1f0b03446a3c8c7b588f/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5ba103fb455be00f3b1c2076c9d4264bfcb037c976167a6047ed82f23153f02e", size = 599737, upload-time = "2025-11-30T20:22:34.419Z" }, - { url = "https://files.pythonhosted.org/packages/5c/e2/714694e4b87b85a18e2c243614974413c60aa107fd815b8cbc42b873d1d7/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7cee9c752c0364588353e627da8a7e808a66873672bcb5f52890c33fd965b394", size = 563120, upload-time = "2025-11-30T20:22:35.903Z" }, - { url = "https://files.pythonhosted.org/packages/6f/ab/d5d5e3bcedb0a77f4f613706b750e50a5a3ba1c15ccd3665ecc636c968fd/rpds_py-0.30.0-cp312-cp312-win32.whl", hash = "sha256:1ab5b83dbcf55acc8b08fc62b796ef672c457b17dbd7820a11d6c52c06839bdf", size = 223782, upload-time = "2025-11-30T20:22:37.271Z" }, - { url = "https://files.pythonhosted.org/packages/39/3b/f786af9957306fdc38a74cef405b7b93180f481fb48453a114bb6465744a/rpds_py-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:a090322ca841abd453d43456ac34db46e8b05fd9b3b4ac0c78bcde8b089f959b", size = 240463, upload-time = "2025-11-30T20:22:39.021Z" }, - { url = "https://files.pythonhosted.org/packages/f3/d2/b91dc748126c1559042cfe41990deb92c4ee3e2b415f6b5234969ffaf0cc/rpds_py-0.30.0-cp312-cp312-win_arm64.whl", hash = "sha256:669b1805bd639dd2989b281be2cfd951c6121b65e729d9b843e9639ef1fd555e", size = 230868, upload-time = "2025-11-30T20:22:40.493Z" }, - { url = "https://files.pythonhosted.org/packages/ed/dc/d61221eb88ff410de3c49143407f6f3147acf2538c86f2ab7ce65ae7d5f9/rpds_py-0.30.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f83424d738204d9770830d35290ff3273fbb02b41f919870479fab14b9d303b2", size = 374887, upload-time = "2025-11-30T20:22:41.812Z" }, - { url = "https://files.pythonhosted.org/packages/fd/32/55fb50ae104061dbc564ef15cc43c013dc4a9f4527a1f4d99baddf56fe5f/rpds_py-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7536cd91353c5273434b4e003cbda89034d67e7710eab8761fd918ec6c69cf8", size = 358904, upload-time = "2025-11-30T20:22:43.479Z" }, - { url = "https://files.pythonhosted.org/packages/58/70/faed8186300e3b9bdd138d0273109784eea2396c68458ed580f885dfe7ad/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2771c6c15973347f50fece41fc447c054b7ac2ae0502388ce3b6738cd366e3d4", size = 389945, upload-time = "2025-11-30T20:22:44.819Z" }, - { url = "https://files.pythonhosted.org/packages/bd/a8/073cac3ed2c6387df38f71296d002ab43496a96b92c823e76f46b8af0543/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0a59119fc6e3f460315fe9d08149f8102aa322299deaa5cab5b40092345c2136", size = 407783, upload-time = "2025-11-30T20:22:46.103Z" }, - { url = "https://files.pythonhosted.org/packages/77/57/5999eb8c58671f1c11eba084115e77a8899d6e694d2a18f69f0ba471ec8b/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76fec018282b4ead0364022e3c54b60bf368b9d926877957a8624b58419169b7", size = 515021, upload-time = "2025-11-30T20:22:47.458Z" }, - { url = "https://files.pythonhosted.org/packages/e0/af/5ab4833eadc36c0a8ed2bc5c0de0493c04f6c06de223170bd0798ff98ced/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:692bef75a5525db97318e8cd061542b5a79812d711ea03dbc1f6f8dbb0c5f0d2", size = 414589, upload-time = "2025-11-30T20:22:48.872Z" }, - { url = "https://files.pythonhosted.org/packages/b7/de/f7192e12b21b9e9a68a6d0f249b4af3fdcdff8418be0767a627564afa1f1/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9027da1ce107104c50c81383cae773ef5c24d296dd11c99e2629dbd7967a20c6", size = 394025, upload-time = "2025-11-30T20:22:50.196Z" }, - { url = "https://files.pythonhosted.org/packages/91/c4/fc70cd0249496493500e7cc2de87504f5aa6509de1e88623431fec76d4b6/rpds_py-0.30.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:9cf69cdda1f5968a30a359aba2f7f9aa648a9ce4b580d6826437f2b291cfc86e", size = 408895, upload-time = "2025-11-30T20:22:51.87Z" }, - { url = "https://files.pythonhosted.org/packages/58/95/d9275b05ab96556fefff73a385813eb66032e4c99f411d0795372d9abcea/rpds_py-0.30.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a4796a717bf12b9da9d3ad002519a86063dcac8988b030e405704ef7d74d2d9d", size = 422799, upload-time = "2025-11-30T20:22:53.341Z" }, - { url = "https://files.pythonhosted.org/packages/06/c1/3088fc04b6624eb12a57eb814f0d4997a44b0d208d6cace713033ff1a6ba/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d4c2aa7c50ad4728a094ebd5eb46c452e9cb7edbfdb18f9e1221f597a73e1e7", size = 572731, upload-time = "2025-11-30T20:22:54.778Z" }, - { url = "https://files.pythonhosted.org/packages/d8/42/c612a833183b39774e8ac8fecae81263a68b9583ee343db33ab571a7ce55/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba81a9203d07805435eb06f536d95a266c21e5b2dfbf6517748ca40c98d19e31", size = 599027, upload-time = "2025-11-30T20:22:56.212Z" }, - { url = "https://files.pythonhosted.org/packages/5f/60/525a50f45b01d70005403ae0e25f43c0384369ad24ffe46e8d9068b50086/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:945dccface01af02675628334f7cf49c2af4c1c904748efc5cf7bbdf0b579f95", size = 563020, upload-time = "2025-11-30T20:22:58.2Z" }, - { url = "https://files.pythonhosted.org/packages/0b/5d/47c4655e9bcd5ca907148535c10e7d489044243cc9941c16ed7cd53be91d/rpds_py-0.30.0-cp313-cp313-win32.whl", hash = "sha256:b40fb160a2db369a194cb27943582b38f79fc4887291417685f3ad693c5a1d5d", size = 223139, upload-time = "2025-11-30T20:23:00.209Z" }, - { url = "https://files.pythonhosted.org/packages/f2/e1/485132437d20aa4d3e1d8b3fb5a5e65aa8139f1e097080c2a8443201742c/rpds_py-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:806f36b1b605e2d6a72716f321f20036b9489d29c51c91f4dd29a3e3afb73b15", size = 240224, upload-time = "2025-11-30T20:23:02.008Z" }, - { url = "https://files.pythonhosted.org/packages/24/95/ffd128ed1146a153d928617b0ef673960130be0009c77d8fbf0abe306713/rpds_py-0.30.0-cp313-cp313-win_arm64.whl", hash = "sha256:d96c2086587c7c30d44f31f42eae4eac89b60dabbac18c7669be3700f13c3ce1", size = 230645, upload-time = "2025-11-30T20:23:03.43Z" }, - { url = "https://files.pythonhosted.org/packages/ff/1b/b10de890a0def2a319a2626334a7f0ae388215eb60914dbac8a3bae54435/rpds_py-0.30.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:eb0b93f2e5c2189ee831ee43f156ed34e2a89a78a66b98cadad955972548be5a", size = 364443, upload-time = "2025-11-30T20:23:04.878Z" }, - { url = "https://files.pythonhosted.org/packages/0d/bf/27e39f5971dc4f305a4fb9c672ca06f290f7c4e261c568f3dea16a410d47/rpds_py-0.30.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:922e10f31f303c7c920da8981051ff6d8c1a56207dbdf330d9047f6d30b70e5e", size = 353375, upload-time = "2025-11-30T20:23:06.342Z" }, - { url = "https://files.pythonhosted.org/packages/40/58/442ada3bba6e8e6615fc00483135c14a7538d2ffac30e2d933ccf6852232/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdc62c8286ba9bf7f47befdcea13ea0e26bf294bda99758fd90535cbaf408000", size = 383850, upload-time = "2025-11-30T20:23:07.825Z" }, - { url = "https://files.pythonhosted.org/packages/14/14/f59b0127409a33c6ef6f5c1ebd5ad8e32d7861c9c7adfa9a624fc3889f6c/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47f9a91efc418b54fb8190a6b4aa7813a23fb79c51f4bb84e418f5476c38b8db", size = 392812, upload-time = "2025-11-30T20:23:09.228Z" }, - { url = "https://files.pythonhosted.org/packages/b3/66/e0be3e162ac299b3a22527e8913767d869e6cc75c46bd844aa43fb81ab62/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3587eb9b17f3789ad50824084fa6f81921bbf9a795826570bda82cb3ed91f2", size = 517841, upload-time = "2025-11-30T20:23:11.186Z" }, - { url = "https://files.pythonhosted.org/packages/3d/55/fa3b9cf31d0c963ecf1ba777f7cf4b2a2c976795ac430d24a1f43d25a6ba/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39c02563fc592411c2c61d26b6c5fe1e51eaa44a75aa2c8735ca88b0d9599daa", size = 408149, upload-time = "2025-11-30T20:23:12.864Z" }, - { url = "https://files.pythonhosted.org/packages/60/ca/780cf3b1a32b18c0f05c441958d3758f02544f1d613abf9488cd78876378/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51a1234d8febafdfd33a42d97da7a43f5dcb120c1060e352a3fbc0c6d36e2083", size = 383843, upload-time = "2025-11-30T20:23:14.638Z" }, - { url = "https://files.pythonhosted.org/packages/82/86/d5f2e04f2aa6247c613da0c1dd87fcd08fa17107e858193566048a1e2f0a/rpds_py-0.30.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:eb2c4071ab598733724c08221091e8d80e89064cd472819285a9ab0f24bcedb9", size = 396507, upload-time = "2025-11-30T20:23:16.105Z" }, - { url = "https://files.pythonhosted.org/packages/4b/9a/453255d2f769fe44e07ea9785c8347edaf867f7026872e76c1ad9f7bed92/rpds_py-0.30.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6bdfdb946967d816e6adf9a3d8201bfad269c67efe6cefd7093ef959683c8de0", size = 414949, upload-time = "2025-11-30T20:23:17.539Z" }, - { url = "https://files.pythonhosted.org/packages/a3/31/622a86cdc0c45d6df0e9ccb6becdba5074735e7033c20e401a6d9d0e2ca0/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c77afbd5f5250bf27bf516c7c4a016813eb2d3e116139aed0096940c5982da94", size = 565790, upload-time = "2025-11-30T20:23:19.029Z" }, - { url = "https://files.pythonhosted.org/packages/1c/5d/15bbf0fb4a3f58a3b1c67855ec1efcc4ceaef4e86644665fff03e1b66d8d/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:61046904275472a76c8c90c9ccee9013d70a6d0f73eecefd38c1ae7c39045a08", size = 590217, upload-time = "2025-11-30T20:23:20.885Z" }, - { url = "https://files.pythonhosted.org/packages/6d/61/21b8c41f68e60c8cc3b2e25644f0e3681926020f11d06ab0b78e3c6bbff1/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c5f36a861bc4b7da6516dbdf302c55313afa09b81931e8280361a4f6c9a2d27", size = 555806, upload-time = "2025-11-30T20:23:22.488Z" }, - { url = "https://files.pythonhosted.org/packages/f9/39/7e067bb06c31de48de3eb200f9fc7c58982a4d3db44b07e73963e10d3be9/rpds_py-0.30.0-cp313-cp313t-win32.whl", hash = "sha256:3d4a69de7a3e50ffc214ae16d79d8fbb0922972da0356dcf4d0fdca2878559c6", size = 211341, upload-time = "2025-11-30T20:23:24.449Z" }, - { url = "https://files.pythonhosted.org/packages/0a/4d/222ef0b46443cf4cf46764d9c630f3fe4abaa7245be9417e56e9f52b8f65/rpds_py-0.30.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f14fc5df50a716f7ece6a80b6c78bb35ea2ca47c499e422aa4463455dd96d56d", size = 225768, upload-time = "2025-11-30T20:23:25.908Z" }, - { url = "https://files.pythonhosted.org/packages/86/81/dad16382ebbd3d0e0328776d8fd7ca94220e4fa0798d1dc5e7da48cb3201/rpds_py-0.30.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:68f19c879420aa08f61203801423f6cd5ac5f0ac4ac82a2368a9fcd6a9a075e0", size = 362099, upload-time = "2025-11-30T20:23:27.316Z" }, - { url = "https://files.pythonhosted.org/packages/2b/60/19f7884db5d5603edf3c6bce35408f45ad3e97e10007df0e17dd57af18f8/rpds_py-0.30.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ec7c4490c672c1a0389d319b3a9cfcd098dcdc4783991553c332a15acf7249be", size = 353192, upload-time = "2025-11-30T20:23:29.151Z" }, - { url = "https://files.pythonhosted.org/packages/bf/c4/76eb0e1e72d1a9c4703c69607cec123c29028bff28ce41588792417098ac/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f251c812357a3fed308d684a5079ddfb9d933860fc6de89f2b7ab00da481e65f", size = 384080, upload-time = "2025-11-30T20:23:30.785Z" }, - { url = "https://files.pythonhosted.org/packages/72/87/87ea665e92f3298d1b26d78814721dc39ed8d2c74b86e83348d6b48a6f31/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac98b175585ecf4c0348fd7b29c3864bda53b805c773cbf7bfdaffc8070c976f", size = 394841, upload-time = "2025-11-30T20:23:32.209Z" }, - { url = "https://files.pythonhosted.org/packages/77/ad/7783a89ca0587c15dcbf139b4a8364a872a25f861bdb88ed99f9b0dec985/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3e62880792319dbeb7eb866547f2e35973289e7d5696c6e295476448f5b63c87", size = 516670, upload-time = "2025-11-30T20:23:33.742Z" }, - { url = "https://files.pythonhosted.org/packages/5b/3c/2882bdac942bd2172f3da574eab16f309ae10a3925644e969536553cb4ee/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e7fc54e0900ab35d041b0601431b0a0eb495f0851a0639b6ef90f7741b39a18", size = 408005, upload-time = "2025-11-30T20:23:35.253Z" }, - { url = "https://files.pythonhosted.org/packages/ce/81/9a91c0111ce1758c92516a3e44776920b579d9a7c09b2b06b642d4de3f0f/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47e77dc9822d3ad616c3d5759ea5631a75e5809d5a28707744ef79d7a1bcfcad", size = 382112, upload-time = "2025-11-30T20:23:36.842Z" }, - { url = "https://files.pythonhosted.org/packages/cf/8e/1da49d4a107027e5fbc64daeab96a0706361a2918da10cb41769244b805d/rpds_py-0.30.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:b4dc1a6ff022ff85ecafef7979a2c6eb423430e05f1165d6688234e62ba99a07", size = 399049, upload-time = "2025-11-30T20:23:38.343Z" }, - { url = "https://files.pythonhosted.org/packages/df/5a/7ee239b1aa48a127570ec03becbb29c9d5a9eb092febbd1699d567cae859/rpds_py-0.30.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4559c972db3a360808309e06a74628b95eaccbf961c335c8fe0d590cf587456f", size = 415661, upload-time = "2025-11-30T20:23:40.263Z" }, - { url = "https://files.pythonhosted.org/packages/70/ea/caa143cf6b772f823bc7929a45da1fa83569ee49b11d18d0ada7f5ee6fd6/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0ed177ed9bded28f8deb6ab40c183cd1192aa0de40c12f38be4d59cd33cb5c65", size = 565606, upload-time = "2025-11-30T20:23:42.186Z" }, - { url = "https://files.pythonhosted.org/packages/64/91/ac20ba2d69303f961ad8cf55bf7dbdb4763f627291ba3d0d7d67333cced9/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ad1fa8db769b76ea911cb4e10f049d80bf518c104f15b3edb2371cc65375c46f", size = 591126, upload-time = "2025-11-30T20:23:44.086Z" }, - { url = "https://files.pythonhosted.org/packages/21/20/7ff5f3c8b00c8a95f75985128c26ba44503fb35b8e0259d812766ea966c7/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:46e83c697b1f1c72b50e5ee5adb4353eef7406fb3f2043d64c33f20ad1c2fc53", size = 553371, upload-time = "2025-11-30T20:23:46.004Z" }, - { url = "https://files.pythonhosted.org/packages/72/c7/81dadd7b27c8ee391c132a6b192111ca58d866577ce2d9b0ca157552cce0/rpds_py-0.30.0-cp314-cp314-win32.whl", hash = "sha256:ee454b2a007d57363c2dfd5b6ca4a5d7e2c518938f8ed3b706e37e5d470801ed", size = 215298, upload-time = "2025-11-30T20:23:47.696Z" }, - { url = "https://files.pythonhosted.org/packages/3e/d2/1aaac33287e8cfb07aab2e6b8ac1deca62f6f65411344f1433c55e6f3eb8/rpds_py-0.30.0-cp314-cp314-win_amd64.whl", hash = "sha256:95f0802447ac2d10bcc69f6dc28fe95fdf17940367b21d34e34c737870758950", size = 228604, upload-time = "2025-11-30T20:23:49.501Z" }, - { url = "https://files.pythonhosted.org/packages/e8/95/ab005315818cc519ad074cb7784dae60d939163108bd2b394e60dc7b5461/rpds_py-0.30.0-cp314-cp314-win_arm64.whl", hash = "sha256:613aa4771c99f03346e54c3f038e4cc574ac09a3ddfb0e8878487335e96dead6", size = 222391, upload-time = "2025-11-30T20:23:50.96Z" }, - { url = "https://files.pythonhosted.org/packages/9e/68/154fe0194d83b973cdedcdcc88947a2752411165930182ae41d983dcefa6/rpds_py-0.30.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:7e6ecfcb62edfd632e56983964e6884851786443739dbfe3582947e87274f7cb", size = 364868, upload-time = "2025-11-30T20:23:52.494Z" }, - { url = "https://files.pythonhosted.org/packages/83/69/8bbc8b07ec854d92a8b75668c24d2abcb1719ebf890f5604c61c9369a16f/rpds_py-0.30.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a1d0bc22a7cdc173fedebb73ef81e07faef93692b8c1ad3733b67e31e1b6e1b8", size = 353747, upload-time = "2025-11-30T20:23:54.036Z" }, - { url = "https://files.pythonhosted.org/packages/ab/00/ba2e50183dbd9abcce9497fa5149c62b4ff3e22d338a30d690f9af970561/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d08f00679177226c4cb8c5265012eea897c8ca3b93f429e546600c971bcbae7", size = 383795, upload-time = "2025-11-30T20:23:55.556Z" }, - { url = "https://files.pythonhosted.org/packages/05/6f/86f0272b84926bcb0e4c972262f54223e8ecc556b3224d281e6598fc9268/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5965af57d5848192c13534f90f9dd16464f3c37aaf166cc1da1cae1fd5a34898", size = 393330, upload-time = "2025-11-30T20:23:57.033Z" }, - { url = "https://files.pythonhosted.org/packages/cb/e9/0e02bb2e6dc63d212641da45df2b0bf29699d01715913e0d0f017ee29438/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a4e86e34e9ab6b667c27f3211ca48f73dba7cd3d90f8d5b11be56e5dbc3fb4e", size = 518194, upload-time = "2025-11-30T20:23:58.637Z" }, - { url = "https://files.pythonhosted.org/packages/ee/ca/be7bca14cf21513bdf9c0606aba17d1f389ea2b6987035eb4f62bd923f25/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5d3e6b26f2c785d65cc25ef1e5267ccbe1b069c5c21b8cc724efee290554419", size = 408340, upload-time = "2025-11-30T20:24:00.2Z" }, - { url = "https://files.pythonhosted.org/packages/c2/c7/736e00ebf39ed81d75544c0da6ef7b0998f8201b369acf842f9a90dc8fce/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:626a7433c34566535b6e56a1b39a7b17ba961e97ce3b80ec62e6f1312c025551", size = 383765, upload-time = "2025-11-30T20:24:01.759Z" }, - { url = "https://files.pythonhosted.org/packages/4a/3f/da50dfde9956aaf365c4adc9533b100008ed31aea635f2b8d7b627e25b49/rpds_py-0.30.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:acd7eb3f4471577b9b5a41baf02a978e8bdeb08b4b355273994f8b87032000a8", size = 396834, upload-time = "2025-11-30T20:24:03.687Z" }, - { url = "https://files.pythonhosted.org/packages/4e/00/34bcc2565b6020eab2623349efbdec810676ad571995911f1abdae62a3a0/rpds_py-0.30.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe5fa731a1fa8a0a56b0977413f8cacac1768dad38d16b3a296712709476fbd5", size = 415470, upload-time = "2025-11-30T20:24:05.232Z" }, - { url = "https://files.pythonhosted.org/packages/8c/28/882e72b5b3e6f718d5453bd4d0d9cf8df36fddeb4ddbbab17869d5868616/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:74a3243a411126362712ee1524dfc90c650a503502f135d54d1b352bd01f2404", size = 565630, upload-time = "2025-11-30T20:24:06.878Z" }, - { url = "https://files.pythonhosted.org/packages/3b/97/04a65539c17692de5b85c6e293520fd01317fd878ea1995f0367d4532fb1/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3e8eeb0544f2eb0d2581774be4c3410356eba189529a6b3e36bbbf9696175856", size = 591148, upload-time = "2025-11-30T20:24:08.445Z" }, - { url = "https://files.pythonhosted.org/packages/85/70/92482ccffb96f5441aab93e26c4d66489eb599efdcf96fad90c14bbfb976/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dbd936cde57abfee19ab3213cf9c26be06d60750e60a8e4dd85d1ab12c8b1f40", size = 556030, upload-time = "2025-11-30T20:24:10.956Z" }, - { url = "https://files.pythonhosted.org/packages/20/53/7c7e784abfa500a2b6b583b147ee4bb5a2b3747a9166bab52fec4b5b5e7d/rpds_py-0.30.0-cp314-cp314t-win32.whl", hash = "sha256:dc824125c72246d924f7f796b4f63c1e9dc810c7d9e2355864b3c3a73d59ade0", size = 211570, upload-time = "2025-11-30T20:24:12.735Z" }, - { url = "https://files.pythonhosted.org/packages/d0/02/fa464cdfbe6b26e0600b62c528b72d8608f5cc49f96b8d6e38c95d60c676/rpds_py-0.30.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27f4b0e92de5bfbc6f86e43959e6edd1425c33b5e69aab0984a72047f2bcf1e3", size = 226532, upload-time = "2025-11-30T20:24:14.634Z" }, +version = "2026.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/43/25a8dcd3feedd735039a8f0b5b7e3b118232b5eae288c4fd9ab200d41094/rpds_py-2026.5.1.tar.gz", hash = "sha256:07b24fea40541e28570e5b795a4a38fbdcd12550c06bd0748005ecc8116ca256", size = 64459, upload-time = "2026-05-28T12:02:13.232Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/e7/a78582dc57caa592dcc7d4fb69b61390561e908eb3d2f5df5928a8e354c0/rpds_py-2026.5.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3abe24a66e57adcfa645d718063a5fa5103ecc71ddbf26d78af8f9368018ff1d", size = 353040, upload-time = "2026-05-28T11:59:12.531Z" }, + { url = "https://files.pythonhosted.org/packages/a3/43/35e3f136343aef451e545ce8c38d36c2f93c0ed88703db8b64ba2b205c68/rpds_py-2026.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:58b1d94308ddf0b1982f61f2eb54bf92997c9ece8a8093ef014250f4a517906c", size = 345775, upload-time = "2026-05-28T11:59:13.827Z" }, + { url = "https://files.pythonhosted.org/packages/20/e1/0f2160c5982d3157734d5cb3ed63d8b2d583a73c9864f77b666449f32cf8/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fa92420128dadce7f54bd73ba1825a273e9268fe9e35dbf7e6362890efa4e08", size = 376329, upload-time = "2026-05-28T11:59:15.271Z" }, + { url = "https://files.pythonhosted.org/packages/d0/11/ee0ba42aff83bf4effdbc576673c6be64c5e173978c3f6d537e94482f77d/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ca653c6546386227cd9800d1bef6a348099acf8db4250341da6d90f663d6dfcb", size = 383539, upload-time = "2026-05-28T11:59:16.665Z" }, + { url = "https://files.pythonhosted.org/packages/11/df/d94aa6a499d4ac40afe2d7620f2c597fd3c0f182e854ad7cf3f596a81cb6/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66c93681c4729e4e3ecba31b8179fae083ff3118841672835140338b4b9867c1", size = 494674, upload-time = "2026-05-28T11:59:17.991Z" }, + { url = "https://files.pythonhosted.org/packages/1f/75/33d30f43bb2f458de11979486a591b1bf6e5651765ed1704c6197c2dc773/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40ff257542e04796880e011e15cd4dc21c2599975df2aaa8f2c8495ca574e1a5", size = 389268, upload-time = "2026-05-28T11:59:19.434Z" }, + { url = "https://files.pythonhosted.org/packages/f4/1e/2c9096fc19d5fd084b0184ca2b651e659aa0a37e6fdbecf6ece47f147fe1/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6825cc329b290e93c5f6a9be2393118a763f6ccf6abd83704e0c102ca583644", size = 376280, upload-time = "2026-05-28T11:59:21Z" }, + { url = "https://files.pythonhosted.org/packages/b9/e5/61ec9f8be8211ea7f48448195549e4aaf02004083475493b0e137702ecb2/rpds_py-2026.5.1-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:de42116e69cb53b911cc34aee5ab98f36c597b822545045d49e938818b99e5e4", size = 387233, upload-time = "2026-05-28T11:59:22.454Z" }, + { url = "https://files.pythonhosted.org/packages/0d/ca/bcec1005c4f4a234f92a29078631fee49206c7265ccae966f18fd332e80e/rpds_py-2026.5.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c0f920015df2a504bebaba6d4c31ccf3fcf942f92655c086da30b671aad19aa6", size = 405009, upload-time = "2026-05-28T11:59:23.845Z" }, + { url = "https://files.pythonhosted.org/packages/72/e6/4d5718c5cf26c522dc7c9999e238da1e77380b81d0c5d1df11e271ddfeb1/rpds_py-2026.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0408a24e44feb919423dc6d9da677cb5cddb894d2ca9e763967d156d9c60fab4", size = 553113, upload-time = "2026-05-28T11:59:25.184Z" }, + { url = "https://files.pythonhosted.org/packages/d4/25/2ee807bdb3e1f0b7eddf7782acd5665a8b5205a331a7d7244a52c4812fd9/rpds_py-2026.5.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cea68bcd53467561ae2f96a6bdad1544299ba97b5b0ddcd5ac3d376e5c781c24", size = 618838, upload-time = "2026-05-28T11:59:26.749Z" }, + { url = "https://files.pythonhosted.org/packages/6a/c1/7d4c26f167f8c41501cc073d30ee22082b16ce358cf5b00ec97cbc7804ea/rpds_py-2026.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4be8b1d2a705cc37d08256004e1d07de143fa0075c8e85a3df020b776f62b732", size = 582436, upload-time = "2026-05-28T11:59:28.11Z" }, + { url = "https://files.pythonhosted.org/packages/04/1d/9d12b0a337bab46f4769f8857f4007e3b2d639e14f9a44a0efe157696e64/rpds_py-2026.5.1-cp312-cp312-win32.whl", hash = "sha256:6736718bd4fc49cbcb538ba30516fdbef161522acefb739657d48b97bd864fed", size = 212734, upload-time = "2026-05-28T11:59:29.689Z" }, + { url = "https://files.pythonhosted.org/packages/c5/93/e4116f2de7f56bc7406a76033dc501811ddeb22b7f056b92d632871ebb0c/rpds_py-2026.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:0a7d1eec967df0e9b22614a5e177622e0c89611d03727fa0cb48e45028907870", size = 229045, upload-time = "2026-05-28T11:59:31.033Z" }, + { url = "https://files.pythonhosted.org/packages/cb/53/6c3419d85eb2ec5938a37627c585b42d76a63bb731d6e42ed4b079ebf486/rpds_py-2026.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:1841d067089e117142d79b98aa0df2f08b52f2ecc1819dd2700636c0db74a473", size = 223967, upload-time = "2026-05-28T11:59:32.318Z" }, + { url = "https://files.pythonhosted.org/packages/6c/32/14c961ad295f490eb0849ada8b79683e93a59b9de3afdd983eaf55fa6867/rpds_py-2026.5.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:efef4ac29c6ff495531eb17ee705b62841ecaa291b7c7077e848ea03e237164d", size = 352787, upload-time = "2026-05-28T11:59:33.655Z" }, + { url = "https://files.pythonhosted.org/packages/ca/bb/d1b85117967c11191441a7274ae616c65d93901d082c588f89a50a8da5ae/rpds_py-2026.5.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c39f5b67a8a2e67179ada2a954227d670fe65fa9098457f698f56ddf248709b3", size = 345179, upload-time = "2026-05-28T11:59:35Z" }, + { url = "https://files.pythonhosted.org/packages/7c/46/d84105f062e626a1b233f863907288a4708c2d833b8b4c6fb2764bc080c0/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5c30f3f04eef4fbd362226a6f31d7c8895ca4fbb6e0b790f6890a98d8da8559", size = 376173, upload-time = "2026-05-28T11:59:36.43Z" }, + { url = "https://files.pythonhosted.org/packages/e2/ae/469d7959ce5b1201e1de135dc735b86db3b35dd0d1734f6a44246d5f061c/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:277f6c82f0580848796c7ecc8a7173aa3bfb928e4ff831261c2f60a81dc270db", size = 383162, upload-time = "2026-05-28T11:59:37.995Z" }, + { url = "https://files.pythonhosted.org/packages/dc/a2/57853d31a1116a561aa072794602ad3f6341e18d70a8523f1bd5b9fc1e5a/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:63c2c4c213f1a4e3f3de28ecab029dbdee976324e729c0d7a55211be72576b02", size = 495093, upload-time = "2026-05-28T11:59:39.453Z" }, + { url = "https://files.pythonhosted.org/packages/99/63/3a8eabcad9314b7daf5c65f451d2c33d989235cd8a5762186cf2c3f5a4f8/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3350ec808fb538fe71a1f94dfaa0e29c598dfad805ce49f0caec5ae3183c652b", size = 389829, upload-time = "2026-05-28T11:59:40.896Z" }, + { url = "https://files.pythonhosted.org/packages/4b/25/05678d97fc25e2622df14dc530fb82023174ecfff6733991ed0d78f167bd/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1b964e3ab599e718dc46c018d104b1ebc007cbc6567d827c94a687fca56d77e", size = 374786, upload-time = "2026-05-28T11:59:42.626Z" }, + { url = "https://files.pythonhosted.org/packages/88/d1/8c90b6431e80a3b91b284a5c7c8c0c4f9c006444d90477a740d6e0f9c694/rpds_py-2026.5.1-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:19cb09fab7b7fc96b2a6e28f2e34b72a3705ff27b37edb77455316e5d3f3dc9b", size = 386920, upload-time = "2026-05-28T11:59:44.124Z" }, + { url = "https://files.pythonhosted.org/packages/ff/99/4638f672ab356682d633ee0da9255f5b67ce6efd0b85eb94ad3e255e65a5/rpds_py-2026.5.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:abe76bcdba31e576cb83eeb8797aa0d882b738fef6dc65d0601fc753806a5b46", size = 405059, upload-time = "2026-05-28T11:59:47.177Z" }, + { url = "https://files.pythonhosted.org/packages/66/3f/3546524b6eb4cc2e1f363a3d638fa52f6c24faae3500c25fb488b02f1740/rpds_py-2026.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8bff7073db3899158fff55ebf57b113a67030af26f80a18978f9f0aa60250ddf", size = 553030, upload-time = "2026-05-28T11:59:48.603Z" }, + { url = "https://files.pythonhosted.org/packages/c6/c3/7b3388c796fcf471bd17194242d4dc1a7608567c0fa422bcc1c5e79f9c1e/rpds_py-2026.5.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8ba264fa49be666cd9cc56bf34ec7002fb3d27a4aee5bcb4d43d0d18feb1bb6f", size = 618975, upload-time = "2026-05-28T11:59:50.314Z" }, + { url = "https://files.pythonhosted.org/packages/61/1e/a3cb07f2795075d1d88efddae2f541359fde5f08c81ee114c29c2949c90a/rpds_py-2026.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4860b603ddda0475a8885499b3729e90229d480105b42651962a5397d995fa89", size = 581178, upload-time = "2026-05-28T11:59:51.673Z" }, + { url = "https://files.pythonhosted.org/packages/a1/74/e758c03a5ef46f04c37f2651a2893db846d569ba8a7bca469d4b58939bcd/rpds_py-2026.5.1-cp313-cp313-win32.whl", hash = "sha256:7944270ae71383f6e2657dd7d5ce4eeb4ac2d0059a6738f0510583d462ab4842", size = 212481, upload-time = "2026-05-28T11:59:53.148Z" }, + { url = "https://files.pythonhosted.org/packages/70/ec/a2aca432db9c7359b40fa393eeeaa0d166c2f70175be956e75fa24197c44/rpds_py-2026.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:88647f43a73c4e01be19b04ceef0c8d3a1958153604d13c773becd8016f2a0cf", size = 228519, upload-time = "2026-05-28T11:59:54.505Z" }, + { url = "https://files.pythonhosted.org/packages/29/60/a73bfdd45b096574556acf303bbd9fa9eed36ca8a818b514e2a5d5fe2b9d/rpds_py-2026.5.1-cp313-cp313-win_arm64.whl", hash = "sha256:453895624ecf7db7063b1004e44037522bbaef9ff6a945e59bc71662d7a03abd", size = 223446, upload-time = "2026-05-28T11:59:56.081Z" }, + { url = "https://files.pythonhosted.org/packages/18/e2/408105fd611823f00882aea810f3989a30d26b1bab8b6beb20f98c724e0e/rpds_py-2026.5.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:b4e4bc98639ec915f512fde3aa7a95e0041d95d9c3cc86eea841fa63cb1e8600", size = 355287, upload-time = "2026-05-28T11:59:57.448Z" }, + { url = "https://files.pythonhosted.org/packages/8d/58/5c4a43436843c90d0f6d19f82c200c80e3843ca9fa07b237623327f6d384/rpds_py-2026.5.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cacedb7a6e167680acba45ad5716e89067d225dc80da0d7040cae8c81d4572fa", size = 347033, upload-time = "2026-05-28T11:59:58.881Z" }, + { url = "https://files.pythonhosted.org/packages/fb/c2/1a71acdacaf4e259b10278fb87b039ded3cf80041bcd89dd8a3ea702ded6/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68700371c5d7ae1412862ddfa719090925c93ecf351c566d66f09d04b136ea00", size = 376891, upload-time = "2026-05-28T12:00:00.516Z" }, + { url = "https://files.pythonhosted.org/packages/c2/c8/535f3d9b65addd8e28aa87b83c6e526799c3717a88273db8ea795beeef7a/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:296c799becfa849c779c8725494fe9ed94959ed886787df4364b058465bad7f0", size = 385646, upload-time = "2026-05-28T12:00:02.394Z" }, + { url = "https://files.pythonhosted.org/packages/1c/91/dc033f313345c354ade914dbe73cdb90b615a4409ea02430d5356794f3d8/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d3858b908218ee108d0bbfb2095ccc237648053c9bf98affad7cb079acaf1d97", size = 498830, upload-time = "2026-05-28T12:00:04.189Z" }, + { url = "https://files.pythonhosted.org/packages/27/fc/90fcbea459dbb8ddc18a2e0fd1de9412b48bc84ffff2db771cf714bacfd6/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4fb8d2e7cb2f850b169806d61d1b991738acec96500a75c30f49caf064ce7cef", size = 392830, upload-time = "2026-05-28T12:00:05.797Z" }, + { url = "https://files.pythonhosted.org/packages/b2/1d/46cd11a228c9750684a798d98f878be6f614aa762438da7378f035e79e35/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27b74c10ed6a8f190f4287f53bcfea348b92a84a9c9f70d30183d1e6172d580d", size = 379613, upload-time = "2026-05-28T12:00:07.433Z" }, + { url = "https://files.pythonhosted.org/packages/24/4a/d9b0c6af3a1de03eb93741bbe8be2bdce84d8fda8224f3005451d86df389/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:b9a6528956191c48c52294a592dbd4a8386d7048bdb25c0efcb6b966466c6d83", size = 388183, upload-time = "2026-05-28T12:00:09.227Z" }, + { url = "https://files.pythonhosted.org/packages/c5/b4/db7aaabdda6d020afc87d981bcc2f57a434c7dec60ecfc2ab3dd50b20351/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:af03e34e860047bc7a352b842856fcf78798fbb81132cc98bd2f907ab4eb9cd2", size = 408578, upload-time = "2026-05-28T12:00:10.779Z" }, + { url = "https://files.pythonhosted.org/packages/08/d6/070f6a41cbb343e2ac4171859bf3f3623e0ab002f72619d6d505313ec2de/rpds_py-2026.5.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:fea6e836d10abbe191d557d33bd58bd5987725fe63aa1eefe557d230209855bd", size = 553573, upload-time = "2026-05-28T12:00:12.443Z" }, + { url = "https://files.pythonhosted.org/packages/75/ab/1a71ea3589c4345dac0a0518f0e6a031cb42689277851b683c46d27463a5/rpds_py-2026.5.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:fc0c0f878ea770a0a8a462456c5ad36fc9fe6358e6b76fdadc7f17575e0b8bf1", size = 620861, upload-time = "2026-05-28T12:00:14.09Z" }, + { url = "https://files.pythonhosted.org/packages/8a/22/9bf80a56069c0c443fcfefac639a86a744550a2898817a6dfd3e26654924/rpds_py-2026.5.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e0b360f316d966b048b085857630b3cc51f3db2f07b06f440eac8f695374d1e3", size = 585633, upload-time = "2026-05-28T12:00:15.66Z" }, + { url = "https://files.pythonhosted.org/packages/da/68/3b2c0a75c9e04125696f84ebdbbf304acf5a40b58ba4481cdb98a922c3ba/rpds_py-2026.5.1-cp313-cp313t-win32.whl", hash = "sha256:a2999883eedf72fdfb7520b92c7d4ec2572a71ff40239377aa604cc529eecafc", size = 210074, upload-time = "2026-05-28T12:00:17.291Z" }, + { url = "https://files.pythonhosted.org/packages/e7/8b/609157d5a25d37d4f29f92840ba531f416907c34ae5c5739dd21fc2bef98/rpds_py-2026.5.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e07be2a9d7122bd6e82dea89814ef8dc893feb1aae97fec1630f3263bbb30e55", size = 228635, upload-time = "2026-05-28T12:00:18.73Z" }, + { url = "https://files.pythonhosted.org/packages/d4/6f/19c1918a4b590d8de87e712e4abe4b3875771eff60216fb6153cf6665c68/rpds_py-2026.5.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:1f2c391c3059798093b65df23aca2cac150460ae9c630d99dec83d703d9485b9", size = 349756, upload-time = "2026-05-28T12:00:20.217Z" }, + { url = "https://files.pythonhosted.org/packages/e5/60/a06fe7da34eca79dacbf958a2ba0c6eea85bc2b29de20080bf40f72f66fa/rpds_py-2026.5.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:413b424f7c4ee65ab5e5be91f5731be0f8b41a1ee2b12dfe810d716312e95a78", size = 343831, upload-time = "2026-05-28T12:00:21.711Z" }, + { url = "https://files.pythonhosted.org/packages/bf/ec/b2333b97b90e2a6ef6ca8ad386ee284968e74bcfe113b3f1a8d9036429a9/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c595a1d9255dce0599e13130d1440ab2506654f2b50294226ee06402f8fef63", size = 375127, upload-time = "2026-05-28T12:00:23.326Z" }, + { url = "https://files.pythonhosted.org/packages/14/7f/e00aae54067f2b488c4637961d5f58204d470795fc791085fa3f15060d2e/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1c27c5f6102eac8c03e7595a00827a53b271ba40a53b59ff8709170e0855ea4a", size = 379034, upload-time = "2026-05-28T12:00:24.89Z" }, + { url = "https://files.pythonhosted.org/packages/be/cc/423999bbb8ae8dc93c77fc1d5e984ade5eb89d237d3bb884ccfa72ae2890/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c7fcf61d44cacecaf3aea542b0e053db77972a4573e7ceda16fb2b399161195", size = 490823, upload-time = "2026-05-28T12:00:26.676Z" }, + { url = "https://files.pythonhosted.org/packages/0f/aa/c671bf660f12e68d3c52ff86c7066ed1372df5a0f4f2ff584e419b8207e7/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2c817a189d4ee14290420e5ff051e4dd6baa13f3edf84685071dee07a6d538ee", size = 388144, upload-time = "2026-05-28T12:00:28.577Z" }, + { url = "https://files.pythonhosted.org/packages/19/c8/d63bb75b68afe77b229e3021c6031bcaf01da5db5b0e69d0d10f9ba679a7/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21846aac0ed2e0589f38c12dc44e77bb64e494b771eadbcf169cba00566ba7ba", size = 371959, upload-time = "2026-05-28T12:00:30.304Z" }, + { url = "https://files.pythonhosted.org/packages/82/35/c51122014d8274ff37dc606d60049c3db7d83da02b5b282511e5a906a9a6/rpds_py-2026.5.1-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:b317c87a13f769a4e787819bd508aaa5d69aa09b0880de9af6d3a8a54571cdec", size = 383558, upload-time = "2026-05-28T12:00:31.764Z" }, + { url = "https://files.pythonhosted.org/packages/e3/f9/2790cb99c136a5363acdeacf5c27c56f3de0d4118a1f48fca83404c99c89/rpds_py-2026.5.1-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ce87129d9f2c14fa6c4a8601fb80eb4488c80d38a20cd13758ef11123e14995d", size = 402789, upload-time = "2026-05-28T12:00:33.247Z" }, + { url = "https://files.pythonhosted.org/packages/e5/1b/e4fb584f8c75d35c38150ff6a332cda949e6f97acba1f4fd123b14ab56fe/rpds_py-2026.5.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9cdddb6c1207d284d94fd1530adf57fbd797fe7c4b8704ba85f49414f2557e7d", size = 551405, upload-time = "2026-05-28T12:00:34.819Z" }, + { url = "https://files.pythonhosted.org/packages/d8/f7/a6731b4216cb3793ea1af5391da240f5683dacc0d13e034fe5fc3503f240/rpds_py-2026.5.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:4e237e139f94d3c036fd28eb9f564c99055476ff4ff05cd42be55ce349b5aa02", size = 616975, upload-time = "2026-05-28T12:00:36.268Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ea/2e051a81d95d8e63f4b35a1c463a87e8766bc3d083c067c5dfb6bf220747/rpds_py-2026.5.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ed0954b524873214369184a9c82b0eaa45a3fbb9a798cd95b17e0d98499e7ea0", size = 578701, upload-time = "2026-05-28T12:00:37.82Z" }, + { url = "https://files.pythonhosted.org/packages/65/56/b5f6fdb2083e32bca8a8993d89e70db114b4756c9e2c38421328126689d2/rpds_py-2026.5.1-cp314-cp314-win32.whl", hash = "sha256:2d88621d6a7d4dfa633d21abe90f280bb205274e16b1d1e61c6ad4640b2453b7", size = 209806, upload-time = "2026-05-28T12:00:39.492Z" }, + { url = "https://files.pythonhosted.org/packages/fb/80/65a5aa96c155e611d1ed844e4e1f57f3e36b021f396d9f8585d756e6b90d/rpds_py-2026.5.1-cp314-cp314-win_amd64.whl", hash = "sha256:cef8ac28d26f4dda3533060c20fbf80a325458fa9fd23ea72a73cdfa8e978838", size = 225985, upload-time = "2026-05-28T12:00:40.94Z" }, + { url = "https://files.pythonhosted.org/packages/27/7c/ad185212e87b05f196daef92bc5f3caf07298eb47c295b5585c3dd3093ac/rpds_py-2026.5.1-cp314-cp314-win_arm64.whl", hash = "sha256:eaaea962c68cdc68d4a533ba985ab8e9484277910bbfaa2ab3ef7732667bfed8", size = 221219, upload-time = "2026-05-28T12:00:43.15Z" }, + { url = "https://files.pythonhosted.org/packages/23/58/e14ae18759020334646b031e708ab4158d653a938822bfb7b95ef2e93aa3/rpds_py-2026.5.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:21942f52dbbd5f8758bf021213d28bd45c39e873e65e2407faf5f1846f5761ad", size = 352148, upload-time = "2026-05-28T12:00:44.638Z" }, + { url = "https://files.pythonhosted.org/packages/31/9b/5f4a1e2f960bca3ac5d052b139dd31eed97b259f9d909173821760d542e8/rpds_py-2026.5.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f414556f6e3958300ff941e40c9f97e3dc9774ddd1b3434c475d73dd354bbed3", size = 345196, upload-time = "2026-05-28T12:00:46.14Z" }, + { url = "https://files.pythonhosted.org/packages/1a/71/1d9574d6a2fa20ab60eaa55c7467f5aa20cbc770f341a05f09c0876f59e2/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef1013a8625c74043210190b246f5b1551e09757c1f356c6e4160ef96c5bc081", size = 374981, upload-time = "2026-05-28T12:00:47.531Z" }, + { url = "https://files.pythonhosted.org/packages/0c/9a/37e99f4915a80aa71670263c1267f7ae0af95f53a3f61e6c3bdc016d4515/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cc68e231a77a5f0d774ae278a1f8e55c0456501820847c1e4efb3829f3441df6", size = 379961, upload-time = "2026-05-28T12:00:49.216Z" }, + { url = "https://files.pythonhosted.org/packages/a8/ff/6e73f74b89d2e0715e0fc86b7dde893f9a61ae2f9b256ff3bdfe41ac4e94/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9baffb505aff33acc69b422a19f77806680f3c8632227d79f48de8a810d1c2c5", size = 495965, upload-time = "2026-05-28T12:00:51.111Z" }, + { url = "https://files.pythonhosted.org/packages/ea/e0/425faba25f59d74d4638b267f7c7a80e8649d2ef4db10a19b0c4a71e6e6f/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8d2f912928d426e8cfa396f7f3f8d29a59e6689c86dcca3c420730c1096322b", size = 389526, upload-time = "2026-05-28T12:00:52.77Z" }, + { url = "https://files.pythonhosted.org/packages/c6/76/7a41960e3fddae47fab43a28684d5da981401dffd88253de0944148654cb/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90f628283be835db980c941767d41c9a27b5239e54ba0a9c1335247e82406964", size = 376190, upload-time = "2026-05-28T12:00:54.215Z" }, + { url = "https://files.pythonhosted.org/packages/27/60/5f38dc70824fc6951b51d35377e577a3a3a4c81a6769cc5a2de25ebe0ad1/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:1ebb2f0ab7e16132995a72de805170e0203df0c3dd22e1ef1cd1fdd90bd7a131", size = 383921, upload-time = "2026-05-28T12:00:55.673Z" }, + { url = "https://files.pythonhosted.org/packages/60/1a/d60a38caa1505f4b9483c3fbbde12c94e1079154f4f401a6da96f7e77621/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f3df3d16ded76f1f8c9cdebd0e1ea55fdf4c23b812de189814da7cf229c22a81", size = 404766, upload-time = "2026-05-28T12:00:57.518Z" }, + { url = "https://files.pythonhosted.org/packages/87/ff/602fd3f174d6425f0bce05ad0dfbec0e96b38d0f7d08a79af5aa20083885/rpds_py-2026.5.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9af8905b8f854990e40d5206aa5ac58d9b0fe0b7f351ff2bb086c20f6c8c6a47", size = 551343, upload-time = "2026-05-28T12:00:58.978Z" }, + { url = "https://files.pythonhosted.org/packages/b8/c1/1be13327acdbead3eca1fde03b6a34dbb011f1e864e217f0d32cc1779a7f/rpds_py-2026.5.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:036a36a87fb1cd3b214d11c4b3c4f7d2ddad933625dca1c900b56a057c07740a", size = 618502, upload-time = "2026-05-28T12:01:00.656Z" }, + { url = "https://files.pythonhosted.org/packages/f3/d7/afb49b49d7f2be8b7ba1a9f0977fa5168003437b93086726f066544e8351/rpds_py-2026.5.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:62ae3853454fe9ef283a03c96c2d835d39e84b14643a9d62c82ef0fb87d702ca", size = 581916, upload-time = "2026-05-28T12:01:02.22Z" }, + { url = "https://files.pythonhosted.org/packages/25/d1/dbef8c1f8a10f07beb62b5f054e20099fd9924b3ec001b8f0b6ac7813a85/rpds_py-2026.5.1-cp314-cp314t-win32.whl", hash = "sha256:6c3d771a46ec18b12af06ce36243a9a80b07a5d0515236332d90863ca8bb326a", size = 207855, upload-time = "2026-05-28T12:01:03.821Z" }, + { url = "https://files.pythonhosted.org/packages/2a/72/bfa4e61ab8e7dc1c8adf397e05e6cbdd4239357bd72b248d3de662f23915/rpds_py-2026.5.1-cp314-cp314t-win_amd64.whl", hash = "sha256:c93c629be4636cf54337bd5f06c104d55e42ced54d681f6fe21ae510a65116f6", size = 225422, upload-time = "2026-05-28T12:01:05.194Z" }, + { url = "https://files.pythonhosted.org/packages/27/3a/7b5da92b640f67b6717ccafc83cdd06bfa7ff2395c3685c68922bb54d703/rpds_py-2026.5.1-cp315-cp315-macosx_10_12_x86_64.whl", hash = "sha256:3574b55c604b8f75dacb007136508bbc0db406e626301778096a133327e7f2fb", size = 349576, upload-time = "2026-05-28T12:01:06.722Z" }, + { url = "https://files.pythonhosted.org/packages/d7/8a/2aafd7ad355a1bd48ca76e2262b74b15e6432b5a1efe150efd4d779cd55d/rpds_py-2026.5.1-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:94068eb3ae6d43f5a786b7db96a406a34e6d5c24489feef32fd6e8946ea7b291", size = 343640, upload-time = "2026-05-28T12:01:08.441Z" }, + { url = "https://files.pythonhosted.org/packages/f7/7d/6c9523c1abbe840a1b7fba3c516d48e1d3487cc80fea4366c4071cf56784/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3a5b10e8ce894825f380a8f1b6444cf73c294dfea62afbb2d13e3a9e630cec1", size = 375322, upload-time = "2026-05-28T12:01:09.934Z" }, + { url = "https://files.pythonhosted.org/packages/5a/5d/0b7b03fb1dc509321f01de3149784ab773e34c8573022029af8076afcb9c/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fc09f82e63d4bcd58149572f857a431bae851dc747e313c3b5bdf7abb907fda8", size = 379066, upload-time = "2026-05-28T12:01:11.48Z" }, + { url = "https://files.pythonhosted.org/packages/d7/e2/8ef6012999ebf1cb1c22f876d9ce5e63d960fd4631d2af3202d3f480aa25/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e10464d17df3b582745c25cec695cb9558bca2cb6ddb631aee1787fc72c767b2", size = 494586, upload-time = "2026-05-28T12:01:13.051Z" }, + { url = "https://files.pythonhosted.org/packages/80/af/1eeb029bec67582c226b7809172207cd005073af4ebd906e65ff494f4983/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ba05adbf15d994c38ec0b7ab32e858e5110c21e9009a00a86545fd220f84e038", size = 388415, upload-time = "2026-05-28T12:01:14.631Z" }, + { url = "https://files.pythonhosted.org/packages/18/23/ffbe10711c4d766c1cab0557d6906c074f795814863c67b351355d29354a/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77c004fdc7b891967106f78ddfd7b076bfe6813c6139c6fff6aed3bcaa960b26", size = 372427, upload-time = "2026-05-28T12:01:16.153Z" }, + { url = "https://files.pythonhosted.org/packages/bd/3a/30ba4a6ad457e5b070c18d742a33fb77d8d922b565cc881f8a5313d63bfe/rpds_py-2026.5.1-cp315-cp315-manylinux_2_31_riscv64.whl", hash = "sha256:83bcf894486c9d78dd290d3c0124ff6dd8875d3025e2090a8ec49fcc37c55fdd", size = 383615, upload-time = "2026-05-28T12:01:17.809Z" }, + { url = "https://files.pythonhosted.org/packages/d3/69/62e242b53ce39c0814bd24e1a6e6eba6c92be716277745f317f9540a2e7b/rpds_py-2026.5.1-cp315-cp315-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c3df104083952a0e0c6f10de33e440eabe98fb6317d23e1a58c68f6df08d01b9", size = 402786, upload-time = "2026-05-28T12:01:19.419Z" }, + { url = "https://files.pythonhosted.org/packages/38/c1/a770b9c186928a1ed0f7e6d7ae50e7f3950ed23e3f9e366dbc8e38cb55de/rpds_py-2026.5.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:980450826cf22e133c57e0835070bdd0dd3f73b9b708c3ce223def2cb9469e14", size = 551583, upload-time = "2026-05-28T12:01:21.013Z" }, + { url = "https://files.pythonhosted.org/packages/21/7c/68e8579b95375b70d2a963103c42e705856cdb98569258bd807f4423891c/rpds_py-2026.5.1-cp315-cp315-musllinux_1_2_i686.whl", hash = "sha256:205dde846f24332ab0c1188699a043b8d165b79bb84529ce272c45048ff6be01", size = 616941, upload-time = "2026-05-28T12:01:22.548Z" }, + { url = "https://files.pythonhosted.org/packages/70/a1/a6135aed5730ff03ab957182259987ac11e55fb392a28dc6f0592048a280/rpds_py-2026.5.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:3966b82dd563176396df030f3dd52a6e54cb69b718e95e78bd555ed3d1e0185d", size = 578349, upload-time = "2026-05-28T12:01:24.118Z" }, + { url = "https://files.pythonhosted.org/packages/09/6e/f24201a76a84e6c49d0bdfdfcb735210e21701e9b21c5bfc0ba497dd62f6/rpds_py-2026.5.1-cp315-cp315-win32.whl", hash = "sha256:7818f8d0a415be74d2be3590b0a1c1f463a642f4d0217e7d10602dceef5b79aa", size = 209922, upload-time = "2026-05-28T12:01:25.522Z" }, + { url = "https://files.pythonhosted.org/packages/9e/e4/966bc240bb0485fc265278f6de44d05834bf0b3618886e0b22e33d54c49a/rpds_py-2026.5.1-cp315-cp315-win_amd64.whl", hash = "sha256:b3cc20c0d800af78fd0fac68086e28c1856cec51ea528bb81ea851aa40d39325", size = 226003, upload-time = "2026-05-28T12:01:27.062Z" }, + { url = "https://files.pythonhosted.org/packages/5c/5c/a15a59269cd5e74472734516c73795c15eccfc841b3d4b0228c3f53f19d0/rpds_py-2026.5.1-cp315-cp315-win_arm64.whl", hash = "sha256:3609e9939a8a76cd904cf98a3f1f13b5dc7e150adeaee89e0ea09652ea213e16", size = 221245, upload-time = "2026-05-28T12:01:28.51Z" }, + { url = "https://files.pythonhosted.org/packages/e0/22/135ce03804e179a71ceb13be095deda4a279bc88f7a6b8fa161c5ad44e12/rpds_py-2026.5.1-cp315-cp315t-macosx_10_12_x86_64.whl", hash = "sha256:5d333a7127d4b307601ac37792bee01bb95c867cbfacf21b6375b804d6bbd723", size = 352015, upload-time = "2026-05-28T12:01:30.214Z" }, + { url = "https://files.pythonhosted.org/packages/3b/5f/f1f6d2652eb9d848f6eb369d8db83a2da6249bb49ad2c2a48f45d54538d3/rpds_py-2026.5.1-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:b5f077b44a4f7808520f66dae234988d867deb9aed9be5da057ce9ba831b2a41", size = 345016, upload-time = "2026-05-28T12:01:31.656Z" }, + { url = "https://files.pythonhosted.org/packages/88/66/b74182775691ea2290c99e52ac8d5db844e56fbec90ce421f107658c8314/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55d8f9b7b78c9538fc9e04e82ec0e888ff0c3cffcfad152c77e57cd09351a98a", size = 374775, upload-time = "2026-05-28T12:01:33.136Z" }, + { url = "https://files.pythonhosted.org/packages/ff/8f/15e5a61d9f0a43902d36561d4f07cae6ae9f4716be825159fd72717f33af/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e3a8ae58895ac107ed934a6bf51e5846f95c53b9b940c2c6d310838fd5846358", size = 380270, upload-time = "2026-05-28T12:01:34.574Z" }, + { url = "https://files.pythonhosted.org/packages/02/c3/f859b12763a80540cdf2af0f15b19904cf756a71d7bdd3f82ff3e5b1bbf9/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0957cf3c2b8632ec7aaebffebea8005b353cc2a237b6e2ae3c2cac0820704cfb", size = 495285, upload-time = "2026-05-28T12:01:36.127Z" }, + { url = "https://files.pythonhosted.org/packages/1c/c7/ff27c2ac8411d30b03b1829fd88cae8dad1a4d0da48dd25e57c4038042e6/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c396c1304de421050b3681ea70f371874b54d41b0151e96109758144c231e30b", size = 389581, upload-time = "2026-05-28T12:01:37.635Z" }, + { url = "https://files.pythonhosted.org/packages/6e/67/fe92ee32a6cc05c77228a2f8b1762e7124f386ec20ff83d0757b762d58d0/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aad1bff7f666b9598e573815affd666aac6a13a585dde336f843e33350c7fadc", size = 376041, upload-time = "2026-05-28T12:01:39.307Z" }, + { url = "https://files.pythonhosted.org/packages/f8/91/b4d6685c27aba55bd82f25b278be8237038117d05f9659a6213ad3408130/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_31_riscv64.whl", hash = "sha256:656a042550878f12d45752452d47094b7cfe5ad1e9d7b87b5a22ad3ae5ff8015", size = 383946, upload-time = "2026-05-28T12:01:41.043Z" }, + { url = "https://files.pythonhosted.org/packages/bd/79/2c1d832a53c8e0f8e98fc970ec257b950fecd4f62be2ab7182b500a0cbc8/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:73c4bd4f70294737b5206a3e8e30ccadbf8a60301831c8ea23eec5dbeea1ecfa", size = 405526, upload-time = "2026-05-28T12:01:43.032Z" }, + { url = "https://files.pythonhosted.org/packages/78/c4/c98117b03c6a8581ab2c2dfccfe9a5ad82bd8128a3c28b46a6ad2d97c393/rpds_py-2026.5.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:43bca78665423cabae77146f2fe7ce55272b6c8d55d82cca83effd42c7e13972", size = 551165, upload-time = "2026-05-28T12:01:44.648Z" }, + { url = "https://files.pythonhosted.org/packages/3b/c1/bc479ca069200af730881b1bd525e3114b2b391a351509fcb1b772f28086/rpds_py-2026.5.1-cp315-cp315t-musllinux_1_2_i686.whl", hash = "sha256:42d0f20e85e549c870749d0e247f0c10d318a45b7e9676d575d2dcb04a1b2e66", size = 618778, upload-time = "2026-05-28T12:01:46.337Z" }, + { url = "https://files.pythonhosted.org/packages/77/65/38ab2f90df44c2febfb63cc10ced40763d9b4bc94d173e734528663fe7f5/rpds_py-2026.5.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:b1be5c35683684d5331b93600c210e8367c254683d8a6df6bd21bd2da3a334fb", size = 581839, upload-time = "2026-05-28T12:01:48.109Z" }, + { url = "https://files.pythonhosted.org/packages/15/2d/ce1f605fe036aadd460e5822e578c6c7ec3a860936cca37d6e0f299daa77/rpds_py-2026.5.1-cp315-cp315t-win32.whl", hash = "sha256:75808f6c38ce7749bb68cc2770161aae5045e6c6f6781a9782e74b93304399df", size = 207866, upload-time = "2026-05-28T12:01:49.648Z" }, + { url = "https://files.pythonhosted.org/packages/79/cb/966040123eb102371559746908ef2c9471f4d43e17ec9a645a2258dab64b/rpds_py-2026.5.1-cp315-cp315t-win_amd64.whl", hash = "sha256:90bd6630002a1c7f09e7843dd79f0d24f3d2897cc25a753480917865d14f15b3", size = 225441, upload-time = "2026-05-28T12:01:51.408Z" }, ] [[package]] @@ -3741,28 +3758,28 @@ wheels = [ [[package]] name = "sse-starlette" -version = "3.4.1" +version = "3.4.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, { name = "starlette" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e1/9a/f35932a8c0eb6b2287b66fa65a0321df8c84e4e355a659c1841a37c39fdb/sse_starlette-3.4.1.tar.gz", hash = "sha256:f780bebcf6c8997fe514e3bd8e8c648d8284976b391c8bed0bcb1f611632b555", size = 35127, upload-time = "2026-04-26T13:32:32.292Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f7/2b/58abc2d1fd397e7dde08e947e05c884d8ef2f78d5e2588c17a12d42d6994/sse_starlette-3.4.4.tar.gz", hash = "sha256:07e0fa0460138baf25cdd5fb28683472c3995dc1642225191b3832d62526bcb0", size = 31819, upload-time = "2026-05-12T17:37:17.019Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ff/07/45c21ed03d708c477367305726b89919b020a3a2a01f72aaf5ad941caf35/sse_starlette-3.4.1-py3-none-any.whl", hash = "sha256:6b43cf21f1d574d582a6e1b0cfbde1c94dc86a32a701a7168c99c4475c6bd1d0", size = 16487, upload-time = "2026-04-26T13:32:30.819Z" }, + { url = "https://files.pythonhosted.org/packages/dc/67/805710444ea8cc75fbf70b920ed431a560c4bf9c57f7d5a3117213189399/sse_starlette-3.4.4-py3-none-any.whl", hash = "sha256:3f4dd50d8aed2771a091f3a83000323fc3844541c16b4fe585ae2420cc6df973", size = 16514, upload-time = "2026-05-12T17:37:15.601Z" }, ] [[package]] name = "starlette" -version = "1.0.0" +version = "1.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/81/69/17425771797c36cded50b7fe44e850315d039f28b15901ab44839e70b593/starlette-1.0.0.tar.gz", hash = "sha256:6a4beaf1f81bb472fd19ea9b918b50dc3a77a6f2e190a12954b25e6ed5eea149", size = 2655289, upload-time = "2026-03-22T18:29:46.779Z" } +sdist = { url = "https://files.pythonhosted.org/packages/eb/e3/7c1dc7381d9f8ab7d854328ebfa884e62cb3f3d8549ddfd37c7814f42afa/starlette-1.3.1.tar.gz", hash = "sha256:05d0213193f2fbaae60e2ecb593b4add4262ad4e46536b54abe36f11a71724e0", size = 2703240, upload-time = "2026-06-12T09:23:11.602Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0b/c9/584bc9651441b4ba60cc4d557d8a547b5aff901af35bda3a4ee30c819b82/starlette-1.0.0-py3-none-any.whl", hash = "sha256:d3ec55e0bb321692d275455ddfd3df75fff145d009685eb40dc91fc66b03d38b", size = 72651, upload-time = "2026-03-22T18:29:45.111Z" }, + { url = "https://files.pythonhosted.org/packages/ec/bb/2799cc2ede3ed41131f8975621e7213dfc7ef4acbbaadfa440f32500c370/starlette-1.3.1-py3-none-any.whl", hash = "sha256:c7372aae11c3c3f26a42df7bd626cec2f47d03483d261d369516a615a53714c6", size = 73632, upload-time = "2026-06-12T09:23:10.017Z" }, ] [[package]] @@ -3776,26 +3793,26 @@ wheels = [ [[package]] name = "tqdm" -version = "4.67.3" +version = "4.68.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/09/a9/6ba95a270c6f1fbcd8dac228323f2777d886cb206987444e4bce66338dd4/tqdm-4.67.3.tar.gz", hash = "sha256:7d825f03f89244ef73f1d4ce193cb1774a8179fd96f31d7e1dcde62092b960bb", size = 169598, upload-time = "2026-02-03T17:35:53.048Z" } +sdist = { url = "https://files.pythonhosted.org/packages/87/d7/0535a28b1f5f24f6612fb3ff1e89fb1a8d160fee0f976e0aa6803862134b/tqdm-4.68.3.tar.gz", hash = "sha256:00dfa48452b6b6cfae3dd9885636c23d3422d1ec97c66d96818cbd5e0821d482", size = 170596, upload-time = "2026-06-17T07:36:52.105Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/16/e1/3079a9ff9b8e11b846c6ac5c8b5bfb7ff225eee721825310c91b3b50304f/tqdm-4.67.3-py3-none-any.whl", hash = "sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf", size = 78374, upload-time = "2026-02-03T17:35:50.982Z" }, + { url = "https://files.pythonhosted.org/packages/d8/8e/bb97bb0c71802080bfc8952937d174e49cfc50de5c951dd47b2496f0dcdb/tqdm-4.68.3-py3-none-any.whl", hash = "sha256:39832cc2def2789a6f29df83f172db7416cea70052c0907a57801c5f2fdccb03", size = 78337, upload-time = "2026-06-17T07:36:50.132Z" }, ] [[package]] name = "types-requests" -version = "2.33.0.20260503" +version = "2.33.0.20260518" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a1/b8/57e94268c0d82ac3eaa2fc35aa8ca7bbc2542f726b67dcf90b0b00a3b14d/types_requests-2.33.0.20260503.tar.gz", hash = "sha256:9721b2d9dbee7131f2fb39f20f0ebb1999c18cef4b512c9a7932f3722de7c5f4", size = 23931, upload-time = "2026-05-03T05:20:08.882Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/01/c5a19253fe1ac159159ddf9a3a07cec8bb5e486ec4d9002ad2821da0e5d2/types_requests-2.33.0.20260518.tar.gz", hash = "sha256:df7bd3bfe0ca8402dfb841e7d9be714bb5578203283d66d7dc4ef69343449a5e", size = 24752, upload-time = "2026-05-18T06:07:37.966Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c3/82/959113a6351f3ca046cd0a8cd2cee071d7ea47473560557a01eeae9a6fe2/types_requests-2.33.0.20260503-py3-none-any.whl", hash = "sha256:02aaa7e3577a13471715bb1bddb693cc985ea514f754b503bf033e6a09a3e528", size = 20736, upload-time = "2026-05-03T05:20:07.858Z" }, + { url = "https://files.pythonhosted.org/packages/1c/bc/b139710a3b6018f7fb2b9508b35c8af564e61bf2bf4fa619d088f3e16f85/types_requests-2.33.0.20260518-py3-none-any.whl", hash = "sha256:626d697d1adaaff76e2044dc8c5c051d8f21abc157bdfe204a75558076fe0bf0", size = 21391, upload-time = "2026-05-18T06:07:37.044Z" }, ] [[package]] @@ -3821,11 +3838,11 @@ wheels = [ [[package]] name = "uncalled-for" -version = "0.3.1" +version = "0.3.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e1/68/35c1d87e608940badbcfeb630347aa0509897284684f61fab6423d02b253/uncalled_for-0.3.1.tar.gz", hash = "sha256:5e412ac6708f04b56bef5867b5dcf6690ebce4eb7316058d9c50787492bb4bca", size = 49693, upload-time = "2026-04-07T13:05:06.462Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b5/82/345cc927f7fbdae6065e7768759932fcc827fc20b29b45dfbafa2f1f7da4/uncalled_for-0.3.2.tar.gz", hash = "sha256:89f5dbcd71e2b8f47c030b1fa302e6cce2ec795d1ac565eeb6525c5fe55cb8a2", size = 50032, upload-time = "2026-05-06T13:38:25.204Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/11/e1/7ec67882ad8fc9f86384bef6421fa252c9cbe5744f8df6ce77afc9eca1f5/uncalled_for-0.3.1-py3-none-any.whl", hash = "sha256:074cdc92da8356278f93d0ded6f2a66dd883dbecaf9bc89437646ee2289cc200", size = 11361, upload-time = "2026-04-07T13:05:05.341Z" }, + { url = "https://files.pythonhosted.org/packages/3b/25/2c87754f3a9e692315f7b811244090e68f362979fc8886b3fbd2985a1d8c/uncalled_for-0.3.2-py3-none-any.whl", hash = "sha256:0ff60b142c7d1f8070bde9d42afaa70aedc77dcc10998c227687e9c15713418e", size = 11444, upload-time = "2026-05-06T13:38:24.025Z" }, ] [[package]] @@ -3839,15 +3856,15 @@ wheels = [ [[package]] name = "uvicorn" -version = "0.46.0" +version = "0.49.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, { name = "h11" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1f/93/041fca8274050e40e6791f267d82e0e2e27dd165627bd640d3e0e378d877/uvicorn-0.46.0.tar.gz", hash = "sha256:fb9da0926999cc6cb22dc7cd71a94a632f078e6ae47ff683c5c420750fb7413d", size = 88758, upload-time = "2026-04-23T07:16:00.151Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c4/1f/fa18009dea8469069cca78a4e877a008ab78f08b064bfc9ab891579077ff/uvicorn-0.49.0.tar.gz", hash = "sha256:ebf4271aa580d9de97f93192d4595176df6e91f9aae919ca73e4fc07df1e66a3", size = 91284, upload-time = "2026-06-03T22:01:30.448Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/31/a3/5b1562db76a5a488274b2332a97199b32d0442aca0ed193697fd47786316/uvicorn-0.46.0-py3-none-any.whl", hash = "sha256:bbebbcbed972d162afca128605223022bedd345b7bc7855ce66deb31487a9048", size = 70926, upload-time = "2026-04-23T07:15:58.355Z" }, + { url = "https://files.pythonhosted.org/packages/88/fa/e1388bbcf24ef3274f45c0c1c7b501fd14971037c1b6ee23610553307497/uvicorn-0.49.0-py3-none-any.whl", hash = "sha256:ba3d14c3ee7e41c6c654c46c9eb489d33213cdd30aa1696eab1374337c13f68f", size = 71376, upload-time = "2026-06-03T22:01:29.037Z" }, ] [package.optional-dependencies] @@ -3895,7 +3912,7 @@ wheels = [ [[package]] name = "virtualenv" -version = "21.3.1" +version = "21.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "distlib" }, @@ -3903,79 +3920,95 @@ dependencies = [ { name = "platformdirs" }, { name = "python-discovery" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ec/0d/915c02c94d207b85580eb09bffab54438a709e7288524094fe781da526c2/virtualenv-21.3.1.tar.gz", hash = "sha256:c2305bc1fddeec40699b8370d13f8d431b0701f00ce895061ce493aeded4426b", size = 7613791, upload-time = "2026-05-05T01:34:31.402Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/a5/81f987504738e6defeed61ec1c47e2aefab3c35d8eeb87e1b3f38cf28254/virtualenv-21.5.1.tar.gz", hash = "sha256:dca3bf98275a59c652b69d68e73433e597d977c2da9198882479d1a7188009c8", size = 4578798, upload-time = "2026-06-16T16:23:58.603Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b1/4f/f71e641e504111a5a74e3a20bc52d01bd86788b22699dd3fee1c63253cf6/virtualenv-21.3.1-py3-none-any.whl", hash = "sha256:d1a71cf58f2f9228fff23a1f6ec15d39785c6b32e03658d104974247145edd35", size = 7594539, upload-time = "2026-05-05T01:34:28.98Z" }, + { url = "https://files.pythonhosted.org/packages/2c/02/3623e6169bed617ed1e2d372f7c69f92ec28d54c4dfc997055c8578ec148/virtualenv-21.5.1-py3-none-any.whl", hash = "sha256:55aa670b67bbfb991b03fda39bd3276d92c419d702376e98c5df1c9989a26783", size = 4558820, upload-time = "2026-06-16T16:23:56.963Z" }, ] [[package]] name = "watchfiles" -version = "1.1.1" +version = "1.2.0" 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, upload-time = "2025-10-14T15:06:21.08Z" } -wheels = [ - { 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" }, +sdist = { url = "https://files.pythonhosted.org/packages/cd/41/5e1a4bb12aac5f1493fa1bdc11154eca3b258ca4eba65d39c473fe19d8e9/watchfiles-1.2.0.tar.gz", hash = "sha256:c995fba777f1ea992f090f9236e9284cf7a5d1a0130dd5a3d82c598cacd76838", size = 108252, upload-time = "2026-05-18T04:32:04.251Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b8/2f/e42c992d2afda3108ea1c02acecc991b9f31d05c14adc2a7cee9ee211fc4/watchfiles-1.2.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:bc13eb17538be00c874699dc0abe4ee2bc8d50bb1166a6b9e175ef3fd7eb8f26", size = 400115, upload-time = "2026-05-18T04:32:02.06Z" }, + { url = "https://files.pythonhosted.org/packages/5f/8f/6af2ea19065c91d8b0ea3516fdfc8c0d349f407e8e9fbf4e5a17360de8ad/watchfiles-1.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2d95ddc1eb6914154253d239089900813f6a767e174b8e6a50e7fdacb7e4236c", size = 393659, upload-time = "2026-05-18T04:30:50.951Z" }, + { url = "https://files.pythonhosted.org/packages/13/01/b32a967c56fb3e3e5be3db52c3d3b87fa4513aa367d8ed1ad96d42952e5f/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f70d8b291ef6e88d19b1f297a6905ddb978888d9272b0d05e6f53309856bcfc", size = 453207, upload-time = "2026-05-18T04:31:04.231Z" }, + { url = "https://files.pythonhosted.org/packages/04/98/97557a812180338cb1abd32e1cffcc4588f59b5f23e0cb006b2ba95ba64a/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:56d8641cf834c2836922899105bd3ce3d0dfc69291d52edf0b4d0436829b34c0", size = 459273, upload-time = "2026-05-18T04:31:50.377Z" }, + { url = "https://files.pythonhosted.org/packages/e8/a8/b4b08dcb7653b8087c6586f7ce649505900e866bbcfe40dc9587af02e686/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2581a94056e55d7d0a31a823ea92bf73749c489ca2285bfdc0fbe6b2bb49d50c", size = 489927, upload-time = "2026-05-18T04:31:42.485Z" }, + { url = "https://files.pythonhosted.org/packages/50/94/3dceea03545d2e5ddfd839f0ddd5e1cecbf1697b5a428d5ba11cef6af95d/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:41bc1199f7523b3f82843c88cbb979180c949caef0342cf90968f178e5d49b01", size = 570476, upload-time = "2026-05-18T04:31:03.071Z" }, + { url = "https://files.pythonhosted.org/packages/cc/f2/d39a5450c3532092b91f81d274360e613c2371bc874a89c7a1a3c5e8d138/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7571e4464cb6e434958f867f7f730b8ab0b75e3f8e5eac0499168486ab3c33a8", size = 465650, upload-time = "2026-05-18T04:30:12.701Z" }, + { url = "https://files.pythonhosted.org/packages/22/24/ed72f68cbc1333ca9b9f2200aa048bb6658ae41709bc1caad4310f4bdffd/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e53a384f76b631c3ae5334ce6a52f0baa3a911eb94a4eac7f160079868b716d5", size = 456398, upload-time = "2026-05-18T04:30:13.784Z" }, + { url = "https://files.pythonhosted.org/packages/0d/64/982ef4a4e5bab5b6e5b6becc8cd5e732f6130a78b855f0abec6439a9a135/watchfiles-1.2.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:d20029a60a71a052a24c4db7673bc4de39ab89adbaccbfb5d67987c5d73f424d", size = 465140, upload-time = "2026-05-18T04:31:52.111Z" }, + { url = "https://files.pythonhosted.org/packages/a0/0c/95282abf4ed680b6096010bcfc30c5fa7a041fc5aa5a2ad17a2cc6c75bba/watchfiles-1.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:2cb93af48550faf1cea04c303107c8b75833de7013e57ce27d3b8d21d8d0f58c", size = 630259, upload-time = "2026-05-18T04:31:25.676Z" }, + { url = "https://files.pythonhosted.org/packages/30/45/607c1de1530c4bdcf2cf1d1ecc2505ddba5d96bd43ba9f2b0e79876f850f/watchfiles-1.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2995c176de7692b86a2e4c58d9ec718f753150a979cb4a754e2b4ffa38e70906", size = 659859, upload-time = "2026-05-18T04:30:24.333Z" }, + { url = "https://files.pythonhosted.org/packages/fa/08/d9e2e0f9e8e6791d33aefc694ad7eefa7f901f63caff84a81ded38692f9c/watchfiles-1.2.0-cp312-cp312-win32.whl", hash = "sha256:7a2cffd17d27d2ecbb310c2b1d8174f222a5495b1a721894afa88ec11e25b898", size = 275480, upload-time = "2026-05-18T04:30:31.307Z" }, + { url = "https://files.pythonhosted.org/packages/1c/e6/9d42569c0102645cc8cea5d8c7d8a1e9d4ada2cb7f05f75e554b8aa2202a/watchfiles-1.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:f155b3a1b2a5fc89cdc70d47ee5d54e3b75e88efa34982028a35daef9ba00379", size = 288718, upload-time = "2026-05-18T04:32:10.745Z" }, + { url = "https://files.pythonhosted.org/packages/0a/26/88e0dc6ee3898169d7fa22bb6a69cabf2502d2ee25cb8c876d1262d204f8/watchfiles-1.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:8fa585ede612ee9f9e91b18bebf9ba11b9ae29a4e3a0d0cf6fca3e382133f0d5", size = 281026, upload-time = "2026-05-18T04:30:22.23Z" }, + { url = "https://files.pythonhosted.org/packages/d1/4d/70a7feced9f87e2ff26dba42667290f41694fc64646c67261fbb8cab5d5c/watchfiles-1.2.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:01ea8d66f0693b9b60a6541c8d10263091ca9a9060d242f3c1f3143f9aad2c98", size = 399730, upload-time = "2026-05-18T04:31:38.162Z" }, + { url = "https://files.pythonhosted.org/packages/31/3a/0da302f2307aee316922806ebd5726c542cbd787c938271cf14a074c7daf/watchfiles-1.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7ba0480b9a74af058f43b337e937a451e109295c420916d68ad24e3dc02f5e44", size = 392842, upload-time = "2026-05-18T04:30:27.051Z" }, + { url = "https://files.pythonhosted.org/packages/db/ef/d5bdb705c224dbc256aa0c1ec47bf4e61ec52558f2afb44a71a1fe4d7015/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f34e26a19f91f710c08e0183429f0d1d15df734e6bc78c31e77b9ea9c433658", size = 452989, upload-time = "2026-05-18T04:31:11.945Z" }, + { url = "https://files.pythonhosted.org/packages/71/29/5495f2c1661949ef7a35e4d71111d129cfe7606414a26887a919d0a55406/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b4e77f6a55f858504069abd35d336a637555c09bca453dde1ee1e5ada8a6a1fb", size = 458978, upload-time = "2026-05-18T04:30:52.606Z" }, + { url = "https://files.pythonhosted.org/packages/d5/8c/7f9c07c433811c2fffd93e13fdfb7135de9aab5f2ae41be08960fa0047dc/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0cb4d80e212f116474a545c21c912b445f16bb0cef9e6a73a498164223e14e2f", size = 490248, upload-time = "2026-05-18T04:31:36.003Z" }, + { url = "https://files.pythonhosted.org/packages/3c/11/d93632febc52fbc21be90231bb7c17fd5387f46c9076fd40a5f9c2ae6910/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b974946a10af379d425e2eef5b62f5c6ebeaccf91d45eaad6f5b27ecd4f91aa0", size = 571847, upload-time = "2026-05-18T04:31:10.862Z" }, + { url = "https://files.pythonhosted.org/packages/55/b4/383173e73aabb07ad1d9c7aa859d95437ac46a6d6a1e11005facda0c9d19/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86bc13c25a8d1fcd70b51d0ce7c9b65e90de5666fcbfd3e34957cc73ee19aeb5", size = 465974, upload-time = "2026-05-18T04:30:17.006Z" }, + { url = "https://files.pythonhosted.org/packages/a7/6c/89b1a230a78f57c52dd8893adb1f92f94411721b6ec12596c56d98c74356/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca148d73dea36c9763aaa351e4d7a51780ec1584217c45276f4fe8239c768b71", size = 454782, upload-time = "2026-05-18T04:30:35.656Z" }, + { url = "https://files.pythonhosted.org/packages/24/62/1732118367cfff0a9fce3bf62ff4bfded09ef5df21d9d446b858b3f70a96/watchfiles-1.2.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:c525543d91961c6955b2636b308569e84a1d1c5f5f2932041ab9ef46422f43e3", size = 465182, upload-time = "2026-05-18T04:30:20.846Z" }, + { url = "https://files.pythonhosted.org/packages/28/96/716f7e5f51339bf22963f3345f9f27d7f3b30e2eadc597e257c881dd3c53/watchfiles-1.2.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:a204794696ffb8f9b10fba6f7cb5216d42f3b2b71860ccac6b6e42f5f10973b0", size = 629841, upload-time = "2026-05-18T04:31:05.397Z" }, + { url = "https://files.pythonhosted.org/packages/4c/fe/c40783950fd771ccf66ab3ec2722d188a9af1c7f96c6e811f36e40c6e03f/watchfiles-1.2.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:10d86db20695afe7997ac9e1717637d6714a8d0220458c33f3d2061f54cec427", size = 658028, upload-time = "2026-05-18T04:31:48.22Z" }, + { url = "https://files.pythonhosted.org/packages/71/72/4508db1856d1d87fcbb3b63f4839bab1b5682cb0e8d224d122263c09654a/watchfiles-1.2.0-cp313-cp313-win32.whl", hash = "sha256:eb283ee99e21ad6443c8cdb06ac5b34b1308c329cbdf03fa02b445363714c799", size = 275183, upload-time = "2026-05-18T04:30:59.57Z" }, + { url = "https://files.pythonhosted.org/packages/f9/36/14b76ca57652e5cc5fd1c11f32a261292c08a0d19a00351013c2549cbfb2/watchfiles-1.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:a0f27f01bee51861392bb6b7c4fdb290b27d1eb194e9e28788d68102a0e898d9", size = 288059, upload-time = "2026-05-18T04:32:07.937Z" }, + { url = "https://files.pythonhosted.org/packages/1b/8d/0a85e395398d8d20fadfe5c5d32c726eee17a519e78fb356f2cf7531bffe/watchfiles-1.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:3651aa7058595e9cfb75d35dd5ada2bf9f48a5b8a0f3562821d3e210c507e077", size = 280186, upload-time = "2026-05-18T04:31:54.484Z" }, + { url = "https://files.pythonhosted.org/packages/37/68/36db056f1fdcc5f07302f56e631774d6835bcd6fa3ace402304621d5f9e5/watchfiles-1.2.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:faea288b6f0ab1902ef08f4ca6de005dccf856c4e0c4f21b8c5fce02d90a1b08", size = 399031, upload-time = "2026-05-18T04:30:44.576Z" }, + { url = "https://files.pythonhosted.org/packages/c1/64/01a9d6f66a82a5c101ce939274106cc72759d62427e153f01edd2b9f87c2/watchfiles-1.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:01859b11fd9fbca670f4d5da00fbac282cfea9bd67a2125d8b2833a3b5617ea9", size = 391205, upload-time = "2026-05-18T04:30:25.413Z" }, + { url = "https://files.pythonhosted.org/packages/84/2c/0a44fe058cb4bb7b8ede6b6670698bbb7c0400740e378d00022189b7b31d/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fff610d7bb2256a317bb1e96f0d7862c7aa8076733ee5df0fd41bbe76a24a4f4", size = 451892, upload-time = "2026-05-18T04:32:14.005Z" }, + { url = "https://files.pythonhosted.org/packages/67/a1/351e0d56cd35e6488b5c8b4fb11a809a5bc923e8fe8fed9faf8920be0c89/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b141a4891c995a039cd89e9a49e62df1dc8a559a5d1a6e4c7106d16c12777a55", size = 458867, upload-time = "2026-05-18T04:31:22.279Z" }, + { url = "https://files.pythonhosted.org/packages/d5/7d/9d09605187f1b838998624049fcf8bf47b73c1a3b76901fcac1782f62277/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f22943b7770483f6ea0721c6b11d022947a98eb0acae14694de034f4d0d38925", size = 490217, upload-time = "2026-05-18T04:31:43.657Z" }, + { url = "https://files.pythonhosted.org/packages/60/5d/a17a16eccb182f04188cd308ec24b1a71a9b5c4e7098269cf35d9fa56d02/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1bc6195825b7dcd217968bb1f801a60fd4c16e8eeab5bedc7fe917d7d5995ab4", size = 571458, upload-time = "2026-05-18T04:32:11.875Z" }, + { url = "https://files.pythonhosted.org/packages/d3/3d/4dd457062083ab1938e5dfd45032eb425cee2ac817287ca8ff4356183e5d/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d4a4b147f5dca2a5d325a06a832fb43f345751adfbc63204aec30e0d9ca965a2", size = 464707, upload-time = "2026-05-18T04:30:43.492Z" }, + { url = "https://files.pythonhosted.org/packages/c6/71/ea8c57b128f5383de74d0c7d2d9c57ad7c9a65a930c451bd25d524b295b7/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4543579a9bdb0c9560039b4ffddbdb39545707659fbc430ce4c10f3f68d557f9", size = 454663, upload-time = "2026-05-18T04:30:16.061Z" }, + { url = "https://files.pythonhosted.org/packages/53/fd/2e812bf938406d7db351f0703ddd3fc6c061cf30d96153a77bc79a943a44/watchfiles-1.2.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:20aa0e708b920bde876a4aa82dc7dd6ebea228a63a67cda6632c2fc87b787efa", size = 463537, upload-time = "2026-05-18T04:31:44.9Z" }, + { url = "https://files.pythonhosted.org/packages/86/56/d17a7f1dd1bc3035f1072694a551301272f1739c2d8e319c927cb9e29b38/watchfiles-1.2.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:d413349d565dab74297f2a63e84a097936be69bf8f3b3801f27f380e32040f44", size = 629194, upload-time = "2026-05-18T04:31:14.141Z" }, + { url = "https://files.pythonhosted.org/packages/be/06/f1ff66bf5cae50aa4062779a0ecd0bbaf15e466195719074078947d9a17d/watchfiles-1.2.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:f28b2725eb8cce327b9b3ab02415c853011dc55c95832fe90de6bc56f5315f72", size = 656194, upload-time = "2026-05-18T04:31:47.14Z" }, + { url = "https://files.pythonhosted.org/packages/e7/54/a9c7ea9a82a4ac65e7004c0a03920b5cdd2f9c3b678757d9cd425aa51d53/watchfiles-1.2.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:b8c8358484d5fa12ef34f05b7f4168eaf1932f408725ff6d023c33ec17bd79d4", size = 400205, upload-time = "2026-05-18T04:32:05.153Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5d/c9ab3534374a4a67450696905d6ef16a04405448b8dc52bd752ae50423d4/watchfiles-1.2.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9f04b092229ad2c50126dd3c922c8822e51e605993764a33058d4a791ab42281", size = 392508, upload-time = "2026-05-18T04:30:54.849Z" }, + { url = "https://files.pythonhosted.org/packages/26/ca/1ad30103535cf0cecd7b993e8d50edc5351b1820e38f2d22e3df58962feb/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a7ce236284f002a156f70add88efe5c70879cccbb658be0822c54b1306fc09d", size = 452448, upload-time = "2026-05-18T04:30:53.727Z" }, + { url = "https://files.pythonhosted.org/packages/37/a1/ceee2cdf2afbd715fa07758d39c9859513eae411b23196f7fd039e5feedd/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b9909cc2b48468b575eefa944919e1fe8a36c5849d5c7c168f80a8c1db69398e", size = 459605, upload-time = "2026-05-18T04:30:23.312Z" }, + { url = "https://files.pythonhosted.org/packages/e8/f6/421e30fd1cb3907a84ed92ab3f1983e37ba2dca015e9a894a048418417a2/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a37faaed405c67e28e6be45a1fa4f206ef5a2860f27c237db9fa30704c38242", size = 490757, upload-time = "2026-05-18T04:30:47.358Z" }, + { url = "https://files.pythonhosted.org/packages/41/b0/55ed1b97ed08be7bba6f9a541cac15f2a858e1d74d2b07b6da70a82aab00/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9649193aa27bd9ff2e80ff29bfaa93085496c7a3a377592823cc58b77ee88add", size = 568672, upload-time = "2026-05-18T04:30:38.915Z" }, + { url = "https://files.pythonhosted.org/packages/d1/cf/d8ae8a80dd7bafab395ea7681c10237311bbf34d37704a8c744e7cf31fc7/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e4ff8e37f99cf1da89e255e07c9c4b37c214038c4283707bdec308cb1b0ea1f", size = 464197, upload-time = "2026-05-18T04:30:09.914Z" }, + { url = "https://files.pythonhosted.org/packages/7c/8a/3076c496ca8dafe0e8cd03fcebdfc47be4b1174b4e5b24ff6e396e6b3af2/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:054dc20fd2e3132b4c3883b4a00d72fd6e1f56fdaf89fccd12e8057d74cd74d7", size = 453181, upload-time = "2026-05-18T04:30:14.829Z" }, + { url = "https://files.pythonhosted.org/packages/e5/10/9745e17c98e7b8a86454df0a3c7b5686bd650383f1e9f26e4ebcbd6cc0c0/watchfiles-1.2.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:e140ed30ebde76796b686e67c182cff10ea2fbab186fafd1560f74bb5a473a6e", size = 465109, upload-time = "2026-05-18T04:30:28.123Z" }, + { url = "https://files.pythonhosted.org/packages/8f/95/8ef4a95481d3e0cb52d62a06fa6e972e81424be2d9698b91a2fecca9904c/watchfiles-1.2.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:bb7e52ecf68ba46d22df23467b87cffeb2146908aa523ebfe803019618cfda06", size = 630653, upload-time = "2026-05-18T04:31:49.304Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e4/3b3bf36b0f829b50c6ebcb8d031583863c59f923d6a6af3d485e470d0fac/watchfiles-1.2.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:23282a321c8baf9b3a3c4afff673f9fe65eb7fdc2338d765ccad9d3d1916a5ba", size = 657838, upload-time = "2026-05-18T04:31:06.497Z" }, + { url = "https://files.pythonhosted.org/packages/21/b1/6cbbb50c1f3002ab568777d44aa21206dfb8807a840990c4037523b51812/watchfiles-1.2.0-cp314-cp314-win32.whl", hash = "sha256:c0db965c5f79aa49fe672d297cf1febc5ad149b658594944f49a54a2b96270a7", size = 275108, upload-time = "2026-05-18T04:30:06.891Z" }, + { url = "https://files.pythonhosted.org/packages/92/45/190ce6db8dcb4536682cf75d3889ff1a27182a58cb519d343cb6d9ea63d8/watchfiles-1.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:71283b39fd17e5408eb123bd37aeecfd9d54c81fc184421943208aadb879d103", size = 288441, upload-time = "2026-05-18T04:32:12.901Z" }, + { url = "https://files.pythonhosted.org/packages/74/0d/3eae1c2313ab08378431d907c3f8095ecca00f3eda33111cf4f0f2591799/watchfiles-1.2.0-cp314-cp314-win_arm64.whl", hash = "sha256:c5c19526f4e54a00f2666a6c0e9e40d582c09e865055ea7378bf0009aab857b3", size = 280684, upload-time = "2026-05-18T04:31:26.902Z" }, + { url = "https://files.pythonhosted.org/packages/b1/75/fb64e6c25d6b5ca636d03df34ffb1c6e9873303e76d27967e045f8df088f/watchfiles-1.2.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:d73a585accffa5ae39c17264c36ec3166d2fad7000c780f5ef83b2722afb9dd2", size = 398857, upload-time = "2026-05-18T04:32:17.108Z" }, + { url = "https://files.pythonhosted.org/packages/73/4e/9f7adf01754cbf81843722ccfec169d8f26c69778281a302855cecd2ee08/watchfiles-1.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ae99b14c5f21e026e0e9d96f40e07d8570ebee6cafd9d8fc318354606daa7a28", size = 392413, upload-time = "2026-05-18T04:31:07.911Z" }, + { url = "https://files.pythonhosted.org/packages/47/c8/bec626bcc2d69f44b9acb24ce7d60ed7b16b73628eea747fcbd169d8edda/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4429f3b105524a10b72c3a819b091c495d2811d419c1e1e8df773a5a5974f831", size = 452409, upload-time = "2026-05-18T04:31:20.142Z" }, + { url = "https://files.pythonhosted.org/packages/00/b7/b6362068e81e7c556d155a34c35d40ac3ef42d747b06d7f6e5bf58e359c2/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:43d818978d06062d9b22c4fab2ebe44cf5213d42dc8e62bda8c2760cfa2eeb33", size = 458827, upload-time = "2026-05-18T04:32:06.219Z" }, + { url = "https://files.pythonhosted.org/packages/67/f8/9a813fa42afb1e0b4625e75f0479826644d3ee8dc287e093799bc01f390c/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b9f732dc58b2dbe69e464ccf8fff7a03b0dd0be439da4c0720d3558527d3d6b4", size = 490104, upload-time = "2026-05-18T04:31:56.034Z" }, + { url = "https://files.pythonhosted.org/packages/2f/bf/27dfb6094ca4c9aad21298b5525b6c53cb36121ee454331d05161e58d130/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f200104103feb097de4cab8fe4f5dd18a2026934c7dea98c55a2f5fd6d5a33b", size = 571360, upload-time = "2026-05-18T04:31:57.133Z" }, + { url = "https://files.pythonhosted.org/packages/fb/39/44a096d67270ea93df91d33877dbe91fbda3aa4f8ec2edf799d93eda8736/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:63ac26eefbf4af1741247d6fb68b11c49a25b2f7413fbd318a83a12aaa9cf666", size = 464644, upload-time = "2026-05-18T04:30:57.33Z" }, + { url = "https://files.pythonhosted.org/packages/0e/80/c7472203bad6268e3ef1ad260739704847898938ad7ea8b63a5131f46b50/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c4997d4e4a55f0d02b6cde327322daf3a0400e5df6c6b15948994bf72497925", size = 454771, upload-time = "2026-05-18T04:30:48.736Z" }, + { url = "https://files.pythonhosted.org/packages/51/cf/3b10b268b4b7f0fc26e9debb5eef1998b515887840f444cd3ec80c688755/watchfiles-1.2.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:4c887eba18b7945ac73067a8b4a66f21cd46c2539b2bc68588f7be6c7eb6d26b", size = 463494, upload-time = "2026-05-18T04:31:33.826Z" }, + { url = "https://files.pythonhosted.org/packages/3d/3e/a4302545cd589262a0dc7d140e86f7688eba3f9c72776c27f7e23b8864c4/watchfiles-1.2.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:3416ff151bb6b5a8d8d11664974fbef4d9305b9b2957839ab5a270468fd8df30", size = 629383, upload-time = "2026-05-18T04:31:15.596Z" }, + { url = "https://files.pythonhosted.org/packages/db/99/d5649df0a9a410d45b7c882304d0b790903ac9b6e8f2cfd12114e0c6b9f2/watchfiles-1.2.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:0e831a271c035d89789cffc386b6aa1375f39f1cd25eb7ca0997e4970d152fc5", size = 656093, upload-time = "2026-05-18T04:31:58.707Z" }, + { url = "https://files.pythonhosted.org/packages/92/b9/362702539275019a54dd2e94511b31a9b89c5f9e6a21966de7eb692549fc/watchfiles-1.2.0-cp315-cp315-macosx_10_12_x86_64.whl", hash = "sha256:37a6721cdf3f65dbb13aa9503510ccb4451603ac837e44d265d7992a597e1374", size = 400109, upload-time = "2026-05-18T04:31:16.879Z" }, + { url = "https://files.pythonhosted.org/packages/8f/75/71d5ba62db781e5587bded1d944c675374bc4aa37ff33d5018d98e8b6538/watchfiles-1.2.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:2b37d10b5a63bd4d87e18472d80fa525bd670586fae62e5dd580452764879b65", size = 392167, upload-time = "2026-05-18T04:31:28.058Z" }, + { url = "https://files.pythonhosted.org/packages/3c/01/c66dd95d0423fe30d31820e2d1d5bda773764131bbb6ac0cb1cf303ac328/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a105bc2283f67e8fbec74253ec2d94925de92ed72c0393f1206bf326b7b7b69", size = 452372, upload-time = "2026-05-18T04:31:00.836Z" }, + { url = "https://files.pythonhosted.org/packages/91/15/2fe99557e72f85627c6a8eed50d889e8d101623e060a22ad75b875cb932d/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5327989a465505f05cfe06f04fa9d0c2fd5432bb243e10e6f012b1bdca3c8579", size = 459596, upload-time = "2026-05-18T04:31:34.96Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/d4acfa0023367428ed48351b3b9b267893037b6cadae55620c61c24bcfd4/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ecb47f183a8025b2aa18b546725c3657e542112ae9c0613a2af79b4fa8d04ad7", size = 490869, upload-time = "2026-05-18T04:31:59.923Z" }, + { url = "https://files.pythonhosted.org/packages/a4/5f/3164cbdce06c9fb95c4f7b9e2f9760b5e2797af43a9ecc317ef42a23a278/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8520a4ab0e37f770afc34459c4f8f7019e153f9124dc101c15538365875d1ab2", size = 571641, upload-time = "2026-05-18T04:32:00.948Z" }, + { url = "https://files.pythonhosted.org/packages/41/e6/85d3731c55e65cd7690f3f803d24c139588aaf863e4bf2148fe7a7fa1a19/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:71cd71740ed2c15211ebb237ced4e39a1cdf6f80566e5fe95428da1626f4fde6", size = 464444, upload-time = "2026-05-18T04:30:34.298Z" }, + { url = "https://files.pythonhosted.org/packages/f4/7d/562641012b8b09872742c3b8adf9629ec479fd78f8d68ae4a0c13da8add6/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f88af53d6ddaf72179ef613ddc905e6f4785f712b49b80b3bef9f3525e6194b4", size = 453593, upload-time = "2026-05-18T04:31:23.464Z" }, + { url = "https://files.pythonhosted.org/packages/56/fe/cb8ef3d6f929d14158fdaaad9925985b7310abc9384dcd4d82dd0016fb59/watchfiles-1.2.0-cp315-cp315-manylinux_2_31_riscv64.whl", hash = "sha256:cee9d5efd929efdac5f7e58f72b3376f676b64050a91c5b99a7094c5b2317488", size = 465096, upload-time = "2026-05-18T04:31:30.384Z" }, + { url = "https://files.pythonhosted.org/packages/25/91/80908e835e100527a9267147b08c0eee1fa6ab0ffec15edc04d1d44885f7/watchfiles-1.2.0-cp315-cp315-musllinux_1_1_aarch64.whl", hash = "sha256:b718bf356bbc15e559bd8ef41782b573b8ae0e3f177ab244b440568d7ea02cfb", size = 630638, upload-time = "2026-05-18T04:30:49.89Z" }, + { url = "https://files.pythonhosted.org/packages/46/4b/95ab2f256bb4af3cb2eb23b9317bda984ee6e0f11733a5c004a6c95b06e3/watchfiles-1.2.0-cp315-cp315-musllinux_1_1_x86_64.whl", hash = "sha256:922c0e019fe68b3ae392965a766b02a71ba1168c932cebc3733cd52c5fe5b377", size = 657684, upload-time = "2026-05-18T04:31:32.027Z" }, ] [[package]] @@ -4101,113 +4134,91 @@ wheels = [ [[package]] name = "yarl" -version = "1.23.0" +version = "1.24.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "idna" }, { name = "multidict" }, { name = "propcache" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/23/6e/beb1beec874a72f23815c1434518bfc4ed2175065173fb138c3705f658d4/yarl-1.23.0.tar.gz", hash = "sha256:53b1ea6ca88ebd4420379c330aea57e258408dd0df9af0992e5de2078dc9f5d5", size = 194676, upload-time = "2026-03-01T22:07:53.373Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/88/8a/94615bc31022f711add374097ad4144d569e95ff3c38d39215d07ac153a0/yarl-1.23.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1932b6b8bba8d0160a9d1078aae5838a66039e8832d41d2992daa9a3a08f7860", size = 124737, upload-time = "2026-03-01T22:05:12.897Z" }, - { url = "https://files.pythonhosted.org/packages/e3/6f/c6554045d59d64052698add01226bc867b52fe4a12373415d7991fdca95d/yarl-1.23.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:411225bae281f114067578891bc75534cfb3d92a3b4dfef7a6ca78ba354e6069", size = 87029, upload-time = "2026-03-01T22:05:14.376Z" }, - { url = "https://files.pythonhosted.org/packages/19/2a/725ecc166d53438bc88f76822ed4b1e3b10756e790bafd7b523fe97c322d/yarl-1.23.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:13a563739ae600a631c36ce096615fe307f131344588b0bc0daec108cdb47b25", size = 86310, upload-time = "2026-03-01T22:05:15.71Z" }, - { url = "https://files.pythonhosted.org/packages/99/30/58260ed98e6ff7f90ba84442c1ddd758c9170d70327394a6227b310cd60f/yarl-1.23.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cbf44c5cb4a7633d078788e1b56387e3d3cf2b8139a3be38040b22d6c3221c8", size = 97587, upload-time = "2026-03-01T22:05:17.384Z" }, - { url = "https://files.pythonhosted.org/packages/76/0a/8b08aac08b50682e65759f7f8dde98ae8168f72487e7357a5d684c581ef9/yarl-1.23.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:53ad387048f6f09a8969631e4de3f1bf70c50e93545d64af4f751b2498755072", size = 92528, upload-time = "2026-03-01T22:05:18.804Z" }, - { url = "https://files.pythonhosted.org/packages/52/07/0b7179101fe5f8385ec6c6bb5d0cb9f76bd9fb4a769591ab6fb5cdbfc69a/yarl-1.23.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4a59ba56f340334766f3a4442e0efd0af895fae9e2b204741ef885c446b3a1a8", size = 105339, upload-time = "2026-03-01T22:05:20.235Z" }, - { url = "https://files.pythonhosted.org/packages/d3/8a/36d82869ab5ec829ca8574dfcb92b51286fcfb1e9c7a73659616362dc880/yarl-1.23.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:803a3c3ce4acc62eaf01eaca1208dcf0783025ef27572c3336502b9c232005e7", size = 105061, upload-time = "2026-03-01T22:05:22.268Z" }, - { url = "https://files.pythonhosted.org/packages/66/3e/868e5c3364b6cee19ff3e1a122194fa4ce51def02c61023970442162859e/yarl-1.23.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3d2bff8f37f8d0f96c7ec554d16945050d54462d6e95414babaa18bfafc7f51", size = 100132, upload-time = "2026-03-01T22:05:23.638Z" }, - { url = "https://files.pythonhosted.org/packages/cf/26/9c89acf82f08a52cb52d6d39454f8d18af15f9d386a23795389d1d423823/yarl-1.23.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c75eb09e8d55bceb4367e83496ff8ef2bc7ea6960efb38e978e8073ea59ecb67", size = 99289, upload-time = "2026-03-01T22:05:25.749Z" }, - { url = "https://files.pythonhosted.org/packages/6f/54/5b0db00d2cb056922356104468019c0a132e89c8d3ab67d8ede9f4483d2a/yarl-1.23.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877b0738624280e34c55680d6054a307aa94f7d52fa0e3034a9cc6e790871da7", size = 96950, upload-time = "2026-03-01T22:05:27.318Z" }, - { url = "https://files.pythonhosted.org/packages/f6/40/10fa93811fd439341fad7e0718a86aca0de9548023bbb403668d6555acab/yarl-1.23.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b5405bb8f0e783a988172993cfc627e4d9d00432d6bbac65a923041edacf997d", size = 93960, upload-time = "2026-03-01T22:05:28.738Z" }, - { url = "https://files.pythonhosted.org/packages/bc/d2/8ae2e6cd77d0805f4526e30ec43b6f9a3dfc542d401ac4990d178e4bf0cf/yarl-1.23.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1c3a3598a832590c5a3ce56ab5576361b5688c12cb1d39429cf5dba30b510760", size = 104703, upload-time = "2026-03-01T22:05:30.438Z" }, - { url = "https://files.pythonhosted.org/packages/2f/0c/b3ceacf82c3fe21183ce35fa2acf5320af003d52bc1fcf5915077681142e/yarl-1.23.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:8419ebd326430d1cbb7efb5292330a2cf39114e82df5cc3d83c9a0d5ebeaf2f2", size = 98325, upload-time = "2026-03-01T22:05:31.835Z" }, - { url = "https://files.pythonhosted.org/packages/9d/e0/12900edd28bdab91a69bd2554b85ad7b151f64e8b521fe16f9ad2f56477a/yarl-1.23.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:be61f6fff406ca40e3b1d84716fde398fc08bc63dd96d15f3a14230a0973ed86", size = 105067, upload-time = "2026-03-01T22:05:33.358Z" }, - { url = "https://files.pythonhosted.org/packages/15/61/74bb1182cf79c9bbe4eb6b1f14a57a22d7a0be5e9cedf8e2d5c2086474c3/yarl-1.23.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3ceb13c5c858d01321b5d9bb65e4cf37a92169ea470b70fec6f236b2c9dd7e34", size = 100285, upload-time = "2026-03-01T22:05:35.4Z" }, - { url = "https://files.pythonhosted.org/packages/69/7f/cd5ef733f2550de6241bd8bd8c3febc78158b9d75f197d9c7baa113436af/yarl-1.23.0-cp312-cp312-win32.whl", hash = "sha256:fffc45637bcd6538de8b85f51e3df3223e4ad89bccbfca0481c08c7fc8b7ed7d", size = 82359, upload-time = "2026-03-01T22:05:36.811Z" }, - { url = "https://files.pythonhosted.org/packages/f5/be/25216a49daeeb7af2bec0db22d5e7df08ed1d7c9f65d78b14f3b74fd72fc/yarl-1.23.0-cp312-cp312-win_amd64.whl", hash = "sha256:f69f57305656a4852f2a7203efc661d8c042e6cc67f7acd97d8667fb448a426e", size = 87674, upload-time = "2026-03-01T22:05:38.171Z" }, - { url = "https://files.pythonhosted.org/packages/d2/35/aeab955d6c425b227d5b7247eafb24f2653fedc32f95373a001af5dfeb9e/yarl-1.23.0-cp312-cp312-win_arm64.whl", hash = "sha256:6e87a6e8735b44816e7db0b2fbc9686932df473c826b0d9743148432e10bb9b9", size = 81879, upload-time = "2026-03-01T22:05:40.006Z" }, - { url = "https://files.pythonhosted.org/packages/9a/4b/a0a6e5d0ee8a2f3a373ddef8a4097d74ac901ac363eea1440464ccbe0898/yarl-1.23.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:16c6994ac35c3e74fb0ae93323bf8b9c2a9088d55946109489667c510a7d010e", size = 123796, upload-time = "2026-03-01T22:05:41.412Z" }, - { url = "https://files.pythonhosted.org/packages/67/b6/8925d68af039b835ae876db5838e82e76ec87b9782ecc97e192b809c4831/yarl-1.23.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4a42e651629dafb64fd5b0286a3580613702b5809ad3f24934ea87595804f2c5", size = 86547, upload-time = "2026-03-01T22:05:42.841Z" }, - { url = "https://files.pythonhosted.org/packages/ae/50/06d511cc4b8e0360d3c94af051a768e84b755c5eb031b12adaaab6dec6e5/yarl-1.23.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7c6b9461a2a8b47c65eef63bb1c76a4f1c119618ffa99ea79bc5bb1e46c5821b", size = 85854, upload-time = "2026-03-01T22:05:44.85Z" }, - { url = "https://files.pythonhosted.org/packages/c4/f4/4e30b250927ffdab4db70da08b9b8d2194d7c7b400167b8fbeca1e4701ca/yarl-1.23.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2569b67d616eab450d262ca7cb9f9e19d2f718c70a8b88712859359d0ab17035", size = 98351, upload-time = "2026-03-01T22:05:46.836Z" }, - { url = "https://files.pythonhosted.org/packages/86/fc/4118c5671ea948208bdb1492d8b76bdf1453d3e73df051f939f563e7dcc5/yarl-1.23.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e9d9a4d06d3481eab79803beb4d9bd6f6a8e781ec078ac70d7ef2dcc29d1bea5", size = 92711, upload-time = "2026-03-01T22:05:48.316Z" }, - { url = "https://files.pythonhosted.org/packages/56/11/1ed91d42bd9e73c13dc9e7eb0dd92298d75e7ac4dd7f046ad0c472e231cd/yarl-1.23.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f514f6474e04179d3d33175ed3f3e31434d3130d42ec153540d5b157deefd735", size = 106014, upload-time = "2026-03-01T22:05:50.028Z" }, - { url = "https://files.pythonhosted.org/packages/ce/c9/74e44e056a23fbc33aca71779ef450ca648a5bc472bdad7a82339918f818/yarl-1.23.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fda207c815b253e34f7e1909840fd14299567b1c0eb4908f8c2ce01a41265401", size = 105557, upload-time = "2026-03-01T22:05:51.416Z" }, - { url = "https://files.pythonhosted.org/packages/66/fe/b1e10b08d287f518994f1e2ff9b6d26f0adeecd8dd7d533b01bab29a3eda/yarl-1.23.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34b6cf500e61c90f305094911f9acc9c86da1a05a7a3f5be9f68817043f486e4", size = 101559, upload-time = "2026-03-01T22:05:52.872Z" }, - { url = "https://files.pythonhosted.org/packages/72/59/c5b8d94b14e3d3c2a9c20cb100119fd534ab5a14b93673ab4cc4a4141ea5/yarl-1.23.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d7504f2b476d21653e4d143f44a175f7f751cd41233525312696c76aa3dbb23f", size = 100502, upload-time = "2026-03-01T22:05:54.954Z" }, - { url = "https://files.pythonhosted.org/packages/77/4f/96976cb54cbfc5c9fd73ed4c51804f92f209481d1fb190981c0f8a07a1d7/yarl-1.23.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:578110dd426f0d209d1509244e6d4a3f1a3e9077655d98c5f22583d63252a08a", size = 98027, upload-time = "2026-03-01T22:05:56.409Z" }, - { url = "https://files.pythonhosted.org/packages/63/6e/904c4f476471afdbad6b7e5b70362fb5810e35cd7466529a97322b6f5556/yarl-1.23.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:609d3614d78d74ebe35f54953c5bbd2ac647a7ddb9c30a5d877580f5e86b22f2", size = 95369, upload-time = "2026-03-01T22:05:58.141Z" }, - { url = "https://files.pythonhosted.org/packages/9d/40/acfcdb3b5f9d68ef499e39e04d25e141fe90661f9d54114556cf83be8353/yarl-1.23.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4966242ec68afc74c122f8459abd597afd7d8a60dc93d695c1334c5fd25f762f", size = 105565, upload-time = "2026-03-01T22:06:00.286Z" }, - { url = "https://files.pythonhosted.org/packages/5e/c6/31e28f3a6ba2869c43d124f37ea5260cac9c9281df803c354b31f4dd1f3c/yarl-1.23.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:e0fd068364a6759bc794459f0a735ab151d11304346332489c7972bacbe9e72b", size = 99813, upload-time = "2026-03-01T22:06:01.712Z" }, - { url = "https://files.pythonhosted.org/packages/08/1f/6f65f59e72d54aa467119b63fc0b0b1762eff0232db1f4720cd89e2f4a17/yarl-1.23.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:39004f0ad156da43e86aa71f44e033de68a44e5a31fc53507b36dd253970054a", size = 105632, upload-time = "2026-03-01T22:06:03.188Z" }, - { url = "https://files.pythonhosted.org/packages/a3/c4/18b178a69935f9e7a338127d5b77d868fdc0f0e49becd286d51b3a18c61d/yarl-1.23.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e5723c01a56c5028c807c701aa66722916d2747ad737a046853f6c46f4875543", size = 101895, upload-time = "2026-03-01T22:06:04.651Z" }, - { url = "https://files.pythonhosted.org/packages/8f/54/f5b870b5505663911dba950a8e4776a0dbd51c9c54c0ae88e823e4b874a0/yarl-1.23.0-cp313-cp313-win32.whl", hash = "sha256:1b6b572edd95b4fa8df75de10b04bc81acc87c1c7d16bcdd2035b09d30acc957", size = 82356, upload-time = "2026-03-01T22:06:06.04Z" }, - { url = "https://files.pythonhosted.org/packages/7a/84/266e8da36879c6edcd37b02b547e2d9ecdfea776be49598e75696e3316e1/yarl-1.23.0-cp313-cp313-win_amd64.whl", hash = "sha256:baaf55442359053c7d62f6f8413a62adba3205119bcb6f49594894d8be47e5e3", size = 87515, upload-time = "2026-03-01T22:06:08.107Z" }, - { url = "https://files.pythonhosted.org/packages/00/fd/7e1c66efad35e1649114fa13f17485f62881ad58edeeb7f49f8c5e748bf9/yarl-1.23.0-cp313-cp313-win_arm64.whl", hash = "sha256:fb4948814a2a98e3912505f09c9e7493b1506226afb1f881825368d6fb776ee3", size = 81785, upload-time = "2026-03-01T22:06:10.181Z" }, - { url = "https://files.pythonhosted.org/packages/9c/fc/119dd07004f17ea43bb91e3ece6587759edd7519d6b086d16bfbd3319982/yarl-1.23.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:aecfed0b41aa72b7881712c65cf764e39ce2ec352324f5e0837c7048d9e6daaa", size = 130719, upload-time = "2026-03-01T22:06:11.708Z" }, - { url = "https://files.pythonhosted.org/packages/e6/0d/9f2348502fbb3af409e8f47730282cd6bc80dec6630c1e06374d882d6eb2/yarl-1.23.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a41bcf68efd19073376eb8cf948b8d9be0af26256403e512bb18f3966f1f9120", size = 89690, upload-time = "2026-03-01T22:06:13.429Z" }, - { url = "https://files.pythonhosted.org/packages/50/93/e88f3c80971b42cfc83f50a51b9d165a1dbf154b97005f2994a79f212a07/yarl-1.23.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cde9a2ecd91668bcb7f077c4966d8ceddb60af01b52e6e3e2680e4cf00ad1a59", size = 89851, upload-time = "2026-03-01T22:06:15.53Z" }, - { url = "https://files.pythonhosted.org/packages/1c/07/61c9dd8ba8f86473263b4036f70fb594c09e99c0d9737a799dfd8bc85651/yarl-1.23.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5023346c4ee7992febc0068e7593de5fa2bf611848c08404b35ebbb76b1b0512", size = 95874, upload-time = "2026-03-01T22:06:17.553Z" }, - { url = "https://files.pythonhosted.org/packages/9e/e9/f9ff8ceefba599eac6abddcfb0b3bee9b9e636e96dbf54342a8577252379/yarl-1.23.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d1009abedb49ae95b136a8904a3f71b342f849ffeced2d3747bf29caeda218c4", size = 88710, upload-time = "2026-03-01T22:06:19.004Z" }, - { url = "https://files.pythonhosted.org/packages/eb/78/0231bfcc5d4c8eec220bc2f9ef82cb4566192ea867a7c5b4148f44f6cbcd/yarl-1.23.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a8d00f29b42f534cc8aa3931cfe773b13b23e561e10d2b26f27a8d309b0e82a1", size = 101033, upload-time = "2026-03-01T22:06:21.203Z" }, - { url = "https://files.pythonhosted.org/packages/cd/9b/30ea5239a61786f18fd25797151a17fbb3be176977187a48d541b5447dd4/yarl-1.23.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:95451e6ce06c3e104556d73b559f5da6c34a069b6b62946d3ad66afcd51642ea", size = 100817, upload-time = "2026-03-01T22:06:22.738Z" }, - { url = "https://files.pythonhosted.org/packages/62/e2/a4980481071791bc83bce2b7a1a1f7adcabfa366007518b4b845e92eeee3/yarl-1.23.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:531ef597132086b6cf96faa7c6c1dcd0361dd5f1694e5cc30375907b9b7d3ea9", size = 97482, upload-time = "2026-03-01T22:06:24.21Z" }, - { url = "https://files.pythonhosted.org/packages/e5/1e/304a00cf5f6100414c4b5a01fc7ff9ee724b62158a08df2f8170dfc72a2d/yarl-1.23.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:88f9fb0116fbfcefcab70f85cf4b74a2b6ce5d199c41345296f49d974ddb4123", size = 95949, upload-time = "2026-03-01T22:06:25.697Z" }, - { url = "https://files.pythonhosted.org/packages/68/03/093f4055ed4cae649ac53bca3d180bd37102e9e11d048588e9ab0c0108d0/yarl-1.23.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e7b0460976dc75cb87ad9cc1f9899a4b97751e7d4e77ab840fc9b6d377b8fd24", size = 95839, upload-time = "2026-03-01T22:06:27.309Z" }, - { url = "https://files.pythonhosted.org/packages/b9/28/4c75ebb108f322aa8f917ae10a8ffa4f07cae10a8a627b64e578617df6a0/yarl-1.23.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:115136c4a426f9da976187d238e84139ff6b51a20839aa6e3720cd1026d768de", size = 90696, upload-time = "2026-03-01T22:06:29.048Z" }, - { url = "https://files.pythonhosted.org/packages/23/9c/42c2e2dd91c1a570402f51bdf066bfdb1241c2240ba001967bad778e77b7/yarl-1.23.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:ead11956716a940c1abc816b7df3fa2b84d06eaed8832ca32f5c5e058c65506b", size = 100865, upload-time = "2026-03-01T22:06:30.525Z" }, - { url = "https://files.pythonhosted.org/packages/74/05/1bcd60a8a0a914d462c305137246b6f9d167628d73568505fce3f1cb2e65/yarl-1.23.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:fe8f8f5e70e6dbdfca9882cd9deaac058729bcf323cf7a58660901e55c9c94f6", size = 96234, upload-time = "2026-03-01T22:06:32.692Z" }, - { url = "https://files.pythonhosted.org/packages/90/b2/f52381aac396d6778ce516b7bc149c79e65bfc068b5de2857ab69eeea3b7/yarl-1.23.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:a0e317df055958a0c1e79e5d2aa5a5eaa4a6d05a20d4b0c9c3f48918139c9fc6", size = 100295, upload-time = "2026-03-01T22:06:34.268Z" }, - { url = "https://files.pythonhosted.org/packages/e5/e8/638bae5bbf1113a659b2435d8895474598afe38b4a837103764f603aba56/yarl-1.23.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f0fd84de0c957b2d280143522c4f91a73aada1923caee763e24a2b3fda9f8a5", size = 97784, upload-time = "2026-03-01T22:06:35.864Z" }, - { url = "https://files.pythonhosted.org/packages/80/25/a3892b46182c586c202629fc2159aa13975d3741d52ebd7347fd501d48d5/yarl-1.23.0-cp313-cp313t-win32.whl", hash = "sha256:93a784271881035ab4406a172edb0faecb6e7d00f4b53dc2f55919d6c9688595", size = 88313, upload-time = "2026-03-01T22:06:37.39Z" }, - { url = "https://files.pythonhosted.org/packages/43/68/8c5b36aa5178900b37387937bc2c2fe0e9505537f713495472dcf6f6fccc/yarl-1.23.0-cp313-cp313t-win_amd64.whl", hash = "sha256:dd00607bffbf30250fe108065f07453ec124dbf223420f57f5e749b04295e090", size = 94932, upload-time = "2026-03-01T22:06:39.579Z" }, - { url = "https://files.pythonhosted.org/packages/c6/cc/d79ba8292f51f81f4dc533a8ccfb9fc6992cabf0998ed3245de7589dc07c/yarl-1.23.0-cp313-cp313t-win_arm64.whl", hash = "sha256:ac09d42f48f80c9ee1635b2fcaa819496a44502737660d3c0f2ade7526d29144", size = 84786, upload-time = "2026-03-01T22:06:41.988Z" }, - { url = "https://files.pythonhosted.org/packages/90/98/b85a038d65d1b92c3903ab89444f48d3cee490a883477b716d7a24b1a78c/yarl-1.23.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:21d1b7305a71a15b4794b5ff22e8eef96ff4a6d7f9657155e5aa419444b28912", size = 124455, upload-time = "2026-03-01T22:06:43.615Z" }, - { url = "https://files.pythonhosted.org/packages/39/54/bc2b45559f86543d163b6e294417a107bb87557609007c007ad889afec18/yarl-1.23.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:85610b4f27f69984932a7abbe52703688de3724d9f72bceb1cca667deff27474", size = 86752, upload-time = "2026-03-01T22:06:45.425Z" }, - { url = "https://files.pythonhosted.org/packages/24/f9/e8242b68362bffe6fb536c8db5076861466fc780f0f1b479fc4ffbebb128/yarl-1.23.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23f371bd662cf44a7630d4d113101eafc0cfa7518a2760d20760b26021454719", size = 86291, upload-time = "2026-03-01T22:06:46.974Z" }, - { url = "https://files.pythonhosted.org/packages/ea/d8/d1cb2378c81dd729e98c716582b1ccb08357e8488e4c24714658cc6630e8/yarl-1.23.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4a80f77dc1acaaa61f0934176fccca7096d9b1ff08c8ba9cddf5ae034a24319", size = 99026, upload-time = "2026-03-01T22:06:48.459Z" }, - { url = "https://files.pythonhosted.org/packages/0a/ff/7196790538f31debe3341283b5b0707e7feb947620fc5e8236ef28d44f72/yarl-1.23.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:bd654fad46d8d9e823afbb4f87c79160b5a374ed1ff5bde24e542e6ba8f41434", size = 92355, upload-time = "2026-03-01T22:06:50.306Z" }, - { url = "https://files.pythonhosted.org/packages/c1/56/25d58c3eddde825890a5fe6aa1866228377354a3c39262235234ab5f616b/yarl-1.23.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:682bae25f0a0dd23a056739f23a134db9f52a63e2afd6bfb37ddc76292bbd723", size = 106417, upload-time = "2026-03-01T22:06:52.1Z" }, - { url = "https://files.pythonhosted.org/packages/51/8a/882c0e7bc8277eb895b31bce0138f51a1ba551fc2e1ec6753ffc1e7c1377/yarl-1.23.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a82836cab5f197a0514235aaf7ffccdc886ccdaa2324bc0aafdd4ae898103039", size = 106422, upload-time = "2026-03-01T22:06:54.424Z" }, - { url = "https://files.pythonhosted.org/packages/42/2b/fef67d616931055bf3d6764885990a3ac647d68734a2d6a9e1d13de437a2/yarl-1.23.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c57676bdedc94cd3bc37724cf6f8cd2779f02f6aba48de45feca073e714fe52", size = 101915, upload-time = "2026-03-01T22:06:55.895Z" }, - { url = "https://files.pythonhosted.org/packages/18/6a/530e16aebce27c5937920f3431c628a29a4b6b430fab3fd1c117b26ff3f6/yarl-1.23.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c7f8dc16c498ff06497c015642333219871effba93e4a2e8604a06264aca5c5c", size = 100690, upload-time = "2026-03-01T22:06:58.21Z" }, - { url = "https://files.pythonhosted.org/packages/88/08/93749219179a45e27b036e03260fda05190b911de8e18225c294ac95bbc9/yarl-1.23.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:5ee586fb17ff8f90c91cf73c6108a434b02d69925f44f5f8e0d7f2f260607eae", size = 98750, upload-time = "2026-03-01T22:06:59.794Z" }, - { url = "https://files.pythonhosted.org/packages/d9/cf/ea424a004969f5d81a362110a6ac1496d79efdc6d50c2c4b2e3ea0fc2519/yarl-1.23.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:17235362f580149742739cc3828b80e24029d08cbb9c4bda0242c7b5bc610a8e", size = 94685, upload-time = "2026-03-01T22:07:01.375Z" }, - { url = "https://files.pythonhosted.org/packages/e2/b7/14341481fe568e2b0408bcf1484c652accafe06a0ade9387b5d3fd9df446/yarl-1.23.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:0793e2bd0cf14234983bbb371591e6bea9e876ddf6896cdcc93450996b0b5c85", size = 106009, upload-time = "2026-03-01T22:07:03.151Z" }, - { url = "https://files.pythonhosted.org/packages/0a/e6/5c744a9b54f4e8007ad35bce96fbc9218338e84812d36f3390cea616881a/yarl-1.23.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:3650dc2480f94f7116c364096bc84b1d602f44224ef7d5c7208425915c0475dd", size = 100033, upload-time = "2026-03-01T22:07:04.701Z" }, - { url = "https://files.pythonhosted.org/packages/0c/23/e3bfc188d0b400f025bc49d99793d02c9abe15752138dcc27e4eaf0c4a9e/yarl-1.23.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f40e782d49630ad384db66d4d8b73ff4f1b8955dc12e26b09a3e3af064b3b9d6", size = 106483, upload-time = "2026-03-01T22:07:06.231Z" }, - { url = "https://files.pythonhosted.org/packages/72/42/f0505f949a90b3f8b7a363d6cbdf398f6e6c58946d85c6d3a3bc70595b26/yarl-1.23.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:94f8575fbdf81749008d980c17796097e645574a3b8c28ee313931068dad14fe", size = 102175, upload-time = "2026-03-01T22:07:08.4Z" }, - { url = "https://files.pythonhosted.org/packages/aa/65/b39290f1d892a9dd671d1c722014ca062a9c35d60885d57e5375db0404b5/yarl-1.23.0-cp314-cp314-win32.whl", hash = "sha256:c8aa34a5c864db1087d911a0b902d60d203ea3607d91f615acd3f3108ac32169", size = 83871, upload-time = "2026-03-01T22:07:09.968Z" }, - { url = "https://files.pythonhosted.org/packages/a9/5b/9b92f54c784c26e2a422e55a8d2607ab15b7ea3349e28359282f84f01d43/yarl-1.23.0-cp314-cp314-win_amd64.whl", hash = "sha256:63e92247f383c85ab00dd0091e8c3fa331a96e865459f5ee80353c70a4a42d70", size = 89093, upload-time = "2026-03-01T22:07:11.501Z" }, - { url = "https://files.pythonhosted.org/packages/e0/7d/8a84dc9381fd4412d5e7ff04926f9865f6372b4c2fd91e10092e65d29eb8/yarl-1.23.0-cp314-cp314-win_arm64.whl", hash = "sha256:70efd20be968c76ece7baa8dafe04c5be06abc57f754d6f36f3741f7aa7a208e", size = 83384, upload-time = "2026-03-01T22:07:13.069Z" }, - { url = "https://files.pythonhosted.org/packages/dd/8d/d2fad34b1c08aa161b74394183daa7d800141aaaee207317e82c790b418d/yarl-1.23.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:9a18d6f9359e45722c064c97464ec883eb0e0366d33eda61cb19a244bf222679", size = 131019, upload-time = "2026-03-01T22:07:14.903Z" }, - { url = "https://files.pythonhosted.org/packages/19/ff/33009a39d3ccf4b94d7d7880dfe17fb5816c5a4fe0096d9b56abceea9ac7/yarl-1.23.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:2803ed8b21ca47a43da80a6fd1ed3019d30061f7061daa35ac54f63933409412", size = 89894, upload-time = "2026-03-01T22:07:17.372Z" }, - { url = "https://files.pythonhosted.org/packages/0c/f1/dab7ac5e7306fb79c0190766a3c00b4cb8d09a1f390ded68c85a5934faf5/yarl-1.23.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:394906945aa8b19fc14a61cf69743a868bb8c465efe85eee687109cc540b98f4", size = 89979, upload-time = "2026-03-01T22:07:19.361Z" }, - { url = "https://files.pythonhosted.org/packages/aa/b1/08e95f3caee1fad6e65017b9f26c1d79877b502622d60e517de01e72f95d/yarl-1.23.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:71d006bee8397a4a89f469b8deb22469fe7508132d3c17fa6ed871e79832691c", size = 95943, upload-time = "2026-03-01T22:07:21.266Z" }, - { url = "https://files.pythonhosted.org/packages/c0/cc/6409f9018864a6aa186c61175b977131f373f1988e198e031236916e87e4/yarl-1.23.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:62694e275c93d54f7ccedcfef57d42761b2aad5234b6be1f3e3026cae4001cd4", size = 88786, upload-time = "2026-03-01T22:07:23.129Z" }, - { url = "https://files.pythonhosted.org/packages/76/40/cc22d1d7714b717fde2006fad2ced5efe5580606cb059ae42117542122f3/yarl-1.23.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a31de1613658308efdb21ada98cbc86a97c181aa050ba22a808120bb5be3ab94", size = 101307, upload-time = "2026-03-01T22:07:24.689Z" }, - { url = "https://files.pythonhosted.org/packages/8f/0d/476c38e85ddb4c6ec6b20b815bdd779aa386a013f3d8b85516feee55c8dc/yarl-1.23.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fb1e8b8d66c278b21d13b0a7ca22c41dd757a7c209c6b12c313e445c31dd3b28", size = 100904, upload-time = "2026-03-01T22:07:26.287Z" }, - { url = "https://files.pythonhosted.org/packages/72/32/0abe4a76d59adf2081dcb0397168553ece4616ada1c54d1c49d8936c74f8/yarl-1.23.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50f9d8d531dfb767c565f348f33dd5139a6c43f5cbdf3f67da40d54241df93f6", size = 97728, upload-time = "2026-03-01T22:07:27.906Z" }, - { url = "https://files.pythonhosted.org/packages/b7/35/7b30f4810fba112f60f5a43237545867504e15b1c7647a785fbaf588fac2/yarl-1.23.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:575aa4405a656e61a540f4a80eaa5260f2a38fff7bfdc4b5f611840d76e9e277", size = 95964, upload-time = "2026-03-01T22:07:30.198Z" }, - { url = "https://files.pythonhosted.org/packages/2d/86/ed7a73ab85ef00e8bb70b0cb5421d8a2a625b81a333941a469a6f4022828/yarl-1.23.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:041b1a4cefacf65840b4e295c6985f334ba83c30607441ae3cf206a0eed1a2e4", size = 95882, upload-time = "2026-03-01T22:07:32.132Z" }, - { url = "https://files.pythonhosted.org/packages/19/90/d56967f61a29d8498efb7afb651e0b2b422a1e9b47b0ab5f4e40a19b699b/yarl-1.23.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:d38c1e8231722c4ce40d7593f28d92b5fc72f3e9774fe73d7e800ec32299f63a", size = 90797, upload-time = "2026-03-01T22:07:34.404Z" }, - { url = "https://files.pythonhosted.org/packages/72/00/8b8f76909259f56647adb1011d7ed8b321bcf97e464515c65016a47ecdf0/yarl-1.23.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:d53834e23c015ee83a99377db6e5e37d8484f333edb03bd15b4bc312cc7254fb", size = 101023, upload-time = "2026-03-01T22:07:35.953Z" }, - { url = "https://files.pythonhosted.org/packages/ac/e2/cab11b126fb7d440281b7df8e9ddbe4851e70a4dde47a202b6642586b8d9/yarl-1.23.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:2e27c8841126e017dd2a054a95771569e6070b9ee1b133366d8b31beb5018a41", size = 96227, upload-time = "2026-03-01T22:07:37.594Z" }, - { url = "https://files.pythonhosted.org/packages/c2/9b/2c893e16bfc50e6b2edf76c1a9eb6cb0c744346197e74c65e99ad8d634d0/yarl-1.23.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:76855800ac56f878847a09ce6dba727c93ca2d89c9e9d63002d26b916810b0a2", size = 100302, upload-time = "2026-03-01T22:07:39.334Z" }, - { url = "https://files.pythonhosted.org/packages/28/ec/5498c4e3a6d5f1003beb23405671c2eb9cdbf3067d1c80f15eeafe301010/yarl-1.23.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e09fd068c2e169a7070d83d3bde728a4d48de0549f975290be3c108c02e499b4", size = 98202, upload-time = "2026-03-01T22:07:41.717Z" }, - { url = "https://files.pythonhosted.org/packages/fe/c3/cd737e2d45e70717907f83e146f6949f20cc23cd4bf7b2688727763aa458/yarl-1.23.0-cp314-cp314t-win32.whl", hash = "sha256:73309162a6a571d4cbd3b6a1dcc703c7311843ae0d1578df6f09be4e98df38d4", size = 90558, upload-time = "2026-03-01T22:07:43.433Z" }, - { url = "https://files.pythonhosted.org/packages/e1/19/3774d162f6732d1cfb0b47b4140a942a35ca82bb19b6db1f80e9e7bdc8f8/yarl-1.23.0-cp314-cp314t-win_amd64.whl", hash = "sha256:4503053d296bc6e4cbd1fad61cf3b6e33b939886c4f249ba7c78b602214fabe2", size = 97610, upload-time = "2026-03-01T22:07:45.773Z" }, - { url = "https://files.pythonhosted.org/packages/51/47/3fa2286c3cb162c71cdb34c4224d5745a1ceceb391b2bd9b19b668a8d724/yarl-1.23.0-cp314-cp314t-win_arm64.whl", hash = "sha256:44bb7bef4ea409384e3f8bc36c063d77ea1b8d4a5b2706956c0d6695f07dcc25", size = 86041, upload-time = "2026-03-01T22:07:49.026Z" }, - { url = "https://files.pythonhosted.org/packages/69/68/c8739671f5699c7dc470580a4f821ef37c32c4cb0b047ce223a7f115757f/yarl-1.23.0-py3-none-any.whl", hash = "sha256:a2df6afe50dea8ae15fa34c9f824a3ee958d785fd5d089063d960bae1daa0a3f", size = 48288, upload-time = "2026-03-01T22:07:51.388Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/79/12/1e8f37460ea0f7eb59c221fdaf0ed75e7ac43e97f8093b9c6f411df50a78/yarl-1.24.2.tar.gz", hash = "sha256:9ac374123c6fd7abf64d1fec93962b0bd4ee2c19751755a762a72dd96c0378f8", size = 210798, upload-time = "2026-05-19T21:31:05.599Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f0/da/866bcb01076ba49d2b42b309867bed3826421f1c479655eb7a607b44f20b/yarl-1.24.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b975866c184564c827e0877380f0dae57dcca7e52782128381b72feff6dfceb8", size = 129957, upload-time = "2026-05-19T21:28:51.695Z" }, + { url = "https://files.pythonhosted.org/packages/bf/1d/fcefb70922ea2268a8971d8e5874d9a8218644200fb8465f1dcad55e6851/yarl-1.24.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3b075301a2836a0e297b1b658cb6d6135df535d62efefdd60366bd589c2c82f2", size = 92164, upload-time = "2026-05-19T21:28:53.242Z" }, + { url = "https://files.pythonhosted.org/packages/29/b6/170e2b8d4e3bc30e6bfdcca53556537f5bf595e938632dfcb059311f3ff6/yarl-1.24.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8ae44649b00947634ab0dab2a374a638f52923a6e67083f2c156cd5cbd1a881d", size = 91688, upload-time = "2026-05-19T21:28:54.865Z" }, + { url = "https://files.pythonhosted.org/packages/fe/a5/c9f655d5553ea0b99fdac9d6a99ad3f9b3e73b8e5758bb46f58c9831f74c/yarl-1.24.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:507cc19f0b45454e2d6dcd62ff7d062b9f77a2812404e62dbdaec05b50faa035", size = 102902, upload-time = "2026-05-19T21:28:56.963Z" }, + { url = "https://files.pythonhosted.org/packages/5d/bc/6b9664d815d79af4ee553337f9d606c56bbf269186ada9172de45f1b5f60/yarl-1.24.2-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c4c17bad5a530912d2111825d3f05e89bab2dd376aaa8cbc77e449e6db63e576", size = 97931, upload-time = "2026-05-19T21:28:58.56Z" }, + { url = "https://files.pythonhosted.org/packages/98/ec/32ba48acae30fecd60928f5791188b80a9d6ee3840507ffda29fecd37b71/yarl-1.24.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f5f0cbb112838a4a293985b6ed73948a547dadcc1ba6d2089938e7abdedceef8", size = 111030, upload-time = "2026-05-19T21:29:00.148Z" }, + { url = "https://files.pythonhosted.org/packages/82/5a/6f4cd081e5f4934d2ae3a8ef4abe3afacc010d26f0035ee91b35cd7d7c37/yarl-1.24.2-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5ec8356b8a6afcf81fc7aeeef13b1ff7a49dec00f313394bbb9e83830d32ccd7", size = 110392, upload-time = "2026-05-19T21:29:02.155Z" }, + { url = "https://files.pythonhosted.org/packages/7a/da/323a01c349bd5fb01bb6652e314d9bb218cee630a736bdb810ad50e4013f/yarl-1.24.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7e7ebcdef69dec6c6451e616f32b622a6d4a2e92b445c992f7c8e5274a6bbc4c", size = 105612, upload-time = "2026-05-19T21:29:04.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/80/264ab684f181e1a876389374519ff05d10248725535ae2ac4e8ac4e563d6/yarl-1.24.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:47a55d6cf6db2f401017a9e96e5288844e5051911fb4e0c8311a3980f5e59a7d", size = 104487, upload-time = "2026-05-19T21:29:06.491Z" }, + { url = "https://files.pythonhosted.org/packages/41/07/efabe5df87e96d7ad5959760b888344be48cd6884db127b407c6b5503adc/yarl-1.24.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3065657c80a2321225e804048597ad55658a7e76b32d6f5ee4074d04c50401db", size = 102333, upload-time = "2026-05-19T21:29:08.267Z" }, + { url = "https://files.pythonhosted.org/packages/44/0c/bcf7c42603e1009295f586d8890f2ba032c8b53310e815adf0a202c73d9f/yarl-1.24.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:cb84b80d88e19ede158619b80813968713d8d008b0e2497a576e6a0557d50712", size = 99025, upload-time = "2026-05-19T21:29:10.682Z" }, + { url = "https://files.pythonhosted.org/packages/4f/82/84482ab1a57a0f21a08afe6a7004c61d741f8f2ecc3b05c321577c612164/yarl-1.24.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:990de4f680b1c217e77ff0d6aa0029f9eb79889c11fb3e9a3942c7eba29c1996", size = 110507, upload-time = "2026-05-19T21:29:12.954Z" }, + { url = "https://files.pythonhosted.org/packages/c4/8d/a546ba1dfe1b0f290e05fef145cd07614c0f15df1a707195e512d1e39d1d/yarl-1.24.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:abb8ec0323b80161e3802da3150ef660b41d0e9be2048b76a363d93eee992c2b", size = 103719, upload-time = "2026-05-19T21:29:14.893Z" }, + { url = "https://files.pythonhosted.org/packages/1a/b6/267f2a09213138473adfce6b8a6e17791d7fee70bd4d9003218e4dec58b0/yarl-1.24.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e7977781f83638a4c73e0f88425563d70173e0dfd90ac006a45c65036293ee3c", size = 110438, upload-time = "2026-05-19T21:29:16.485Z" }, + { url = "https://files.pythonhosted.org/packages/48/2d/1c8d89c7c5f9cad9fb2902445d94e2ab1d7aa35de029afbb8ae95c42d00f/yarl-1.24.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e30dd55825dc554ec5b66a94953b8eda8745926514c5089dfcacecb9c99b5bd1", size = 105719, upload-time = "2026-05-19T21:29:18.367Z" }, + { url = "https://files.pythonhosted.org/packages/a7/25/722e3b93bd687009afb2d59a35e13d30ddd8f80571445bb0c4e4ce26ec66/yarl-1.24.2-cp312-cp312-win_amd64.whl", hash = "sha256:7dafe10c12ddd4d120d528c4b5599c953bd7b12845347d507b95451195bb6cad", size = 92901, upload-time = "2026-05-19T21:29:20.014Z" }, + { url = "https://files.pythonhosted.org/packages/39/47/4486ccfb674c04854a1ef8aa77868b6a6f765feaf69633409d7ca4f02cb8/yarl-1.24.2-cp312-cp312-win_arm64.whl", hash = "sha256:044a09d8401fcf8681977faef6d286b8ade1e2d2e9dceda175d1cfa5ca496f30", size = 87229, upload-time = "2026-05-19T21:29:22.1Z" }, + { url = "https://files.pythonhosted.org/packages/82/62/fcf0ce677f17e5c471c06311dd25964be38a4c586993632910d2e75278bc/yarl-1.24.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:491ac9141decf49ee8030199e1ee251cdff0e131f25678817ff6aa5f837a3536", size = 128978, upload-time = "2026-05-19T21:29:23.83Z" }, + { url = "https://files.pythonhosted.org/packages/d3/58/8e63299bb71ed61a834121d9d3fe6c9fcf2a6a5d09754ff4f20f2d20baf5/yarl-1.24.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e89418f65eda18f99030386305bd44d7d504e328a7945db1ead514fbe03a0607", size = 91733, upload-time = "2026-05-19T21:29:25.375Z" }, + { url = "https://files.pythonhosted.org/packages/c1/24/16748d5dab6daec8b0ed81ccec639a1cded0f18dcc62a4f696b4fe366c37/yarl-1.24.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cdfcce633b4a4bb8281913c57fcafd4b5933fbc19111a5e3930bbd299d6102f1", size = 91113, upload-time = "2026-05-19T21:29:26.928Z" }, + { url = "https://files.pythonhosted.org/packages/1b/66/b63fff7b71211e866624b21432d5943cbb633eb0c2872d9ee3070648f22c/yarl-1.24.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:863297ddede92ee49024e9a9b11ecb59f310ca85b60d8537f56bed9bbb5b1986", size = 103899, upload-time = "2026-05-19T21:29:28.842Z" }, + { url = "https://files.pythonhosted.org/packages/9d/ac/ba1974b8533909636f7733fe86cf677e3619527c3c2fa913e0ea89c48757/yarl-1.24.2-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:374423f70754a2c96942ede36a29d37dc6b0cb8f92f8d009ddf3ed78d3da5488", size = 97862, upload-time = "2026-05-19T21:29:31.086Z" }, + { url = "https://files.pythonhosted.org/packages/1b/a5/123ac993b5c2ba6f554a140305620cb8f150fa543711bbc49be3ec0a65a4/yarl-1.24.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:33a29b5d00ccbf3219bb3e351d7875739c19481e030779f48cc46a7a71681a9b", size = 111060, upload-time = "2026-05-19T21:29:32.657Z" }, + { url = "https://files.pythonhosted.org/packages/23/37/c472d3af3509688392134a88a825276770a187f1daa4de3f6dc0a327a751/yarl-1.24.2-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a9532c57211730c515341af11fef6e9b61d157487272a096d0c04da445642592", size = 110613, upload-time = "2026-05-19T21:29:34.379Z" }, + { url = "https://files.pythonhosted.org/packages/df/88/09c28dad91e662ccfaa1b78f1c57badde74fc9d0b23e74aef644750ecd73/yarl-1.24.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:91e72cf093fd833483a97ee648e0c053c7c629f51ff4a0e7edd84f806b0c5617", size = 107012, upload-time = "2026-05-19T21:29:36.216Z" }, + { url = "https://files.pythonhosted.org/packages/07/ab/9d4f69d571a94f4d112fa7e2e007200f5a54d319f58c82ac7b7baa61f5c6/yarl-1.24.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b3177bc0a768ef3bacceb4f272632990b7bea352f1b2f1eee9d6d6ff16516f92", size = 105887, upload-time = "2026-05-19T21:29:38.746Z" }, + { url = "https://files.pythonhosted.org/packages/8e/9a/000b2b66c0d772a499fc531d21dab92dfeb73b640a12eed6ba89f49bb2d0/yarl-1.24.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e196952aacaf3b232e265ff02980b64d483dc0972bd49bcb061171ff22ac203a", size = 103620, upload-time = "2026-05-19T21:29:40.368Z" }, + { url = "https://files.pythonhosted.org/packages/41/7c/7c1050f73450fbdaa3f0c72017059f00ce5e13366692f3dba25275a1083d/yarl-1.24.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:204e7a61ce99919c0de1bf904ab5d7aa188a129ea8f690a8f76cfb6e2844dc44", size = 100599, upload-time = "2026-05-19T21:29:42.66Z" }, + { url = "https://files.pythonhosted.org/packages/ec/b1/29e5756b3926705f5f6089bd5b9f50a56eaac550da6e260bf713ead44d04/yarl-1.24.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b156914620f0b9d78dc1adb3751141daee561cfec796088abb89ed49d220f1a", size = 110604, upload-time = "2026-05-19T21:29:44.632Z" }, + { url = "https://files.pythonhosted.org/packages/a3/4b/8415bc96e9b150cde942fbac9a8182985e58f40ce5c54c34ed015407d3ee/yarl-1.24.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:8372a2b976cf70654b2be6619ab6068acabb35f724c0fda7b277fbf53d66a5cf", size = 105161, upload-time = "2026-05-19T21:29:46.755Z" }, + { url = "https://files.pythonhosted.org/packages/8b/d4/cde059abfa229553b7298a2eadde2752e723d50aeedaef86ce59da2718ee/yarl-1.24.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:f9a1e9b622ca284143aab5d885848686dcd85453bb1ca9abcdb7503e64dc0056", size = 110619, upload-time = "2026-05-19T21:29:48.972Z" }, + { url = "https://files.pythonhosted.org/packages/e7/2c/d6a6c9a61549f7b6c7e6dc6937d195bcf069582b47b7200dcd0e7b256acf/yarl-1.24.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:810e19b685c8c3c5862f6a38160a1f4e4c0916c9390024ec347b6157a45a0992", size = 107362, upload-time = "2026-05-19T21:29:51Z" }, + { url = "https://files.pythonhosted.org/packages/92/dd/3ae5fe417e9d1c353a548553326eb9935e76b6b727161563b424cc296df3/yarl-1.24.2-cp313-cp313-win_amd64.whl", hash = "sha256:7d37fb7c38f2b6edab0f845c4f85148d4c44204f52bc127021bd2bc9fdbf1656", size = 92667, upload-time = "2026-05-19T21:29:52.743Z" }, + { url = "https://files.pythonhosted.org/packages/10/cc/a7beb239f78f27fca1b053c8e8595e4179c02e62249b4687ec218c370c50/yarl-1.24.2-cp313-cp313-win_arm64.whl", hash = "sha256:1e831894be7c2954240e49791fa4b50c05a0dc881de2552cfe3ffd8631c7f461", size = 87069, upload-time = "2026-05-19T21:29:54.442Z" }, + { url = "https://files.pythonhosted.org/packages/40/0e/e08087695fc12789263821c5dc0f8dc52b5b17efd0887cacf419f8a43ba3/yarl-1.24.2-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:f9312b3c02d9b3d23840f67952913c9c8721d7f1b7db305289faefa878f364c2", size = 129670, upload-time = "2026-05-19T21:29:56.631Z" }, + { url = "https://files.pythonhosted.org/packages/3a/98/ab4b5ed1b1b5cd973c8a3eb994c3a6aefb6ce6d399e21bb5f0316c33815c/yarl-1.24.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a4f4d6cd615823bfc7fb7e9b5987c3f41666371d870d51058f77e2680fbe9630", size = 91916, upload-time = "2026-05-19T21:29:58.645Z" }, + { url = "https://files.pythonhosted.org/packages/ba/b1/5297bb6a7df4782f7605bffc43b31f5044070935fbbcaa6c705a07e6ac65/yarl-1.24.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0c3063e5c0a8e8e62fae6c2596fa01da1561e4cd1da6fec5789f5cf99a8aefd8", size = 91625, upload-time = "2026-05-19T21:30:00.412Z" }, + { url = "https://files.pythonhosted.org/packages/02/a7/45baabfff76829264e623b185cff0c340d7e11bf3e1cd9ea37e7d17934bd/yarl-1.24.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fecd17873a096036c1c87ab3486f1aef7f269ada7f23f7f856f93b1cc7744f14", size = 104574, upload-time = "2026-05-19T21:30:02.544Z" }, + { url = "https://files.pythonhosted.org/packages/f3/40/3a5ab144d3d650ca37d4f4b57e56169be8af3ca34c448793e064b30baaed/yarl-1.24.2-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a46d1ab4ba4d32e6dc80daf8a28ce0bd83d08df52fbc32f3e288663427734535", size = 97534, upload-time = "2026-05-19T21:30:04.319Z" }, + { url = "https://files.pythonhosted.org/packages/9c/b5/5658fef3681fb5776b4513b052bec750009f47b3a592251c705d75375798/yarl-1.24.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:73e68edf6dfd5f73f9ca127d84e2a6f9213c65bdffb736bda19524c0564fcd14", size = 111481, upload-time = "2026-05-19T21:30:05.988Z" }, + { url = "https://files.pythonhosted.org/packages/4c/06/fdcd7dde037f00866dce123ed4ba23dba94beb56fc4cf561668d27be37f2/yarl-1.24.2-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a296ca617f2d25fbceafb962b88750d627e5984e75732c712154d058ae8d79a3", size = 111529, upload-time = "2026-05-19T21:30:07.738Z" }, + { url = "https://files.pythonhosted.org/packages/c2/53/d81269aaafccea0d33396c03035de997b743f11e648e6e27a0df99c72980/yarl-1.24.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e51b2cf5ec89a8b8470177641ed62a3ba22d74e1e898e06ad53aa77972487208", size = 107338, upload-time = "2026-05-19T21:30:09.713Z" }, + { url = "https://files.pythonhosted.org/packages/ae/04/23049463f729bd899df203a7960505a75333edd499cda8aa1d5a82b64df5/yarl-1.24.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:310fc687f7b2044ec54e372c8cbe923bb88f5c37bded0d3079e5791c2fc3cf50", size = 106147, upload-time = "2026-05-19T21:30:11.365Z" }, + { url = "https://files.pythonhosted.org/packages/14/18/04a4b5830b43ed5e4c5015b40e9f6241ad91487d71611061b4e111d6ac80/yarl-1.24.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:297a2fe352ecf858b30a98f87948746ec16f001d279f84aebdbd3bd965e2f1bd", size = 104272, upload-time = "2026-05-19T21:30:12.978Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f7/8cffdf319aee7a7c1dbd07b61d91c3e3fda460c7a93b5f93e445f3806c4c/yarl-1.24.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:2a263e76b97bc42bdcd7c5f4953dec1f7cd62a1112fa7f869e57255229390d67", size = 99962, upload-time = "2026-05-19T21:30:15.001Z" }, + { url = "https://files.pythonhosted.org/packages/d7/39/b3cce3b7dbef64ac700ad4cea156a207d01bede0f507587616c364b5468e/yarl-1.24.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:822519b64cf0b474f1a0aaef1dc621438ea46bb77c94df97a5b4d213a7d8a8b1", size = 111063, upload-time = "2026-05-19T21:30:16.683Z" }, + { url = "https://files.pythonhosted.org/packages/a1/ea/100818505e7ebf165c7242ff17fdf7d9fee79e27234aeca871c1082920d7/yarl-1.24.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:b6067060d9dc594899ba83e6db6c48c68d1e494a6dab158156ed86977ca7bcb1", size = 105438, upload-time = "2026-05-19T21:30:18.769Z" }, + { url = "https://files.pythonhosted.org/packages/8f/d2/e075a0b32aa6625087de9e653087df0759fed5de4a435fef594181102a77/yarl-1.24.2-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:0063adad533e57171b79db3943b229d40dfafeeee579767f96541f106bac5f1b", size = 111458, upload-time = "2026-05-19T21:30:21.024Z" }, + { url = "https://files.pythonhosted.org/packages/e6/5c/ceea7ba98b65c8eb8d947fdc52f9bedfcd43c6a57c9e3c90c17be8f324a3/yarl-1.24.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ee8e3fb34513e8dc082b586ef4910c98335d43a6fab688cd44d4851bacfce3e8", size = 107589, upload-time = "2026-05-19T21:30:23.412Z" }, + { url = "https://files.pythonhosted.org/packages/fa/d9/5582d57e2b2db9b85eb6663a22efdd78e08805f3f5389566e9fcad254d1b/yarl-1.24.2-cp314-cp314-win_amd64.whl", hash = "sha256:afb00d7fd8e0f285ca29a44cc50df2d622ff2f7a6d933fa641577b5f9d5f3db0", size = 94424, upload-time = "2026-05-19T21:30:25.425Z" }, + { url = "https://files.pythonhosted.org/packages/92/10/7dc07a0e22806a9280f42a57361395506e800c64e22737cd7b0886feab42/yarl-1.24.2-cp314-cp314-win_arm64.whl", hash = "sha256:68cf6eacd6028ef1142bc4b48376b81566385ca6f9e7dde3b0fa91be08ffcb57", size = 88690, upload-time = "2026-05-19T21:30:27.623Z" }, + { url = "https://files.pythonhosted.org/packages/9e/13/d5b8e2c8667db955bcb3de233f18798fefe7edf1d7429c2c9d4f9c401114/yarl-1.24.2-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:221ce1dd921ac4f603957f17d7c18c5cc0797fbb52f156941f92e04605d1d67b", size = 136248, upload-time = "2026-05-19T21:30:29.297Z" }, + { url = "https://files.pythonhosted.org/packages/de/46/a4a97c05c9c9b8fd266bb2a0df12992c7fbd02391eb9640583411b6dab32/yarl-1.24.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:5f3224db28173a00d7afacdee07045cc4673dfab2b15492c7ae10deddbece761", size = 95084, upload-time = "2026-05-19T21:30:31.031Z" }, + { url = "https://files.pythonhosted.org/packages/95/b2/845cf2074a015e6fe0d0808cf1a2d9e868386c4220d657ebd8302b199043/yarl-1.24.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c557165320d6244ebe3a02431b2a201a20080e02f41f0cfa0ccc47a183765da8", size = 95272, upload-time = "2026-05-19T21:30:33.062Z" }, + { url = "https://files.pythonhosted.org/packages/fe/16/e69d4aa244aef45235ddfebc0e04036a6829842bc5a6a795aedc6c998d23/yarl-1.24.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:904065e6e85b1fa54d0d87438bd58c14c0bad97aad654ad1077fd9d87e8478ed", size = 101497, upload-time = "2026-05-19T21:30:34.842Z" }, + { url = "https://files.pythonhosted.org/packages/15/94/c07107715d621076863ee88b3ddf183fa5e9d4aba5769623c9979828410a/yarl-1.24.2-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8cec2a38d70edc10e0e856ceda886af5327a017ccbde8e1de1bd44d300357543", size = 94002, upload-time = "2026-05-19T21:30:37.724Z" }, + { url = "https://files.pythonhosted.org/packages/a9/35/fc1bbdd895b5e4010b8fdd037f7ed3aa289d3863e08231b30231ca9a0815/yarl-1.24.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e7484b9361ed222ee1ca5b4337aa4cbdcc4618ce5aff57d9ef1582fd95893fc0", size = 106524, upload-time = "2026-05-19T21:30:40.196Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f2/32b66d0a4ba47c296cf86d03e2c67bff58399fe6d6d84d5205c04c66cc6d/yarl-1.24.2-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:84f9670b89f34db07f81e53aee83e0b938a3412329d51c8f922488be7fcc4024", size = 106165, upload-time = "2026-05-19T21:30:41.888Z" }, + { url = "https://files.pythonhosted.org/packages/95/47/37cb5ff50c5e825d4d38e81bb04d1b7e96bf960f7ab89f9850b162f3f114/yarl-1.24.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:abb2759733d63a28b4956500a5dd57140f26486c92b2caedfb964ab7d9b79dbf", size = 103010, upload-time = "2026-05-19T21:30:43.985Z" }, + { url = "https://files.pythonhosted.org/packages/6f/d2/4597912315096f7bb359e46e13bf8b60994fcbb2db29b804c0902ef4eff5/yarl-1.24.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:081c2bf54efe03774d0311172bc04fedf9ca01e644d4cd8c805688e527209bdc", size = 101128, upload-time = "2026-05-19T21:30:46.291Z" }, + { url = "https://files.pythonhosted.org/packages/b9/d5/c8e86e120521e646013d02a8e3b8884392e28494be8f392366e50d208efc/yarl-1.24.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:86746bef442aa479107fe28132e1277237f9c24c2f00b0b0cf22b3ee0904f2bb", size = 101382, upload-time = "2026-05-19T21:30:48.085Z" }, + { url = "https://files.pythonhosted.org/packages/fa/98/70b229236118f89dbeb739b76f10225bbf53b5497725502594c9a01d699a/yarl-1.24.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:2d07d21d0bc4b17558e8de0b02fbfdf1e347d3bb3699edd00bb92e7c57925420", size = 95964, upload-time = "2026-05-19T21:30:49.785Z" }, + { url = "https://files.pythonhosted.org/packages/87/f8/56c386981e3c8648d279fdef2397ffec577e8320fd5649745e34d54faeb7/yarl-1.24.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:4fb1ac3fc5fecd8ae7453ea237e4d22b49befa70266dfe1629924245c21a0c7f", size = 106204, upload-time = "2026-05-19T21:30:51.862Z" }, + { url = "https://files.pythonhosted.org/packages/1a/1e/765afe97811ca35933e2a7de70ac57b1997ea2e4ee895719ee7a231fb7e5/yarl-1.24.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:4da31a5512ed1729ca8d8aacde3f7faeb8843cde3165d6bcf7f88f74f17bb8aa", size = 101510, upload-time = "2026-05-19T21:30:53.62Z" }, + { url = "https://files.pythonhosted.org/packages/ee/78/393913f4b9039e1edd09ae8a9bbb9d539be909a8abf6d8a2084585bed4b7/yarl-1.24.2-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:533ded4dceb5f1f3da7906244f4e82cf46cfd40d84c69a1faf5ac506aa65ecbe", size = 105584, upload-time = "2026-05-19T21:30:55.962Z" }, + { url = "https://files.pythonhosted.org/packages/78/87/deb17b7049bbe74ea11a713b86f8f27800cc1c8648b0b797243ebb4830ba/yarl-1.24.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:7b3a85525f6e7eeabcfdd372862b21ee1915db1b498a04e8bf0e389b607ff0bd", size = 103410, upload-time = "2026-05-19T21:30:57.962Z" }, + { url = "https://files.pythonhosted.org/packages/8f/be/f9f7594e23b5b93affff0318e4593c1920331bcaefda326cabcad94296a1/yarl-1.24.2-cp314-cp314t-win_amd64.whl", hash = "sha256:a7624b1ca46ca5d7b864ef0d2f8efe3091454085ee1855b4e992314529972215", size = 102980, upload-time = "2026-05-19T21:30:59.735Z" }, + { url = "https://files.pythonhosted.org/packages/65/a4/ba80dccd3593ff1f01051a818694d07b58cb8232677ee9a22a5a1f93a9fc/yarl-1.24.2-cp314-cp314t-win_arm64.whl", hash = "sha256:e434a45ce2e7a947f951fc5a8944c8cc080b7e59f9c50ae80fd39107cf88126d", size = 91219, upload-time = "2026-05-19T21:31:01.934Z" }, + { url = "https://files.pythonhosted.org/packages/fd/4d/4b880086bd0d3e034d25647be1d830afc3e3f610e98c4ab3490af6b1b6d5/yarl-1.24.2-py3-none-any.whl", hash = "sha256:2783d9226db8797636cd6896e4de81feed252d1db72265686c9558d97a4d94b9", size = 53576, upload-time = "2026-05-19T21:31:03.909Z" }, ] [[package]] name = "zipp" -version = "3.23.1" +version = "4.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/30/21/093488dfc7cc8964ded15ab726fad40f25fd3d788fd741cc1c5a17d78ee8/zipp-3.23.1.tar.gz", hash = "sha256:32120e378d32cd9714ad503c1d024619063ec28aad2248dc6672ad13edfa5110", size = 25965, upload-time = "2026-04-13T23:21:46.6Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b9/d8/eab98a517c14134c0b2eb4e2387bc5f457334293ec5d2dd3857ec2966802/zipp-4.1.0.tar.gz", hash = "sha256:4cb57381f544315db7688e976e922a2b18cdb513d21cc194eb42232ba2a3e602", size = 26214, upload-time = "2026-05-18T20:08:57.967Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/08/8a/0861bec20485572fbddf3dfba2910e38fe249796cb73ecdeb74e07eeb8d3/zipp-3.23.1-py3-none-any.whl", hash = "sha256:0b3596c50a5c700c9cb40ba8d86d9f2cc4807e9bedb06bcdf7fac85633e444dc", size = 10378, upload-time = "2026-04-13T23:21:45.386Z" }, + { url = "https://files.pythonhosted.org/packages/3a/13/547360d81e6d88d58492968ffda9f9542854f11310ee556fef14260cc886/zipp-4.1.0-py3-none-any.whl", hash = "sha256:25ad4e16390cd314347dd8f1de67a2ac538ae658ed4ab9db16029c07c188e97f", size = 10238, upload-time = "2026-05-18T20:08:57.045Z" }, ]