From 18eda1416657211369d960fa14e4a308b2841775 Mon Sep 17 00:00:00 2001 From: jonahgabriel Date: Mon, 6 Apr 2026 10:46:37 -0400 Subject: [PATCH 1/4] =?UTF-8?q?feat:=20add=206=20critical=20Kafka=E2=86=92?= =?UTF-8?q?DB=20projection=20nodes=20[OMN-7610]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Port omnidash TypeScript read-model consumers to standalone Python services using aiokafka + asyncpg. Each node subscribes to its Kafka topic(s) and projects events into the omnidash_analytics PostgreSQL database with idempotent upsert/insert SQL. Nodes: - session_outcome: session-outcome.v1 → session_outcomes - llm_cost: llm-call-completed.v1 → llm_cost_aggregates - savings: savings-estimated.v1 → savings_estimates - delegation: task-delegated.v1 + shadow-comparison.v1 → 2 tables - baselines: baselines-computed.v1 → 4 tables transactionally - registration: introspection/heartbeat/state-change → node_service_registry Infrastructure: - BaseProjectionRunner ABC with Kafka lifecycle, envelope unwrapping, watermark tracking, graceful shutdown, retry with exponential backoff - AsyncpgAdapter with connection pooling - ONEX envelope unwrapping matching omnidash parseMessage() exactly - Docker Compose for .201 deployment - 39 unit tests (all passing) --- Dockerfile.projection | 18 + docker-compose.projection.yml | 62 + pyproject.toml | 3 + src/omnimarket/adapters/asyncpg_adapter.py | 64 + src/omnimarket/adapters/database.py | 25 + .../node_projection_baselines/__init__.py | 0 .../handlers/__init__.py | 0 .../handlers/handler_baselines.py | 274 +++++ .../tests/__init__.py | 0 .../node_projection_delegation/__init__.py | 0 .../handlers/__init__.py | 0 .../handlers/handler_delegation.py | 248 ++++ .../tests/__init__.py | 0 .../node_projection_llm_cost/__init__.py | 0 .../handlers/__init__.py | 0 .../handlers/handler_llm_cost.py | 169 +++ .../tests/__init__.py | 0 .../node_projection_registration/__init__.py | 0 .../handlers/__init__.py | 0 .../handlers/handler_registration.py | 174 +++ .../tests/__init__.py | 0 .../nodes/node_projection_savings/__init__.py | 0 .../handlers/__init__.py | 0 .../handlers/handler_savings.py | 181 +++ .../node_projection_savings/tests/__init__.py | 0 .../__init__.py | 0 .../handlers/__init__.py | 0 .../handlers/handler_session_outcome.py | 78 ++ .../tests/__init__.py | 0 src/omnimarket/projection/__init__.py | 1 + src/omnimarket/projection/envelope.py | 43 + src/omnimarket/projection/runner.py | 273 +++++ tests/test_projection_envelope.py | 54 + tests/test_projection_handlers.py | 329 ++++++ tests/test_projection_runner.py | 98 ++ uv.lock | 1049 +++++++++++++++++ 36 files changed, 3143 insertions(+) create mode 100644 Dockerfile.projection create mode 100644 docker-compose.projection.yml create mode 100644 src/omnimarket/adapters/asyncpg_adapter.py create mode 100644 src/omnimarket/adapters/database.py create mode 100644 src/omnimarket/nodes/node_projection_baselines/__init__.py create mode 100644 src/omnimarket/nodes/node_projection_baselines/handlers/__init__.py create mode 100644 src/omnimarket/nodes/node_projection_baselines/handlers/handler_baselines.py create mode 100644 src/omnimarket/nodes/node_projection_baselines/tests/__init__.py create mode 100644 src/omnimarket/nodes/node_projection_delegation/__init__.py create mode 100644 src/omnimarket/nodes/node_projection_delegation/handlers/__init__.py create mode 100644 src/omnimarket/nodes/node_projection_delegation/handlers/handler_delegation.py create mode 100644 src/omnimarket/nodes/node_projection_delegation/tests/__init__.py create mode 100644 src/omnimarket/nodes/node_projection_llm_cost/__init__.py create mode 100644 src/omnimarket/nodes/node_projection_llm_cost/handlers/__init__.py create mode 100644 src/omnimarket/nodes/node_projection_llm_cost/handlers/handler_llm_cost.py create mode 100644 src/omnimarket/nodes/node_projection_llm_cost/tests/__init__.py create mode 100644 src/omnimarket/nodes/node_projection_registration/__init__.py create mode 100644 src/omnimarket/nodes/node_projection_registration/handlers/__init__.py create mode 100644 src/omnimarket/nodes/node_projection_registration/handlers/handler_registration.py create mode 100644 src/omnimarket/nodes/node_projection_registration/tests/__init__.py create mode 100644 src/omnimarket/nodes/node_projection_savings/__init__.py create mode 100644 src/omnimarket/nodes/node_projection_savings/handlers/__init__.py create mode 100644 src/omnimarket/nodes/node_projection_savings/handlers/handler_savings.py create mode 100644 src/omnimarket/nodes/node_projection_savings/tests/__init__.py create mode 100644 src/omnimarket/nodes/node_projection_session_outcome/__init__.py create mode 100644 src/omnimarket/nodes/node_projection_session_outcome/handlers/__init__.py create mode 100644 src/omnimarket/nodes/node_projection_session_outcome/handlers/handler_session_outcome.py create mode 100644 src/omnimarket/nodes/node_projection_session_outcome/tests/__init__.py create mode 100644 src/omnimarket/projection/__init__.py create mode 100644 src/omnimarket/projection/envelope.py create mode 100644 src/omnimarket/projection/runner.py create mode 100644 tests/test_projection_envelope.py create mode 100644 tests/test_projection_handlers.py create mode 100644 tests/test_projection_runner.py create mode 100644 uv.lock diff --git a/Dockerfile.projection b/Dockerfile.projection new file mode 100644 index 0000000000..ae9bfd82cf --- /dev/null +++ b/Dockerfile.projection @@ -0,0 +1,18 @@ +FROM python:3.12-slim + +WORKDIR /app + +# Install uv for fast dependency resolution +RUN pip install --no-cache-dir uv + +# Copy dependency file first for layer caching +COPY pyproject.toml ./ +COPY src/ ./src/ + +# Install the package with projection dependencies +RUN uv pip install --system --no-cache ".[projection]" 2>/dev/null || \ + uv pip install --system --no-cache aiokafka asyncpg python-dateutil && \ + uv pip install --system --no-cache -e . + +# Default: overridden by docker-compose command +CMD ["python", "-m", "omnimarket"] diff --git a/docker-compose.projection.yml b/docker-compose.projection.yml new file mode 100644 index 0000000000..0c3111ae9d --- /dev/null +++ b/docker-compose.projection.yml @@ -0,0 +1,62 @@ +############################################################################### +# docker-compose.projection.yml — OMN-7610 projection nodes +# +# Deploys all 6 critical Kafka→DB projection consumers as independent +# Docker services on .201 server. Each service is a standalone Python +# process consuming from its Kafka topic(s) and projecting into the +# omnidash_analytics PostgreSQL database. +# +# Usage: +# docker compose -f docker-compose.projection.yml up -d +# docker compose -f docker-compose.projection.yml logs -f +# docker compose -f docker-compose.projection.yml down +############################################################################### + +x-projection-defaults: &projection-defaults + build: + context: . + dockerfile: Dockerfile.projection + restart: unless-stopped + network_mode: host + env_file: .env + environment: + KAFKA_BROKERS: ${KAFKA_BROKERS:-192.168.86.201:19092} + OMNIDASH_ANALYTICS_DB_URL: ${OMNIDASH_ANALYTICS_DB_URL} + KAFKA_CONSUMER_GROUP: ${KAFKA_CONSUMER_GROUP:-omnimarket-projections-v1} + PYTHONUNBUFFERED: "1" + logging: + driver: json-file + options: + max-size: "10m" + max-file: "3" + +services: + projection-session-outcome: + <<: *projection-defaults + container_name: omnimarket-projection-session-outcome + command: ["python", "-m", "omnimarket.nodes.node_projection_session_outcome.handlers.handler_session_outcome"] + + projection-llm-cost: + <<: *projection-defaults + container_name: omnimarket-projection-llm-cost + command: ["python", "-m", "omnimarket.nodes.node_projection_llm_cost.handlers.handler_llm_cost"] + + projection-savings: + <<: *projection-defaults + container_name: omnimarket-projection-savings + command: ["python", "-m", "omnimarket.nodes.node_projection_savings.handlers.handler_savings"] + + projection-delegation: + <<: *projection-defaults + container_name: omnimarket-projection-delegation + command: ["python", "-m", "omnimarket.nodes.node_projection_delegation.handlers.handler_delegation"] + + projection-baselines: + <<: *projection-defaults + container_name: omnimarket-projection-baselines + command: ["python", "-m", "omnimarket.nodes.node_projection_baselines.handlers.handler_baselines"] + + projection-registration: + <<: *projection-defaults + container_name: omnimarket-projection-registration + command: ["python", "-m", "omnimarket.nodes.node_projection_registration.handlers.handler_registration"] diff --git a/pyproject.toml b/pyproject.toml index 7a23d04980..6659d89c89 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,6 +32,9 @@ dependencies = [ "omnibase_core>=0.39.0", "pydantic>=2.12.5,<3.0.0", "pyyaml>=6.0.2,<7.0.0", + "aiokafka>=0.12.0", + "asyncpg>=0.30.0", + "python-dateutil>=2.9.0", ] [project.urls] diff --git a/src/omnimarket/adapters/asyncpg_adapter.py b/src/omnimarket/adapters/asyncpg_adapter.py new file mode 100644 index 0000000000..999dc942d2 --- /dev/null +++ b/src/omnimarket/adapters/asyncpg_adapter.py @@ -0,0 +1,64 @@ +"""Asyncpg implementation of DatabaseAdapter.""" + +from __future__ import annotations + +import logging +import os +from typing import Any + +import asyncpg + +logger = logging.getLogger(__name__) + +DB_URL_ENV = "OMNIDASH_ANALYTICS_DB_URL" + + +class AsyncpgAdapter: + """DatabaseAdapter backed by asyncpg connection pool.""" + + def __init__(self, dsn: str | None = None, min_size: int = 2, max_size: int = 10) -> None: + self._dsn = dsn or os.environ.get(DB_URL_ENV, "") + self._min_size = min_size + self._max_size = max_size + self._pool: asyncpg.Pool | None = None + + async def connect(self) -> None: + if not self._dsn: + raise RuntimeError(f"{DB_URL_ENV} not set and no DSN provided") + self._pool = await asyncpg.create_pool( + self._dsn, + min_size=self._min_size, + max_size=self._max_size, + command_timeout=30, + ) + logger.info("asyncpg pool connected (min=%d, max=%d)", self._min_size, self._max_size) + + async def execute(self, query: str, *params: Any) -> list[dict[str, Any]]: + assert self._pool is not None, "call connect() first" + async with self._pool.acquire() as conn: + rows = await conn.fetch(query, *params) + return [dict(r) for r in rows] + + async def execute_many(self, query: str, params_list: list[tuple[Any, ...]]) -> None: + assert self._pool is not None, "call connect() first" + async with self._pool.acquire() as conn: + await conn.executemany(query, params_list) + + async def fetchval(self, query: str, *params: Any) -> Any: + assert self._pool is not None, "call connect() first" + async with self._pool.acquire() as conn: + return await conn.fetchval(query, *params) + + async def execute_in_transaction(self, queries: list[tuple[str, tuple[Any, ...]]]) -> None: + """Execute multiple queries in a single transaction.""" + assert self._pool is not None, "call connect() first" + async with self._pool.acquire() as conn: + async with conn.transaction(): + for query, params in queries: + await conn.execute(query, *params) + + async def close(self) -> None: + if self._pool: + await self._pool.close() + self._pool = None + logger.info("asyncpg pool closed") diff --git a/src/omnimarket/adapters/database.py b/src/omnimarket/adapters/database.py new file mode 100644 index 0000000000..ec18091e03 --- /dev/null +++ b/src/omnimarket/adapters/database.py @@ -0,0 +1,25 @@ +"""DatabaseAdapter protocol for projection nodes.""" + +from __future__ import annotations + +from typing import Any, Protocol + + +class DatabaseAdapter(Protocol): + """Async database adapter for projection handlers.""" + + async def execute(self, query: str, *params: Any) -> list[dict[str, Any]]: + """Execute a parameterized query and return rows as dicts.""" + ... + + async def execute_many(self, query: str, params_list: list[tuple[Any, ...]]) -> None: + """Execute a parameterized query for each set of params.""" + ... + + async def fetchval(self, query: str, *params: Any) -> Any: + """Execute a query and return a single value.""" + ... + + async def close(self) -> None: + """Close the connection pool.""" + ... diff --git a/src/omnimarket/nodes/node_projection_baselines/__init__.py b/src/omnimarket/nodes/node_projection_baselines/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/omnimarket/nodes/node_projection_baselines/handlers/__init__.py b/src/omnimarket/nodes/node_projection_baselines/handlers/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/omnimarket/nodes/node_projection_baselines/handlers/handler_baselines.py b/src/omnimarket/nodes/node_projection_baselines/handlers/handler_baselines.py new file mode 100644 index 0000000000..73793cffc5 --- /dev/null +++ b/src/omnimarket/nodes/node_projection_baselines/handlers/handler_baselines.py @@ -0,0 +1,274 @@ +"""Baselines projection: Kafka -> 4 tables transactionally.""" + +from __future__ import annotations + +import asyncio +import json +import logging +import re +from typing import Any + +from omnimarket.projection.runner import ( + BaseProjectionRunner, + MessageMeta, + deterministic_correlation_id, + safe_parse_date, +) + +logger = logging.getLogger(__name__) + +TOPIC = "onex.evt.omnibase-infra.baselines-computed.v1" + +UUID_RE = re.compile(r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", re.I) +MAX_BATCH_ROWS = 4000 +DATE_RE = re.compile(r"^\d{4}-\d{2}-\d{2}$") +VALID_PROMOTION_ACTIONS = {"promote", "shadow", "demote", "retire", "hold"} +VALID_CONFIDENCE_LEVELS = {"low", "medium", "high"} + + +class BaselinesProjectionRunner(BaseProjectionRunner): + """Projects baselines-computed events into 4 tables transactionally. + + Tables: baselines_snapshots, baselines_comparisons, baselines_trend, baselines_breakdown + Uses DELETE+INSERT for child tables within a transaction. + Matches omnidash projectBaselinesSnapshot() exactly. + """ + + @property + def topics(self) -> list[str]: + return [TOPIC] + + async def project_event( + self, topic: str, data: dict[str, Any], meta: MessageMeta + ) -> bool: + raw_snapshot_id = data.get("snapshot_id") + if raw_snapshot_id and UUID_RE.match(str(raw_snapshot_id)): + snapshot_id = str(raw_snapshot_id) + else: + snapshot_id = deterministic_correlation_id( + "baselines-computed", meta.partition, meta.offset + ) + + contract_version = _safe_int(data.get("contract_version"), 1) + computed_at_utc = safe_parse_date( + data.get("computed_at_utc") or data.get("computedAtUtc") or data.get("computed_at") + ) + window_start_utc = ( + safe_parse_date(data.get("window_start_utc") or data.get("windowStartUtc")) + if (data.get("window_start_utc") or data.get("windowStartUtc")) + else None + ) + window_end_utc = ( + safe_parse_date(data.get("window_end_utc") or data.get("windowEndUtc")) + if (data.get("window_end_utc") or data.get("windowEndUtc")) + else None + ) + + # Parse child arrays with batch caps + raw_comparisons_all = data.get("comparisons") if isinstance(data.get("comparisons"), list) else [] + if len(raw_comparisons_all) > MAX_BATCH_ROWS: + logger.warning( + "baselines snapshot %s: %d comparison rows, capping at %d", + snapshot_id, len(raw_comparisons_all), MAX_BATCH_ROWS, + ) + raw_comparisons = raw_comparisons_all[:MAX_BATCH_ROWS] + + raw_trend_all = data.get("trend") if isinstance(data.get("trend"), list) else [] + if len(raw_trend_all) > MAX_BATCH_ROWS: + logger.warning( + "baselines snapshot %s: %d trend rows, capping at %d", + snapshot_id, len(raw_trend_all), MAX_BATCH_ROWS, + ) + raw_trend = raw_trend_all[:MAX_BATCH_ROWS] + + raw_breakdown_all = data.get("breakdown") if isinstance(data.get("breakdown"), list) else [] + if len(raw_breakdown_all) > MAX_BATCH_ROWS: + logger.warning( + "baselines snapshot %s: %d breakdown rows, capping at %d", + snapshot_id, len(raw_breakdown_all), MAX_BATCH_ROWS, + ) + raw_breakdown = raw_breakdown_all[:MAX_BATCH_ROWS] + + # Build trend rows with validation and dedup + trend_by_date: dict[str, dict[str, Any]] = {} + for t in raw_trend: + if not isinstance(t, dict): + continue + date_val = str(t.get("date") or t.get("dateStr") or "") + if not date_val or not DATE_RE.match(date_val): + logger.warning("Skipping trend row with invalid date: %s", date_val) + continue + trend_by_date[date_val] = { + "snapshot_id": snapshot_id, + "date": date_val, + "avg_cost_savings": str(max(0.0, min(99.0, _safe_float( + t.get("avg_cost_savings") or t.get("avgCostSavings") + )))), + "avg_outcome_improvement": str(max(0.0, min(99.0, _safe_float( + t.get("avg_outcome_improvement") or t.get("avgOutcomeImprovement") + )))), + "comparisons_evaluated": _safe_int( + t.get("comparisons_evaluated") or t.get("comparisonsEvaluated") + ), + } + + # Build comparison rows + comparison_rows = [] + for c in raw_comparisons: + if not isinstance(c, dict): + continue + pattern_id = str(c.get("pattern_id") or c.get("patternId") or "").strip() + if not pattern_id: + logger.warning("Skipping comparison row with blank pattern_id for snapshot %s", snapshot_id) + continue + + rec_raw = str(c.get("recommendation") or "") + recommendation = rec_raw if rec_raw in VALID_PROMOTION_ACTIONS else "shadow" + conf_raw = str(c.get("confidence") or "").lower() + confidence = conf_raw if conf_raw in VALID_CONFIDENCE_LEVELS else "low" + + comparison_rows.append({ + "snapshot_id": snapshot_id, + "pattern_id": pattern_id, + "pattern_name": str(c.get("pattern_name") or c.get("patternName") or ""), + "sample_size": _safe_int(c.get("sample_size") or c.get("sampleSize")), + "window_start": str(c.get("window_start") or c.get("windowStart") or ""), + "window_end": str(c.get("window_end") or c.get("windowEnd") or ""), + "token_delta": json.dumps(c.get("token_delta") or c.get("tokenDelta") or {}), + "time_delta": json.dumps(c.get("time_delta") or c.get("timeDelta") or {}), + "retry_delta": json.dumps(c.get("retry_delta") or c.get("retryDelta") or {}), + "test_pass_rate_delta": json.dumps(c.get("test_pass_rate_delta") or c.get("testPassRateDelta") or {}), + "review_iteration_delta": json.dumps(c.get("review_iteration_delta") or c.get("reviewIterationDelta") or {}), + "recommendation": recommendation, + "confidence": confidence, + "rationale": str(c.get("rationale") or ""), + }) + + # Build breakdown rows with dedup + breakdown_by_action: dict[str, dict[str, Any]] = {} + for b in raw_breakdown: + if not isinstance(b, dict): + continue + action_raw = str(b.get("action") or "") + action = action_raw if action_raw in VALID_PROMOTION_ACTIONS else "shadow" + breakdown_by_action[action] = { + "snapshot_id": snapshot_id, + "action": action, + "count": _safe_int(b.get("count")), + "avg_confidence": str(max(0.0, min(1.0, _safe_float( + b.get("avg_confidence") or b.get("avgConfidence") + )))), + } + + # Execute all in a single transaction + queries: list[tuple[str, tuple[Any, ...]]] = [] + + # 1. Upsert snapshot header + queries.append(( + """ + INSERT INTO baselines_snapshots ( + snapshot_id, contract_version, computed_at_utc, window_start_utc, window_end_utc + ) VALUES ($1, $2, $3, $4, $5) + ON CONFLICT (snapshot_id) DO UPDATE SET + contract_version = EXCLUDED.contract_version, + computed_at_utc = EXCLUDED.computed_at_utc, + window_start_utc = EXCLUDED.window_start_utc, + window_end_utc = EXCLUDED.window_end_utc, + projected_at = NOW() + """, + (snapshot_id, contract_version, computed_at_utc, window_start_utc, window_end_utc), + )) + + # 2. Delete + re-insert comparisons + queries.append(( + "DELETE FROM baselines_comparisons WHERE snapshot_id = $1", + (snapshot_id,), + )) + for row in comparison_rows: + queries.append(( + """ + INSERT INTO baselines_comparisons ( + snapshot_id, pattern_id, pattern_name, sample_size, + window_start, window_end, token_delta, time_delta, + retry_delta, test_pass_rate_delta, review_iteration_delta, + recommendation, confidence, rationale + ) VALUES ( + $1, $2, $3, $4, $5, $6, $7::jsonb, $8::jsonb, + $9::jsonb, $10::jsonb, $11::jsonb, $12, $13, $14 + ) + """, + ( + row["snapshot_id"], row["pattern_id"], row["pattern_name"], row["sample_size"], + row["window_start"], row["window_end"], row["token_delta"], row["time_delta"], + row["retry_delta"], row["test_pass_rate_delta"], row["review_iteration_delta"], + row["recommendation"], row["confidence"], row["rationale"], + ), + )) + + # 3. Delete + re-insert trend + queries.append(( + "DELETE FROM baselines_trend WHERE snapshot_id = $1", + (snapshot_id,), + )) + for row in trend_by_date.values(): + queries.append(( + """ + INSERT INTO baselines_trend ( + snapshot_id, date, avg_cost_savings, avg_outcome_improvement, comparisons_evaluated + ) VALUES ($1, $2, $3, $4, $5) + """, + ( + row["snapshot_id"], row["date"], row["avg_cost_savings"], + row["avg_outcome_improvement"], row["comparisons_evaluated"], + ), + )) + + # 4. Delete + re-insert breakdown + queries.append(( + "DELETE FROM baselines_breakdown WHERE snapshot_id = $1", + (snapshot_id,), + )) + for row in breakdown_by_action.values(): + queries.append(( + """ + INSERT INTO baselines_breakdown ( + snapshot_id, action, count, avg_confidence + ) VALUES ($1, $2, $3, $4) + """, + (row["snapshot_id"], row["action"], row["count"], row["avg_confidence"]), + )) + + await self.db.execute_in_transaction(queries) + + logger.info( + "Projected baselines snapshot %s (%d comparisons, %d trend, %d breakdown)", + snapshot_id, + len(comparison_rows), + len(trend_by_date), + len(breakdown_by_action), + ) + return True + + +def _safe_int(value: Any, default: int = 0) -> int: + if value is None: + return default + try: + return int(float(value)) + except (ValueError, TypeError): + return default + + +def _safe_float(value: Any, default: float = 0.0) -> float: + if value is None: + return default + try: + return float(value) + except (ValueError, TypeError): + return default + + +if __name__ == "__main__": + logging.basicConfig(level=logging.INFO, format="%(asctime)s %(name)s %(levelname)s %(message)s") + runner = BaselinesProjectionRunner() + asyncio.run(runner.run()) diff --git a/src/omnimarket/nodes/node_projection_baselines/tests/__init__.py b/src/omnimarket/nodes/node_projection_baselines/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/omnimarket/nodes/node_projection_delegation/__init__.py b/src/omnimarket/nodes/node_projection_delegation/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/omnimarket/nodes/node_projection_delegation/handlers/__init__.py b/src/omnimarket/nodes/node_projection_delegation/handlers/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/omnimarket/nodes/node_projection_delegation/handlers/handler_delegation.py b/src/omnimarket/nodes/node_projection_delegation/handlers/handler_delegation.py new file mode 100644 index 0000000000..b7145d27bb --- /dev/null +++ b/src/omnimarket/nodes/node_projection_delegation/handlers/handler_delegation.py @@ -0,0 +1,248 @@ +"""Delegation projection: Kafka -> delegation_events + delegation_shadow_comparisons tables.""" + +from __future__ import annotations + +import asyncio +import logging +import math +from typing import Any + +from omnimarket.projection.runner import ( + BaseProjectionRunner, + MessageMeta, + safe_parse_date, +) + +logger = logging.getLogger(__name__) + +TOPIC_TASK_DELEGATED = "onex.evt.omniclaude.task-delegated.v1" +TOPIC_SHADOW_COMPARISON = "onex.evt.omniclaude.delegation-shadow-comparison.v1" + + +class DelegationProjectionRunner(BaseProjectionRunner): + """Projects task-delegated and delegation-shadow-comparison events. + + Two topics -> two tables, each with ON CONFLICT (correlation_id) DO NOTHING. + Matches omnidash projectTaskDelegatedEvent() and + projectDelegationShadowComparisonEvent() exactly. + """ + + @property + def topics(self) -> list[str]: + return [TOPIC_TASK_DELEGATED, TOPIC_SHADOW_COMPARISON] + + async def project_event( + self, topic: str, data: dict[str, Any], meta: MessageMeta + ) -> bool: + if topic == TOPIC_TASK_DELEGATED: + return await self._project_task_delegated(data, meta) + if topic == TOPIC_SHADOW_COMPARISON: + return await self._project_shadow_comparison(data, meta) + return False + + async def _project_task_delegated( + self, data: dict[str, Any], meta: MessageMeta + ) -> bool: + correlation_id = ( + data.get("correlation_id") + or data.get("correlationId") + or meta.fallback_id + ) + + task_type = data.get("task_type") or data.get("taskType") + delegated_to = ( + data.get("delegated_to") + or data.get("delegatedTo") + or data.get("model_used") + or data.get("modelUsed") + ) + if not task_type or not delegated_to: + logger.warning( + "task-delegated event missing required fields (correlation_id=%s)", + correlation_id, + ) + return True + + session_id = data.get("session_id") or data.get("sessionId") or None + timestamp = safe_parse_date(data.get("timestamp") or data.get("emitted_at")) + delegated_by = ( + data.get("delegated_by") + or data.get("delegatedBy") + or data.get("handler_used") + or data.get("handlerUsed") + or None + ) + quality_gate_passed = bool( + data.get("quality_gate_passed") + if data.get("quality_gate_passed") is not None + else data.get("qualityGatePassed") or False + ) + + import json + + quality_gates_checked = data.get("quality_gates_checked") or data.get("qualityGatesChecked") + quality_gates_failed = data.get("quality_gates_failed") or data.get("qualityGatesFailed") + qgc_json = json.dumps(quality_gates_checked) if quality_gates_checked else None + qgf_json = json.dumps(quality_gates_failed) if quality_gates_failed else None + + cost_usd = _safe_numeric_str(data.get("cost_usd") or data.get("costUsd")) + cost_savings_usd = _safe_numeric_str( + data.get("cost_savings_usd") + or data.get("costSavingsUsd") + or data.get("estimated_savings_usd") + or data.get("estimatedSavingsUsd") + ) + delegation_latency_ms = _safe_int_or_none( + data.get("delegation_latency_ms") + or data.get("delegationLatencyMs") + or data.get("latency_ms") + or data.get("latencyMs") + ) + repo = data.get("repo") or None + is_shadow = bool( + data.get("is_shadow") + if data.get("is_shadow") is not None + else data.get("isShadow") or False + ) + + await self.db.execute( + """ + INSERT INTO delegation_events ( + correlation_id, session_id, timestamp, task_type, + delegated_to, delegated_by, quality_gate_passed, + quality_gates_checked, quality_gates_failed, + cost_usd, cost_savings_usd, delegation_latency_ms, + repo, is_shadow + ) VALUES ( + $1, $2, $3, $4, + $5, $6, $7, + $8::jsonb, $9::jsonb, + $10, $11, $12, + $13, $14 + ) + ON CONFLICT (correlation_id) DO NOTHING + """, + correlation_id, + str(session_id) if session_id else None, + timestamp, + str(task_type), + str(delegated_to), + str(delegated_by) if delegated_by else None, + quality_gate_passed, + qgc_json, + qgf_json, + cost_usd, + cost_savings_usd, + delegation_latency_ms, + str(repo) if repo else None, + is_shadow, + ) + return True + + async def _project_shadow_comparison( + self, data: dict[str, Any], meta: MessageMeta + ) -> bool: + correlation_id = ( + data.get("correlation_id") + or data.get("correlationId") + or meta.fallback_id + ) + + task_type = data.get("task_type") or data.get("taskType") + primary_agent = data.get("primary_agent") or data.get("primaryAgent") + shadow_agent = data.get("shadow_agent") or data.get("shadowAgent") + if not task_type or not primary_agent or not shadow_agent: + logger.warning( + "delegation-shadow-comparison event missing required fields (correlation_id=%s)", + correlation_id, + ) + return True + + session_id = data.get("session_id") or data.get("sessionId") or None + timestamp = safe_parse_date(data.get("timestamp")) + divergence_detected = bool( + data.get("divergence_detected") + if data.get("divergence_detected") is not None + else data.get("divergenceDetected") or False + ) + divergence_score = _safe_numeric_str( + data.get("divergence_score") or data.get("divergenceScore") + ) + primary_latency_ms = _safe_int_or_none( + data.get("primary_latency_ms") or data.get("primaryLatencyMs") + ) + shadow_latency_ms = _safe_int_or_none( + data.get("shadow_latency_ms") or data.get("shadowLatencyMs") + ) + primary_cost_usd = _safe_numeric_str( + data.get("primary_cost_usd") or data.get("primaryCostUsd") + ) + shadow_cost_usd = _safe_numeric_str( + data.get("shadow_cost_usd") or data.get("shadowCostUsd") + ) + divergence_reason = ( + data.get("divergence_reason") + or data.get("divergenceReason") + or None + ) + + await self.db.execute( + """ + INSERT INTO delegation_shadow_comparisons ( + correlation_id, session_id, timestamp, task_type, + primary_agent, shadow_agent, divergence_detected, + divergence_score, primary_latency_ms, shadow_latency_ms, + primary_cost_usd, shadow_cost_usd, divergence_reason + ) VALUES ( + $1, $2, $3, $4, + $5, $6, $7, + $8, $9, $10, + $11, $12, $13 + ) + ON CONFLICT (correlation_id) DO NOTHING + """, + correlation_id, + str(session_id) if session_id else None, + timestamp, + str(task_type), + str(primary_agent), + str(shadow_agent), + divergence_detected, + divergence_score, + primary_latency_ms, + shadow_latency_ms, + primary_cost_usd, + shadow_cost_usd, + str(divergence_reason) if divergence_reason else None, + ) + return True + + +def _safe_numeric_str(value: Any) -> str | None: + if value is None: + return None + try: + n = float(value) + if not math.isfinite(n): + return None + return str(n) + except (ValueError, TypeError): + return None + + +def _safe_int_or_none(value: Any) -> int | None: + if value is None: + return None + try: + n = float(value) + if not math.isfinite(n): + return None + return round(n) + except (ValueError, TypeError): + return None + + +if __name__ == "__main__": + logging.basicConfig(level=logging.INFO, format="%(asctime)s %(name)s %(levelname)s %(message)s") + runner = DelegationProjectionRunner() + asyncio.run(runner.run()) diff --git a/src/omnimarket/nodes/node_projection_delegation/tests/__init__.py b/src/omnimarket/nodes/node_projection_delegation/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/omnimarket/nodes/node_projection_llm_cost/__init__.py b/src/omnimarket/nodes/node_projection_llm_cost/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/omnimarket/nodes/node_projection_llm_cost/handlers/__init__.py b/src/omnimarket/nodes/node_projection_llm_cost/handlers/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/omnimarket/nodes/node_projection_llm_cost/handlers/handler_llm_cost.py b/src/omnimarket/nodes/node_projection_llm_cost/handlers/handler_llm_cost.py new file mode 100644 index 0000000000..eef8d9c887 --- /dev/null +++ b/src/omnimarket/nodes/node_projection_llm_cost/handlers/handler_llm_cost.py @@ -0,0 +1,169 @@ +"""LLM cost projection: Kafka -> llm_cost_aggregates table.""" + +from __future__ import annotations + +import asyncio +import logging +import math +from typing import Any + +from omnimarket.projection.runner import ( + BaseProjectionRunner, + MessageMeta, + safe_parse_date, +) + +logger = logging.getLogger(__name__) + +TOPIC = "onex.evt.omniintelligence.llm-call-completed.v1" + +VALID_USAGE_SOURCES = {"API", "ESTIMATED", "MISSING"} + + +class LlmCostProjectionRunner(BaseProjectionRunner): + """Projects llm-call-completed events into llm_cost_aggregates table. + + Append-only (no ON CONFLICT) -- matches omnidash projectLlmCostEvent() exactly. + """ + + @property + def topics(self) -> list[str]: + return [TOPIC] + + async def project_event( + self, topic: str, data: dict[str, Any], meta: MessageMeta + ) -> bool: + bucket_time = safe_parse_date( + data.get("timestamp_iso") + or data.get("bucket_time") + or data.get("bucketTime") + or data.get("timestamp") + or data.get("created_at") + ) + + # usage_source normalization + usage_normalized = data.get("usage_normalized") or {} + usage_source_raw = ( + (usage_normalized.get("source") if isinstance(usage_normalized, dict) else None) + or data.get("usage_source") + or data.get("usageSource") + or ("ESTIMATED" if data.get("usage_is_estimated") else "API") + ) + usage_source_upper = str(usage_source_raw).upper() + if usage_source_upper not in VALID_USAGE_SOURCES: + logger.warning( + "LLM cost event has unrecognised usage_source %r -- defaulting to API", + usage_source_raw, + ) + usage_source_upper = "API" + usage_source = usage_source_upper + + granularity_raw = data.get("granularity") or "hour" + granularity = granularity_raw if granularity_raw in ("hour", "day") else "hour" + + prompt_tokens = _safe_int(data.get("prompt_tokens") or data.get("promptTokens")) + completion_tokens = _safe_int(data.get("completion_tokens") or data.get("completionTokens")) + raw_total = _safe_int(data.get("total_tokens") or data.get("totalTokens")) + derived_total = prompt_tokens + completion_tokens + + if raw_total == 0 and derived_total > 0: + total_tokens = derived_total + else: + if raw_total != 0 and derived_total != 0 and raw_total != derived_total: + logger.warning( + "LLM cost event token total mismatch: total=%d but prompt(%d)+completion(%d)=%d", + raw_total, + prompt_tokens, + completion_tokens, + derived_total, + ) + total_tokens = raw_total + + estimated_cost_usd = _safe_cost(data.get("estimated_cost_usd") or data.get("estimatedCostUsd")) + total_cost_usd = _safe_cost( + data.get("total_cost_usd") + or data.get("totalCostUsd") + or data.get("estimated_cost_usd") + or data.get("estimatedCostUsd") + ) + reported_cost_usd = _safe_cost(data.get("reported_cost_usd") or data.get("reportedCostUsd")) + + model_name = ( + data.get("model_id") + or data.get("model_name") + or data.get("modelName") + or "unknown" + ) + + reporting_source = data.get("reporting_source") or data.get("reportingSource") + explicit_repo = data.get("repo_name") or data.get("repoName") + repo_name = explicit_repo or ( + reporting_source + if (reporting_source and len(str(reporting_source)) < 64 and " " not in str(reporting_source)) + else None + ) + + session_id = data.get("session_id") or data.get("sessionId") or None + pattern_id = data.get("pattern_id") or data.get("patternId") or None + pattern_name = data.get("pattern_name") or data.get("patternName") or None + request_count = _safe_int(data.get("request_count") or data.get("requestCount") or 1) + + if model_name == "unknown": + logger.warning("LLM cost event missing model_id/model_name -- inserting as 'unknown'") + + await self.db.execute( + """ + INSERT INTO llm_cost_aggregates ( + bucket_time, granularity, model_name, repo_name, + pattern_id, pattern_name, session_id, usage_source, + request_count, prompt_tokens, completion_tokens, total_tokens, + total_cost_usd, reported_cost_usd, estimated_cost_usd + ) VALUES ( + $1, $2, $3, $4, + $5, $6, $7, $8, + $9, $10, $11, $12, + $13, $14, $15 + ) + """, + bucket_time, + granularity, + str(model_name), + str(repo_name) if repo_name else None, + str(pattern_id) if pattern_id else None, + str(pattern_name) if pattern_name else None, + str(session_id) if session_id else None, + usage_source, + request_count, + prompt_tokens, + completion_tokens, + total_tokens, + str(total_cost_usd), + str(reported_cost_usd), + str(estimated_cost_usd), + ) + return True + + +def _safe_int(value: Any, default: int = 0) -> int: + if value is None: + return default + try: + return int(float(value)) + except (ValueError, TypeError): + return default + + +def _safe_cost(value: Any) -> float: + if value is None: + return 0.0 + try: + n = float(value) + return n if math.isfinite(n) else 0.0 + except (ValueError, TypeError): + return 0.0 + + +if __name__ == "__main__": + logging.basicConfig(level=logging.INFO, format="%(asctime)s %(name)s %(levelname)s %(message)s") + runner = LlmCostProjectionRunner() + asyncio.run(runner.run()) diff --git a/src/omnimarket/nodes/node_projection_llm_cost/tests/__init__.py b/src/omnimarket/nodes/node_projection_llm_cost/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/omnimarket/nodes/node_projection_registration/__init__.py b/src/omnimarket/nodes/node_projection_registration/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/omnimarket/nodes/node_projection_registration/handlers/__init__.py b/src/omnimarket/nodes/node_projection_registration/handlers/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/omnimarket/nodes/node_projection_registration/handlers/handler_registration.py b/src/omnimarket/nodes/node_projection_registration/handlers/handler_registration.py new file mode 100644 index 0000000000..97a9aff25f --- /dev/null +++ b/src/omnimarket/nodes/node_projection_registration/handlers/handler_registration.py @@ -0,0 +1,174 @@ +"""Node registration projection: Kafka -> node_service_registry table.""" + +from __future__ import annotations + +import asyncio +import json +import logging +from typing import Any + +from omnimarket.projection.runner import ( + BaseProjectionRunner, + MessageMeta, +) + +logger = logging.getLogger(__name__) + +TOPIC_INTROSPECTION = "onex.evt.platform.node-introspection.v1" +TOPIC_HEARTBEAT = "onex.evt.platform.node-heartbeat.v1" +TOPIC_STATE_CHANGE = "onex.evt.platform.node-state-change.v1" + + +class RegistrationProjectionRunner(BaseProjectionRunner): + """Projects node registration events into node_service_registry table. + + Three sub-handlers: + - introspection: full upsert (all columns) + - heartbeat: liveness update only (health_status, last_health_check) + - state-change: state update only (health_status, is_active) + + Matches omnidash projectNodeIntrospectionEvent(), projectNodeHeartbeatEvent(), + and projectNodeStateChangeEvent() exactly. + """ + + @property + def topics(self) -> list[str]: + return [TOPIC_INTROSPECTION, TOPIC_HEARTBEAT, TOPIC_STATE_CHANGE] + + async def project_event( + self, topic: str, data: dict[str, Any], meta: MessageMeta + ) -> bool: + if topic == TOPIC_INTROSPECTION: + return await self._project_introspection(data) + if topic == TOPIC_HEARTBEAT: + return await self._project_heartbeat(data) + if topic == TOPIC_STATE_CHANGE: + return await self._project_state_change(data) + return False + + async def _project_introspection(self, data: dict[str, Any]) -> bool: + node_name = data.get("node_name") or data.get("nodeName") or None + node_id = data.get("node_id") or data.get("nodeId") or None + service_name = data.get("service_name") or node_name or node_id + if not service_name: + logger.warning("node-introspection missing service_name/node_name/node_id -- skipping") + return True + + service_url = data.get("service_url") or data.get("serviceUrl") or "" + service_type = ( + data.get("service_type") + or data.get("serviceType") + or data.get("node_type") + or data.get("nodeType") + or None + ) + health_status = ( + data.get("health_status") + or data.get("healthStatus") + or data.get("current_state") + or "unknown" + ) + + raw_metadata = data.get("metadata") or {} + if not isinstance(raw_metadata, dict): + raw_metadata = {} + metadata = {**raw_metadata} + if node_name: + metadata["node_name"] = node_name + if node_id: + metadata["node_id"] = node_id + metadata_json = json.dumps(metadata) + + await self.db.execute( + """ + INSERT INTO node_service_registry ( + service_name, service_url, service_type, health_status, + last_health_check, metadata, is_active, updated_at, projected_at + ) VALUES ( + $1, $2, $3, $4, + NOW(), $5::jsonb, true, NOW(), NOW() + ) + ON CONFLICT (service_name) DO UPDATE SET + service_url = EXCLUDED.service_url, + service_type = EXCLUDED.service_type, + health_status = EXCLUDED.health_status, + last_health_check = EXCLUDED.last_health_check, + metadata = EXCLUDED.metadata, + is_active = EXCLUDED.is_active, + updated_at = EXCLUDED.updated_at, + projected_at = EXCLUDED.projected_at + """, + str(service_name), + str(service_url), + str(service_type) if service_type else None, + str(health_status), + metadata_json, + ) + return True + + async def _project_heartbeat(self, data: dict[str, Any]) -> bool: + service_name = ( + data.get("service_name") + or data.get("node_name") + or data.get("nodeName") + or data.get("node_id") + or data.get("nodeId") + ) + if not service_name: + logger.warning("node-heartbeat missing service_name/node_id -- skipping") + return True + + health_status = data.get("health_status") or data.get("healthStatus") or "healthy" + + await self.db.execute( + """ + UPDATE node_service_registry + SET health_status = $1, + last_health_check = NOW(), + updated_at = NOW() + WHERE service_name = $2 + """, + str(health_status), + str(service_name), + ) + return True + + async def _project_state_change(self, data: dict[str, Any]) -> bool: + service_name = ( + data.get("service_name") + or data.get("node_name") + or data.get("nodeName") + or data.get("node_id") + or data.get("nodeId") + ) + if not service_name: + logger.warning("node-state-change missing service_name/node_id -- skipping") + return True + + new_state = ( + data.get("new_state") + or data.get("newState") + or data.get("health_status") + or "unknown" + ) + is_active = str(new_state).lower() == "active" + + await self.db.execute( + """ + UPDATE node_service_registry + SET health_status = $1, + is_active = $2, + updated_at = NOW() + WHERE service_name = $3 + """, + str(new_state), + is_active, + str(service_name), + ) + return True + + +if __name__ == "__main__": + logging.basicConfig(level=logging.INFO, format="%(asctime)s %(name)s %(levelname)s %(message)s") + runner = RegistrationProjectionRunner() + asyncio.run(runner.run()) diff --git a/src/omnimarket/nodes/node_projection_registration/tests/__init__.py b/src/omnimarket/nodes/node_projection_registration/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/omnimarket/nodes/node_projection_savings/__init__.py b/src/omnimarket/nodes/node_projection_savings/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/omnimarket/nodes/node_projection_savings/handlers/__init__.py b/src/omnimarket/nodes/node_projection_savings/handlers/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/omnimarket/nodes/node_projection_savings/handlers/handler_savings.py b/src/omnimarket/nodes/node_projection_savings/handlers/handler_savings.py new file mode 100644 index 0000000000..017731b506 --- /dev/null +++ b/src/omnimarket/nodes/node_projection_savings/handlers/handler_savings.py @@ -0,0 +1,181 @@ +"""Savings projection: Kafka -> savings_estimates table.""" + +from __future__ import annotations + +import asyncio +import logging +import math +from typing import Any + +from omnimarket.projection.runner import ( + BaseProjectionRunner, + MessageMeta, + deterministic_correlation_id, + safe_parse_date, +) + +logger = logging.getLogger(__name__) + +TOPIC = "onex.evt.omnibase-infra.savings-estimated.v1" + + +class SavingsProjectionRunner(BaseProjectionRunner): + """Projects savings-estimated events into savings_estimates table. + + SQL: INSERT ... ON CONFLICT (source_event_id) DO UPDATE + Matches omnidash projectSavingsEstimated() exactly. + """ + + @property + def topics(self) -> list[str]: + return [TOPIC] + + async def project_event( + self, topic: str, data: dict[str, Any], meta: MessageMeta + ) -> bool: + session_id = str(data.get("session_id") or data.get("sessionId") or "").strip() + if not session_id: + logger.warning("savings-estimated event missing session_id") + return True + + correlation_id = str(data.get("correlation_id") or data.get("correlationId") or "").strip() + source_event_id = correlation_id or deterministic_correlation_id( + TOPIC, meta.partition, meta.offset + ) + + event_timestamp = safe_parse_date( + data.get("timestamp_iso") or data.get("timestamp") or data.get("emitted_at") + ) + + actual_total_tokens = _safe_int(data.get("actual_total_tokens") or data.get("actualTotalTokens")) + actual_cost_usd = _safe_cost_str(data.get("actual_cost_usd") or data.get("actualCostUsd")) + actual_model_id = _str_or_none(data.get("actual_model_id") or data.get("actualModelId")) + counterfactual_model_id = _str_or_none( + data.get("counterfactual_model_id") or data.get("counterfactualModelId") + ) + direct_savings_usd = _safe_cost_str(data.get("direct_savings_usd") or data.get("directSavingsUsd")) + direct_tokens_saved = _safe_int(data.get("direct_tokens_saved") or data.get("directTokensSaved")) + estimated_total_savings_usd = _safe_cost_str( + data.get("estimated_total_savings_usd") or data.get("estimatedTotalSavingsUsd") + ) + estimated_total_tokens_saved = _safe_int( + data.get("estimated_total_tokens_saved") or data.get("estimatedTotalTokensSaved") + ) + categories = data.get("categories") or [] + direct_confidence = _safe_float(data.get("direct_confidence") or data.get("directConfidence")) + heuristic_confidence_avg = _safe_float( + data.get("heuristic_confidence_avg") or data.get("heuristicConfidenceAvg") + ) + estimation_method = str( + data.get("estimation_method") or data.get("estimationMethod") or "tiered_attribution_v1" + ) + treatment_group = _str_or_none(data.get("treatment_group") or data.get("treatmentGroup")) + is_measured = bool(data.get("is_measured") or data.get("isMeasured") or False) + completeness_status = str( + data.get("completeness_status") or data.get("completenessStatus") or "complete" + ) + pricing_manifest_version = _str_or_none( + data.get("pricing_manifest_version") or data.get("pricingManifestVersion") + ) + schema_version = str(data.get("schema_version") or "1.0") + + import json + + categories_json = json.dumps(categories) if categories else "[]" + + await self.db.execute( + """ + INSERT INTO savings_estimates ( + source_event_id, session_id, correlation_id, schema_version, + actual_total_tokens, actual_cost_usd, actual_model_id, counterfactual_model_id, + direct_savings_usd, direct_tokens_saved, + estimated_total_savings_usd, estimated_total_tokens_saved, + categories, direct_confidence, heuristic_confidence_avg, + estimation_method, treatment_group, is_measured, + completeness_status, pricing_manifest_version, event_timestamp + ) VALUES ( + $1, $2, $3, $4, + $5, $6, $7, $8, + $9, $10, + $11, $12, + $13::jsonb, $14, $15, + $16, $17, $18, + $19, $20, $21 + ) + ON CONFLICT (source_event_id) DO UPDATE SET + actual_total_tokens = EXCLUDED.actual_total_tokens, + actual_cost_usd = EXCLUDED.actual_cost_usd, + direct_savings_usd = EXCLUDED.direct_savings_usd, + direct_tokens_saved = EXCLUDED.direct_tokens_saved, + estimated_total_savings_usd = EXCLUDED.estimated_total_savings_usd, + estimated_total_tokens_saved = EXCLUDED.estimated_total_tokens_saved, + categories = EXCLUDED.categories, + direct_confidence = EXCLUDED.direct_confidence, + heuristic_confidence_avg = EXCLUDED.heuristic_confidence_avg, + completeness_status = EXCLUDED.completeness_status, + ingested_at = NOW() + """, + source_event_id, + session_id, + correlation_id or None, + schema_version, + actual_total_tokens, + actual_cost_usd, + actual_model_id, + counterfactual_model_id, + direct_savings_usd, + direct_tokens_saved, + estimated_total_savings_usd, + estimated_total_tokens_saved, + categories_json, + direct_confidence, + heuristic_confidence_avg, + estimation_method, + treatment_group, + is_measured, + completeness_status, + pricing_manifest_version, + event_timestamp, + ) + logger.info( + "Projected savings-estimated for session %s (total_savings=$%.4f)", + session_id, + float(estimated_total_savings_usd), + ) + return True + + +def _safe_int(value: Any, default: int = 0) -> int: + if value is None: + return default + try: + return int(float(value)) + except (ValueError, TypeError): + return default + + +def _safe_float(value: Any, default: float = 0.0) -> float: + if value is None: + return default + try: + n = float(value) + return n if math.isfinite(n) else default + except (ValueError, TypeError): + return default + + +def _safe_cost_str(value: Any) -> str: + n = _safe_float(value) + return str(n) + + +def _str_or_none(value: Any) -> str | None: + if value is None: + return None + return str(value) + + +if __name__ == "__main__": + logging.basicConfig(level=logging.INFO, format="%(asctime)s %(name)s %(levelname)s %(message)s") + runner = SavingsProjectionRunner() + asyncio.run(runner.run()) diff --git a/src/omnimarket/nodes/node_projection_savings/tests/__init__.py b/src/omnimarket/nodes/node_projection_savings/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/omnimarket/nodes/node_projection_session_outcome/__init__.py b/src/omnimarket/nodes/node_projection_session_outcome/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/omnimarket/nodes/node_projection_session_outcome/handlers/__init__.py b/src/omnimarket/nodes/node_projection_session_outcome/handlers/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/omnimarket/nodes/node_projection_session_outcome/handlers/handler_session_outcome.py b/src/omnimarket/nodes/node_projection_session_outcome/handlers/handler_session_outcome.py new file mode 100644 index 0000000000..9a4dd1d74f --- /dev/null +++ b/src/omnimarket/nodes/node_projection_session_outcome/handlers/handler_session_outcome.py @@ -0,0 +1,78 @@ +"""Session outcome projection: Kafka -> session_outcomes table.""" + +from __future__ import annotations + +import asyncio +import logging +from typing import Any + +from omnimarket.projection.runner import ( + BaseProjectionRunner, + MessageMeta, + safe_parse_date, +) + +logger = logging.getLogger(__name__) + +TOPIC = "onex.evt.omniclaude.session-outcome.v1" + + +class SessionOutcomeProjectionRunner(BaseProjectionRunner): + """Projects session-outcome events into session_outcomes table. + + SQL: INSERT ... ON CONFLICT (session_id) DO UPDATE + Matches omnidash projectSessionOutcome() exactly. + """ + + @property + def topics(self) -> list[str]: + return [TOPIC] + + async def project_event( + self, topic: str, data: dict[str, Any], meta: MessageMeta + ) -> bool: + session_id = ( + data.get("session_id") + or data.get("sessionId") + or data.get("correlation_id") + or data.get("correlationId") + or data.get("_correlation_id") + or "" + ) + + if not session_id: + keys = sorted(k for k in data if not k.startswith("_")) + logger.warning( + "session-outcome event missing session_id -- skipping. Keys: %s", + keys, + ) + return True + + outcome = data.get("outcome") or "unknown" + emitted_at = safe_parse_date( + data.get("emitted_at") + or data.get("emittedAt") + or data.get("timestamp") + or data.get("created_at") + ) + + await self.db.execute( + """ + INSERT INTO session_outcomes (session_id, outcome, emitted_at, ingested_at) + VALUES ($1, $2, $3, NOW()) + ON CONFLICT (session_id) DO UPDATE SET + outcome = EXCLUDED.outcome, + emitted_at = EXCLUDED.emitted_at, + ingested_at = NOW() + """, + session_id, + outcome, + emitted_at, + ) + return True + + +if __name__ == "__main__": + logging.basicConfig(level=logging.INFO, format="%(asctime)s %(name)s %(levelname)s %(message)s") + runner = SessionOutcomeProjectionRunner() + asyncio.run(runner.run()) diff --git a/src/omnimarket/nodes/node_projection_session_outcome/tests/__init__.py b/src/omnimarket/nodes/node_projection_session_outcome/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/omnimarket/projection/__init__.py b/src/omnimarket/projection/__init__.py new file mode 100644 index 0000000000..4cc8c7956a --- /dev/null +++ b/src/omnimarket/projection/__init__.py @@ -0,0 +1 @@ +"""Projection infrastructure for Kafka->DB event projection.""" diff --git a/src/omnimarket/projection/envelope.py b/src/omnimarket/projection/envelope.py new file mode 100644 index 0000000000..873fc1a39c --- /dev/null +++ b/src/omnimarket/projection/envelope.py @@ -0,0 +1,43 @@ +"""ONEX envelope unwrapping -- matches omnidash TypeScript parseMessage() exactly.""" + +from __future__ import annotations + +import json +import logging +from typing import Any + +logger = logging.getLogger(__name__) + + +def unwrap_envelope(raw_bytes: bytes) -> dict[str, Any] | None: + """Parse a Kafka message value and unwrap ONEX envelope. + + Replicates the omnidash read-model-consumer.ts parseMessage() logic: + - { payload: { ... } } -> use payload, attach _envelope + - { data: { ... } } -> use data, attach _envelope, _event_type, _correlation_id + - Otherwise use raw parsed object + """ + try: + raw = json.loads(raw_bytes) + except (json.JSONDecodeError, UnicodeDecodeError): + return None + + if not isinstance(raw, dict): + return None + + # Unwrap payload envelope + if isinstance(raw.get("payload"), dict): + result = dict(raw["payload"]) + result["_envelope"] = raw + return result + + # Unwrap data envelope (if data is a dict, not a list) + data = raw.get("data") + if isinstance(data, dict): + result = dict(data) + result["_envelope"] = raw + result["_event_type"] = raw.get("event_type") + result["_correlation_id"] = raw.get("correlation_id") + return result + + return raw diff --git a/src/omnimarket/projection/runner.py b/src/omnimarket/projection/runner.py new file mode 100644 index 0000000000..66cb3305d8 --- /dev/null +++ b/src/omnimarket/projection/runner.py @@ -0,0 +1,273 @@ +"""BaseProjectionRunner -- Kafka consumer lifecycle for projection nodes.""" + +from __future__ import annotations + +import asyncio +import hashlib +import logging +import os +import signal +from abc import ABC, abstractmethod +from dataclasses import dataclass, field +from datetime import datetime, timezone +from typing import Any + +from aiokafka import AIOKafkaConsumer + +from omnimarket.adapters.asyncpg_adapter import AsyncpgAdapter +from omnimarket.projection.envelope import unwrap_envelope + +logger = logging.getLogger(__name__) + +KAFKA_BROKERS_ENV = "KAFKA_BROKERS" +DEFAULT_GROUP_ID = "omnimarket-projections-v1" +DEFAULT_CLIENT_ID = "omnimarket-projection" +RETRY_BASE_DELAY = 2.0 +RETRY_MAX_DELAY = 30.0 +MAX_RETRY_ATTEMPTS = 10 + + +@dataclass +class MessageMeta: + """Kafka message coordinates for deterministic dedup.""" + + partition: int + offset: int + fallback_id: str + + +@dataclass +class ProjectionStats: + """In-memory projection stats.""" + + events_projected: int = 0 + errors_count: int = 0 + last_projected_at: datetime | None = None + topic_stats: dict[str, dict[str, int]] = field(default_factory=dict) + + +def deterministic_correlation_id(topic: str, partition: int, offset: int) -> str: + """Derive a deterministic UUID-shaped string from Kafka coordinates. + + Matches omnidash deterministicCorrelationId() exactly. + """ + raw = f"{topic}:{partition}:{offset}" + hex_digest = hashlib.sha256(raw.encode()).hexdigest()[:32] + return f"{hex_digest[:8]}-{hex_digest[8:12]}-{hex_digest[12:16]}-{hex_digest[16:20]}-{hex_digest[20:32]}" + + +def safe_parse_date(value: Any) -> datetime: + """Parse a date string, falling back to current wall-clock time.""" + if not value: + return datetime.now(timezone.utc) + if isinstance(value, datetime): + return value + try: + from dateutil.parser import isoparse + + dt = isoparse(str(value)) + return dt + except Exception: + pass + try: + dt = datetime.fromisoformat(str(value).replace("Z", "+00:00")) + return dt + except (ValueError, TypeError): + logger.warning("safe_parse_date: malformed timestamp %r, using wall-clock", value) + return datetime.now(timezone.utc) + + +def safe_float(value: Any, default: float = 0.0) -> float: + """Parse a float safely, returning default for non-finite values.""" + if value is None: + return default + try: + f = float(value) + if not (f == f): # NaN check + return default + return f + except (ValueError, TypeError): + return default + + +def safe_int(value: Any, default: int = 0) -> int: + """Parse an int safely.""" + if value is None: + return default + try: + return int(value) + except (ValueError, TypeError): + return default + + +def coalesce(*values: Any) -> Any: + """Return the first truthy value, or the last value.""" + for v in values: + if v: + return v + return values[-1] if values else None + + +class BaseProjectionRunner(ABC): + """Base class for Kafka->DB projection consumers. + + Subclasses implement: + - topics: list of Kafka topics to subscribe to + - project_event(topic, data, meta): project a single event to DB + """ + + def __init__( + self, + *, + group_id: str | None = None, + client_id: str | None = None, + ) -> None: + self._group_id = group_id or os.environ.get("KAFKA_CONSUMER_GROUP", DEFAULT_GROUP_ID) + self._client_id = client_id or DEFAULT_CLIENT_ID + self._db = AsyncpgAdapter() + self._stats = ProjectionStats() + self._running = False + self._consumer: AIOKafkaConsumer | None = None + + @property + @abstractmethod + def topics(self) -> list[str]: + """Kafka topics this runner subscribes to.""" + ... + + @abstractmethod + async def project_event( + self, topic: str, data: dict[str, Any], meta: MessageMeta + ) -> bool: + """Project a single event into the database. + + Returns True if projection succeeded, False if DB unavailable. + """ + ... + + @property + def db(self) -> AsyncpgAdapter: + return self._db + + @property + def stats(self) -> ProjectionStats: + return self._stats + + async def run(self) -> None: + """Main entry point -- connect to DB and Kafka, consume events.""" + loop = asyncio.get_event_loop() + for sig in (signal.SIGINT, signal.SIGTERM): + loop.add_signal_handler(sig, lambda: asyncio.ensure_future(self.shutdown())) + + await self._db.connect() + logger.info("DB connected") + + brokers = os.environ.get(KAFKA_BROKERS_ENV, "localhost:9092") + attempts = 0 + + while attempts < MAX_RETRY_ATTEMPTS and self._running is not False: + try: + self._consumer = AIOKafkaConsumer( + *self.topics, + bootstrap_servers=brokers, + group_id=self._group_id, + client_id=self._client_id, + auto_offset_reset="earliest", + enable_auto_commit=True, + value_deserializer=None, + ) + await self._consumer.start() + self._running = True + logger.info( + "Kafka consumer started. Topics: %s, Group: %s", + self.topics, + self._group_id, + ) + + async for msg in self._consumer: + if not self._running: + break + await self._handle_message(msg) + + except Exception as err: + attempts += 1 + delay = min(RETRY_BASE_DELAY * (2**attempts), RETRY_MAX_DELAY) + logger.error( + "Consumer attempt %d/%d failed: %s. Retrying in %.1fs", + attempts, + MAX_RETRY_ATTEMPTS, + err, + delay, + ) + if self._consumer: + try: + await self._consumer.stop() + except Exception: + pass + self._consumer = None + await asyncio.sleep(delay) + + logger.error("Consumer failed after %d retries", MAX_RETRY_ATTEMPTS) + await self._db.close() + + async def shutdown(self) -> None: + """Graceful shutdown.""" + logger.info("Shutting down...") + self._running = False + if self._consumer: + try: + await self._consumer.stop() + except Exception: + pass + await self._db.close() + + async def _handle_message(self, msg: Any) -> None: + """Parse, unwrap, dispatch, and track a single Kafka message.""" + topic = msg.topic + try: + if msg.value is None: + return + + data = unwrap_envelope(msg.value) + if data is None: + return + + fallback_id = deterministic_correlation_id(topic, msg.partition, msg.offset) + meta = MessageMeta( + partition=msg.partition, + offset=msg.offset, + fallback_id=fallback_id, + ) + + projected = await self.project_event(topic, data, meta) + + if projected: + self._stats.events_projected += 1 + self._stats.last_projected_at = datetime.now(timezone.utc) + ts = self._stats.topic_stats.setdefault(topic, {"projected": 0, "errors": 0}) + ts["projected"] += 1 + await self._update_watermark(f"{topic}:{msg.partition}", msg.offset) + + except Exception as err: + self._stats.errors_count += 1 + ts = self._stats.topic_stats.setdefault(topic, {"projected": 0, "errors": 0}) + ts["errors"] += 1 + logger.error("Error projecting %s: %s", topic, err) + + async def _update_watermark(self, projection_name: str, offset: int) -> None: + """Update projection_watermarks table -- matches omnidash SQL exactly.""" + try: + await self._db.execute( + """ + INSERT INTO projection_watermarks (projection_name, last_offset, events_projected, updated_at) + VALUES ($1, $2, 1, NOW()) + ON CONFLICT (projection_name) DO UPDATE SET + last_offset = GREATEST(projection_watermarks.last_offset, EXCLUDED.last_offset), + events_projected = projection_watermarks.events_projected + 1, + last_projected_at = NOW(), updated_at = NOW() + """, + projection_name, + offset, + ) + except Exception as err: + logger.warning("Failed to update watermark: %s", err) diff --git a/tests/test_projection_envelope.py b/tests/test_projection_envelope.py new file mode 100644 index 0000000000..a8584e2b95 --- /dev/null +++ b/tests/test_projection_envelope.py @@ -0,0 +1,54 @@ +"""Tests for ONEX envelope unwrapping logic.""" + +import json + +import pytest + +from omnimarket.projection.envelope import unwrap_envelope + + +class TestUnwrapEnvelope: + def test_payload_envelope(self) -> None: + raw = json.dumps({"payload": {"session_id": "abc", "outcome": "success"}}).encode() + result = unwrap_envelope(raw) + assert result is not None + assert result["session_id"] == "abc" + assert result["outcome"] == "success" + assert "_envelope" in result + + def test_data_envelope(self) -> None: + raw = json.dumps({ + "event_type": "session-outcome.v1", + "correlation_id": "corr-123", + "data": {"session_id": "xyz", "outcome": "failure"}, + }).encode() + result = unwrap_envelope(raw) + assert result is not None + assert result["session_id"] == "xyz" + assert result["_event_type"] == "session-outcome.v1" + assert result["_correlation_id"] == "corr-123" + + def test_raw_fallback(self) -> None: + raw = json.dumps({"session_id": "raw-123", "outcome": "unknown"}).encode() + result = unwrap_envelope(raw) + assert result is not None + assert result["session_id"] == "raw-123" + + def test_invalid_json(self) -> None: + result = unwrap_envelope(b"not-json") + assert result is None + + def test_non_dict(self) -> None: + result = unwrap_envelope(json.dumps([1, 2, 3]).encode()) + assert result is None + + def test_empty_bytes(self) -> None: + result = unwrap_envelope(b"") + assert result is None + + def test_data_envelope_with_list_data(self) -> None: + """When data is a list (not dict), return the raw object instead.""" + raw = json.dumps({"data": [1, 2, 3], "event_type": "test"}).encode() + result = unwrap_envelope(raw) + assert result is not None + assert result["data"] == [1, 2, 3] diff --git a/tests/test_projection_handlers.py b/tests/test_projection_handlers.py new file mode 100644 index 0000000000..1ca2b8137f --- /dev/null +++ b/tests/test_projection_handlers.py @@ -0,0 +1,329 @@ +"""Tests for projection handler event extraction logic. + +These tests verify field extraction and SQL parameter construction +without connecting to a real database. They mock the AsyncpgAdapter +and verify the handler calls execute() with the correct arguments. +""" + +from __future__ import annotations + +from datetime import datetime, timezone +from typing import Any +from unittest.mock import AsyncMock, MagicMock + +import pytest + +from omnimarket.projection.runner import MessageMeta + + +def _make_meta(partition: int = 0, offset: int = 0) -> MessageMeta: + return MessageMeta(partition=partition, offset=offset, fallback_id="fallback-id-1234") + + +@pytest.fixture +def mock_db() -> AsyncMock: + db = AsyncMock() + db.execute = AsyncMock(return_value=[]) + db.execute_many = AsyncMock() + db.execute_in_transaction = AsyncMock() + db.fetchval = AsyncMock(return_value=None) + db.connect = AsyncMock() + db.close = AsyncMock() + return db + + +class TestSessionOutcomeHandler: + @pytest.mark.asyncio + async def test_basic_projection(self, mock_db: AsyncMock) -> None: + from omnimarket.nodes.node_projection_session_outcome.handlers.handler_session_outcome import ( + SessionOutcomeProjectionRunner, + ) + + runner = SessionOutcomeProjectionRunner() + runner._db = mock_db + + data = { + "session_id": "sess-001", + "outcome": "success", + "emitted_at": "2026-04-06T12:00:00Z", + } + + result = await runner.project_event("onex.evt.omniclaude.session-outcome.v1", data, _make_meta()) + assert result is True + mock_db.execute.assert_called_once() + args = mock_db.execute.call_args + assert "sess-001" in args[0] + assert "success" in args[0] + + @pytest.mark.asyncio + async def test_missing_session_id_skips(self, mock_db: AsyncMock) -> None: + from omnimarket.nodes.node_projection_session_outcome.handlers.handler_session_outcome import ( + SessionOutcomeProjectionRunner, + ) + + runner = SessionOutcomeProjectionRunner() + runner._db = mock_db + + data = {"outcome": "success"} + result = await runner.project_event("onex.evt.omniclaude.session-outcome.v1", data, _make_meta()) + assert result is True + mock_db.execute.assert_not_called() + + @pytest.mark.asyncio + async def test_correlation_id_fallback(self, mock_db: AsyncMock) -> None: + from omnimarket.nodes.node_projection_session_outcome.handlers.handler_session_outcome import ( + SessionOutcomeProjectionRunner, + ) + + runner = SessionOutcomeProjectionRunner() + runner._db = mock_db + + data = {"correlation_id": "corr-123", "outcome": "failure"} + result = await runner.project_event("onex.evt.omniclaude.session-outcome.v1", data, _make_meta()) + assert result is True + mock_db.execute.assert_called_once() + args = mock_db.execute.call_args + assert "corr-123" in args[0] + + +class TestLlmCostHandler: + @pytest.mark.asyncio + async def test_basic_projection(self, mock_db: AsyncMock) -> None: + from omnimarket.nodes.node_projection_llm_cost.handlers.handler_llm_cost import ( + LlmCostProjectionRunner, + ) + + runner = LlmCostProjectionRunner() + runner._db = mock_db + + data = { + "model_id": "claude-sonnet-4-6", + "prompt_tokens": 1000, + "completion_tokens": 500, + "total_tokens": 1500, + "estimated_cost_usd": 0.015, + "timestamp": "2026-04-06T12:00:00Z", + } + + result = await runner.project_event("onex.evt.omniintelligence.llm-call-completed.v1", data, _make_meta()) + assert result is True + mock_db.execute.assert_called_once() + + @pytest.mark.asyncio + async def test_usage_source_normalization(self, mock_db: AsyncMock) -> None: + from omnimarket.nodes.node_projection_llm_cost.handlers.handler_llm_cost import ( + LlmCostProjectionRunner, + ) + + runner = LlmCostProjectionRunner() + runner._db = mock_db + + data = { + "model_id": "test-model", + "usage_source": "invalid_source", + "timestamp": "2026-04-06T12:00:00Z", + } + + result = await runner.project_event("onex.evt.omniintelligence.llm-call-completed.v1", data, _make_meta()) + assert result is True + # Should default to API for unrecognized source + call_args = mock_db.execute.call_args[0] + # The 8th positional param is usage_source (index 7 in the args tuple) + assert "API" in call_args + + +class TestDelegationHandler: + @pytest.mark.asyncio + async def test_task_delegated(self, mock_db: AsyncMock) -> None: + from omnimarket.nodes.node_projection_delegation.handlers.handler_delegation import ( + DelegationProjectionRunner, + ) + + runner = DelegationProjectionRunner() + runner._db = mock_db + + data = { + "correlation_id": "corr-del-1", + "task_type": "code_review", + "delegated_to": "claude-haiku-4-5", + "timestamp": "2026-04-06T12:00:00Z", + } + + result = await runner.project_event("onex.evt.omniclaude.task-delegated.v1", data, _make_meta()) + assert result is True + mock_db.execute.assert_called_once() + + @pytest.mark.asyncio + async def test_shadow_comparison(self, mock_db: AsyncMock) -> None: + from omnimarket.nodes.node_projection_delegation.handlers.handler_delegation import ( + DelegationProjectionRunner, + ) + + runner = DelegationProjectionRunner() + runner._db = mock_db + + data = { + "correlation_id": "corr-shadow-1", + "task_type": "code_review", + "primary_agent": "claude-sonnet-4-6", + "shadow_agent": "claude-haiku-4-5", + "divergence_detected": True, + } + + result = await runner.project_event("onex.evt.omniclaude.delegation-shadow-comparison.v1", data, _make_meta()) + assert result is True + mock_db.execute.assert_called_once() + + @pytest.mark.asyncio + async def test_missing_required_fields_skips(self, mock_db: AsyncMock) -> None: + from omnimarket.nodes.node_projection_delegation.handlers.handler_delegation import ( + DelegationProjectionRunner, + ) + + runner = DelegationProjectionRunner() + runner._db = mock_db + + data = {"correlation_id": "corr-1"} # missing task_type, delegated_to + result = await runner.project_event("onex.evt.omniclaude.task-delegated.v1", data, _make_meta()) + assert result is True # skip, don't error + mock_db.execute.assert_not_called() + + +class TestRegistrationHandler: + @pytest.mark.asyncio + async def test_introspection(self, mock_db: AsyncMock) -> None: + from omnimarket.nodes.node_projection_registration.handlers.handler_registration import ( + RegistrationProjectionRunner, + ) + + runner = RegistrationProjectionRunner() + runner._db = mock_db + + data = { + "node_name": "node_build_loop", + "node_id": "abc-123", + "service_url": "http://localhost:8080", + "health_status": "healthy", + "metadata": {"version": "1.0"}, + } + + result = await runner.project_event("onex.evt.platform.node-introspection.v1", data, _make_meta()) + assert result is True + mock_db.execute.assert_called_once() + + @pytest.mark.asyncio + async def test_heartbeat(self, mock_db: AsyncMock) -> None: + from omnimarket.nodes.node_projection_registration.handlers.handler_registration import ( + RegistrationProjectionRunner, + ) + + runner = RegistrationProjectionRunner() + runner._db = mock_db + + data = {"node_name": "node_build_loop", "health_status": "healthy"} + + result = await runner.project_event("onex.evt.platform.node-heartbeat.v1", data, _make_meta()) + assert result is True + mock_db.execute.assert_called_once() + + @pytest.mark.asyncio + async def test_state_change(self, mock_db: AsyncMock) -> None: + from omnimarket.nodes.node_projection_registration.handlers.handler_registration import ( + RegistrationProjectionRunner, + ) + + runner = RegistrationProjectionRunner() + runner._db = mock_db + + data = {"node_name": "node_build_loop", "new_state": "active"} + + result = await runner.project_event("onex.evt.platform.node-state-change.v1", data, _make_meta()) + assert result is True + mock_db.execute.assert_called_once() + + +class TestBaselinesHandler: + @pytest.mark.asyncio + async def test_basic_projection(self, mock_db: AsyncMock) -> None: + from omnimarket.nodes.node_projection_baselines.handlers.handler_baselines import ( + BaselinesProjectionRunner, + ) + + runner = BaselinesProjectionRunner() + runner._db = mock_db + + data = { + "snapshot_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", + "contract_version": 2, + "computed_at_utc": "2026-04-06T12:00:00Z", + "comparisons": [ + { + "pattern_id": "p1", + "pattern_name": "test_pattern", + "sample_size": 100, + "window_start": "2026-04-01", + "window_end": "2026-04-06", + "recommendation": "promote", + "confidence": "high", + } + ], + "trend": [ + { + "date": "2026-04-05", + "avg_cost_savings": 0.15, + "avg_outcome_improvement": 0.2, + } + ], + "breakdown": [ + {"action": "promote", "count": 5, "avg_confidence": 0.8} + ], + } + + result = await runner.project_event("onex.evt.omnibase-infra.baselines-computed.v1", data, _make_meta()) + assert result is True + mock_db.execute_in_transaction.assert_called_once() + + # Verify the transaction has the right number of queries: + # 1 snapshot upsert + 1 delete comparisons + 1 insert comparison + # + 1 delete trend + 1 insert trend + 1 delete breakdown + 1 insert breakdown = 7 + queries = mock_db.execute_in_transaction.call_args[0][0] + assert len(queries) == 7 + + +class TestSavingsHandler: + @pytest.mark.asyncio + async def test_basic_projection(self, mock_db: AsyncMock) -> None: + from omnimarket.nodes.node_projection_savings.handlers.handler_savings import ( + SavingsProjectionRunner, + ) + + runner = SavingsProjectionRunner() + runner._db = mock_db + + data = { + "session_id": "sess-savings-1", + "correlation_id": "corr-sav-1", + "actual_total_tokens": 5000, + "actual_cost_usd": 0.05, + "direct_savings_usd": 0.03, + "estimated_total_savings_usd": 0.04, + "timestamp": "2026-04-06T12:00:00Z", + } + + result = await runner.project_event("onex.evt.omnibase-infra.savings-estimated.v1", data, _make_meta()) + assert result is True + mock_db.execute.assert_called_once() + + @pytest.mark.asyncio + async def test_missing_session_id_skips(self, mock_db: AsyncMock) -> None: + from omnimarket.nodes.node_projection_savings.handlers.handler_savings import ( + SavingsProjectionRunner, + ) + + runner = SavingsProjectionRunner() + runner._db = mock_db + + data = {"actual_total_tokens": 100} + result = await runner.project_event("onex.evt.omnibase-infra.savings-estimated.v1", data, _make_meta()) + assert result is True + mock_db.execute.assert_not_called() diff --git a/tests/test_projection_runner.py b/tests/test_projection_runner.py new file mode 100644 index 0000000000..ff6b717030 --- /dev/null +++ b/tests/test_projection_runner.py @@ -0,0 +1,98 @@ +"""Tests for BaseProjectionRunner helper functions.""" + +from datetime import datetime, timezone + +import pytest + +from omnimarket.projection.runner import ( + deterministic_correlation_id, + safe_float, + safe_int, + safe_parse_date, + coalesce, +) + + +class TestDeterministicCorrelationId: + def test_format(self) -> None: + result = deterministic_correlation_id("topic", 0, 42) + # UUID-shaped: 8-4-4-4-12 hex + parts = result.split("-") + assert len(parts) == 5 + assert len(parts[0]) == 8 + assert len(parts[1]) == 4 + assert len(parts[2]) == 4 + assert len(parts[3]) == 4 + assert len(parts[4]) == 12 + + def test_deterministic(self) -> None: + a = deterministic_correlation_id("topic", 0, 100) + b = deterministic_correlation_id("topic", 0, 100) + assert a == b + + def test_different_inputs(self) -> None: + a = deterministic_correlation_id("topic", 0, 100) + b = deterministic_correlation_id("topic", 0, 101) + assert a != b + + +class TestSafeParseDate: + def test_iso_string(self) -> None: + result = safe_parse_date("2026-04-06T12:00:00Z") + assert isinstance(result, datetime) + + def test_none(self) -> None: + result = safe_parse_date(None) + assert isinstance(result, datetime) + # Should be approximately now + delta = abs((datetime.now(timezone.utc) - result).total_seconds()) + assert delta < 5 + + def test_empty_string(self) -> None: + result = safe_parse_date("") + assert isinstance(result, datetime) + + def test_datetime_passthrough(self) -> None: + dt = datetime(2026, 1, 1, tzinfo=timezone.utc) + result = safe_parse_date(dt) + assert result == dt + + def test_malformed(self) -> None: + result = safe_parse_date("not-a-date") + assert isinstance(result, datetime) + + +class TestSafeFloat: + def test_valid(self) -> None: + assert safe_float("3.14") == 3.14 + + def test_none(self) -> None: + assert safe_float(None) == 0.0 + + def test_invalid(self) -> None: + assert safe_float("abc") == 0.0 + + def test_nan(self) -> None: + assert safe_float(float("nan")) == 0.0 + + +class TestSafeInt: + def test_valid(self) -> None: + assert safe_int("42") == 42 + + def test_none(self) -> None: + assert safe_int(None) == 0 + + def test_invalid(self) -> None: + assert safe_int("abc") == 0 + + +class TestCoalesce: + def test_first_truthy(self) -> None: + assert coalesce(None, "", "hello") == "hello" + + def test_all_falsy(self) -> None: + assert coalesce(None, "", 0) == 0 + + def test_empty(self) -> None: + assert coalesce() is None diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000000..0d488959d6 --- /dev/null +++ b/uv.lock @@ -0,0 +1,1049 @@ +version = 1 +revision = 3 +requires-python = ">=3.12" + +[[package]] +name = "aiokafka" +version = "0.13.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "async-timeout" }, + { name = "packaging" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/87/18/d3a4f8f9ad099fc59217b8cdf66eeecde3a9ef3bb31fe676e431a3b0010f/aiokafka-0.13.0.tar.gz", hash = "sha256:7d634af3c8d694a37a6c8535c54f01a740e74cccf7cc189ecc4a3d64e31ce122", size = 598580, upload-time = "2026-01-02T13:55:18.911Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/17/715ac23b4f8df3ff8d7c0a6f1c5fd3a179a8a675205be62d1d1bb27dffa2/aiokafka-0.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:231ecc0038c2736118f1c95149550dbbdf7b7a12069f70c005764fa1824c35d4", size = 346168, upload-time = "2026-01-02T13:54:49.128Z" }, + { url = "https://files.pythonhosted.org/packages/00/26/71c6f4cce2c710c6ffa18b9e294384157f46b0491d5b020de300802d167e/aiokafka-0.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2e2817593cab4c71c1d3b265b2446da91121a467ff7477c65f0f39a80047bc28", size = 349037, upload-time = "2026-01-02T13:54:50.48Z" }, + { url = "https://files.pythonhosted.org/packages/82/18/7b86418a4d3dc1303e89c0391942258ead31c02309e90eb631f3081eec1d/aiokafka-0.13.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b80e0aa1c811a9a12edb0b94445a0638d61a345932f785d47901d28b8aad86c8", size = 1140066, upload-time = "2026-01-02T13:54:52.33Z" }, + { url = "https://files.pythonhosted.org/packages/f9/51/45e46b4407d39b950c8493e19498aeeb5af4fc461fb54fa0247da16bfd75/aiokafka-0.13.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:79672c456bd1642769e74fc2db1c34f23b15500e978fd38411662e8ca07590ad", size = 1130088, upload-time = "2026-01-02T13:54:53.786Z" }, + { url = "https://files.pythonhosted.org/packages/49/7f/6a66f6fd6fb73e15bd34f574e38703ba36d3f9256c80e7aba007bd8a9256/aiokafka-0.13.0-cp312-cp312-win32.whl", hash = "sha256:00bb4e3d5a237b8618883eb1dd8c08d671db91d3e8e33ac98b04edf64225658c", size = 309581, upload-time = "2026-01-02T13:54:55.444Z" }, + { url = "https://files.pythonhosted.org/packages/d3/e0/a2d5a8912699dd0fee28e6fb780358c63c7a4727517fffc110cb7e43f874/aiokafka-0.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:0f0cccdf2fd16927fbe077279524950676fbffa7b102d6b117041b3461b5d927", size = 329327, upload-time = "2026-01-02T13:54:56.981Z" }, + { url = "https://files.pythonhosted.org/packages/e3/f6/a74c49759233e98b61182ba3d49d5ac9c8de0643651892acba2704fba1cc/aiokafka-0.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:39d71c40cff733221a6b2afff4beeac5dacbd119fb99eec5198af59115264a1a", size = 343733, upload-time = "2026-01-02T13:54:58.536Z" }, + { url = "https://files.pythonhosted.org/packages/cf/52/4f7e80eee2c69cd8b047c18145469bf0dc27542a5dca3f96ff81ade575b0/aiokafka-0.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:faa2f5f3d0d2283a0c1a149748cc7e3a3862ef327fa5762e2461088eedde230a", size = 346258, upload-time = "2026-01-02T13:55:00.947Z" }, + { url = "https://files.pythonhosted.org/packages/81/9b/d2766bb3b0bad53eb25a88e51a884be4b77a1706053ad717b893b4daea4b/aiokafka-0.13.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b890d535e55f5073f939585bef5301634df669e97832fda77aa743498f008662", size = 1114744, upload-time = "2026-01-02T13:55:02.475Z" }, + { url = "https://files.pythonhosted.org/packages/8f/00/12e0a39cd4809149a09b4a52b629abc9bf80e7b8bad9950040b1adae99fc/aiokafka-0.13.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e22eb8a1475b9c0f45b553b6e2dcaf4ec3c0014bf4e389e00a0a0ec85d0e3bdc", size = 1105676, upload-time = "2026-01-02T13:55:04.036Z" }, + { url = "https://files.pythonhosted.org/packages/38/4a/0bc91e90faf55533fe6468461c2dd31c22b0e1d274b9386f341cca3f7eb7/aiokafka-0.13.0-cp313-cp313-win32.whl", hash = "sha256:ae507c7b09e882484f709f2e7172b3a4f75afffcd896d00517feb35c619495bb", size = 308257, upload-time = "2026-01-02T13:55:05.873Z" }, + { url = "https://files.pythonhosted.org/packages/23/63/5433d1aa10c4fb4cf85bd73013263c36d7da4604b0c77ed4d1ad42fae70c/aiokafka-0.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:fec1a7e3458365a72809edaa2b990f65ca39b01a2a579f879ac4da6c9b2dbc5c", size = 326968, upload-time = "2026-01-02T13:55:07.351Z" }, + { url = "https://files.pythonhosted.org/packages/3c/cc/45b04c3a5fd3d2d5f444889ecceb80b2f78d6d66aa45e3042767e55579e2/aiokafka-0.13.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:9a403785f7092c72906c37f7618f7b16a4219eba8ed0bdda90fba410a7dd50b5", size = 344503, upload-time = "2026-01-02T13:55:08.723Z" }, + { url = "https://files.pythonhosted.org/packages/76/df/0b76fe3b93558ae71b856940e384909c4c2c7a1c330423003191e4ba7782/aiokafka-0.13.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:256807326831b7eee253ea1017bd2b19ab1c2298ce6b20a87fde97c253c572bc", size = 347621, upload-time = "2026-01-02T13:55:10.147Z" }, + { url = "https://files.pythonhosted.org/packages/34/1a/d59932f98fd3c106e2a7c8d4d5ebd8df25403436dfc27b3031918a37385e/aiokafka-0.13.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:64d90f91291da265d7f25296ba68fc6275684eebd6d1cf05a1b2abe6c2ba3543", size = 1111410, upload-time = "2026-01-02T13:55:11.763Z" }, + { url = "https://files.pythonhosted.org/packages/7e/04/fbf3e34ab3bc21e6e760c3fcd089375052fccc04eb8745459a82a58a647b/aiokafka-0.13.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b5a33cc043c8d199bcf101359d86f2d31fd54f4b157ac12028bdc34e3e1cf74a", size = 1094799, upload-time = "2026-01-02T13:55:13.795Z" }, + { url = "https://files.pythonhosted.org/packages/85/10/509f709fd3b7c3e568a5b8044be0e80a1504f8da6ddc72c128b21e270913/aiokafka-0.13.0-cp314-cp314-win32.whl", hash = "sha256:538950384b539ba2333d35a853f09214c0409e818e5d5f366ef759eea50bae9c", size = 311553, upload-time = "2026-01-02T13:55:15.928Z" }, + { url = "https://files.pythonhosted.org/packages/2b/18/424d6a4eb6f4835a371c1e2cfafce800540b33d957c6638795d911f98973/aiokafka-0.13.0-cp314-cp314-win_amd64.whl", hash = "sha256:c906dd42daadd14b4506a2e6c62dfef3d4919b5953d32ae5e5f0d99efd103c89", size = 330648, upload-time = "2026-01-02T13:55:17.421Z" }, +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + +[[package]] +name = "anyio" +version = "4.13.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" } +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" }, +] + +[[package]] +name = "async-timeout" +version = "5.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a5/ae/136395dfbfe00dfc94da3f3e136d0b13f394cba8f4841120e34226265780/async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3", size = 9274, upload-time = "2024-11-06T16:41:39.6Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/ba/e2081de779ca30d473f21f5b30e0e737c438205440784c7dfc81efc2b029/async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c", size = 6233, upload-time = "2024-11-06T16:41:37.9Z" }, +] + +[[package]] +name = "asyncpg" +version = "0.31.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/cc/d18065ce2380d80b1bcce927c24a2642efd38918e33fd724bc4bca904877/asyncpg-0.31.0.tar.gz", hash = "sha256:c989386c83940bfbd787180f2b1519415e2d3d6277a70d9d0f0145ac73500735", size = 993667, upload-time = "2025-11-24T23:27:00.812Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/a6/59d0a146e61d20e18db7396583242e32e0f120693b67a8de43f1557033e2/asyncpg-0.31.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b44c31e1efc1c15188ef183f287c728e2046abb1d26af4d20858215d50d91fad", size = 662042, upload-time = "2025-11-24T23:25:49.578Z" }, + { url = "https://files.pythonhosted.org/packages/36/01/ffaa189dcb63a2471720615e60185c3f6327716fdc0fc04334436fbb7c65/asyncpg-0.31.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0c89ccf741c067614c9b5fc7f1fc6f3b61ab05ae4aaa966e6fd6b93097c7d20d", size = 638504, upload-time = "2025-11-24T23:25:51.501Z" }, + { url = "https://files.pythonhosted.org/packages/9f/62/3f699ba45d8bd24c5d65392190d19656d74ff0185f42e19d0bbd973bb371/asyncpg-0.31.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:12b3b2e39dc5470abd5e98c8d3373e4b1d1234d9fbdedf538798b2c13c64460a", size = 3426241, upload-time = "2025-11-24T23:25:53.278Z" }, + { url = "https://files.pythonhosted.org/packages/8c/d1/a867c2150f9c6e7af6462637f613ba67f78a314b00db220cd26ff559d532/asyncpg-0.31.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:aad7a33913fb8bcb5454313377cc330fbb19a0cd5faa7272407d8a0c4257b671", size = 3520321, upload-time = "2025-11-24T23:25:54.982Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1a/cce4c3f246805ecd285a3591222a2611141f1669d002163abef999b60f98/asyncpg-0.31.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3df118d94f46d85b2e434fd62c84cb66d5834d5a890725fe625f498e72e4d5ec", size = 3316685, upload-time = "2025-11-24T23:25:57.43Z" }, + { url = "https://files.pythonhosted.org/packages/40/ae/0fc961179e78cc579e138fad6eb580448ecae64908f95b8cb8ee2f241f67/asyncpg-0.31.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bd5b6efff3c17c3202d4b37189969acf8927438a238c6257f66be3c426beba20", size = 3471858, upload-time = "2025-11-24T23:25:59.636Z" }, + { url = "https://files.pythonhosted.org/packages/52/b2/b20e09670be031afa4cbfabd645caece7f85ec62d69c312239de568e058e/asyncpg-0.31.0-cp312-cp312-win32.whl", hash = "sha256:027eaa61361ec735926566f995d959ade4796f6a49d3bde17e5134b9964f9ba8", size = 527852, upload-time = "2025-11-24T23:26:01.084Z" }, + { url = "https://files.pythonhosted.org/packages/b5/f0/f2ed1de154e15b107dc692262395b3c17fc34eafe2a78fc2115931561730/asyncpg-0.31.0-cp312-cp312-win_amd64.whl", hash = "sha256:72d6bdcbc93d608a1158f17932de2321f68b1a967a13e014998db87a72ed3186", size = 597175, upload-time = "2025-11-24T23:26:02.564Z" }, + { url = "https://files.pythonhosted.org/packages/95/11/97b5c2af72a5d0b9bc3fa30cd4b9ce22284a9a943a150fdc768763caf035/asyncpg-0.31.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c204fab1b91e08b0f47e90a75d1b3c62174dab21f670ad6c5d0f243a228f015b", size = 661111, upload-time = "2025-11-24T23:26:04.467Z" }, + { url = "https://files.pythonhosted.org/packages/1b/71/157d611c791a5e2d0423f09f027bd499935f0906e0c2a416ce712ba51ef3/asyncpg-0.31.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:54a64f91839ba59008eccf7aad2e93d6e3de688d796f35803235ea1c4898ae1e", size = 636928, upload-time = "2025-11-24T23:26:05.944Z" }, + { url = "https://files.pythonhosted.org/packages/2e/fc/9e3486fb2bbe69d4a867c0b76d68542650a7ff1574ca40e84c3111bb0c6e/asyncpg-0.31.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0e0822b1038dc7253b337b0f3f676cadc4ac31b126c5d42691c39691962e403", size = 3424067, upload-time = "2025-11-24T23:26:07.957Z" }, + { url = "https://files.pythonhosted.org/packages/12/c6/8c9d076f73f07f995013c791e018a1cd5f31823c2a3187fc8581706aa00f/asyncpg-0.31.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bef056aa502ee34204c161c72ca1f3c274917596877f825968368b2c33f585f4", size = 3518156, upload-time = "2025-11-24T23:26:09.591Z" }, + { url = "https://files.pythonhosted.org/packages/ae/3b/60683a0baf50fbc546499cfb53132cb6835b92b529a05f6a81471ab60d0c/asyncpg-0.31.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0bfbcc5b7ffcd9b75ab1558f00db2ae07db9c80637ad1b2469c43df79d7a5ae2", size = 3319636, upload-time = "2025-11-24T23:26:11.168Z" }, + { url = "https://files.pythonhosted.org/packages/50/dc/8487df0f69bd398a61e1792b3cba0e47477f214eff085ba0efa7eac9ce87/asyncpg-0.31.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:22bc525ebbdc24d1261ecbf6f504998244d4e3be1721784b5f64664d61fbe602", size = 3472079, upload-time = "2025-11-24T23:26:13.164Z" }, + { url = "https://files.pythonhosted.org/packages/13/a1/c5bbeeb8531c05c89135cb8b28575ac2fac618bcb60119ee9696c3faf71c/asyncpg-0.31.0-cp313-cp313-win32.whl", hash = "sha256:f890de5e1e4f7e14023619399a471ce4b71f5418cd67a51853b9910fdfa73696", size = 527606, upload-time = "2025-11-24T23:26:14.78Z" }, + { url = "https://files.pythonhosted.org/packages/91/66/b25ccb84a246b470eb943b0107c07edcae51804912b824054b3413995a10/asyncpg-0.31.0-cp313-cp313-win_amd64.whl", hash = "sha256:dc5f2fa9916f292e5c5c8b2ac2813763bcd7f58e130055b4ad8a0531314201ab", size = 596569, upload-time = "2025-11-24T23:26:16.189Z" }, + { url = "https://files.pythonhosted.org/packages/3c/36/e9450d62e84a13aea6580c83a47a437f26c7ca6fa0f0fd40b6670793ea30/asyncpg-0.31.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f6b56b91bb0ffc328c4e3ed113136cddd9deefdf5f79ab448598b9772831df44", size = 660867, upload-time = "2025-11-24T23:26:17.631Z" }, + { url = "https://files.pythonhosted.org/packages/82/4b/1d0a2b33b3102d210439338e1beea616a6122267c0df459ff0265cd5807a/asyncpg-0.31.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:334dec28cf20d7f5bb9e45b39546ddf247f8042a690bff9b9573d00086e69cb5", size = 638349, upload-time = "2025-11-24T23:26:19.689Z" }, + { url = "https://files.pythonhosted.org/packages/41/aa/e7f7ac9a7974f08eff9183e392b2d62516f90412686532d27e196c0f0eeb/asyncpg-0.31.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:98cc158c53f46de7bb677fd20c417e264fc02b36d901cc2a43bd6cb0dc6dbfd2", size = 3410428, upload-time = "2025-11-24T23:26:21.275Z" }, + { url = "https://files.pythonhosted.org/packages/6f/de/bf1b60de3dede5c2731e6788617a512bc0ebd9693eac297ee74086f101d7/asyncpg-0.31.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9322b563e2661a52e3cdbc93eed3be7748b289f792e0011cb2720d278b366ce2", size = 3471678, upload-time = "2025-11-24T23:26:23.627Z" }, + { url = "https://files.pythonhosted.org/packages/46/78/fc3ade003e22d8bd53aaf8f75f4be48f0b460fa73738f0391b9c856a9147/asyncpg-0.31.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:19857a358fc811d82227449b7ca40afb46e75b33eb8897240c3839dd8b744218", size = 3313505, upload-time = "2025-11-24T23:26:25.235Z" }, + { url = "https://files.pythonhosted.org/packages/bf/e9/73eb8a6789e927816f4705291be21f2225687bfa97321e40cd23055e903a/asyncpg-0.31.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ba5f8886e850882ff2c2ace5732300e99193823e8107e2c53ef01c1ebfa1e85d", size = 3434744, upload-time = "2025-11-24T23:26:26.944Z" }, + { url = "https://files.pythonhosted.org/packages/08/4b/f10b880534413c65c5b5862f79b8e81553a8f364e5238832ad4c0af71b7f/asyncpg-0.31.0-cp314-cp314-win32.whl", hash = "sha256:cea3a0b2a14f95834cee29432e4ddc399b95700eb1d51bbc5bfee8f31fa07b2b", size = 532251, upload-time = "2025-11-24T23:26:28.404Z" }, + { url = "https://files.pythonhosted.org/packages/d3/2d/7aa40750b7a19efa5d66e67fc06008ca0f27ba1bd082e457ad82f59aba49/asyncpg-0.31.0-cp314-cp314-win_amd64.whl", hash = "sha256:04d19392716af6b029411a0264d92093b6e5e8285ae97a39957b9a9c14ea72be", size = 604901, upload-time = "2025-11-24T23:26:30.34Z" }, + { url = "https://files.pythonhosted.org/packages/ce/fe/b9dfe349b83b9dee28cc42360d2c86b2cdce4cb551a2c2d27e156bcac84d/asyncpg-0.31.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:bdb957706da132e982cc6856bb2f7b740603472b54c3ebc77fe60ea3e57e1bd2", size = 702280, upload-time = "2025-11-24T23:26:32Z" }, + { url = "https://files.pythonhosted.org/packages/6a/81/e6be6e37e560bd91e6c23ea8a6138a04fd057b08cf63d3c5055c98e81c1d/asyncpg-0.31.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6d11b198111a72f47154fa03b85799f9be63701e068b43f84ac25da0bda9cb31", size = 682931, upload-time = "2025-11-24T23:26:33.572Z" }, + { url = "https://files.pythonhosted.org/packages/a6/45/6009040da85a1648dd5bc75b3b0a062081c483e75a1a29041ae63a0bf0dc/asyncpg-0.31.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:18c83b03bc0d1b23e6230f5bf8d4f217dc9bc08644ce0502a9d91dc9e634a9c7", size = 3581608, upload-time = "2025-11-24T23:26:35.638Z" }, + { url = "https://files.pythonhosted.org/packages/7e/06/2e3d4d7608b0b2b3adbee0d0bd6a2d29ca0fc4d8a78f8277df04e2d1fd7b/asyncpg-0.31.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e009abc333464ff18b8f6fd146addffd9aaf63e79aa3bb40ab7a4c332d0c5e9e", size = 3498738, upload-time = "2025-11-24T23:26:37.275Z" }, + { url = "https://files.pythonhosted.org/packages/7d/aa/7d75ede780033141c51d83577ea23236ba7d3a23593929b32b49db8ed36e/asyncpg-0.31.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3b1fbcb0e396a5ca435a8826a87e5c2c2cc0c8c68eb6fadf82168056b0e53a8c", size = 3401026, upload-time = "2025-11-24T23:26:39.423Z" }, + { url = "https://files.pythonhosted.org/packages/ba/7a/15e37d45e7f7c94facc1e9148c0e455e8f33c08f0b8a0b1deb2c5171771b/asyncpg-0.31.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8df714dba348efcc162d2adf02d213e5fab1bd9f557e1305633e851a61814a7a", size = 3429426, upload-time = "2025-11-24T23:26:41.032Z" }, + { url = "https://files.pythonhosted.org/packages/13/d5/71437c5f6ae5f307828710efbe62163974e71237d5d46ebd2869ea052d10/asyncpg-0.31.0-cp314-cp314t-win32.whl", hash = "sha256:1b41f1afb1033f2b44f3234993b15096ddc9cd71b21a42dbd87fc6a57b43d65d", size = 614495, upload-time = "2025-11-24T23:26:42.659Z" }, + { url = "https://files.pythonhosted.org/packages/3c/d7/8fb3044eaef08a310acfe23dae9a8e2e07d305edc29a53497e52bc76eca7/asyncpg-0.31.0-cp314-cp314t-win_amd64.whl", hash = "sha256:bd4107bb7cdd0e9e65fae66a62afd3a249663b844fa34d479f6d5b3bef9c04c3", size = 706062, upload-time = "2025-11-24T23:26:44.086Z" }, +] + +[[package]] +name = "attrs" +version = "26.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/8e/82a0fe20a541c03148528be8cac2408564a6c9a0cc7e9171802bc1d26985/attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32", size = 952055, upload-time = "2026-03-19T14:22:25.026Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" }, +] + +[[package]] +name = "blake3" +version = "1.0.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/75/aa/abcd75e9600987a0bc6cfe9b6b2ff3f0e2cb08c170addc6e76035b5c4cb3/blake3-1.0.8.tar.gz", hash = "sha256:513cc7f0f5a7c035812604c2c852a0c1468311345573de647e310aca4ab165ba", size = 117308, upload-time = "2025-10-14T06:47:48.83Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/a0/b7b6dff04012cfd6e665c09ee446f749bd8ea161b00f730fe1bdecd0f033/blake3-1.0.8-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:d8da4233984d51471bd4e4366feda1d90d781e712e0a504ea54b1f2b3577557b", size = 347983, upload-time = "2025-10-14T06:45:47.214Z" }, + { url = "https://files.pythonhosted.org/packages/5b/a2/264091cac31d7ae913f1f296abc20b8da578b958ffb86100a7ce80e8bf5c/blake3-1.0.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1257be19f2d381c868a34cc822fc7f12f817ddc49681b6d1a2790bfbda1a9865", size = 325415, upload-time = "2025-10-14T06:45:48.482Z" }, + { url = "https://files.pythonhosted.org/packages/ee/7d/85a4c0782f613de23d114a7a78fcce270f75b193b3ff3493a0de24ba104a/blake3-1.0.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:269f255b110840e52b6ce9db02217e39660ebad3e34ddd5bca8b8d378a77e4e1", size = 371296, upload-time = "2025-10-14T06:45:49.674Z" }, + { url = "https://files.pythonhosted.org/packages/e3/20/488475254976ed93fab57c67aa80d3b40df77f7d9db6528c9274bff53e08/blake3-1.0.8-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:66ca28a673025c40db3eba21a9cac52f559f83637efa675b3f6bd8683f0415f3", size = 374516, upload-time = "2025-10-14T06:45:51.23Z" }, + { url = "https://files.pythonhosted.org/packages/7b/21/2a1c47fedb77fb396512677ec6d46caf42ac6e9a897db77edd0a2a46f7bb/blake3-1.0.8-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bcb04966537777af56c1f399b35525aa70a1225816e121ff95071c33c0f7abca", size = 447911, upload-time = "2025-10-14T06:45:52.637Z" }, + { url = "https://files.pythonhosted.org/packages/cb/7d/db0626df16029713e7e61b67314c4835e85c296d82bd907c21c6ea271da2/blake3-1.0.8-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e5b5da177d62cc4b7edf0cea08fe4dec960c9ac27f916131efa890a01f747b93", size = 505420, upload-time = "2025-10-14T06:45:54.445Z" }, + { url = "https://files.pythonhosted.org/packages/5b/55/6e737850c2d58a6d9de8a76dad2ae0f75b852a23eb4ecb07a0b165e6e436/blake3-1.0.8-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:38209b10482c97e151681ea3e91cc7141f56adbbf4820a7d701a923124b41e6a", size = 394189, upload-time = "2025-10-14T06:45:55.719Z" }, + { url = "https://files.pythonhosted.org/packages/5b/94/eafaa5cdddadc0c9c603a6a6d8339433475e1a9f60c8bb9c2eed2d8736b6/blake3-1.0.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:504d1399b7fb91dfe5c25722d2807990493185faa1917456455480c36867adb5", size = 388001, upload-time = "2025-10-14T06:45:57.067Z" }, + { url = "https://files.pythonhosted.org/packages/17/81/735fa00d13de7f68b25e1b9cb36ff08c6f165e688d85d8ec2cbfcdedccc5/blake3-1.0.8-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c84af132aa09abeadf9a0118c8fb26f4528f3f42c10ef8be0fcf31c478774ec4", size = 550302, upload-time = "2025-10-14T06:45:58.657Z" }, + { url = "https://files.pythonhosted.org/packages/0e/c6/d1fe8bdea4a6088bd54b5a58bc40aed89a4e784cd796af7722a06f74bae7/blake3-1.0.8-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a25db3d36b55f5ed6a86470155cc749fc9c5b91c949b8d14f48658f9d960d9ec", size = 554211, upload-time = "2025-10-14T06:46:00.269Z" }, + { url = "https://files.pythonhosted.org/packages/55/d1/ca74aa450cbe10e396e061f26f7a043891ffa1485537d6b30d3757e20995/blake3-1.0.8-cp312-cp312-win32.whl", hash = "sha256:e0fee93d5adcd44378b008c147e84f181f23715307a64f7b3db432394bbfce8b", size = 228343, upload-time = "2025-10-14T06:46:01.533Z" }, + { url = "https://files.pythonhosted.org/packages/4d/42/bbd02647169e3fbed27558555653ac2578c6f17ccacf7d1956c58ef1d214/blake3-1.0.8-cp312-cp312-win_amd64.whl", hash = "sha256:6a6eafc29e4f478d365a87d2f25782a521870c8514bb43734ac85ae9be71caf7", size = 215704, upload-time = "2025-10-14T06:46:02.79Z" }, + { url = "https://files.pythonhosted.org/packages/55/b8/11de9528c257f7f1633f957ccaff253b706838d22c5d2908e4735798ec01/blake3-1.0.8-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:46dc20976bd6c235959ef0246ec73420d1063c3da2839a9c87ca395cf1fd7943", size = 347771, upload-time = "2025-10-14T06:46:04.248Z" }, + { url = "https://files.pythonhosted.org/packages/50/26/f7668be55c909678b001ecacff11ad7016cd9b4e9c7cc87b5971d638c5a9/blake3-1.0.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d17eb6382634b3a5bc0c0e0454d5265b0becaeeadb6801ed25150b39a999d0cc", size = 325431, upload-time = "2025-10-14T06:46:06.136Z" }, + { url = "https://files.pythonhosted.org/packages/77/57/e8a85fa261894bf7ce7af928ff3408aab60287ab8d58b55d13a3f700b619/blake3-1.0.8-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19fc6f2b7edab8acff6895fc6e38c19bd79f4c089e21153020c75dfc7397d52d", size = 370994, upload-time = "2025-10-14T06:46:07.398Z" }, + { url = "https://files.pythonhosted.org/packages/62/cd/765b76bb48b8b294fea94c9008b0d82b4cfa0fa2f3c6008d840d01a597e4/blake3-1.0.8-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4f54cff7f15d91dc78a63a2dd02a3dccdc932946f271e2adb4130e0b4cf608ba", size = 374372, upload-time = "2025-10-14T06:46:08.698Z" }, + { url = "https://files.pythonhosted.org/packages/36/7a/32084eadbb28592bb07298f0de316d2da586c62f31500a6b1339a7e7b29b/blake3-1.0.8-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7e12a777f6b798eb8d06f875d6e108e3008bd658d274d8c676dcf98e0f10537", size = 447627, upload-time = "2025-10-14T06:46:10.002Z" }, + { url = "https://files.pythonhosted.org/packages/a7/f4/3788a1d86e17425eea147e28d7195d7053565fc279236a9fd278c2ec495e/blake3-1.0.8-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ddfc59b0176fb31168f08d5dd536e69b1f4f13b5a0f4b0c3be1003efd47f9308", size = 507536, upload-time = "2025-10-14T06:46:11.614Z" }, + { url = "https://files.pythonhosted.org/packages/fe/01/4639cba48513b94192681b4da472cdec843d3001c5344d7051ee5eaef606/blake3-1.0.8-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a2336d5b2a801a7256da21150348f41610a6c21dae885a3acb1ebbd7333d88d8", size = 394105, upload-time = "2025-10-14T06:46:12.808Z" }, + { url = "https://files.pythonhosted.org/packages/21/ae/6e55c19c8460fada86cd1306a390a09b0c5a2e2e424f9317d2edacea439f/blake3-1.0.8-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4072196547484c95a5a09adbb952e9bb501949f03f9e2a85e7249ef85faaba8", size = 386928, upload-time = "2025-10-14T06:46:16.284Z" }, + { url = "https://files.pythonhosted.org/packages/ee/6c/05b7a5a907df1be53a8f19e7828986fc6b608a44119641ef9c0804fbef15/blake3-1.0.8-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:0eab3318ec02f8e16fe549244791ace2ada2c259332f0c77ab22cf94dfff7130", size = 550003, upload-time = "2025-10-14T06:46:17.791Z" }, + { url = "https://files.pythonhosted.org/packages/b4/03/f0ea4adfedc1717623be6460b3710fcb725ca38082c14274369803f727e1/blake3-1.0.8-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:a33b9a1fb6d1d559a8e0d04b041e99419a6bb771311c774f6ff57ed7119c70ed", size = 553857, upload-time = "2025-10-14T06:46:19.088Z" }, + { url = "https://files.pythonhosted.org/packages/cc/6f/e5410d2e2a30c8aba8389ffc1c0061356916bf5ecd0a210344e7b69b62ab/blake3-1.0.8-cp313-cp313-win32.whl", hash = "sha256:e171b169cb7ea618e362a4dddb7a4d4c173bbc08b9ba41ea3086dd1265530d4f", size = 228315, upload-time = "2025-10-14T06:46:20.391Z" }, + { url = "https://files.pythonhosted.org/packages/79/ef/d9c297956dfecd893f29f59e7b22445aba5b47b7f6815d9ba5dcd73fcae6/blake3-1.0.8-cp313-cp313-win_amd64.whl", hash = "sha256:3168c457255b5d2a2fc356ba696996fcaff5d38284f968210d54376312107662", size = 215477, upload-time = "2025-10-14T06:46:21.542Z" }, + { url = "https://files.pythonhosted.org/packages/20/ba/eaa7723d66dd8ab762a3e85e139bb9c46167b751df6e950ad287adb8fb61/blake3-1.0.8-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:b4d672c24dc15ec617d212a338a4ca14b449829b6072d09c96c63b6e6b621aed", size = 347289, upload-time = "2025-10-14T06:46:22.772Z" }, + { url = "https://files.pythonhosted.org/packages/47/b3/6957f6ee27f0d5b8c4efdfda68a1298926a88c099f4dd89c711049d16526/blake3-1.0.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:1af0e5a29aa56d4fba904452ae784740997440afd477a15e583c38338e641f41", size = 324444, upload-time = "2025-10-14T06:46:24.729Z" }, + { url = "https://files.pythonhosted.org/packages/13/da/722cebca11238f3b24d3cefd2361c9c9ea47cfa0ad9288eeb4d1e0b7cf93/blake3-1.0.8-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef153c5860d5bf1cc71aece69b28097d2a392913eb323d6b52555c875d0439fc", size = 370441, upload-time = "2025-10-14T06:46:26.29Z" }, + { url = "https://files.pythonhosted.org/packages/2e/d5/2f7440c8e41c0af995bad3a159e042af0f4ed1994710af5b4766ca918f65/blake3-1.0.8-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e8ae3689f0c7bfa6ce6ae45cab110e4c3442125c4c23b28f1f097856de26e4d1", size = 374312, upload-time = "2025-10-14T06:46:27.451Z" }, + { url = "https://files.pythonhosted.org/packages/a6/6c/fb6a7812e60ce3e110bcbbb11f167caf3e975c589572c41e1271f35f2c41/blake3-1.0.8-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3fb83532f7456ddeb68dae1b36e1f7c52f9cb72852ac01159bbcb1a12b0f8be0", size = 447007, upload-time = "2025-10-14T06:46:29.056Z" }, + { url = "https://files.pythonhosted.org/packages/13/3b/c99b43fae5047276ea9d944077c190fc1e5f22f57528b9794e21f7adedc6/blake3-1.0.8-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6ae7754c7d96e92a70a52e07c732d594cf9924d780f49fffd3a1e9235e0f5ba7", size = 507323, upload-time = "2025-10-14T06:46:30.661Z" }, + { url = "https://files.pythonhosted.org/packages/fc/bb/ba90eddd592f8c074a0694cb0a744b6bd76bfe67a14c2b490c8bdfca3119/blake3-1.0.8-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4bacaae75e98dee3b7da6c5ee3b81ee21a3352dd2477d6f1d1dbfd38cdbf158a", size = 393449, upload-time = "2025-10-14T06:46:31.805Z" }, + { url = "https://files.pythonhosted.org/packages/25/ed/58a2acd0b9e14459cdaef4344db414d4a36e329b9720921b442a454dd443/blake3-1.0.8-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9456c829601d72852d8ba0af8dae0610f7def1d59f5942efde1e2ef93e8a8b57", size = 386844, upload-time = "2025-10-14T06:46:33.195Z" }, + { url = "https://files.pythonhosted.org/packages/4a/04/fed09845b18d90862100c8e48308261e2f663aab25d3c71a6a0bdda6618b/blake3-1.0.8-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:497ef8096ec4ac1ffba9a66152cee3992337cebf8ea434331d8fd9ce5423d227", size = 549550, upload-time = "2025-10-14T06:46:35.23Z" }, + { url = "https://files.pythonhosted.org/packages/d6/65/1859fddfabc1cc72548c2269d988819aad96d854e25eae00531517925901/blake3-1.0.8-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:511133bab85ff60ed143424ce484d08c60894ff7323f685d7a6095f43f0c85c3", size = 553805, upload-time = "2025-10-14T06:46:36.532Z" }, + { url = "https://files.pythonhosted.org/packages/c1/c7/2969352017f62378e388bb07bb2191bc9a953f818dc1cd6b9dd5c24916e1/blake3-1.0.8-cp313-cp313t-win32.whl", hash = "sha256:9c9fbdacfdeb68f7ca53bb5a7a5a593ec996eaf21155ad5b08d35e6f97e60877", size = 228068, upload-time = "2025-10-14T06:46:37.826Z" }, + { url = "https://files.pythonhosted.org/packages/d8/fc/923e25ac9cadfff1cd20038bcc0854d0f98061eb6bc78e42c43615f5982d/blake3-1.0.8-cp313-cp313t-win_amd64.whl", hash = "sha256:3cec94ed5676821cf371e9c9d25a41b4f3ebdb5724719b31b2749653b7cc1dfa", size = 215369, upload-time = "2025-10-14T06:46:39.054Z" }, + { url = "https://files.pythonhosted.org/packages/2e/2a/9f13ea01b03b1b4751a1cc2b6c1ef4b782e19433a59cf35b59cafb2a2696/blake3-1.0.8-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:2c33dac2c6112bc23f961a7ca305c7e34702c8177040eb98d0389d13a347b9e1", size = 347016, upload-time = "2025-10-14T06:46:40.318Z" }, + { url = "https://files.pythonhosted.org/packages/06/8e/8458c4285fbc5de76414f243e4e0fcab795d71a8b75324e14959aee699da/blake3-1.0.8-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c445eff665d21c3b3b44f864f849a2225b1164c08654beb23224a02f087b7ff1", size = 324496, upload-time = "2025-10-14T06:46:42.355Z" }, + { url = "https://files.pythonhosted.org/packages/49/fa/b913eb9cc4af708c03e01e6b88a8bb3a74833ba4ae4b16b87e2829198e06/blake3-1.0.8-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a47939f04b89c5c6ff1e51e883e5efab1ea1bf01a02f4d208d216dddd63d0dd8", size = 370654, upload-time = "2025-10-14T06:46:43.907Z" }, + { url = "https://files.pythonhosted.org/packages/7f/4f/245e0800c33b99c8f2b570d9a7199b51803694913ee4897f339648502933/blake3-1.0.8-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:73e0b4fa25f6e3078526a592fb38fca85ef204fd02eced6731e1cdd9396552d4", size = 374693, upload-time = "2025-10-14T06:46:45.186Z" }, + { url = "https://files.pythonhosted.org/packages/a2/a6/8cb182c8e482071dbdfcc6ec0048271fd48bcb78782d346119ff54993700/blake3-1.0.8-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b0543c57eb9d6dac9d4bced63e9f7f7b546886ac04cec8da3c3d9c8f30cbbb7", size = 447673, upload-time = "2025-10-14T06:46:46.358Z" }, + { url = "https://files.pythonhosted.org/packages/06/b7/1cbbb5574d2a9436d1b15e7eb5b9d82e178adcaca71a97b0fddaca4bfe3a/blake3-1.0.8-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ed972ebd553c0c25363459e9fc71a38c045d8419e365b59acd8cd791eff13981", size = 507233, upload-time = "2025-10-14T06:46:48.109Z" }, + { url = "https://files.pythonhosted.org/packages/9c/45/b55825d90af353b3e26c653bab278da9d6563afcf66736677f9397e465be/blake3-1.0.8-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3bafdec95dfffa3f6571e529644744e280337df15ddd9728f224ba70c5779b23", size = 393852, upload-time = "2025-10-14T06:46:49.511Z" }, + { url = "https://files.pythonhosted.org/packages/34/73/9058a1a457dd20491d1b37de53d6876eff125e1520d9b2dd7d0acbc88de2/blake3-1.0.8-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d78f06f3fb838b34c330e2987090376145cbe5944d8608a0c4779c779618f7b", size = 386442, upload-time = "2025-10-14T06:46:51.205Z" }, + { url = "https://files.pythonhosted.org/packages/30/6d/561d537ffc17985e276e08bf4513f1c106f1fdbef571e782604dc4e44070/blake3-1.0.8-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:dd03ff08d1b6e4fdda1cd03826f971ae8966ef6f683a8c68aa27fb21904b5aa9", size = 549929, upload-time = "2025-10-14T06:46:52.494Z" }, + { url = "https://files.pythonhosted.org/packages/03/2f/dbe20d2c57f1a67c63be4ba310bcebc707b945c902a0bde075d2a8f5cd5c/blake3-1.0.8-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:4e02a3c499e35bf51fc15b2738aca1a76410804c877bcd914752cac4f71f052a", size = 553750, upload-time = "2025-10-14T06:46:54.194Z" }, + { url = "https://files.pythonhosted.org/packages/6b/da/c6cb712663c869b2814870c2798e57289c4268c5ac5fb12d467fce244860/blake3-1.0.8-cp314-cp314-win32.whl", hash = "sha256:a585357d5d8774aad9ffc12435de457f9e35cde55e0dc8bc43ab590a6929e59f", size = 228404, upload-time = "2025-10-14T06:46:56.807Z" }, + { url = "https://files.pythonhosted.org/packages/dc/b6/c7dcd8bc3094bba1c4274e432f9e77a7df703532ca000eaa550bd066b870/blake3-1.0.8-cp314-cp314-win_amd64.whl", hash = "sha256:9ab5998e2abd9754819753bc2f1cf3edf82d95402bff46aeef45ed392a5468bf", size = 215460, upload-time = "2025-10-14T06:46:58.15Z" }, + { url = "https://files.pythonhosted.org/packages/75/3c/6c8afd856c353176836daa5cc33a7989e8f54569e9d53eb1c53fc8f80c34/blake3-1.0.8-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:e2df12f295f95a804338bd300e8fad4a6f54fd49bd4d9c5893855a230b5188a8", size = 347482, upload-time = "2025-10-14T06:47:00.189Z" }, + { url = "https://files.pythonhosted.org/packages/6a/35/92cd5501ce8e1f5cabdc0c3ac62d69fdb13ff0b60b62abbb2b6d0a53a790/blake3-1.0.8-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:63379be58438878eeb76ebe4f0efbeaabf42b79f2cff23b6126b7991588ced67", size = 324376, upload-time = "2025-10-14T06:47:01.413Z" }, + { url = "https://files.pythonhosted.org/packages/11/33/503b37220a3e2e31917ef13722efd00055af51c5e88ae30974c733d7ece6/blake3-1.0.8-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88d527c247f9609dc1d45a08fd243e39f0d5300d54c57e048de24d4fa9240ebb", size = 370220, upload-time = "2025-10-14T06:47:02.573Z" }, + { url = "https://files.pythonhosted.org/packages/3e/df/fe817843adf59516c04d44387bd643b422a3b0400ea95c6ede6a49920737/blake3-1.0.8-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506a47897a11ebe8f3cdeb52f1365d6a2f83959e98ccb0c830f8f73277d4d358", size = 373454, upload-time = "2025-10-14T06:47:03.784Z" }, + { url = "https://files.pythonhosted.org/packages/d1/4d/90a2a623575373dfc9b683f1bad1bf017feafa5a6d65d94fb09543050740/blake3-1.0.8-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5122a61b3b004bbbd979bdf83a3aaab432da3e2a842d7ddf1c273f2503b4884", size = 447102, upload-time = "2025-10-14T06:47:04.958Z" }, + { url = "https://files.pythonhosted.org/packages/93/ff/4e8ce314f60115c4c657b1fdbe9225b991da4f5bcc5d1c1f1d151e2f39d6/blake3-1.0.8-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0171e85d56dec1219abdae5f49a0ed12cb3f86a454c29160a64fd8a8166bba37", size = 506791, upload-time = "2025-10-14T06:47:06.82Z" }, + { url = "https://files.pythonhosted.org/packages/44/88/2963a1f18aab52bdcf35379b2b48c34bbc462320c37e76960636b8602c36/blake3-1.0.8-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:003f61e8c41dd9931edddf1cc6a1bb680fb2ac0ad15493ef4a1df9adc59ce9df", size = 393717, upload-time = "2025-10-14T06:47:09.085Z" }, + { url = "https://files.pythonhosted.org/packages/45/d1/a848ed8e8d4e236b9b16381768c9ae99d92890c24886bb4505aa9c3d2033/blake3-1.0.8-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2c3151955efb09ba58cd3e1263521e15e9e3866a40d6bd3556d86fc968e8f95", size = 386150, upload-time = "2025-10-14T06:47:10.363Z" }, + { url = "https://files.pythonhosted.org/packages/96/09/e3eb5d60f97c01de23d9f434e6e1fc117efb466eaa1f6ddbbbcb62580d6e/blake3-1.0.8-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:5eb25bca3cee2e0dd746a214784fb36be6a43640c01c55b6b4e26196e72d076c", size = 549120, upload-time = "2025-10-14T06:47:11.713Z" }, + { url = "https://files.pythonhosted.org/packages/14/ad/3d9661c710febb8957dd685fdb3e5a861aa0ac918eda3031365ce45789e2/blake3-1.0.8-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:ab4e1dea4fa857944944db78e8f20d99ee2e16b2dea5a14f514fb0607753ac83", size = 553264, upload-time = "2025-10-14T06:47:13.317Z" }, + { url = "https://files.pythonhosted.org/packages/11/55/e332a5b49edf377d0690e95951cca21a00c568f6e37315f9749efee52617/blake3-1.0.8-cp314-cp314t-win32.whl", hash = "sha256:67f1bc11bf59464ef092488c707b13dd4e872db36e25c453dfb6e0c7498df9f1", size = 228116, upload-time = "2025-10-14T06:47:14.516Z" }, + { url = "https://files.pythonhosted.org/packages/b0/5c/dbd00727a3dd165d7e0e8af40e630cd7e45d77b525a3218afaff8a87358e/blake3-1.0.8-cp314-cp314t-win_amd64.whl", hash = "sha256:421b99cdf1ff2d1bf703bc56c454f4b286fce68454dd8711abbcb5a0df90c19a", size = 215133, upload-time = "2025-10-14T06:47:16.069Z" }, +] + +[[package]] +name = "certifi" +version = "2026.2.25" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029, upload-time = "2026-02-25T02:54:17.342Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z" }, +] + +[[package]] +name = "cffi" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, + { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, + { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, + { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" }, + { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" }, + { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" }, + { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" }, + { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" }, + { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" }, + { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, + { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, + { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" }, + { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" }, + { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" }, + { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" }, + { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" }, + { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" }, + { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" }, + { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" }, + { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, + { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, + { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, + { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" }, + { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" }, + { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, + { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, + { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, + { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, + { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, + { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, + { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, + { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, + { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" }, + { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, + { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, + { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, + { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, + { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, + { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, + { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, + { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, +] + +[[package]] +name = "click" +version = "8.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/57/75/31212c6bf2503fdf920d87fee5d7a86a2e3bcf444984126f13d8e4016804/click-8.3.2.tar.gz", hash = "sha256:14162b8b3b3550a7d479eafa77dfd3c38d9dc8951f6f69c78913a8f9a7540fd5", size = 302856, upload-time = "2026-04-03T19:14:45.118Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e4/20/71885d8b97d4f3dde17b1fdb92dbd4908b00541c5a3379787137285f602e/click-8.3.2-py3-none-any.whl", hash = "sha256:1924d2c27c5653561cd2cae4548d1406039cb79b858b747cfea24924bbc1616d", size = 108379, upload-time = "2026-04-03T19:14:43.505Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "cryptography" +version = "46.0.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a4/ba/04b1bd4218cbc58dc90ce967106d51582371b898690f3ae0402876cc4f34/cryptography-46.0.6.tar.gz", hash = "sha256:27550628a518c5c6c903d84f637fbecf287f6cb9ced3804838a1295dc1fd0759", size = 750542, upload-time = "2026-03-25T23:34:53.396Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/23/9285e15e3bc57325b0a72e592921983a701efc1ee8f91c06c5f0235d86d9/cryptography-46.0.6-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:64235194bad039a10bb6d2d930ab3323baaec67e2ce36215fd0952fad0930ca8", size = 7176401, upload-time = "2026-03-25T23:33:22.096Z" }, + { url = "https://files.pythonhosted.org/packages/60/f8/e61f8f13950ab6195b31913b42d39f0f9afc7d93f76710f299b5ec286ae6/cryptography-46.0.6-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:26031f1e5ca62fcb9d1fcb34b2b60b390d1aacaa15dc8b895a9ed00968b97b30", size = 4275275, upload-time = "2026-03-25T23:33:23.844Z" }, + { url = "https://files.pythonhosted.org/packages/19/69/732a736d12c2631e140be2348b4ad3d226302df63ef64d30dfdb8db7ad1c/cryptography-46.0.6-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9a693028b9cbe51b5a1136232ee8f2bc242e4e19d456ded3fa7c86e43c713b4a", size = 4425320, upload-time = "2026-03-25T23:33:25.703Z" }, + { url = "https://files.pythonhosted.org/packages/d4/12/123be7292674abf76b21ac1fc0e1af50661f0e5b8f0ec8285faac18eb99e/cryptography-46.0.6-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:67177e8a9f421aa2d3a170c3e56eca4e0128883cf52a071a7cbf53297f18b175", size = 4278082, upload-time = "2026-03-25T23:33:27.423Z" }, + { url = "https://files.pythonhosted.org/packages/5b/ba/d5e27f8d68c24951b0a484924a84c7cdaed7502bac9f18601cd357f8b1d2/cryptography-46.0.6-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:d9528b535a6c4f8ff37847144b8986a9a143585f0540fbcb1a98115b543aa463", size = 4926514, upload-time = "2026-03-25T23:33:29.206Z" }, + { url = "https://files.pythonhosted.org/packages/34/71/1ea5a7352ae516d5512d17babe7e1b87d9db5150b21f794b1377eac1edc0/cryptography-46.0.6-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:22259338084d6ae497a19bae5d4c66b7ca1387d3264d1c2c0e72d9e9b6a77b97", size = 4457766, upload-time = "2026-03-25T23:33:30.834Z" }, + { url = "https://files.pythonhosted.org/packages/01/59/562be1e653accee4fdad92c7a2e88fced26b3fdfce144047519bbebc299e/cryptography-46.0.6-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:760997a4b950ff00d418398ad73fbc91aa2894b5c1db7ccb45b4f68b42a63b3c", size = 3986535, upload-time = "2026-03-25T23:33:33.02Z" }, + { url = "https://files.pythonhosted.org/packages/d6/8b/b1ebfeb788bf4624d36e45ed2662b8bd43a05ff62157093c1539c1288a18/cryptography-46.0.6-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:3dfa6567f2e9e4c5dceb8ccb5a708158a2a871052fa75c8b78cb0977063f1507", size = 4277618, upload-time = "2026-03-25T23:33:34.567Z" }, + { url = "https://files.pythonhosted.org/packages/dd/52/a005f8eabdb28df57c20f84c44d397a755782d6ff6d455f05baa2785bd91/cryptography-46.0.6-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:cdcd3edcbc5d55757e5f5f3d330dd00007ae463a7e7aa5bf132d1f22a4b62b19", size = 4890802, upload-time = "2026-03-25T23:33:37.034Z" }, + { url = "https://files.pythonhosted.org/packages/ec/4d/8e7d7245c79c617d08724e2efa397737715ca0ec830ecb3c91e547302555/cryptography-46.0.6-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:d4e4aadb7fc1f88687f47ca20bb7227981b03afaae69287029da08096853b738", size = 4457425, upload-time = "2026-03-25T23:33:38.904Z" }, + { url = "https://files.pythonhosted.org/packages/1d/5c/f6c3596a1430cec6f949085f0e1a970638d76f81c3ea56d93d564d04c340/cryptography-46.0.6-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2b417edbe8877cda9022dde3a008e2deb50be9c407eef034aeeb3a8b11d9db3c", size = 4405530, upload-time = "2026-03-25T23:33:40.842Z" }, + { url = "https://files.pythonhosted.org/packages/7e/c9/9f9cea13ee2dbde070424e0c4f621c091a91ffcc504ffea5e74f0e1daeff/cryptography-46.0.6-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:380343e0653b1c9d7e1f55b52aaa2dbb2fdf2730088d48c43ca1c7c0abb7cc2f", size = 4667896, upload-time = "2026-03-25T23:33:42.781Z" }, + { url = "https://files.pythonhosted.org/packages/ad/b5/1895bc0821226f129bc74d00eccfc6a5969e2028f8617c09790bf89c185e/cryptography-46.0.6-cp311-abi3-win32.whl", hash = "sha256:bcb87663e1f7b075e48c3be3ecb5f0b46c8fc50b50a97cf264e7f60242dca3f2", size = 3026348, upload-time = "2026-03-25T23:33:45.021Z" }, + { url = "https://files.pythonhosted.org/packages/c3/f8/c9bcbf0d3e6ad288b9d9aa0b1dee04b063d19e8c4f871855a03ab3a297ab/cryptography-46.0.6-cp311-abi3-win_amd64.whl", hash = "sha256:6739d56300662c468fddb0e5e291f9b4d084bead381667b9e654c7dd81705124", size = 3483896, upload-time = "2026-03-25T23:33:46.649Z" }, + { url = "https://files.pythonhosted.org/packages/01/41/3a578f7fd5c70611c0aacba52cd13cb364a5dee895a5c1d467208a9380b0/cryptography-46.0.6-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:2ef9e69886cbb137c2aef9772c2e7138dc581fad4fcbcf13cc181eb5a3ab6275", size = 7117147, upload-time = "2026-03-25T23:33:48.249Z" }, + { url = "https://files.pythonhosted.org/packages/fa/87/887f35a6fca9dde90cad08e0de0c89263a8e59b2d2ff904fd9fcd8025b6f/cryptography-46.0.6-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7f417f034f91dcec1cb6c5c35b07cdbb2ef262557f701b4ecd803ee8cefed4f4", size = 4266221, upload-time = "2026-03-25T23:33:49.874Z" }, + { url = "https://files.pythonhosted.org/packages/aa/a8/0a90c4f0b0871e0e3d1ed126aed101328a8a57fd9fd17f00fb67e82a51ca/cryptography-46.0.6-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d24c13369e856b94892a89ddf70b332e0b70ad4a5c43cf3e9cb71d6d7ffa1f7b", size = 4408952, upload-time = "2026-03-25T23:33:52.128Z" }, + { url = "https://files.pythonhosted.org/packages/16/0b/b239701eb946523e4e9f329336e4ff32b1247e109cbab32d1a7b61da8ed7/cryptography-46.0.6-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:aad75154a7ac9039936d50cf431719a2f8d4ed3d3c277ac03f3339ded1a5e707", size = 4270141, upload-time = "2026-03-25T23:33:54.11Z" }, + { url = "https://files.pythonhosted.org/packages/0f/a8/976acdd4f0f30df7b25605f4b9d3d89295351665c2091d18224f7ad5cdbf/cryptography-46.0.6-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:3c21d92ed15e9cfc6eb64c1f5a0326db22ca9c2566ca46d845119b45b4400361", size = 4904178, upload-time = "2026-03-25T23:33:55.725Z" }, + { url = "https://files.pythonhosted.org/packages/b1/1b/bf0e01a88efd0e59679b69f42d4afd5bced8700bb5e80617b2d63a3741af/cryptography-46.0.6-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:4668298aef7cddeaf5c6ecc244c2302a2b8e40f384255505c22875eebb47888b", size = 4441812, upload-time = "2026-03-25T23:33:57.364Z" }, + { url = "https://files.pythonhosted.org/packages/bb/8b/11df86de2ea389c65aa1806f331cae145f2ed18011f30234cc10ca253de8/cryptography-46.0.6-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:8ce35b77aaf02f3b59c90b2c8a05c73bac12cea5b4e8f3fbece1f5fddea5f0ca", size = 3963923, upload-time = "2026-03-25T23:33:59.361Z" }, + { url = "https://files.pythonhosted.org/packages/91/e0/207fb177c3a9ef6a8108f234208c3e9e76a6aa8cf20d51932916bd43bda0/cryptography-46.0.6-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:c89eb37fae9216985d8734c1afd172ba4927f5a05cfd9bf0e4863c6d5465b013", size = 4269695, upload-time = "2026-03-25T23:34:00.909Z" }, + { url = "https://files.pythonhosted.org/packages/21/5e/19f3260ed1e95bced52ace7501fabcd266df67077eeb382b79c81729d2d3/cryptography-46.0.6-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:ed418c37d095aeddf5336898a132fba01091f0ac5844e3e8018506f014b6d2c4", size = 4869785, upload-time = "2026-03-25T23:34:02.796Z" }, + { url = "https://files.pythonhosted.org/packages/10/38/cd7864d79aa1d92ef6f1a584281433419b955ad5a5ba8d1eb6c872165bcb/cryptography-46.0.6-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:69cf0056d6947edc6e6760e5f17afe4bea06b56a9ac8a06de9d2bd6b532d4f3a", size = 4441404, upload-time = "2026-03-25T23:34:04.35Z" }, + { url = "https://files.pythonhosted.org/packages/09/0a/4fe7a8d25fed74419f91835cf5829ade6408fd1963c9eae9c4bce390ecbb/cryptography-46.0.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8e7304c4f4e9490e11efe56af6713983460ee0780f16c63f219984dab3af9d2d", size = 4397549, upload-time = "2026-03-25T23:34:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/5f/a0/7d738944eac6513cd60a8da98b65951f4a3b279b93479a7e8926d9cd730b/cryptography-46.0.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b928a3ca837c77a10e81a814a693f2295200adb3352395fad024559b7be7a736", size = 4651874, upload-time = "2026-03-25T23:34:07.916Z" }, + { url = "https://files.pythonhosted.org/packages/cb/f1/c2326781ca05208845efca38bf714f76939ae446cd492d7613808badedf1/cryptography-46.0.6-cp314-cp314t-win32.whl", hash = "sha256:97c8115b27e19e592a05c45d0dd89c57f81f841cc9880e353e0d3bf25b2139ed", size = 3001511, upload-time = "2026-03-25T23:34:09.892Z" }, + { url = "https://files.pythonhosted.org/packages/c9/57/fe4a23eb549ac9d903bd4698ffda13383808ef0876cc912bcb2838799ece/cryptography-46.0.6-cp314-cp314t-win_amd64.whl", hash = "sha256:c797e2517cb7880f8297e2c0f43bb910e91381339336f75d2c1c2cbf811b70b4", size = 3471692, upload-time = "2026-03-25T23:34:11.613Z" }, + { url = "https://files.pythonhosted.org/packages/c4/cc/f330e982852403da79008552de9906804568ae9230da8432f7496ce02b71/cryptography-46.0.6-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:12cae594e9473bca1a7aceb90536060643128bb274fcea0fc459ab90f7d1ae7a", size = 7162776, upload-time = "2026-03-25T23:34:13.308Z" }, + { url = "https://files.pythonhosted.org/packages/49/b3/dc27efd8dcc4bff583b3f01d4a3943cd8b5821777a58b3a6a5f054d61b79/cryptography-46.0.6-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:639301950939d844a9e1c4464d7e07f902fe9a7f6b215bb0d4f28584729935d8", size = 4270529, upload-time = "2026-03-25T23:34:15.019Z" }, + { url = "https://files.pythonhosted.org/packages/e6/05/e8d0e6eb4f0d83365b3cb0e00eb3c484f7348db0266652ccd84632a3d58d/cryptography-46.0.6-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ed3775295fb91f70b4027aeba878d79b3e55c0b3e97eaa4de71f8f23a9f2eb77", size = 4414827, upload-time = "2026-03-25T23:34:16.604Z" }, + { url = "https://files.pythonhosted.org/packages/2f/97/daba0f5d2dc6d855e2dcb70733c812558a7977a55dd4a6722756628c44d1/cryptography-46.0.6-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:8927ccfbe967c7df312ade694f987e7e9e22b2425976ddbf28271d7e58845290", size = 4271265, upload-time = "2026-03-25T23:34:18.586Z" }, + { url = "https://files.pythonhosted.org/packages/89/06/fe1fce39a37ac452e58d04b43b0855261dac320a2ebf8f5260dd55b201a9/cryptography-46.0.6-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:b12c6b1e1651e42ab5de8b1e00dc3b6354fdfd778e7fa60541ddacc27cd21410", size = 4916800, upload-time = "2026-03-25T23:34:20.561Z" }, + { url = "https://files.pythonhosted.org/packages/ff/8a/b14f3101fe9c3592603339eb5d94046c3ce5f7fc76d6512a2d40efd9724e/cryptography-46.0.6-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:063b67749f338ca9c5a0b7fe438a52c25f9526b851e24e6c9310e7195aad3b4d", size = 4448771, upload-time = "2026-03-25T23:34:22.406Z" }, + { url = "https://files.pythonhosted.org/packages/01/b3/0796998056a66d1973fd52ee89dc1bb3b6581960a91ad4ac705f182d398f/cryptography-46.0.6-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:02fad249cb0e090b574e30b276a3da6a149e04ee2f049725b1f69e7b8351ec70", size = 3978333, upload-time = "2026-03-25T23:34:24.281Z" }, + { url = "https://files.pythonhosted.org/packages/c5/3d/db200af5a4ffd08918cd55c08399dc6c9c50b0bc72c00a3246e099d3a849/cryptography-46.0.6-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:7e6142674f2a9291463e5e150090b95a8519b2fb6e6aaec8917dd8d094ce750d", size = 4271069, upload-time = "2026-03-25T23:34:25.895Z" }, + { url = "https://files.pythonhosted.org/packages/d7/18/61acfd5b414309d74ee838be321c636fe71815436f53c9f0334bf19064fa/cryptography-46.0.6-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:456b3215172aeefb9284550b162801d62f5f264a081049a3e94307fe20792cfa", size = 4878358, upload-time = "2026-03-25T23:34:27.67Z" }, + { url = "https://files.pythonhosted.org/packages/8b/65/5bf43286d566f8171917cae23ac6add941654ccf085d739195a4eacf1674/cryptography-46.0.6-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:341359d6c9e68834e204ceaf25936dffeafea3829ab80e9503860dcc4f4dac58", size = 4448061, upload-time = "2026-03-25T23:34:29.375Z" }, + { url = "https://files.pythonhosted.org/packages/e0/25/7e49c0fa7205cf3597e525d156a6bce5b5c9de1fd7e8cb01120e459f205a/cryptography-46.0.6-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9a9c42a2723999a710445bc0d974e345c32adfd8d2fac6d8a251fa829ad31cfb", size = 4399103, upload-time = "2026-03-25T23:34:32.036Z" }, + { url = "https://files.pythonhosted.org/packages/44/46/466269e833f1c4718d6cd496ffe20c56c9c8d013486ff66b4f69c302a68d/cryptography-46.0.6-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6617f67b1606dfd9fe4dbfa354a9508d4a6d37afe30306fe6c101b7ce3274b72", size = 4659255, upload-time = "2026-03-25T23:34:33.679Z" }, + { url = "https://files.pythonhosted.org/packages/0a/09/ddc5f630cc32287d2c953fc5d32705e63ec73e37308e5120955316f53827/cryptography-46.0.6-cp38-abi3-win32.whl", hash = "sha256:7f6690b6c55e9c5332c0b59b9c8a3fb232ebf059094c17f9019a51e9827df91c", size = 3010660, upload-time = "2026-03-25T23:34:35.418Z" }, + { url = "https://files.pythonhosted.org/packages/1b/82/ca4893968aeb2709aacfb57a30dec6fa2ab25b10fa9f064b8882ce33f599/cryptography-46.0.6-cp38-abi3-win_amd64.whl", hash = "sha256:79e865c642cfc5c0b3eb12af83c35c5aeff4fa5c672dc28c43721c2c9fdd2f0f", size = 3471160, upload-time = "2026-03-25T23:34:37.191Z" }, +] + +[[package]] +name = "deepdiff" +version = "9.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "orderly-set" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/24/20/63dd34163ed07393968128dc8c7ab948c96e47c4ce76976ea533de64909d/deepdiff-9.0.0.tar.gz", hash = "sha256:4872005306237b5b50829803feff58a1dfd20b2b357a55de22e7ded65b2008a7", size = 151952, upload-time = "2026-03-30T05:52:23.769Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/c4/da7089cd7aa4ab554f56e18a7fb08dcfed8fd2ae91fa528f5b1be207a148/deepdiff-9.0.0-py3-none-any.whl", hash = "sha256:b1ae0dd86290d86a03de5fbee728fde43095c1472ae4974bdab23ab4656305bd", size = 170540, upload-time = "2026-03-30T05:52:22.008Z" }, +] + +[[package]] +name = "dependency-injector" +version = "4.49.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f3/be/26bb530d06618fb0bb34244d46b0d0ccc53d0974e680d8653f1b1b313a0e/dependency_injector-4.49.0.tar.gz", hash = "sha256:17a04dbfaa8159f1dc068fc26bc2fa0af9774cdd87f99e3b61bd74c9e7171589", size = 1168930, upload-time = "2026-03-22T21:20:05.524Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/5d/cc49fb34e0c03aa56d7583de00e2f8f5aa1b8a878b695e970dcdb751a477/dependency_injector-4.49.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:9690192fd5aed07f21dfdfae07696fef12c68bf98e4c0e1af8f8128b255a74a7", size = 1769395, upload-time = "2026-03-22T21:19:14.163Z" }, + { url = "https://files.pythonhosted.org/packages/7f/97/b3b144c96e1f7fff0a7e2e83eb0767bd23b6bacffd0ac8cff397d350e94d/dependency_injector-4.49.0-cp310-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f91f2a191bdb17bd3068f32fe65f04128bc162c6237ea554c117b303c22aaabb", size = 1852089, upload-time = "2026-03-22T21:19:16.354Z" }, + { url = "https://files.pythonhosted.org/packages/4c/e7/33061f427bcb56c8936d5db464d757d926bf752a874683fb64b2ee225463/dependency_injector-4.49.0-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:733c0d88b26be17a48e5741cc3e3956080112e40c07a38ff38e99dfa772f9772", size = 1765608, upload-time = "2026-03-22T21:19:19.217Z" }, + { url = "https://files.pythonhosted.org/packages/1e/4d/2751a6c055de4a200d65af297ecd926d6b6107f66f3849e8122928abf461/dependency_injector-4.49.0-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:45720b30a2a3df6e5e2320e242f6dd94540ba27c3da57cafdc37fdeec59d5ce3", size = 1746555, upload-time = "2026-03-22T21:19:21.231Z" }, + { url = "https://files.pythonhosted.org/packages/02/6f/f74fee9629528f0879295b9f89a5c751d3ad931eca0c78407f715e5472a6/dependency_injector-4.49.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3b5d2f1be2dc971db47b1305a83b5a8c24d0eba7fb4cea7845679f9c9f24a0a9", size = 1843223, upload-time = "2026-03-22T21:19:23.356Z" }, + { url = "https://files.pythonhosted.org/packages/c5/f0/45948c7c933f063039a44afb4bd61747a7bafd50693e6ccdc972fac0839c/dependency_injector-4.49.0-cp310-abi3-win32.whl", hash = "sha256:0593c8aaade651a5a88ff8ba1271a8364773e76d3aa2efbeacc3be4969cafd1c", size = 1546172, upload-time = "2026-03-22T21:19:25.392Z" }, + { url = "https://files.pythonhosted.org/packages/e2/b5/1d8e5627137cb9a6812ecaa468eaf39154f6605c5088da4749e5a8579483/dependency_injector-4.49.0-cp310-abi3-win_amd64.whl", hash = "sha256:fa4b587158b0d65a1f9681ca648da3f9bf90f312f68c2f2e73cc58296ec2bf45", size = 1674743, upload-time = "2026-03-22T21:19:27.018Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + +[[package]] +name = "idna" +version = "3.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "jsonschema" +version = "4.26.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "jsonschema-specifications" }, + { name = "referencing" }, + { name = "rpds-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b3/fc/e067678238fa451312d4c62bf6e6cf5ec56375422aee02f9cb5f909b3047/jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326", size = 366583, upload-time = "2026-01-07T13:41:07.246Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce", size = 90630, upload-time = "2026-01-07T13:41:05.306Z" }, +] + +[[package]] +name = "jsonschema-specifications" +version = "2025.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "referencing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, +] + +[[package]] +name = "librt" +version = "0.8.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/56/9c/b4b0c54d84da4a94b37bd44151e46d5e583c9534c7e02250b961b1b6d8a8/librt-0.8.1.tar.gz", hash = "sha256:be46a14693955b3bd96014ccbdb8339ee8c9346fbe11c1b78901b55125f14c73", size = 177471, upload-time = "2026-02-17T16:13:06.101Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/21/d39b0a87ac52fc98f621fb6f8060efb017a767ebbbac2f99fbcbc9ddc0d7/librt-0.8.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a28f2612ab566b17f3698b0da021ff9960610301607c9a5e8eaca62f5e1c350a", size = 66516, upload-time = "2026-02-17T16:11:41.604Z" }, + { url = "https://files.pythonhosted.org/packages/69/f1/46375e71441c43e8ae335905e069f1c54febee63a146278bcee8782c84fd/librt-0.8.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:60a78b694c9aee2a0f1aaeaa7d101cf713e92e8423a941d2897f4fa37908dab9", size = 68634, upload-time = "2026-02-17T16:11:43.268Z" }, + { url = "https://files.pythonhosted.org/packages/0a/33/c510de7f93bf1fa19e13423a606d8189a02624a800710f6e6a0a0f0784b3/librt-0.8.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:758509ea3f1eba2a57558e7e98f4659d0ea7670bff49673b0dde18a3c7e6c0eb", size = 198941, upload-time = "2026-02-17T16:11:44.28Z" }, + { url = "https://files.pythonhosted.org/packages/dd/36/e725903416409a533d92398e88ce665476f275081d0d7d42f9c4951999e5/librt-0.8.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:039b9f2c506bd0ab0f8725aa5ba339c6f0cd19d3b514b50d134789809c24285d", size = 209991, upload-time = "2026-02-17T16:11:45.462Z" }, + { url = "https://files.pythonhosted.org/packages/30/7a/8d908a152e1875c9f8eac96c97a480df425e657cdb47854b9efaa4998889/librt-0.8.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bb54f1205a3a6ab41a6fd71dfcdcbd278670d3a90ca502a30d9da583105b6f7", size = 224476, upload-time = "2026-02-17T16:11:46.542Z" }, + { url = "https://files.pythonhosted.org/packages/a8/b8/a22c34f2c485b8903a06f3fe3315341fe6876ef3599792344669db98fcff/librt-0.8.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:05bd41cdee35b0c59c259f870f6da532a2c5ca57db95b5f23689fcb5c9e42440", size = 217518, upload-time = "2026-02-17T16:11:47.746Z" }, + { url = "https://files.pythonhosted.org/packages/79/6f/5c6fea00357e4f82ba44f81dbfb027921f1ab10e320d4a64e1c408d035d9/librt-0.8.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:adfab487facf03f0d0857b8710cf82d0704a309d8ffc33b03d9302b4c64e91a9", size = 225116, upload-time = "2026-02-17T16:11:49.298Z" }, + { url = "https://files.pythonhosted.org/packages/f2/a0/95ced4e7b1267fe1e2720a111685bcddf0e781f7e9e0ce59d751c44dcfe5/librt-0.8.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:153188fe98a72f206042be10a2c6026139852805215ed9539186312d50a8e972", size = 217751, upload-time = "2026-02-17T16:11:50.49Z" }, + { url = "https://files.pythonhosted.org/packages/93/c2/0517281cb4d4101c27ab59472924e67f55e375bc46bedae94ac6dc6e1902/librt-0.8.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:dd3c41254ee98604b08bd5b3af5bf0a89740d4ee0711de95b65166bf44091921", size = 218378, upload-time = "2026-02-17T16:11:51.783Z" }, + { url = "https://files.pythonhosted.org/packages/43/e8/37b3ac108e8976888e559a7b227d0ceac03c384cfd3e7a1c2ee248dbae79/librt-0.8.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e0d138c7ae532908cbb342162b2611dbd4d90c941cd25ab82084aaf71d2c0bd0", size = 241199, upload-time = "2026-02-17T16:11:53.561Z" }, + { url = "https://files.pythonhosted.org/packages/4b/5b/35812d041c53967fedf551a39399271bbe4257e681236a2cf1a69c8e7fa1/librt-0.8.1-cp312-cp312-win32.whl", hash = "sha256:43353b943613c5d9c49a25aaffdba46f888ec354e71e3529a00cca3f04d66a7a", size = 54917, upload-time = "2026-02-17T16:11:54.758Z" }, + { url = "https://files.pythonhosted.org/packages/de/d1/fa5d5331b862b9775aaf2a100f5ef86854e5d4407f71bddf102f4421e034/librt-0.8.1-cp312-cp312-win_amd64.whl", hash = "sha256:ff8baf1f8d3f4b6b7257fcb75a501f2a5499d0dda57645baa09d4d0d34b19444", size = 62017, upload-time = "2026-02-17T16:11:55.748Z" }, + { url = "https://files.pythonhosted.org/packages/c7/7c/c614252f9acda59b01a66e2ddfd243ed1c7e1deab0293332dfbccf862808/librt-0.8.1-cp312-cp312-win_arm64.whl", hash = "sha256:0f2ae3725904f7377e11cc37722d5d401e8b3d5851fb9273d7f4fe04f6b3d37d", size = 52441, upload-time = "2026-02-17T16:11:56.801Z" }, + { url = "https://files.pythonhosted.org/packages/c5/3c/f614c8e4eaac7cbf2bbdf9528790b21d89e277ee20d57dc6e559c626105f/librt-0.8.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7e6bad1cd94f6764e1e21950542f818a09316645337fd5ab9a7acc45d99a8f35", size = 66529, upload-time = "2026-02-17T16:11:57.809Z" }, + { url = "https://files.pythonhosted.org/packages/ab/96/5836544a45100ae411eda07d29e3d99448e5258b6e9c8059deb92945f5c2/librt-0.8.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cf450f498c30af55551ba4f66b9123b7185362ec8b625a773b3d39aa1a717583", size = 68669, upload-time = "2026-02-17T16:11:58.843Z" }, + { url = "https://files.pythonhosted.org/packages/06/53/f0b992b57af6d5531bf4677d75c44f095f2366a1741fb695ee462ae04b05/librt-0.8.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:eca45e982fa074090057132e30585a7e8674e9e885d402eae85633e9f449ce6c", size = 199279, upload-time = "2026-02-17T16:11:59.862Z" }, + { url = "https://files.pythonhosted.org/packages/f3/ad/4848cc16e268d14280d8168aee4f31cea92bbd2b79ce33d3e166f2b4e4fc/librt-0.8.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c3811485fccfda840861905b8c70bba5ec094e02825598bb9d4ca3936857a04", size = 210288, upload-time = "2026-02-17T16:12:00.954Z" }, + { url = "https://files.pythonhosted.org/packages/52/05/27fdc2e95de26273d83b96742d8d3b7345f2ea2bdbd2405cc504644f2096/librt-0.8.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e4af413908f77294605e28cfd98063f54b2c790561383971d2f52d113d9c363", size = 224809, upload-time = "2026-02-17T16:12:02.108Z" }, + { url = "https://files.pythonhosted.org/packages/7a/d0/78200a45ba3240cb042bc597d6f2accba9193a2c57d0356268cbbe2d0925/librt-0.8.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5212a5bd7fae98dae95710032902edcd2ec4dc994e883294f75c857b83f9aba0", size = 218075, upload-time = "2026-02-17T16:12:03.631Z" }, + { url = "https://files.pythonhosted.org/packages/af/72/a210839fa74c90474897124c064ffca07f8d4b347b6574d309686aae7ca6/librt-0.8.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e692aa2d1d604e6ca12d35e51fdc36f4cda6345e28e36374579f7ef3611b3012", size = 225486, upload-time = "2026-02-17T16:12:04.725Z" }, + { url = "https://files.pythonhosted.org/packages/a3/c1/a03cc63722339ddbf087485f253493e2b013039f5b707e8e6016141130fa/librt-0.8.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4be2a5c926b9770c9e08e717f05737a269b9d0ebc5d2f0060f0fe3fe9ce47acb", size = 218219, upload-time = "2026-02-17T16:12:05.828Z" }, + { url = "https://files.pythonhosted.org/packages/58/f5/fff6108af0acf941c6f274a946aea0e484bd10cd2dc37610287ce49388c5/librt-0.8.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:fd1a720332ea335ceb544cf0a03f81df92abd4bb887679fd1e460976b0e6214b", size = 218750, upload-time = "2026-02-17T16:12:07.09Z" }, + { url = "https://files.pythonhosted.org/packages/71/67/5a387bfef30ec1e4b4f30562c8586566faf87e47d696768c19feb49e3646/librt-0.8.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2af9e01e0ef80d95ae3c720be101227edae5f2fe7e3dc63d8857fadfc5a1d", size = 241624, upload-time = "2026-02-17T16:12:08.43Z" }, + { url = "https://files.pythonhosted.org/packages/d4/be/24f8502db11d405232ac1162eb98069ca49c3306c1d75c6ccc61d9af8789/librt-0.8.1-cp313-cp313-win32.whl", hash = "sha256:086a32dbb71336627e78cc1d6ee305a68d038ef7d4c39aaff41ae8c9aa46e91a", size = 54969, upload-time = "2026-02-17T16:12:09.633Z" }, + { url = "https://files.pythonhosted.org/packages/5c/73/c9fdf6cb2a529c1a092ce769a12d88c8cca991194dfe641b6af12fa964d2/librt-0.8.1-cp313-cp313-win_amd64.whl", hash = "sha256:e11769a1dbda4da7b00a76cfffa67aa47cfa66921d2724539eee4b9ede780b79", size = 62000, upload-time = "2026-02-17T16:12:10.632Z" }, + { url = "https://files.pythonhosted.org/packages/d3/97/68f80ca3ac4924f250cdfa6e20142a803e5e50fca96ef5148c52ee8c10ea/librt-0.8.1-cp313-cp313-win_arm64.whl", hash = "sha256:924817ab3141aca17893386ee13261f1d100d1ef410d70afe4389f2359fea4f0", size = 52495, upload-time = "2026-02-17T16:12:11.633Z" }, + { url = "https://files.pythonhosted.org/packages/c9/6a/907ef6800f7bca71b525a05f1839b21f708c09043b1c6aa77b6b827b3996/librt-0.8.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:6cfa7fe54fd4d1f47130017351a959fe5804bda7a0bc7e07a2cdbc3fdd28d34f", size = 66081, upload-time = "2026-02-17T16:12:12.766Z" }, + { url = "https://files.pythonhosted.org/packages/1b/18/25e991cd5640c9fb0f8d91b18797b29066b792f17bf8493da183bf5caabe/librt-0.8.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:228c2409c079f8c11fb2e5d7b277077f694cb93443eb760e00b3b83cb8b3176c", size = 68309, upload-time = "2026-02-17T16:12:13.756Z" }, + { url = "https://files.pythonhosted.org/packages/a4/36/46820d03f058cfb5a9de5940640ba03165ed8aded69e0733c417bb04df34/librt-0.8.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7aae78ab5e3206181780e56912d1b9bb9f90a7249ce12f0e8bf531d0462dd0fc", size = 196804, upload-time = "2026-02-17T16:12:14.818Z" }, + { url = "https://files.pythonhosted.org/packages/59/18/5dd0d3b87b8ff9c061849fbdb347758d1f724b9a82241aa908e0ec54ccd0/librt-0.8.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:172d57ec04346b047ca6af181e1ea4858086c80bdf455f61994c4aa6fc3f866c", size = 206907, upload-time = "2026-02-17T16:12:16.513Z" }, + { url = "https://files.pythonhosted.org/packages/d1/96/ef04902aad1424fd7299b62d1890e803e6ab4018c3044dca5922319c4b97/librt-0.8.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6b1977c4ea97ce5eb7755a78fae68d87e4102e4aaf54985e8b56806849cc06a3", size = 221217, upload-time = "2026-02-17T16:12:17.906Z" }, + { url = "https://files.pythonhosted.org/packages/6d/ff/7e01f2dda84a8f5d280637a2e5827210a8acca9a567a54507ef1c75b342d/librt-0.8.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:10c42e1f6fd06733ef65ae7bebce2872bcafd8d6e6b0a08fe0a05a23b044fb14", size = 214622, upload-time = "2026-02-17T16:12:19.108Z" }, + { url = "https://files.pythonhosted.org/packages/1e/8c/5b093d08a13946034fed57619742f790faf77058558b14ca36a6e331161e/librt-0.8.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4c8dfa264b9193c4ee19113c985c95f876fae5e51f731494fc4e0cf594990ba7", size = 221987, upload-time = "2026-02-17T16:12:20.331Z" }, + { url = "https://files.pythonhosted.org/packages/d3/cc/86b0b3b151d40920ad45a94ce0171dec1aebba8a9d72bb3fa00c73ab25dd/librt-0.8.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:01170b6729a438f0dedc4a26ed342e3dc4f02d1000b4b19f980e1877f0c297e6", size = 215132, upload-time = "2026-02-17T16:12:21.54Z" }, + { url = "https://files.pythonhosted.org/packages/fc/be/8588164a46edf1e69858d952654e216a9a91174688eeefb9efbb38a9c799/librt-0.8.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:7b02679a0d783bdae30d443025b94465d8c3dc512f32f5b5031f93f57ac32071", size = 215195, upload-time = "2026-02-17T16:12:23.073Z" }, + { url = "https://files.pythonhosted.org/packages/f5/f2/0b9279bea735c734d69344ecfe056c1ba211694a72df10f568745c899c76/librt-0.8.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:190b109bb69592a3401fe1ffdea41a2e73370ace2ffdc4a0e8e2b39cdea81b78", size = 237946, upload-time = "2026-02-17T16:12:24.275Z" }, + { url = "https://files.pythonhosted.org/packages/e9/cc/5f2a34fbc8aeb35314a3641f9956fa9051a947424652fad9882be7a97949/librt-0.8.1-cp314-cp314-win32.whl", hash = "sha256:e70a57ecf89a0f64c24e37f38d3fe217a58169d2fe6ed6d70554964042474023", size = 50689, upload-time = "2026-02-17T16:12:25.766Z" }, + { url = "https://files.pythonhosted.org/packages/a0/76/cd4d010ab2147339ca2b93e959c3686e964edc6de66ddacc935c325883d7/librt-0.8.1-cp314-cp314-win_amd64.whl", hash = "sha256:7e2f3edca35664499fbb36e4770650c4bd4a08abc1f4458eab9df4ec56389730", size = 57875, upload-time = "2026-02-17T16:12:27.465Z" }, + { url = "https://files.pythonhosted.org/packages/84/0f/2143cb3c3ca48bd3379dcd11817163ca50781927c4537345d608b5045998/librt-0.8.1-cp314-cp314-win_arm64.whl", hash = "sha256:0d2f82168e55ddefd27c01c654ce52379c0750ddc31ee86b4b266bcf4d65f2a3", size = 48058, upload-time = "2026-02-17T16:12:28.556Z" }, + { url = "https://files.pythonhosted.org/packages/d2/0e/9b23a87e37baf00311c3efe6b48d6b6c168c29902dfc3f04c338372fd7db/librt-0.8.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2c74a2da57a094bd48d03fa5d196da83d2815678385d2978657499063709abe1", size = 68313, upload-time = "2026-02-17T16:12:29.659Z" }, + { url = "https://files.pythonhosted.org/packages/db/9a/859c41e5a4f1c84200a7d2b92f586aa27133c8243b6cac9926f6e54d01b9/librt-0.8.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a355d99c4c0d8e5b770313b8b247411ed40949ca44e33e46a4789b9293a907ee", size = 70994, upload-time = "2026-02-17T16:12:31.516Z" }, + { url = "https://files.pythonhosted.org/packages/4c/28/10605366ee599ed34223ac2bf66404c6fb59399f47108215d16d5ad751a8/librt-0.8.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2eb345e8b33fb748227409c9f1233d4df354d6e54091f0e8fc53acdb2ffedeb7", size = 220770, upload-time = "2026-02-17T16:12:33.294Z" }, + { url = "https://files.pythonhosted.org/packages/af/8d/16ed8fd452dafae9c48d17a6bc1ee3e818fd40ef718d149a8eff2c9f4ea2/librt-0.8.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9be2f15e53ce4e83cc08adc29b26fb5978db62ef2a366fbdf716c8a6c8901040", size = 235409, upload-time = "2026-02-17T16:12:35.443Z" }, + { url = "https://files.pythonhosted.org/packages/89/1b/7bdf3e49349c134b25db816e4a3db6b94a47ac69d7d46b1e682c2c4949be/librt-0.8.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:785ae29c1f5c6e7c2cde2c7c0e148147f4503da3abc5d44d482068da5322fd9e", size = 246473, upload-time = "2026-02-17T16:12:36.656Z" }, + { url = "https://files.pythonhosted.org/packages/4e/8a/91fab8e4fd2a24930a17188c7af5380eb27b203d72101c9cc000dbdfd95a/librt-0.8.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1d3a7da44baf692f0c6aeb5b2a09c5e6fc7a703bca9ffa337ddd2e2da53f7732", size = 238866, upload-time = "2026-02-17T16:12:37.849Z" }, + { url = "https://files.pythonhosted.org/packages/b9/e0/c45a098843fc7c07e18a7f8a24ca8496aecbf7bdcd54980c6ca1aaa79a8e/librt-0.8.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5fc48998000cbc39ec0d5311312dda93ecf92b39aaf184c5e817d5d440b29624", size = 250248, upload-time = "2026-02-17T16:12:39.445Z" }, + { url = "https://files.pythonhosted.org/packages/82/30/07627de23036640c952cce0c1fe78972e77d7d2f8fd54fa5ef4554ff4a56/librt-0.8.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:e96baa6820280077a78244b2e06e416480ed859bbd8e5d641cf5742919d8beb4", size = 240629, upload-time = "2026-02-17T16:12:40.889Z" }, + { url = "https://files.pythonhosted.org/packages/fb/c1/55bfe1ee3542eba055616f9098eaf6eddb966efb0ca0f44eaa4aba327307/librt-0.8.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:31362dbfe297b23590530007062c32c6f6176f6099646bb2c95ab1b00a57c382", size = 239615, upload-time = "2026-02-17T16:12:42.446Z" }, + { url = "https://files.pythonhosted.org/packages/2b/39/191d3d28abc26c9099b19852e6c99f7f6d400b82fa5a4e80291bd3803e19/librt-0.8.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cc3656283d11540ab0ea01978378e73e10002145117055e03722417aeab30994", size = 263001, upload-time = "2026-02-17T16:12:43.627Z" }, + { url = "https://files.pythonhosted.org/packages/b9/eb/7697f60fbe7042ab4e88f4ee6af496b7f222fffb0a4e3593ef1f29f81652/librt-0.8.1-cp314-cp314t-win32.whl", hash = "sha256:738f08021b3142c2918c03692608baed43bc51144c29e35807682f8070ee2a3a", size = 51328, upload-time = "2026-02-17T16:12:45.148Z" }, + { url = "https://files.pythonhosted.org/packages/7c/72/34bf2eb7a15414a23e5e70ecb9440c1d3179f393d9349338a91e2781c0fb/librt-0.8.1-cp314-cp314t-win_amd64.whl", hash = "sha256:89815a22daf9c51884fb5dbe4f1ef65ee6a146e0b6a8df05f753e2e4a9359bf4", size = 58722, upload-time = "2026-02-17T16:12:46.85Z" }, + { url = "https://files.pythonhosted.org/packages/b2/c8/d148e041732d631fc76036f8b30fae4e77b027a1e95b7a84bb522481a940/librt-0.8.1-cp314-cp314t-win_arm64.whl", hash = "sha256:bf512a71a23504ed08103a13c941f763db13fb11177beb3d9244c98c29fb4a61", size = 48755, upload-time = "2026-02-17T16:12:47.943Z" }, +] + +[[package]] +name = "mypy" +version = "1.20.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "librt", marker = "platform_python_implementation != 'PyPy'" }, + { name = "mypy-extensions" }, + { name = "pathspec" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f8/5c/b0089fe7fef0a994ae5ee07029ced0526082c6cfaaa4c10d40a10e33b097/mypy-1.20.0.tar.gz", hash = "sha256:eb96c84efcc33f0b5e0e04beacf00129dd963b67226b01c00b9dfc8affb464c3", size = 3815028, upload-time = "2026-03-31T16:55:14.959Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/dd/3afa29b58c2e57c79116ed55d700721c3c3b15955e2b6251dd165d377c0e/mypy-1.20.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:002b613ae19f4ac7d18b7e168ffe1cb9013b37c57f7411984abbd3b817b0a214", size = 14509525, upload-time = "2026-03-31T16:55:01.824Z" }, + { url = "https://files.pythonhosted.org/packages/54/eb/227b516ab8cad9f2a13c5e7a98d28cd6aa75e9c83e82776ae6c1c4c046c7/mypy-1.20.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a9336b5e6712f4adaf5afc3203a99a40b379049104349d747eb3e5a3aa23ac2e", size = 13326469, upload-time = "2026-03-31T16:51:41.23Z" }, + { url = "https://files.pythonhosted.org/packages/57/d4/1ddb799860c1b5ac6117ec307b965f65deeb47044395ff01ab793248a591/mypy-1.20.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f13b3e41bce9d257eded794c0f12878af3129d80aacd8a3ee0dee51f3a978651", size = 13705953, upload-time = "2026-03-31T16:48:55.69Z" }, + { url = "https://files.pythonhosted.org/packages/c5/b7/54a720f565a87b893182a2a393370289ae7149e4715859e10e1c05e49154/mypy-1.20.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9804c3ad27f78e54e58b32e7cb532d128b43dbfb9f3f9f06262b821a0f6bd3f5", size = 14710363, upload-time = "2026-03-31T16:53:26.948Z" }, + { url = "https://files.pythonhosted.org/packages/b2/2a/74810274848d061f8a8ea4ac23aaad43bd3d8c1882457999c2e568341c57/mypy-1.20.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:697f102c5c1d526bdd761a69f17c6070f9892eebcb94b1a5963d679288c09e78", size = 14947005, upload-time = "2026-03-31T16:50:17.591Z" }, + { url = "https://files.pythonhosted.org/packages/77/91/21b8ba75f958bcda75690951ce6fa6b7138b03471618959529d74b8544e2/mypy-1.20.0-cp312-cp312-win_amd64.whl", hash = "sha256:0ecd63f75fdd30327e4ad8b5704bd6d91fc6c1b2e029f8ee14705e1207212489", size = 10880616, upload-time = "2026-03-31T16:52:19.986Z" }, + { url = "https://files.pythonhosted.org/packages/8a/15/3d8198ef97c1ca03aea010cce4f1d4f3bc5d9849e8c0140111ca2ead9fdd/mypy-1.20.0-cp312-cp312-win_arm64.whl", hash = "sha256:f194db59657c58593a3c47c6dfd7bad4ef4ac12dbc94d01b3a95521f78177e33", size = 9813091, upload-time = "2026-03-31T16:53:44.385Z" }, + { url = "https://files.pythonhosted.org/packages/d6/a7/f64ea7bd592fa431cb597418b6dec4a47f7d0c36325fec7ac67bc8402b94/mypy-1.20.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b20c8b0fd5877abdf402e79a3af987053de07e6fb208c18df6659f708b535134", size = 14485344, upload-time = "2026-03-31T16:49:16.78Z" }, + { url = "https://files.pythonhosted.org/packages/bb/72/8927d84cfc90c6abea6e96663576e2e417589347eb538749a464c4c218a0/mypy-1.20.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:367e5c993ba34d5054d11937d0485ad6dfc60ba760fa326c01090fc256adf15c", size = 13327400, upload-time = "2026-03-31T16:53:08.02Z" }, + { url = "https://files.pythonhosted.org/packages/ab/4a/11ab99f9afa41aa350178d24a7d2da17043228ea10f6456523f64b5a6cf6/mypy-1.20.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f799d9db89fc00446f03281f84a221e50018fc40113a3ba9864b132895619ebe", size = 13706384, upload-time = "2026-03-31T16:52:28.577Z" }, + { url = "https://files.pythonhosted.org/packages/42/79/694ca73979cfb3535ebfe78733844cd5aff2e63304f59bf90585110d975a/mypy-1.20.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:555658c611099455b2da507582ea20d2043dfdfe7f5ad0add472b1c6238b433f", size = 14700378, upload-time = "2026-03-31T16:48:45.527Z" }, + { url = "https://files.pythonhosted.org/packages/84/24/a022ccab3a46e3d2cdf2e0e260648633640eb396c7e75d5a42818a8d3971/mypy-1.20.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:efe8d70949c3023698c3fca1e94527e7e790a361ab8116f90d11221421cd8726", size = 14932170, upload-time = "2026-03-31T16:49:36.038Z" }, + { url = "https://files.pythonhosted.org/packages/d8/9b/549228d88f574d04117e736f55958bd4908f980f9f5700a07aeb85df005b/mypy-1.20.0-cp313-cp313-win_amd64.whl", hash = "sha256:f49590891d2c2f8a9de15614e32e459a794bcba84693c2394291a2038bbaaa69", size = 10888526, upload-time = "2026-03-31T16:50:59.827Z" }, + { url = "https://files.pythonhosted.org/packages/91/17/15095c0e54a8bc04d22d4ff06b2139d5f142c2e87520b4e39010c4862771/mypy-1.20.0-cp313-cp313-win_arm64.whl", hash = "sha256:76a70bf840495729be47510856b978f1b0ec7d08f257ca38c9d932720bf6b43e", size = 9816456, upload-time = "2026-03-31T16:49:59.537Z" }, + { url = "https://files.pythonhosted.org/packages/4e/0e/6ca4a84cbed9e62384bc0b2974c90395ece5ed672393e553996501625fc5/mypy-1.20.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:0f42dfaab7ec1baff3b383ad7af562ab0de573c5f6edb44b2dab016082b89948", size = 14483331, upload-time = "2026-03-31T16:52:57.999Z" }, + { url = "https://files.pythonhosted.org/packages/7d/c5/5fe9d8a729dd9605064691816243ae6c49fde0bd28f6e5e17f6a24203c43/mypy-1.20.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:31b5dbb55293c1bd27c0fc813a0d2bb5ceef9d65ac5afa2e58f829dab7921fd5", size = 13342047, upload-time = "2026-03-31T16:54:21.555Z" }, + { url = "https://files.pythonhosted.org/packages/4c/33/e18bcfa338ca4e6b2771c85d4c5203e627d0c69d9de5c1a2cf2ba13320ba/mypy-1.20.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49d11c6f573a5a08f77fad13faff2139f6d0730ebed2cfa9b3d2702671dd7188", size = 13719585, upload-time = "2026-03-31T16:51:53.89Z" }, + { url = "https://files.pythonhosted.org/packages/6b/8d/93491ff7b79419edc7eabf95cb3b3f7490e2e574b2855c7c7e7394ff933f/mypy-1.20.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7d3243c406773185144527f83be0e0aefc7bf4601b0b2b956665608bf7c98a83", size = 14685075, upload-time = "2026-03-31T16:54:04.464Z" }, + { url = "https://files.pythonhosted.org/packages/b5/9d/d924b38a4923f8d164bf2b4ec98bf13beaf6e10a5348b4b137eadae40a6e/mypy-1.20.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a79c1eba7ac4209f2d850f0edd0a2f8bba88cbfdfefe6fb76a19e9d4fe5e71a2", size = 14919141, upload-time = "2026-03-31T16:54:51.785Z" }, + { url = "https://files.pythonhosted.org/packages/59/98/1da9977016678c0b99d43afe52ed00bb3c1a0c4c995d3e6acca1a6ebb9b4/mypy-1.20.0-cp314-cp314-win_amd64.whl", hash = "sha256:00e047c74d3ec6e71a2eb88e9ea551a2edb90c21f993aefa9e0d2a898e0bb732", size = 11050925, upload-time = "2026-03-31T16:51:30.758Z" }, + { url = "https://files.pythonhosted.org/packages/5e/e3/ba0b7a3143e49a9c4f5967dde6ea4bf8e0b10ecbbcca69af84027160ee89/mypy-1.20.0-cp314-cp314-win_arm64.whl", hash = "sha256:931a7630bba591593dcf6e97224a21ff80fb357e7982628d25e3c618e7f598ef", size = 10001089, upload-time = "2026-03-31T16:49:43.632Z" }, + { url = "https://files.pythonhosted.org/packages/12/28/e617e67b3be9d213cda7277913269c874eb26472489f95d09d89765ce2d8/mypy-1.20.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:26c8b52627b6552f47ff11adb4e1509605f094e29815323e487fc0053ebe93d1", size = 15534710, upload-time = "2026-03-31T16:52:12.506Z" }, + { url = "https://files.pythonhosted.org/packages/6e/0c/3b5f2d3e45dc7169b811adce8451679d9430399d03b168f9b0489f43adaa/mypy-1.20.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:39362cdb4ba5f916e7976fccecaab1ba3a83e35f60fa68b64e9a70e221bb2436", size = 14393013, upload-time = "2026-03-31T16:54:41.186Z" }, + { url = "https://files.pythonhosted.org/packages/a3/49/edc8b0aa145cc09c1c74f7ce2858eead9329931dcbbb26e2ad40906daa4e/mypy-1.20.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:34506397dbf40c15dc567635d18a21d33827e9ab29014fb83d292a8f4f8953b6", size = 15047240, upload-time = "2026-03-31T16:54:31.955Z" }, + { url = "https://files.pythonhosted.org/packages/42/37/a946bb416e37a57fa752b3100fd5ede0e28df94f92366d1716555d47c454/mypy-1.20.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:555493c44a4f5a1b58d611a43333e71a9981c6dbe26270377b6f8174126a0526", size = 15858565, upload-time = "2026-03-31T16:53:36.997Z" }, + { url = "https://files.pythonhosted.org/packages/2f/99/7690b5b5b552db1bd4ff362e4c0eb3107b98d680835e65823fbe888c8b78/mypy-1.20.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2721f0ce49cb74a38f00c50da67cb7d36317b5eda38877a49614dc018e91c787", size = 16087874, upload-time = "2026-03-31T16:52:48.313Z" }, + { url = "https://files.pythonhosted.org/packages/aa/76/53e893a498138066acd28192b77495c9357e5a58cc4be753182846b43315/mypy-1.20.0-cp314-cp314t-win_amd64.whl", hash = "sha256:47781555a7aa5fedcc2d16bcd72e0dc83eb272c10dd657f9fb3f9cc08e2e6abb", size = 12572380, upload-time = "2026-03-31T16:49:52.454Z" }, + { url = "https://files.pythonhosted.org/packages/76/9c/6dbdae21f01b7aacddc2c0bbf3c5557aa547827fdf271770fe1e521e7093/mypy-1.20.0-cp314-cp314t-win_arm64.whl", hash = "sha256:c70380fe5d64010f79fb863b9081c7004dd65225d2277333c219d93a10dad4dd", size = 10381174, upload-time = "2026-03-31T16:51:20.179Z" }, + { url = "https://files.pythonhosted.org/packages/21/66/4d734961ce167f0fd8380769b3b7c06dbdd6ff54c2190f3f2ecd22528158/mypy-1.20.0-py3-none-any.whl", hash = "sha256:a6e0641147cbfa7e4e94efdb95c2dab1aff8cfc159ded13e07f308ddccc8c48e", size = 2636365, upload-time = "2026-03-31T16:51:44.911Z" }, +] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, +] + +[[package]] +name = "omnibase-core" +version = "0.39.0" +source = { editable = "../omnibase_core" } +dependencies = [ + { name = "blake3" }, + { name = "click" }, + { name = "cryptography" }, + { name = "deepdiff" }, + { name = "dependency-injector" }, + { name = "httpx" }, + { name = "jsonschema" }, + { name = "pydantic" }, + { name = "pyyaml" }, + { name = "ruamel-yaml" }, +] + +[package.metadata] +requires-dist = [ + { name = "blake3", specifier = ">=1.0.8,<2.0.0" }, + { name = "click", specifier = ">=8.3.1,<9.0.0" }, + { name = "cryptography", specifier = ">=46.0.3,<47.0.0" }, + { name = "deepdiff", specifier = ">=8.0.0,<10.0.0" }, + { name = "dependency-injector", specifier = ">=4.48.3,<5.0.0" }, + { name = "httpx", specifier = ">=0.27.0,<1.0.0" }, + { name = "jsonschema", specifier = ">=4.25.1,<5.0.0" }, + { name = "omnibase-spi", marker = "extra == 'full'", specifier = ">=0.20.2" }, + { name = "omnibase-spi", marker = "extra == 'spi'", specifier = ">=0.20.2" }, + { name = "prometheus-client", marker = "extra == 'full'", specifier = ">=0.21.0,<1.0.0" }, + { name = "prometheus-client", marker = "extra == 'metrics'", specifier = ">=0.21.0,<1.0.0" }, + { name = "psutil", marker = "extra == 'cli'", specifier = ">=7.2.1" }, + { name = "psutil", marker = "extra == 'full'", specifier = ">=7.2.1" }, + { name = "pydantic", specifier = ">=2.12.5,<3.0.0" }, + { name = "pyyaml", specifier = ">=6.0.2,<7.0.0" }, + { name = "redis", marker = "extra == 'cache'", specifier = ">=5.0.0,<8.0.0" }, + { name = "redis", marker = "extra == 'full'", specifier = ">=5.0.0,<8.0.0" }, + { name = "ruamel-yaml", specifier = ">=0.18.0" }, + { name = "sqlglot", marker = "extra == 'full'", specifier = ">=26.0.0,<31.0.0" }, + { name = "sqlglot", marker = "extra == 'sql-parser'", specifier = ">=26.0.0,<31.0.0" }, +] +provides-extras = ["spi", "cli", "cache", "metrics", "sql-parser", "full"] + +[package.metadata.requires-dev] +dev = [ + { name = "hypothesis", specifier = ">=6.150" }, + { name = "interrogate", specifier = ">=1.7.0" }, + { name = "mypy", specifier = ">=1.19.1" }, + { name = "onex-change-control", git = "https://github.com/OmniNode-ai/onex_change_control.git?branch=main" }, + { name = "pre-commit", specifier = ">=4.5.1" }, + { name = "prometheus-client", specifier = ">=0.21.0,<1.0.0" }, + { name = "psutil", specifier = ">=7.2.1" }, + { name = "pyright", specifier = ">=1.1.408" }, + { name = "pytest", specifier = ">=8.4.0" }, + { name = "pytest-asyncio", specifier = ">=1.3.0" }, + { name = "pytest-cov", specifier = ">=7.0.0" }, + { name = "pytest-rerunfailures", specifier = ">=16.1" }, + { name = "pytest-split", specifier = ">=0.10.0" }, + { name = "pytest-testmon", specifier = ">=2.2.0" }, + { name = "pytest-timeout", specifier = ">=2.4.0" }, + { name = "pytest-xdist", specifier = ">=3.8.0" }, + { name = "ruff", specifier = ">=0.14.13,<0.16.0" }, + { name = "sqlglot", specifier = ">=26.0.0,<31.0.0" }, + { name = "types-psutil", specifier = ">=7.2.1.20260116" }, + { name = "types-pyyaml", specifier = ">=6.0.12.20250822" }, + { name = "types-requests", specifier = ">=2.32.4.20250913" }, +] + +[[package]] +name = "omnimarket" +version = "0.1.0" +source = { editable = "." } +dependencies = [ + { name = "aiokafka" }, + { name = "asyncpg" }, + { name = "omnibase-core" }, + { name = "pydantic" }, + { name = "python-dateutil" }, + { name = "pyyaml" }, +] + +[package.dev-dependencies] +dev = [ + { name = "mypy" }, + { name = "pytest" }, + { name = "pytest-asyncio" }, + { name = "ruff" }, + { name = "types-pyyaml" }, +] + +[package.metadata] +requires-dist = [ + { name = "aiokafka", specifier = ">=0.12.0" }, + { name = "asyncpg", specifier = ">=0.30.0" }, + { name = "omnibase-core", editable = "../omnibase_core" }, + { name = "pydantic", specifier = ">=2.12.5,<3.0.0" }, + { name = "python-dateutil", specifier = ">=2.9.0" }, + { name = "pyyaml", specifier = ">=6.0.2,<7.0.0" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "mypy", specifier = ">=1.19.1" }, + { name = "pytest", specifier = ">=8.4.0" }, + { name = "pytest-asyncio", specifier = ">=1.3.0" }, + { name = "ruff", specifier = ">=0.14.13,<0.16.0" }, + { name = "types-pyyaml", specifier = ">=6.0.12.20250822" }, +] + +[[package]] +name = "orderly-set" +version = "5.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4a/88/39c83c35d5e97cc203e9e77a4f93bf87ec89cf6a22ac4818fdcc65d66584/orderly_set-5.5.0.tar.gz", hash = "sha256:e87185c8e4d8afa64e7f8160ee2c542a475b738bc891dc3f58102e654125e6ce", size = 27414, upload-time = "2025-07-10T20:10:55.885Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/27/fb8d7338b4d551900fa3e580acbe7a0cf655d940e164cb5c00ec31961094/orderly_set-5.5.0-py3-none-any.whl", hash = "sha256:46f0b801948e98f427b412fcabb831677194c05c3b699b80de260374baa0b1e7", size = 13068, upload-time = "2025-07-10T20:10:54.377Z" }, +] + +[[package]] +name = "packaging" +version = "26.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, +] + +[[package]] +name = "pathspec" +version = "1.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fa/36/e27608899f9b8d4dff0617b2d9ab17ca5608956ca44461ac14ac48b44015/pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645", size = 131200, upload-time = "2026-01-27T03:59:46.938Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723", size = 55206, upload-time = "2026-01-27T03:59:45.137Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "pycparser" +version = "3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, +] + +[[package]] +name = "pydantic" +version = "2.12.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591, upload-time = "2025-11-26T15:11:46.471Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580, upload-time = "2025-11-26T15:11:44.605Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.41.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/5d/5f6c63eebb5afee93bcaae4ce9a898f3373ca23df3ccaef086d0233a35a7/pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7", size = 2110990, upload-time = "2025-11-04T13:39:58.079Z" }, + { url = "https://files.pythonhosted.org/packages/aa/32/9c2e8ccb57c01111e0fd091f236c7b371c1bccea0fa85247ac55b1e2b6b6/pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0", size = 1896003, upload-time = "2025-11-04T13:39:59.956Z" }, + { url = "https://files.pythonhosted.org/packages/68/b8/a01b53cb0e59139fbc9e4fda3e9724ede8de279097179be4ff31f1abb65a/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69", size = 1919200, upload-time = "2025-11-04T13:40:02.241Z" }, + { url = "https://files.pythonhosted.org/packages/38/de/8c36b5198a29bdaade07b5985e80a233a5ac27137846f3bc2d3b40a47360/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75", size = 2052578, upload-time = "2025-11-04T13:40:04.401Z" }, + { url = "https://files.pythonhosted.org/packages/00/b5/0e8e4b5b081eac6cb3dbb7e60a65907549a1ce035a724368c330112adfdd/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05", size = 2208504, upload-time = "2025-11-04T13:40:06.072Z" }, + { url = "https://files.pythonhosted.org/packages/77/56/87a61aad59c7c5b9dc8caad5a41a5545cba3810c3e828708b3d7404f6cef/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc", size = 2335816, upload-time = "2025-11-04T13:40:07.835Z" }, + { url = "https://files.pythonhosted.org/packages/0d/76/941cc9f73529988688a665a5c0ecff1112b3d95ab48f81db5f7606f522d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c", size = 2075366, upload-time = "2025-11-04T13:40:09.804Z" }, + { url = "https://files.pythonhosted.org/packages/d3/43/ebef01f69baa07a482844faaa0a591bad1ef129253ffd0cdaa9d8a7f72d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5", size = 2171698, upload-time = "2025-11-04T13:40:12.004Z" }, + { url = "https://files.pythonhosted.org/packages/b1/87/41f3202e4193e3bacfc2c065fab7706ebe81af46a83d3e27605029c1f5a6/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c", size = 2132603, upload-time = "2025-11-04T13:40:13.868Z" }, + { url = "https://files.pythonhosted.org/packages/49/7d/4c00df99cb12070b6bccdef4a195255e6020a550d572768d92cc54dba91a/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294", size = 2329591, upload-time = "2025-11-04T13:40:15.672Z" }, + { url = "https://files.pythonhosted.org/packages/cc/6a/ebf4b1d65d458f3cda6a7335d141305dfa19bdc61140a884d165a8a1bbc7/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1", size = 2319068, upload-time = "2025-11-04T13:40:17.532Z" }, + { url = "https://files.pythonhosted.org/packages/49/3b/774f2b5cd4192d5ab75870ce4381fd89cf218af999515baf07e7206753f0/pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d", size = 1985908, upload-time = "2025-11-04T13:40:19.309Z" }, + { url = "https://files.pythonhosted.org/packages/86/45/00173a033c801cacf67c190fef088789394feaf88a98a7035b0e40d53dc9/pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815", size = 2020145, upload-time = "2025-11-04T13:40:21.548Z" }, + { url = "https://files.pythonhosted.org/packages/f9/22/91fbc821fa6d261b376a3f73809f907cec5ca6025642c463d3488aad22fb/pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3", size = 1976179, upload-time = "2025-11-04T13:40:23.393Z" }, + { url = "https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", size = 2120403, upload-time = "2025-11-04T13:40:25.248Z" }, + { url = "https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", size = 1896206, upload-time = "2025-11-04T13:40:27.099Z" }, + { url = "https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", size = 1919307, upload-time = "2025-11-04T13:40:29.806Z" }, + { url = "https://files.pythonhosted.org/packages/9a/e3/6324802931ae1d123528988e0e86587c2072ac2e5394b4bc2bc34b61ff6e/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33", size = 2063258, upload-time = "2025-11-04T13:40:33.544Z" }, + { url = "https://files.pythonhosted.org/packages/c9/d4/2230d7151d4957dd79c3044ea26346c148c98fbf0ee6ebd41056f2d62ab5/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e", size = 2214917, upload-time = "2025-11-04T13:40:35.479Z" }, + { url = "https://files.pythonhosted.org/packages/e6/9f/eaac5df17a3672fef0081b6c1bb0b82b33ee89aa5cec0d7b05f52fd4a1fa/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2", size = 2332186, upload-time = "2025-11-04T13:40:37.436Z" }, + { url = "https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586", size = 2073164, upload-time = "2025-11-04T13:40:40.289Z" }, + { url = "https://files.pythonhosted.org/packages/bf/e3/f6e262673c6140dd3305d144d032f7bd5f7497d3871c1428521f19f9efa2/pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d", size = 2179146, upload-time = "2025-11-04T13:40:42.809Z" }, + { url = "https://files.pythonhosted.org/packages/75/c7/20bd7fc05f0c6ea2056a4565c6f36f8968c0924f19b7d97bbfea55780e73/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740", size = 2137788, upload-time = "2025-11-04T13:40:44.752Z" }, + { url = "https://files.pythonhosted.org/packages/3a/8d/34318ef985c45196e004bc46c6eab2eda437e744c124ef0dbe1ff2c9d06b/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e", size = 2340133, upload-time = "2025-11-04T13:40:46.66Z" }, + { url = "https://files.pythonhosted.org/packages/9c/59/013626bf8c78a5a5d9350d12e7697d3d4de951a75565496abd40ccd46bee/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858", size = 2324852, upload-time = "2025-11-04T13:40:48.575Z" }, + { url = "https://files.pythonhosted.org/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", size = 1994679, upload-time = "2025-11-04T13:40:50.619Z" }, + { url = "https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", size = 2019766, upload-time = "2025-11-04T13:40:52.631Z" }, + { url = "https://files.pythonhosted.org/packages/73/7d/f2f9db34af103bea3e09735bb40b021788a5e834c81eedb541991badf8f5/pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd", size = 1981005, upload-time = "2025-11-04T13:40:54.734Z" }, + { url = "https://files.pythonhosted.org/packages/ea/28/46b7c5c9635ae96ea0fbb779e271a38129df2550f763937659ee6c5dbc65/pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a", size = 2119622, upload-time = "2025-11-04T13:40:56.68Z" }, + { url = "https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14", size = 1891725, upload-time = "2025-11-04T13:40:58.807Z" }, + { url = "https://files.pythonhosted.org/packages/23/04/e89c29e267b8060b40dca97bfc64a19b2a3cf99018167ea1677d96368273/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1", size = 1915040, upload-time = "2025-11-04T13:41:00.853Z" }, + { url = "https://files.pythonhosted.org/packages/84/a3/15a82ac7bd97992a82257f777b3583d3e84bdb06ba6858f745daa2ec8a85/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66", size = 2063691, upload-time = "2025-11-04T13:41:03.504Z" }, + { url = "https://files.pythonhosted.org/packages/74/9b/0046701313c6ef08c0c1cf0e028c67c770a4e1275ca73131563c5f2a310a/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869", size = 2213897, upload-time = "2025-11-04T13:41:05.804Z" }, + { url = "https://files.pythonhosted.org/packages/8a/cd/6bac76ecd1b27e75a95ca3a9a559c643b3afcd2dd62086d4b7a32a18b169/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2", size = 2333302, upload-time = "2025-11-04T13:41:07.809Z" }, + { url = "https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375", size = 2064877, upload-time = "2025-11-04T13:41:09.827Z" }, + { url = "https://files.pythonhosted.org/packages/18/66/e9db17a9a763d72f03de903883c057b2592c09509ccfe468187f2a2eef29/pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553", size = 2180680, upload-time = "2025-11-04T13:41:12.379Z" }, + { url = "https://files.pythonhosted.org/packages/d3/9e/3ce66cebb929f3ced22be85d4c2399b8e85b622db77dad36b73c5387f8f8/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90", size = 2138960, upload-time = "2025-11-04T13:41:14.627Z" }, + { url = "https://files.pythonhosted.org/packages/a6/62/205a998f4327d2079326b01abee48e502ea739d174f0a89295c481a2272e/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07", size = 2339102, upload-time = "2025-11-04T13:41:16.868Z" }, + { url = "https://files.pythonhosted.org/packages/3c/0d/f05e79471e889d74d3d88f5bd20d0ed189ad94c2423d81ff8d0000aab4ff/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb", size = 2326039, upload-time = "2025-11-04T13:41:18.934Z" }, + { url = "https://files.pythonhosted.org/packages/ec/e1/e08a6208bb100da7e0c4b288eed624a703f4d129bde2da475721a80cab32/pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23", size = 1995126, upload-time = "2025-11-04T13:41:21.418Z" }, + { url = "https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf", size = 2015489, upload-time = "2025-11-04T13:41:24.076Z" }, + { url = "https://files.pythonhosted.org/packages/4e/bb/f7a190991ec9e3e0ba22e4993d8755bbc4a32925c0b5b42775c03e8148f9/pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0", size = 1977288, upload-time = "2025-11-04T13:41:26.33Z" }, + { url = "https://files.pythonhosted.org/packages/92/ed/77542d0c51538e32e15afe7899d79efce4b81eee631d99850edc2f5e9349/pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a", size = 2120255, upload-time = "2025-11-04T13:41:28.569Z" }, + { url = "https://files.pythonhosted.org/packages/bb/3d/6913dde84d5be21e284439676168b28d8bbba5600d838b9dca99de0fad71/pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3", size = 1863760, upload-time = "2025-11-04T13:41:31.055Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f0/e5e6b99d4191da102f2b0eb9687aaa7f5bea5d9964071a84effc3e40f997/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c", size = 1878092, upload-time = "2025-11-04T13:41:33.21Z" }, + { url = "https://files.pythonhosted.org/packages/71/48/36fb760642d568925953bcc8116455513d6e34c4beaa37544118c36aba6d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612", size = 2053385, upload-time = "2025-11-04T13:41:35.508Z" }, + { url = "https://files.pythonhosted.org/packages/20/25/92dc684dd8eb75a234bc1c764b4210cf2646479d54b47bf46061657292a8/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d", size = 2218832, upload-time = "2025-11-04T13:41:37.732Z" }, + { url = "https://files.pythonhosted.org/packages/e2/09/f53e0b05023d3e30357d82eb35835d0f6340ca344720a4599cd663dca599/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9", size = 2327585, upload-time = "2025-11-04T13:41:40Z" }, + { url = "https://files.pythonhosted.org/packages/aa/4e/2ae1aa85d6af35a39b236b1b1641de73f5a6ac4d5a7509f77b814885760c/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660", size = 2041078, upload-time = "2025-11-04T13:41:42.323Z" }, + { url = "https://files.pythonhosted.org/packages/cd/13/2e215f17f0ef326fc72afe94776edb77525142c693767fc347ed6288728d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9", size = 2173914, upload-time = "2025-11-04T13:41:45.221Z" }, + { url = "https://files.pythonhosted.org/packages/02/7a/f999a6dcbcd0e5660bc348a3991c8915ce6599f4f2c6ac22f01d7a10816c/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3", size = 2129560, upload-time = "2025-11-04T13:41:47.474Z" }, + { url = "https://files.pythonhosted.org/packages/3a/b1/6c990ac65e3b4c079a4fb9f5b05f5b013afa0f4ed6780a3dd236d2cbdc64/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf", size = 2329244, upload-time = "2025-11-04T13:41:49.992Z" }, + { url = "https://files.pythonhosted.org/packages/d9/02/3c562f3a51afd4d88fff8dffb1771b30cfdfd79befd9883ee094f5b6c0d8/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470", size = 2331955, upload-time = "2025-11-04T13:41:54.079Z" }, + { url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906, upload-time = "2025-11-04T13:41:56.606Z" }, + { url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607, upload-time = "2025-11-04T13:41:58.889Z" }, + { url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769, upload-time = "2025-11-04T13:42:01.186Z" }, + { url = "https://files.pythonhosted.org/packages/09/32/59b0c7e63e277fa7911c2fc70ccfb45ce4b98991e7ef37110663437005af/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd", size = 2110495, upload-time = "2025-11-04T13:42:49.689Z" }, + { url = "https://files.pythonhosted.org/packages/aa/81/05e400037eaf55ad400bcd318c05bb345b57e708887f07ddb2d20e3f0e98/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc", size = 1915388, upload-time = "2025-11-04T13:42:52.215Z" }, + { url = "https://files.pythonhosted.org/packages/6e/0d/e3549b2399f71d56476b77dbf3cf8937cec5cd70536bdc0e374a421d0599/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56", size = 1942879, upload-time = "2025-11-04T13:42:56.483Z" }, + { url = "https://files.pythonhosted.org/packages/f7/07/34573da085946b6a313d7c42f82f16e8920bfd730665de2d11c0c37a74b5/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b", size = 2139017, upload-time = "2025-11-04T13:42:59.471Z" }, +] + +[[package]] +name = "pygments" +version = "2.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, +] + +[[package]] +name = "pytest" +version = "9.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, +] + +[[package]] +name = "pytest-asyncio" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/90/2c/8af215c0f776415f3590cac4f9086ccefd6fd463befeae41cd4d3f193e5a/pytest_asyncio-1.3.0.tar.gz", hash = "sha256:d7f52f36d231b80ee124cd216ffb19369aa168fc10095013c6b014a34d3ee9e5", size = 50087, upload-time = "2025-11-10T16:07:47.256Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/35/f8b19922b6a25bc0880171a2f1a003eaeb93657475193ab516fd87cac9da/pytest_asyncio-1.3.0-py3-none-any.whl", hash = "sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5", size = 15075, upload-time = "2025-11-10T16:07:45.537Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, +] + +[[package]] +name = "referencing" +version = "0.37.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "rpds-py" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" }, +] + +[[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" }, +] + +[[package]] +name = "ruamel-yaml" +version = "0.19.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/3b/ebda527b56beb90cb7652cb1c7e4f91f48649fbcd8d2eb2fb6e77cd3329b/ruamel_yaml-0.19.1.tar.gz", hash = "sha256:53eb66cd27849eff968ebf8f0bf61f46cdac2da1d1f3576dd4ccee9b25c31993", size = 142709, upload-time = "2026-01-02T16:50:31.84Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b8/0c/51f6841f1d84f404f92463fc2b1ba0da357ca1e3db6b7fbda26956c3b82a/ruamel_yaml-0.19.1-py3-none-any.whl", hash = "sha256:27592957fedf6e0b62f281e96effd28043345e0e66001f97683aa9a40c667c93", size = 118102, upload-time = "2026-01-02T16:50:29.201Z" }, +] + +[[package]] +name = "ruff" +version = "0.15.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e6/97/e9f1ca355108ef7194e38c812ef40ba98c7208f47b13ad78d023caa583da/ruff-0.15.9.tar.gz", hash = "sha256:29cbb1255a9797903f6dde5ba0188c707907ff44a9006eb273b5a17bfa0739a2", size = 4617361, upload-time = "2026-04-02T18:17:20.829Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/1f/9cdfd0ac4b9d1e5a6cf09bedabdf0b56306ab5e333c85c87281273e7b041/ruff-0.15.9-py3-none-linux_armv6l.whl", hash = "sha256:6efbe303983441c51975c243e26dff328aca11f94b70992f35b093c2e71801e1", size = 10511206, upload-time = "2026-04-02T18:16:41.574Z" }, + { url = "https://files.pythonhosted.org/packages/3d/f6/32bfe3e9c136b35f02e489778d94384118bb80fd92c6d92e7ccd97db12ce/ruff-0.15.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:4965bac6ac9ea86772f4e23587746f0b7a395eccabb823eb8bfacc3fa06069f7", size = 10923307, upload-time = "2026-04-02T18:17:08.645Z" }, + { url = "https://files.pythonhosted.org/packages/ca/25/de55f52ab5535d12e7aaba1de37a84be6179fb20bddcbe71ec091b4a3243/ruff-0.15.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:eaf05aad70ca5b5a0a4b0e080df3a6b699803916d88f006efd1f5b46302daab8", size = 10316722, upload-time = "2026-04-02T18:16:44.206Z" }, + { url = "https://files.pythonhosted.org/packages/48/11/690d75f3fd6278fe55fff7c9eb429c92d207e14b25d1cae4064a32677029/ruff-0.15.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9439a342adb8725f32f92732e2bafb6d5246bd7a5021101166b223d312e8fc59", size = 10623674, upload-time = "2026-04-02T18:16:50.951Z" }, + { url = "https://files.pythonhosted.org/packages/bd/ec/176f6987be248fc5404199255522f57af1b4a5a1b57727e942479fec98ad/ruff-0.15.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9c5e6faf9d97c8edc43877c3f406f47446fc48c40e1442d58cfcdaba2acea745", size = 10351516, upload-time = "2026-04-02T18:16:57.206Z" }, + { url = "https://files.pythonhosted.org/packages/b2/fc/51cffbd2b3f240accc380171d51446a32aa2ea43a40d4a45ada67368fbd2/ruff-0.15.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b34a9766aeec27a222373d0b055722900fbc0582b24f39661aa96f3fe6ad901", size = 11150202, upload-time = "2026-04-02T18:17:06.452Z" }, + { url = "https://files.pythonhosted.org/packages/d6/d4/25292a6dfc125f6b6528fe6af31f5e996e19bf73ca8e3ce6eb7fa5b95885/ruff-0.15.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:89dd695bc72ae76ff484ae54b7e8b0f6b50f49046e198355e44ea656e521fef9", size = 11988891, upload-time = "2026-04-02T18:17:18.575Z" }, + { url = "https://files.pythonhosted.org/packages/13/e1/1eebcb885c10e19f969dcb93d8413dfee8172578709d7ee933640f5e7147/ruff-0.15.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ce187224ef1de1bd225bc9a152ac7102a6171107f026e81f317e4257052916d5", size = 11480576, upload-time = "2026-04-02T18:16:52.986Z" }, + { url = "https://files.pythonhosted.org/packages/ff/6b/a1548ac378a78332a4c3dcf4a134c2475a36d2a22ddfa272acd574140b50/ruff-0.15.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b0c7c341f68adb01c488c3b7d4b49aa8ea97409eae6462d860a79cf55f431b6", size = 11254525, upload-time = "2026-04-02T18:17:02.041Z" }, + { url = "https://files.pythonhosted.org/packages/42/aa/4bb3af8e61acd9b1281db2ab77e8b2c3c5e5599bf2a29d4a942f1c62b8d6/ruff-0.15.9-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:55cc15eee27dc0eebdfcb0d185a6153420efbedc15eb1d38fe5e685657b0f840", size = 11204072, upload-time = "2026-04-02T18:17:13.581Z" }, + { url = "https://files.pythonhosted.org/packages/69/48/d550dc2aa6e423ea0bcc1d0ff0699325ffe8a811e2dba156bd80750b86dc/ruff-0.15.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a6537f6eed5cda688c81073d46ffdfb962a5f29ecb6f7e770b2dc920598997ed", size = 10594998, upload-time = "2026-04-02T18:16:46.369Z" }, + { url = "https://files.pythonhosted.org/packages/63/47/321167e17f5344ed5ec6b0aa2cff64efef5f9e985af8f5622cfa6536043f/ruff-0.15.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:6d3fcbca7388b066139c523bda744c822258ebdcfbba7d24410c3f454cc9af71", size = 10359769, upload-time = "2026-04-02T18:17:10.994Z" }, + { url = "https://files.pythonhosted.org/packages/67/5e/074f00b9785d1d2c6f8c22a21e023d0c2c1817838cfca4c8243200a1fa87/ruff-0.15.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:058d8e99e1bfe79d8a0def0b481c56059ee6716214f7e425d8e737e412d69677", size = 10850236, upload-time = "2026-04-02T18:16:48.749Z" }, + { url = "https://files.pythonhosted.org/packages/76/37/804c4135a2a2caf042925d30d5f68181bdbd4461fd0d7739da28305df593/ruff-0.15.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:8e1ddb11dbd61d5983fa2d7d6370ef3eb210951e443cace19594c01c72abab4c", size = 11358343, upload-time = "2026-04-02T18:16:55.068Z" }, + { url = "https://files.pythonhosted.org/packages/88/3d/1364fcde8656962782aa9ea93c92d98682b1ecec2f184e625a965ad3b4a6/ruff-0.15.9-py3-none-win32.whl", hash = "sha256:bde6ff36eaf72b700f32b7196088970bf8fdb2b917b7accd8c371bfc0fd573ec", size = 10583382, upload-time = "2026-04-02T18:17:04.261Z" }, + { url = "https://files.pythonhosted.org/packages/4c/56/5c7084299bd2cacaa07ae63a91c6f4ba66edc08bf28f356b24f6b717c799/ruff-0.15.9-py3-none-win_amd64.whl", hash = "sha256:45a70921b80e1c10cf0b734ef09421f71b5aa11d27404edc89d7e8a69505e43d", size = 11744969, upload-time = "2026-04-02T18:16:59.611Z" }, + { url = "https://files.pythonhosted.org/packages/03/36/76704c4f312257d6dbaae3c959add2a622f63fcca9d864659ce6d8d97d3d/ruff-0.15.9-py3-none-win_arm64.whl", hash = "sha256:0694e601c028fd97dc5c6ee244675bc241aeefced7ef80cd9c6935a871078f53", size = 11005870, upload-time = "2026-04-02T18:17:15.773Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "types-pyyaml" +version = "6.0.12.20250915" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/69/3c51b36d04da19b92f9e815be12753125bd8bc247ba0470a982e6979e71c/types_pyyaml-6.0.12.20250915.tar.gz", hash = "sha256:0f8b54a528c303f0e6f7165687dd33fafa81c807fcac23f632b63aa624ced1d3", size = 17522, upload-time = "2025-09-15T03:01:00.728Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bd/e0/1eed384f02555dde685fff1a1ac805c1c7dcb6dd019c916fe659b1c1f9ec/types_pyyaml-6.0.12.20250915-py3-none-any.whl", hash = "sha256:e7d4d9e064e89a3b3cae120b4990cd370874d2bf12fa5f46c97018dd5d3c9ab6", size = 20338, upload-time = "2025-09-15T03:00:59.218Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, +] From b05d30e1ff535de69203071fa714378932d6484a Mon Sep 17 00:00:00 2001 From: jonahgabriel Date: Mon, 6 Apr 2026 11:20:29 -0400 Subject: [PATCH 2/4] ci: checkout omnibase_core in CI for path dependency resolution The pyproject.toml declares omnibase-core as a local editable path dependency (../omnibase_core). CI fails because that sibling directory does not exist in the GitHub Actions runner. Add a checkout step for omnibase_core in each CI job. --- .github/workflows/ci.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index afc771c820..167efc4c20 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,6 +11,11 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + - name: Checkout omnibase_core + uses: actions/checkout@v4 + with: + repository: OmniNode-ai/omnibase_core + path: ../omnibase_core - name: Install uv uses: astral-sh/setup-uv@v4 with: @@ -28,6 +33,11 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + - name: Checkout omnibase_core + uses: actions/checkout@v4 + with: + repository: OmniNode-ai/omnibase_core + path: ../omnibase_core - name: Install uv uses: astral-sh/setup-uv@v4 with: @@ -43,6 +53,11 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + - name: Checkout omnibase_core + uses: actions/checkout@v4 + with: + repository: OmniNode-ai/omnibase_core + path: ../omnibase_core - name: Install uv uses: astral-sh/setup-uv@v4 with: From a0547f1cc72b4df7b9db13ae76d1f6b710a2de38 Mon Sep 17 00:00:00 2001 From: jonahgabriel Date: Mon, 6 Apr 2026 11:22:37 -0400 Subject: [PATCH 3/4] ci: use git clone for omnibase_core instead of actions/checkout actions/checkout does not allow paths outside the workspace directory. Use git clone --depth=1 to place omnibase_core at ../omnibase_core where the uv path source expects it. --- .github/workflows/ci.yml | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 167efc4c20..5d71a526bd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,11 +11,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Checkout omnibase_core - uses: actions/checkout@v4 - with: - repository: OmniNode-ai/omnibase_core - path: ../omnibase_core + - name: Clone omnibase_core + run: git clone --depth=1 https://github.com/OmniNode-ai/omnibase_core.git ../omnibase_core - name: Install uv uses: astral-sh/setup-uv@v4 with: @@ -33,11 +30,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Checkout omnibase_core - uses: actions/checkout@v4 - with: - repository: OmniNode-ai/omnibase_core - path: ../omnibase_core + - name: Clone omnibase_core + run: git clone --depth=1 https://github.com/OmniNode-ai/omnibase_core.git ../omnibase_core - name: Install uv uses: astral-sh/setup-uv@v4 with: @@ -53,11 +47,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Checkout omnibase_core - uses: actions/checkout@v4 - with: - repository: OmniNode-ai/omnibase_core - path: ../omnibase_core + - name: Clone omnibase_core + run: git clone --depth=1 https://github.com/OmniNode-ai/omnibase_core.git ../omnibase_core - name: Install uv uses: astral-sh/setup-uv@v4 with: From a556b9d951fa4bb9d47f882e5baac7b27decc52f Mon Sep 17 00:00:00 2001 From: jonahgabriel Date: Mon, 6 Apr 2026 11:37:38 -0400 Subject: [PATCH 4/4] fix: resolve lint and typecheck CI failures - Add type: ignore[import-untyped] for asyncpg, aiokafka, dateutil - Fix handler_baselines.py type narrowing for list fields - Add SLF001 to per-file test ignores - Run ruff format on all files --- pyproject.toml | 2 +- src/omnimarket/adapters/asyncpg_adapter.py | 25 +- src/omnimarket/adapters/database.py | 4 +- .../handlers/handler_baselines.py | 247 +++++++++++++----- .../handlers/handler_delegation.py | 24 +- .../handlers/handler_llm_cost.py | 36 ++- .../handlers/handler_registration.py | 12 +- .../handlers/handler_savings.py | 50 +++- .../handlers/handler_session_outcome.py | 4 +- src/omnimarket/projection/runner.py | 41 +-- tests/test_golden_chain_local_review.py | 4 +- tests/test_projection_envelope.py | 18 +- tests/test_projection_handlers.py | 68 +++-- tests/test_projection_runner.py | 10 +- 14 files changed, 371 insertions(+), 174 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 6659d89c89..06f288bd4f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -108,7 +108,7 @@ ignore = [ ] [tool.ruff.lint.per-file-ignores] -"tests/**/*.py" = ["T201"] +"tests/**/*.py" = ["T201", "SLF001"] [tool.mypy] strict = true diff --git a/src/omnimarket/adapters/asyncpg_adapter.py b/src/omnimarket/adapters/asyncpg_adapter.py index 999dc942d2..6c4245b3e8 100644 --- a/src/omnimarket/adapters/asyncpg_adapter.py +++ b/src/omnimarket/adapters/asyncpg_adapter.py @@ -6,7 +6,7 @@ import os from typing import Any -import asyncpg +import asyncpg # type: ignore[import-untyped] logger = logging.getLogger(__name__) @@ -16,7 +16,9 @@ class AsyncpgAdapter: """DatabaseAdapter backed by asyncpg connection pool.""" - def __init__(self, dsn: str | None = None, min_size: int = 2, max_size: int = 10) -> None: + def __init__( + self, dsn: str | None = None, min_size: int = 2, max_size: int = 10 + ) -> None: self._dsn = dsn or os.environ.get(DB_URL_ENV, "") self._min_size = min_size self._max_size = max_size @@ -31,7 +33,9 @@ async def connect(self) -> None: max_size=self._max_size, command_timeout=30, ) - logger.info("asyncpg pool connected (min=%d, max=%d)", self._min_size, self._max_size) + logger.info( + "asyncpg pool connected (min=%d, max=%d)", self._min_size, self._max_size + ) async def execute(self, query: str, *params: Any) -> list[dict[str, Any]]: assert self._pool is not None, "call connect() first" @@ -39,7 +43,9 @@ async def execute(self, query: str, *params: Any) -> list[dict[str, Any]]: rows = await conn.fetch(query, *params) return [dict(r) for r in rows] - async def execute_many(self, query: str, params_list: list[tuple[Any, ...]]) -> None: + async def execute_many( + self, query: str, params_list: list[tuple[Any, ...]] + ) -> None: assert self._pool is not None, "call connect() first" async with self._pool.acquire() as conn: await conn.executemany(query, params_list) @@ -49,13 +55,14 @@ async def fetchval(self, query: str, *params: Any) -> Any: async with self._pool.acquire() as conn: return await conn.fetchval(query, *params) - async def execute_in_transaction(self, queries: list[tuple[str, tuple[Any, ...]]]) -> None: + async def execute_in_transaction( + self, queries: list[tuple[str, tuple[Any, ...]]] + ) -> None: """Execute multiple queries in a single transaction.""" assert self._pool is not None, "call connect() first" - async with self._pool.acquire() as conn: - async with conn.transaction(): - for query, params in queries: - await conn.execute(query, *params) + async with self._pool.acquire() as conn, conn.transaction(): + for query, params in queries: + await conn.execute(query, *params) async def close(self) -> None: if self._pool: diff --git a/src/omnimarket/adapters/database.py b/src/omnimarket/adapters/database.py index ec18091e03..91f1e177e0 100644 --- a/src/omnimarket/adapters/database.py +++ b/src/omnimarket/adapters/database.py @@ -12,7 +12,9 @@ async def execute(self, query: str, *params: Any) -> list[dict[str, Any]]: """Execute a parameterized query and return rows as dicts.""" ... - async def execute_many(self, query: str, params_list: list[tuple[Any, ...]]) -> None: + async def execute_many( + self, query: str, params_list: list[tuple[Any, ...]] + ) -> None: """Execute a parameterized query for each set of params.""" ... diff --git a/src/omnimarket/nodes/node_projection_baselines/handlers/handler_baselines.py b/src/omnimarket/nodes/node_projection_baselines/handlers/handler_baselines.py index 73793cffc5..b690f7b535 100644 --- a/src/omnimarket/nodes/node_projection_baselines/handlers/handler_baselines.py +++ b/src/omnimarket/nodes/node_projection_baselines/handlers/handler_baselines.py @@ -19,7 +19,9 @@ TOPIC = "onex.evt.omnibase-infra.baselines-computed.v1" -UUID_RE = re.compile(r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", re.I) +UUID_RE = re.compile( + r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", re.I +) MAX_BATCH_ROWS = 4000 DATE_RE = re.compile(r"^\d{4}-\d{2}-\d{2}$") VALID_PROMOTION_ACTIONS = {"promote", "shadow", "demote", "retire", "hold"} @@ -51,7 +53,9 @@ async def project_event( contract_version = _safe_int(data.get("contract_version"), 1) computed_at_utc = safe_parse_date( - data.get("computed_at_utc") or data.get("computedAtUtc") or data.get("computed_at") + data.get("computed_at_utc") + or data.get("computedAtUtc") + or data.get("computed_at") ) window_start_utc = ( safe_parse_date(data.get("window_start_utc") or data.get("windowStartUtc")) @@ -65,27 +69,38 @@ async def project_event( ) # Parse child arrays with batch caps - raw_comparisons_all = data.get("comparisons") if isinstance(data.get("comparisons"), list) else [] + _raw_comp = data.get("comparisons") + raw_comparisons_all: list[Any] = ( + _raw_comp if isinstance(_raw_comp, list) else [] + ) if len(raw_comparisons_all) > MAX_BATCH_ROWS: logger.warning( "baselines snapshot %s: %d comparison rows, capping at %d", - snapshot_id, len(raw_comparisons_all), MAX_BATCH_ROWS, + snapshot_id, + len(raw_comparisons_all), + MAX_BATCH_ROWS, ) raw_comparisons = raw_comparisons_all[:MAX_BATCH_ROWS] - raw_trend_all = data.get("trend") if isinstance(data.get("trend"), list) else [] + _raw_tr = data.get("trend") + raw_trend_all: list[Any] = _raw_tr if isinstance(_raw_tr, list) else [] if len(raw_trend_all) > MAX_BATCH_ROWS: logger.warning( "baselines snapshot %s: %d trend rows, capping at %d", - snapshot_id, len(raw_trend_all), MAX_BATCH_ROWS, + snapshot_id, + len(raw_trend_all), + MAX_BATCH_ROWS, ) raw_trend = raw_trend_all[:MAX_BATCH_ROWS] - raw_breakdown_all = data.get("breakdown") if isinstance(data.get("breakdown"), list) else [] + _raw_bd = data.get("breakdown") + raw_breakdown_all: list[Any] = _raw_bd if isinstance(_raw_bd, list) else [] if len(raw_breakdown_all) > MAX_BATCH_ROWS: logger.warning( "baselines snapshot %s: %d breakdown rows, capping at %d", - snapshot_id, len(raw_breakdown_all), MAX_BATCH_ROWS, + snapshot_id, + len(raw_breakdown_all), + MAX_BATCH_ROWS, ) raw_breakdown = raw_breakdown_all[:MAX_BATCH_ROWS] @@ -101,12 +116,29 @@ async def project_event( trend_by_date[date_val] = { "snapshot_id": snapshot_id, "date": date_val, - "avg_cost_savings": str(max(0.0, min(99.0, _safe_float( - t.get("avg_cost_savings") or t.get("avgCostSavings") - )))), - "avg_outcome_improvement": str(max(0.0, min(99.0, _safe_float( - t.get("avg_outcome_improvement") or t.get("avgOutcomeImprovement") - )))), + "avg_cost_savings": str( + max( + 0.0, + min( + 99.0, + _safe_float( + t.get("avg_cost_savings") or t.get("avgCostSavings") + ), + ), + ) + ), + "avg_outcome_improvement": str( + max( + 0.0, + min( + 99.0, + _safe_float( + t.get("avg_outcome_improvement") + or t.get("avgOutcomeImprovement") + ), + ), + ) + ), "comparisons_evaluated": _safe_int( t.get("comparisons_evaluated") or t.get("comparisonsEvaluated") ), @@ -119,7 +151,10 @@ async def project_event( continue pattern_id = str(c.get("pattern_id") or c.get("patternId") or "").strip() if not pattern_id: - logger.warning("Skipping comparison row with blank pattern_id for snapshot %s", snapshot_id) + logger.warning( + "Skipping comparison row with blank pattern_id for snapshot %s", + snapshot_id, + ) continue rec_raw = str(c.get("recommendation") or "") @@ -127,22 +162,44 @@ async def project_event( conf_raw = str(c.get("confidence") or "").lower() confidence = conf_raw if conf_raw in VALID_CONFIDENCE_LEVELS else "low" - comparison_rows.append({ - "snapshot_id": snapshot_id, - "pattern_id": pattern_id, - "pattern_name": str(c.get("pattern_name") or c.get("patternName") or ""), - "sample_size": _safe_int(c.get("sample_size") or c.get("sampleSize")), - "window_start": str(c.get("window_start") or c.get("windowStart") or ""), - "window_end": str(c.get("window_end") or c.get("windowEnd") or ""), - "token_delta": json.dumps(c.get("token_delta") or c.get("tokenDelta") or {}), - "time_delta": json.dumps(c.get("time_delta") or c.get("timeDelta") or {}), - "retry_delta": json.dumps(c.get("retry_delta") or c.get("retryDelta") or {}), - "test_pass_rate_delta": json.dumps(c.get("test_pass_rate_delta") or c.get("testPassRateDelta") or {}), - "review_iteration_delta": json.dumps(c.get("review_iteration_delta") or c.get("reviewIterationDelta") or {}), - "recommendation": recommendation, - "confidence": confidence, - "rationale": str(c.get("rationale") or ""), - }) + comparison_rows.append( + { + "snapshot_id": snapshot_id, + "pattern_id": pattern_id, + "pattern_name": str( + c.get("pattern_name") or c.get("patternName") or "" + ), + "sample_size": _safe_int( + c.get("sample_size") or c.get("sampleSize") + ), + "window_start": str( + c.get("window_start") or c.get("windowStart") or "" + ), + "window_end": str(c.get("window_end") or c.get("windowEnd") or ""), + "token_delta": json.dumps( + c.get("token_delta") or c.get("tokenDelta") or {} + ), + "time_delta": json.dumps( + c.get("time_delta") or c.get("timeDelta") or {} + ), + "retry_delta": json.dumps( + c.get("retry_delta") or c.get("retryDelta") or {} + ), + "test_pass_rate_delta": json.dumps( + c.get("test_pass_rate_delta") + or c.get("testPassRateDelta") + or {} + ), + "review_iteration_delta": json.dumps( + c.get("review_iteration_delta") + or c.get("reviewIterationDelta") + or {} + ), + "recommendation": recommendation, + "confidence": confidence, + "rationale": str(c.get("rationale") or ""), + } + ) # Build breakdown rows with dedup breakdown_by_action: dict[str, dict[str, Any]] = {} @@ -155,17 +212,26 @@ async def project_event( "snapshot_id": snapshot_id, "action": action, "count": _safe_int(b.get("count")), - "avg_confidence": str(max(0.0, min(1.0, _safe_float( - b.get("avg_confidence") or b.get("avgConfidence") - )))), + "avg_confidence": str( + max( + 0.0, + min( + 1.0, + _safe_float( + b.get("avg_confidence") or b.get("avgConfidence") + ), + ), + ) + ), } # Execute all in a single transaction queries: list[tuple[str, tuple[Any, ...]]] = [] # 1. Upsert snapshot header - queries.append(( - """ + queries.append( + ( + """ INSERT INTO baselines_snapshots ( snapshot_id, contract_version, computed_at_utc, window_start_utc, window_end_utc ) VALUES ($1, $2, $3, $4, $5) @@ -176,17 +242,27 @@ async def project_event( window_end_utc = EXCLUDED.window_end_utc, projected_at = NOW() """, - (snapshot_id, contract_version, computed_at_utc, window_start_utc, window_end_utc), - )) + ( + snapshot_id, + contract_version, + computed_at_utc, + window_start_utc, + window_end_utc, + ), + ) + ) # 2. Delete + re-insert comparisons - queries.append(( - "DELETE FROM baselines_comparisons WHERE snapshot_id = $1", - (snapshot_id,), - )) + queries.append( + ( + "DELETE FROM baselines_comparisons WHERE snapshot_id = $1", + (snapshot_id,), + ) + ) for row in comparison_rows: - queries.append(( - """ + queries.append( + ( + """ INSERT INTO baselines_comparisons ( snapshot_id, pattern_id, pattern_name, sample_size, window_start, window_end, token_delta, time_delta, @@ -197,46 +273,73 @@ async def project_event( $9::jsonb, $10::jsonb, $11::jsonb, $12, $13, $14 ) """, - ( - row["snapshot_id"], row["pattern_id"], row["pattern_name"], row["sample_size"], - row["window_start"], row["window_end"], row["token_delta"], row["time_delta"], - row["retry_delta"], row["test_pass_rate_delta"], row["review_iteration_delta"], - row["recommendation"], row["confidence"], row["rationale"], - ), - )) + ( + row["snapshot_id"], + row["pattern_id"], + row["pattern_name"], + row["sample_size"], + row["window_start"], + row["window_end"], + row["token_delta"], + row["time_delta"], + row["retry_delta"], + row["test_pass_rate_delta"], + row["review_iteration_delta"], + row["recommendation"], + row["confidence"], + row["rationale"], + ), + ) + ) # 3. Delete + re-insert trend - queries.append(( - "DELETE FROM baselines_trend WHERE snapshot_id = $1", - (snapshot_id,), - )) + queries.append( + ( + "DELETE FROM baselines_trend WHERE snapshot_id = $1", + (snapshot_id,), + ) + ) for row in trend_by_date.values(): - queries.append(( - """ + queries.append( + ( + """ INSERT INTO baselines_trend ( snapshot_id, date, avg_cost_savings, avg_outcome_improvement, comparisons_evaluated ) VALUES ($1, $2, $3, $4, $5) """, - ( - row["snapshot_id"], row["date"], row["avg_cost_savings"], - row["avg_outcome_improvement"], row["comparisons_evaluated"], - ), - )) + ( + row["snapshot_id"], + row["date"], + row["avg_cost_savings"], + row["avg_outcome_improvement"], + row["comparisons_evaluated"], + ), + ) + ) # 4. Delete + re-insert breakdown - queries.append(( - "DELETE FROM baselines_breakdown WHERE snapshot_id = $1", - (snapshot_id,), - )) + queries.append( + ( + "DELETE FROM baselines_breakdown WHERE snapshot_id = $1", + (snapshot_id,), + ) + ) for row in breakdown_by_action.values(): - queries.append(( - """ + queries.append( + ( + """ INSERT INTO baselines_breakdown ( snapshot_id, action, count, avg_confidence ) VALUES ($1, $2, $3, $4) """, - (row["snapshot_id"], row["action"], row["count"], row["avg_confidence"]), - )) + ( + row["snapshot_id"], + row["action"], + row["count"], + row["avg_confidence"], + ), + ) + ) await self.db.execute_in_transaction(queries) @@ -269,6 +372,8 @@ def _safe_float(value: Any, default: float = 0.0) -> float: if __name__ == "__main__": - logging.basicConfig(level=logging.INFO, format="%(asctime)s %(name)s %(levelname)s %(message)s") + logging.basicConfig( + level=logging.INFO, format="%(asctime)s %(name)s %(levelname)s %(message)s" + ) runner = BaselinesProjectionRunner() asyncio.run(runner.run()) diff --git a/src/omnimarket/nodes/node_projection_delegation/handlers/handler_delegation.py b/src/omnimarket/nodes/node_projection_delegation/handlers/handler_delegation.py index b7145d27bb..24e194d954 100644 --- a/src/omnimarket/nodes/node_projection_delegation/handlers/handler_delegation.py +++ b/src/omnimarket/nodes/node_projection_delegation/handlers/handler_delegation.py @@ -44,9 +44,7 @@ async def _project_task_delegated( self, data: dict[str, Any], meta: MessageMeta ) -> bool: correlation_id = ( - data.get("correlation_id") - or data.get("correlationId") - or meta.fallback_id + data.get("correlation_id") or data.get("correlationId") or meta.fallback_id ) task_type = data.get("task_type") or data.get("taskType") @@ -80,8 +78,12 @@ async def _project_task_delegated( import json - quality_gates_checked = data.get("quality_gates_checked") or data.get("qualityGatesChecked") - quality_gates_failed = data.get("quality_gates_failed") or data.get("qualityGatesFailed") + quality_gates_checked = data.get("quality_gates_checked") or data.get( + "qualityGatesChecked" + ) + quality_gates_failed = data.get("quality_gates_failed") or data.get( + "qualityGatesFailed" + ) qgc_json = json.dumps(quality_gates_checked) if quality_gates_checked else None qgf_json = json.dumps(quality_gates_failed) if quality_gates_failed else None @@ -143,9 +145,7 @@ async def _project_shadow_comparison( self, data: dict[str, Any], meta: MessageMeta ) -> bool: correlation_id = ( - data.get("correlation_id") - or data.get("correlationId") - or meta.fallback_id + data.get("correlation_id") or data.get("correlationId") or meta.fallback_id ) task_type = data.get("task_type") or data.get("taskType") @@ -181,9 +181,7 @@ async def _project_shadow_comparison( data.get("shadow_cost_usd") or data.get("shadowCostUsd") ) divergence_reason = ( - data.get("divergence_reason") - or data.get("divergenceReason") - or None + data.get("divergence_reason") or data.get("divergenceReason") or None ) await self.db.execute( @@ -243,6 +241,8 @@ def _safe_int_or_none(value: Any) -> int | None: if __name__ == "__main__": - logging.basicConfig(level=logging.INFO, format="%(asctime)s %(name)s %(levelname)s %(message)s") + logging.basicConfig( + level=logging.INFO, format="%(asctime)s %(name)s %(levelname)s %(message)s" + ) runner = DelegationProjectionRunner() asyncio.run(runner.run()) diff --git a/src/omnimarket/nodes/node_projection_llm_cost/handlers/handler_llm_cost.py b/src/omnimarket/nodes/node_projection_llm_cost/handlers/handler_llm_cost.py index eef8d9c887..846af2f52e 100644 --- a/src/omnimarket/nodes/node_projection_llm_cost/handlers/handler_llm_cost.py +++ b/src/omnimarket/nodes/node_projection_llm_cost/handlers/handler_llm_cost.py @@ -44,7 +44,11 @@ async def project_event( # usage_source normalization usage_normalized = data.get("usage_normalized") or {} usage_source_raw = ( - (usage_normalized.get("source") if isinstance(usage_normalized, dict) else None) + ( + usage_normalized.get("source") + if isinstance(usage_normalized, dict) + else None + ) or data.get("usage_source") or data.get("usageSource") or ("ESTIMATED" if data.get("usage_is_estimated") else "API") @@ -62,7 +66,9 @@ async def project_event( granularity = granularity_raw if granularity_raw in ("hour", "day") else "hour" prompt_tokens = _safe_int(data.get("prompt_tokens") or data.get("promptTokens")) - completion_tokens = _safe_int(data.get("completion_tokens") or data.get("completionTokens")) + completion_tokens = _safe_int( + data.get("completion_tokens") or data.get("completionTokens") + ) raw_total = _safe_int(data.get("total_tokens") or data.get("totalTokens")) derived_total = prompt_tokens + completion_tokens @@ -79,14 +85,18 @@ async def project_event( ) total_tokens = raw_total - estimated_cost_usd = _safe_cost(data.get("estimated_cost_usd") or data.get("estimatedCostUsd")) + estimated_cost_usd = _safe_cost( + data.get("estimated_cost_usd") or data.get("estimatedCostUsd") + ) total_cost_usd = _safe_cost( data.get("total_cost_usd") or data.get("totalCostUsd") or data.get("estimated_cost_usd") or data.get("estimatedCostUsd") ) - reported_cost_usd = _safe_cost(data.get("reported_cost_usd") or data.get("reportedCostUsd")) + reported_cost_usd = _safe_cost( + data.get("reported_cost_usd") or data.get("reportedCostUsd") + ) model_name = ( data.get("model_id") @@ -99,17 +109,25 @@ async def project_event( explicit_repo = data.get("repo_name") or data.get("repoName") repo_name = explicit_repo or ( reporting_source - if (reporting_source and len(str(reporting_source)) < 64 and " " not in str(reporting_source)) + if ( + reporting_source + and len(str(reporting_source)) < 64 + and " " not in str(reporting_source) + ) else None ) session_id = data.get("session_id") or data.get("sessionId") or None pattern_id = data.get("pattern_id") or data.get("patternId") or None pattern_name = data.get("pattern_name") or data.get("patternName") or None - request_count = _safe_int(data.get("request_count") or data.get("requestCount") or 1) + request_count = _safe_int( + data.get("request_count") or data.get("requestCount") or 1 + ) if model_name == "unknown": - logger.warning("LLM cost event missing model_id/model_name -- inserting as 'unknown'") + logger.warning( + "LLM cost event missing model_id/model_name -- inserting as 'unknown'" + ) await self.db.execute( """ @@ -164,6 +182,8 @@ def _safe_cost(value: Any) -> float: if __name__ == "__main__": - logging.basicConfig(level=logging.INFO, format="%(asctime)s %(name)s %(levelname)s %(message)s") + logging.basicConfig( + level=logging.INFO, format="%(asctime)s %(name)s %(levelname)s %(message)s" + ) runner = LlmCostProjectionRunner() asyncio.run(runner.run()) diff --git a/src/omnimarket/nodes/node_projection_registration/handlers/handler_registration.py b/src/omnimarket/nodes/node_projection_registration/handlers/handler_registration.py index 97a9aff25f..71fbc7999d 100644 --- a/src/omnimarket/nodes/node_projection_registration/handlers/handler_registration.py +++ b/src/omnimarket/nodes/node_projection_registration/handlers/handler_registration.py @@ -51,7 +51,9 @@ async def _project_introspection(self, data: dict[str, Any]) -> bool: node_id = data.get("node_id") or data.get("nodeId") or None service_name = data.get("service_name") or node_name or node_id if not service_name: - logger.warning("node-introspection missing service_name/node_name/node_id -- skipping") + logger.warning( + "node-introspection missing service_name/node_name/node_id -- skipping" + ) return True service_url = data.get("service_url") or data.get("serviceUrl") or "" @@ -118,7 +120,9 @@ async def _project_heartbeat(self, data: dict[str, Any]) -> bool: logger.warning("node-heartbeat missing service_name/node_id -- skipping") return True - health_status = data.get("health_status") or data.get("healthStatus") or "healthy" + health_status = ( + data.get("health_status") or data.get("healthStatus") or "healthy" + ) await self.db.execute( """ @@ -169,6 +173,8 @@ async def _project_state_change(self, data: dict[str, Any]) -> bool: if __name__ == "__main__": - logging.basicConfig(level=logging.INFO, format="%(asctime)s %(name)s %(levelname)s %(message)s") + logging.basicConfig( + level=logging.INFO, format="%(asctime)s %(name)s %(levelname)s %(message)s" + ) runner = RegistrationProjectionRunner() asyncio.run(runner.run()) diff --git a/src/omnimarket/nodes/node_projection_savings/handlers/handler_savings.py b/src/omnimarket/nodes/node_projection_savings/handlers/handler_savings.py index 017731b506..13fd5e5fb0 100644 --- a/src/omnimarket/nodes/node_projection_savings/handlers/handler_savings.py +++ b/src/omnimarket/nodes/node_projection_savings/handlers/handler_savings.py @@ -38,7 +38,9 @@ async def project_event( logger.warning("savings-estimated event missing session_id") return True - correlation_id = str(data.get("correlation_id") or data.get("correlationId") or "").strip() + correlation_id = str( + data.get("correlation_id") or data.get("correlationId") or "" + ).strip() source_event_id = correlation_id or deterministic_correlation_id( TOPIC, meta.partition, meta.offset ) @@ -47,32 +49,52 @@ async def project_event( data.get("timestamp_iso") or data.get("timestamp") or data.get("emitted_at") ) - actual_total_tokens = _safe_int(data.get("actual_total_tokens") or data.get("actualTotalTokens")) - actual_cost_usd = _safe_cost_str(data.get("actual_cost_usd") or data.get("actualCostUsd")) - actual_model_id = _str_or_none(data.get("actual_model_id") or data.get("actualModelId")) + actual_total_tokens = _safe_int( + data.get("actual_total_tokens") or data.get("actualTotalTokens") + ) + actual_cost_usd = _safe_cost_str( + data.get("actual_cost_usd") or data.get("actualCostUsd") + ) + actual_model_id = _str_or_none( + data.get("actual_model_id") or data.get("actualModelId") + ) counterfactual_model_id = _str_or_none( data.get("counterfactual_model_id") or data.get("counterfactualModelId") ) - direct_savings_usd = _safe_cost_str(data.get("direct_savings_usd") or data.get("directSavingsUsd")) - direct_tokens_saved = _safe_int(data.get("direct_tokens_saved") or data.get("directTokensSaved")) + direct_savings_usd = _safe_cost_str( + data.get("direct_savings_usd") or data.get("directSavingsUsd") + ) + direct_tokens_saved = _safe_int( + data.get("direct_tokens_saved") or data.get("directTokensSaved") + ) estimated_total_savings_usd = _safe_cost_str( - data.get("estimated_total_savings_usd") or data.get("estimatedTotalSavingsUsd") + data.get("estimated_total_savings_usd") + or data.get("estimatedTotalSavingsUsd") ) estimated_total_tokens_saved = _safe_int( - data.get("estimated_total_tokens_saved") or data.get("estimatedTotalTokensSaved") + data.get("estimated_total_tokens_saved") + or data.get("estimatedTotalTokensSaved") ) categories = data.get("categories") or [] - direct_confidence = _safe_float(data.get("direct_confidence") or data.get("directConfidence")) + direct_confidence = _safe_float( + data.get("direct_confidence") or data.get("directConfidence") + ) heuristic_confidence_avg = _safe_float( data.get("heuristic_confidence_avg") or data.get("heuristicConfidenceAvg") ) estimation_method = str( - data.get("estimation_method") or data.get("estimationMethod") or "tiered_attribution_v1" + data.get("estimation_method") + or data.get("estimationMethod") + or "tiered_attribution_v1" + ) + treatment_group = _str_or_none( + data.get("treatment_group") or data.get("treatmentGroup") ) - treatment_group = _str_or_none(data.get("treatment_group") or data.get("treatmentGroup")) is_measured = bool(data.get("is_measured") or data.get("isMeasured") or False) completeness_status = str( - data.get("completeness_status") or data.get("completenessStatus") or "complete" + data.get("completeness_status") + or data.get("completenessStatus") + or "complete" ) pricing_manifest_version = _str_or_none( data.get("pricing_manifest_version") or data.get("pricingManifestVersion") @@ -176,6 +198,8 @@ def _str_or_none(value: Any) -> str | None: if __name__ == "__main__": - logging.basicConfig(level=logging.INFO, format="%(asctime)s %(name)s %(levelname)s %(message)s") + logging.basicConfig( + level=logging.INFO, format="%(asctime)s %(name)s %(levelname)s %(message)s" + ) runner = SavingsProjectionRunner() asyncio.run(runner.run()) diff --git a/src/omnimarket/nodes/node_projection_session_outcome/handlers/handler_session_outcome.py b/src/omnimarket/nodes/node_projection_session_outcome/handlers/handler_session_outcome.py index 9a4dd1d74f..38e46a822c 100644 --- a/src/omnimarket/nodes/node_projection_session_outcome/handlers/handler_session_outcome.py +++ b/src/omnimarket/nodes/node_projection_session_outcome/handlers/handler_session_outcome.py @@ -73,6 +73,8 @@ async def project_event( if __name__ == "__main__": - logging.basicConfig(level=logging.INFO, format="%(asctime)s %(name)s %(levelname)s %(message)s") + logging.basicConfig( + level=logging.INFO, format="%(asctime)s %(name)s %(levelname)s %(message)s" + ) runner = SessionOutcomeProjectionRunner() asyncio.run(runner.run()) diff --git a/src/omnimarket/projection/runner.py b/src/omnimarket/projection/runner.py index 66cb3305d8..18f8cec7bb 100644 --- a/src/omnimarket/projection/runner.py +++ b/src/omnimarket/projection/runner.py @@ -3,16 +3,17 @@ from __future__ import annotations import asyncio +import contextlib import hashlib import logging import os import signal from abc import ABC, abstractmethod from dataclasses import dataclass, field -from datetime import datetime, timezone +from datetime import UTC, datetime from typing import Any -from aiokafka import AIOKafkaConsumer +from aiokafka import AIOKafkaConsumer # type: ignore[import-untyped] from omnimarket.adapters.asyncpg_adapter import AsyncpgAdapter from omnimarket.projection.envelope import unwrap_envelope @@ -59,13 +60,13 @@ def deterministic_correlation_id(topic: str, partition: int, offset: int) -> str def safe_parse_date(value: Any) -> datetime: """Parse a date string, falling back to current wall-clock time.""" if not value: - return datetime.now(timezone.utc) + return datetime.now(UTC) if isinstance(value, datetime): return value try: - from dateutil.parser import isoparse + from dateutil.parser import isoparse # type: ignore[import-untyped] - dt = isoparse(str(value)) + dt: datetime = isoparse(str(value)) return dt except Exception: pass @@ -73,8 +74,10 @@ def safe_parse_date(value: Any) -> datetime: dt = datetime.fromisoformat(str(value).replace("Z", "+00:00")) return dt except (ValueError, TypeError): - logger.warning("safe_parse_date: malformed timestamp %r, using wall-clock", value) - return datetime.now(timezone.utc) + logger.warning( + "safe_parse_date: malformed timestamp %r, using wall-clock", value + ) + return datetime.now(UTC) def safe_float(value: Any, default: float = 0.0) -> float: @@ -83,7 +86,7 @@ def safe_float(value: Any, default: float = 0.0) -> float: return default try: f = float(value) - if not (f == f): # NaN check + if f != f: # NaN check return default return f except (ValueError, TypeError): @@ -122,7 +125,9 @@ def __init__( group_id: str | None = None, client_id: str | None = None, ) -> None: - self._group_id = group_id or os.environ.get("KAFKA_CONSUMER_GROUP", DEFAULT_GROUP_ID) + self._group_id = group_id or os.environ.get( + "KAFKA_CONSUMER_GROUP", DEFAULT_GROUP_ID + ) self._client_id = client_id or DEFAULT_CLIENT_ID self._db = AsyncpgAdapter() self._stats = ProjectionStats() @@ -200,10 +205,8 @@ async def run(self) -> None: delay, ) if self._consumer: - try: + with contextlib.suppress(Exception): await self._consumer.stop() - except Exception: - pass self._consumer = None await asyncio.sleep(delay) @@ -215,10 +218,8 @@ async def shutdown(self) -> None: logger.info("Shutting down...") self._running = False if self._consumer: - try: + with contextlib.suppress(Exception): await self._consumer.stop() - except Exception: - pass await self._db.close() async def _handle_message(self, msg: Any) -> None: @@ -243,14 +244,18 @@ async def _handle_message(self, msg: Any) -> None: if projected: self._stats.events_projected += 1 - self._stats.last_projected_at = datetime.now(timezone.utc) - ts = self._stats.topic_stats.setdefault(topic, {"projected": 0, "errors": 0}) + self._stats.last_projected_at = datetime.now(UTC) + ts = self._stats.topic_stats.setdefault( + topic, {"projected": 0, "errors": 0} + ) ts["projected"] += 1 await self._update_watermark(f"{topic}:{msg.partition}", msg.offset) except Exception as err: self._stats.errors_count += 1 - ts = self._stats.topic_stats.setdefault(topic, {"projected": 0, "errors": 0}) + ts = self._stats.topic_stats.setdefault( + topic, {"projected": 0, "errors": 0} + ) ts["errors"] += 1 logger.error("Error projecting %s: %s", topic, err) diff --git a/tests/test_golden_chain_local_review.py b/tests/test_golden_chain_local_review.py index 6d4b1e1e36..bffb8aac86 100644 --- a/tests/test_golden_chain_local_review.py +++ b/tests/test_golden_chain_local_review.py @@ -65,7 +65,7 @@ async def test_loop_on_dirty_then_clean(self, event_bus: EventBusInmemory) -> No handler = HandlerLocalReview() command = _make_command() - state, events, completed = handler.run_full_pipeline( + state, events, _completed = handler.run_full_pipeline( command, check_clean_results=[False, True] ) @@ -104,7 +104,7 @@ async def test_max_iterations_stops(self, event_bus: EventBusInmemory) -> None: handler = HandlerLocalReview() command = _make_command(max_iterations=2) - state, _events, completed = handler.run_full_pipeline( + state, _events, _completed = handler.run_full_pipeline( command, check_clean_results=[False, False, False] ) diff --git a/tests/test_projection_envelope.py b/tests/test_projection_envelope.py index a8584e2b95..0b3a9ae15e 100644 --- a/tests/test_projection_envelope.py +++ b/tests/test_projection_envelope.py @@ -2,14 +2,14 @@ import json -import pytest - from omnimarket.projection.envelope import unwrap_envelope class TestUnwrapEnvelope: def test_payload_envelope(self) -> None: - raw = json.dumps({"payload": {"session_id": "abc", "outcome": "success"}}).encode() + raw = json.dumps( + {"payload": {"session_id": "abc", "outcome": "success"}} + ).encode() result = unwrap_envelope(raw) assert result is not None assert result["session_id"] == "abc" @@ -17,11 +17,13 @@ def test_payload_envelope(self) -> None: assert "_envelope" in result def test_data_envelope(self) -> None: - raw = json.dumps({ - "event_type": "session-outcome.v1", - "correlation_id": "corr-123", - "data": {"session_id": "xyz", "outcome": "failure"}, - }).encode() + raw = json.dumps( + { + "event_type": "session-outcome.v1", + "correlation_id": "corr-123", + "data": {"session_id": "xyz", "outcome": "failure"}, + } + ).encode() result = unwrap_envelope(raw) assert result is not None assert result["session_id"] == "xyz" diff --git a/tests/test_projection_handlers.py b/tests/test_projection_handlers.py index 1ca2b8137f..86a54cd52f 100644 --- a/tests/test_projection_handlers.py +++ b/tests/test_projection_handlers.py @@ -7,9 +7,7 @@ from __future__ import annotations -from datetime import datetime, timezone -from typing import Any -from unittest.mock import AsyncMock, MagicMock +from unittest.mock import AsyncMock import pytest @@ -17,7 +15,9 @@ def _make_meta(partition: int = 0, offset: int = 0) -> MessageMeta: - return MessageMeta(partition=partition, offset=offset, fallback_id="fallback-id-1234") + return MessageMeta( + partition=partition, offset=offset, fallback_id="fallback-id-1234" + ) @pytest.fixture @@ -48,7 +48,9 @@ async def test_basic_projection(self, mock_db: AsyncMock) -> None: "emitted_at": "2026-04-06T12:00:00Z", } - result = await runner.project_event("onex.evt.omniclaude.session-outcome.v1", data, _make_meta()) + result = await runner.project_event( + "onex.evt.omniclaude.session-outcome.v1", data, _make_meta() + ) assert result is True mock_db.execute.assert_called_once() args = mock_db.execute.call_args @@ -65,7 +67,9 @@ async def test_missing_session_id_skips(self, mock_db: AsyncMock) -> None: runner._db = mock_db data = {"outcome": "success"} - result = await runner.project_event("onex.evt.omniclaude.session-outcome.v1", data, _make_meta()) + result = await runner.project_event( + "onex.evt.omniclaude.session-outcome.v1", data, _make_meta() + ) assert result is True mock_db.execute.assert_not_called() @@ -79,7 +83,9 @@ async def test_correlation_id_fallback(self, mock_db: AsyncMock) -> None: runner._db = mock_db data = {"correlation_id": "corr-123", "outcome": "failure"} - result = await runner.project_event("onex.evt.omniclaude.session-outcome.v1", data, _make_meta()) + result = await runner.project_event( + "onex.evt.omniclaude.session-outcome.v1", data, _make_meta() + ) assert result is True mock_db.execute.assert_called_once() args = mock_db.execute.call_args @@ -105,7 +111,9 @@ async def test_basic_projection(self, mock_db: AsyncMock) -> None: "timestamp": "2026-04-06T12:00:00Z", } - result = await runner.project_event("onex.evt.omniintelligence.llm-call-completed.v1", data, _make_meta()) + result = await runner.project_event( + "onex.evt.omniintelligence.llm-call-completed.v1", data, _make_meta() + ) assert result is True mock_db.execute.assert_called_once() @@ -124,7 +132,9 @@ async def test_usage_source_normalization(self, mock_db: AsyncMock) -> None: "timestamp": "2026-04-06T12:00:00Z", } - result = await runner.project_event("onex.evt.omniintelligence.llm-call-completed.v1", data, _make_meta()) + result = await runner.project_event( + "onex.evt.omniintelligence.llm-call-completed.v1", data, _make_meta() + ) assert result is True # Should default to API for unrecognized source call_args = mock_db.execute.call_args[0] @@ -149,7 +159,9 @@ async def test_task_delegated(self, mock_db: AsyncMock) -> None: "timestamp": "2026-04-06T12:00:00Z", } - result = await runner.project_event("onex.evt.omniclaude.task-delegated.v1", data, _make_meta()) + result = await runner.project_event( + "onex.evt.omniclaude.task-delegated.v1", data, _make_meta() + ) assert result is True mock_db.execute.assert_called_once() @@ -170,7 +182,9 @@ async def test_shadow_comparison(self, mock_db: AsyncMock) -> None: "divergence_detected": True, } - result = await runner.project_event("onex.evt.omniclaude.delegation-shadow-comparison.v1", data, _make_meta()) + result = await runner.project_event( + "onex.evt.omniclaude.delegation-shadow-comparison.v1", data, _make_meta() + ) assert result is True mock_db.execute.assert_called_once() @@ -184,7 +198,9 @@ async def test_missing_required_fields_skips(self, mock_db: AsyncMock) -> None: runner._db = mock_db data = {"correlation_id": "corr-1"} # missing task_type, delegated_to - result = await runner.project_event("onex.evt.omniclaude.task-delegated.v1", data, _make_meta()) + result = await runner.project_event( + "onex.evt.omniclaude.task-delegated.v1", data, _make_meta() + ) assert result is True # skip, don't error mock_db.execute.assert_not_called() @@ -207,7 +223,9 @@ async def test_introspection(self, mock_db: AsyncMock) -> None: "metadata": {"version": "1.0"}, } - result = await runner.project_event("onex.evt.platform.node-introspection.v1", data, _make_meta()) + result = await runner.project_event( + "onex.evt.platform.node-introspection.v1", data, _make_meta() + ) assert result is True mock_db.execute.assert_called_once() @@ -222,7 +240,9 @@ async def test_heartbeat(self, mock_db: AsyncMock) -> None: data = {"node_name": "node_build_loop", "health_status": "healthy"} - result = await runner.project_event("onex.evt.platform.node-heartbeat.v1", data, _make_meta()) + result = await runner.project_event( + "onex.evt.platform.node-heartbeat.v1", data, _make_meta() + ) assert result is True mock_db.execute.assert_called_once() @@ -237,7 +257,9 @@ async def test_state_change(self, mock_db: AsyncMock) -> None: data = {"node_name": "node_build_loop", "new_state": "active"} - result = await runner.project_event("onex.evt.platform.node-state-change.v1", data, _make_meta()) + result = await runner.project_event( + "onex.evt.platform.node-state-change.v1", data, _make_meta() + ) assert result is True mock_db.execute.assert_called_once() @@ -274,12 +296,12 @@ async def test_basic_projection(self, mock_db: AsyncMock) -> None: "avg_outcome_improvement": 0.2, } ], - "breakdown": [ - {"action": "promote", "count": 5, "avg_confidence": 0.8} - ], + "breakdown": [{"action": "promote", "count": 5, "avg_confidence": 0.8}], } - result = await runner.project_event("onex.evt.omnibase-infra.baselines-computed.v1", data, _make_meta()) + result = await runner.project_event( + "onex.evt.omnibase-infra.baselines-computed.v1", data, _make_meta() + ) assert result is True mock_db.execute_in_transaction.assert_called_once() @@ -310,7 +332,9 @@ async def test_basic_projection(self, mock_db: AsyncMock) -> None: "timestamp": "2026-04-06T12:00:00Z", } - result = await runner.project_event("onex.evt.omnibase-infra.savings-estimated.v1", data, _make_meta()) + result = await runner.project_event( + "onex.evt.omnibase-infra.savings-estimated.v1", data, _make_meta() + ) assert result is True mock_db.execute.assert_called_once() @@ -324,6 +348,8 @@ async def test_missing_session_id_skips(self, mock_db: AsyncMock) -> None: runner._db = mock_db data = {"actual_total_tokens": 100} - result = await runner.project_event("onex.evt.omnibase-infra.savings-estimated.v1", data, _make_meta()) + result = await runner.project_event( + "onex.evt.omnibase-infra.savings-estimated.v1", data, _make_meta() + ) assert result is True mock_db.execute.assert_not_called() diff --git a/tests/test_projection_runner.py b/tests/test_projection_runner.py index ff6b717030..60dadf58c0 100644 --- a/tests/test_projection_runner.py +++ b/tests/test_projection_runner.py @@ -1,15 +1,13 @@ """Tests for BaseProjectionRunner helper functions.""" -from datetime import datetime, timezone - -import pytest +from datetime import UTC, datetime from omnimarket.projection.runner import ( + coalesce, deterministic_correlation_id, safe_float, safe_int, safe_parse_date, - coalesce, ) @@ -45,7 +43,7 @@ def test_none(self) -> None: result = safe_parse_date(None) assert isinstance(result, datetime) # Should be approximately now - delta = abs((datetime.now(timezone.utc) - result).total_seconds()) + delta = abs((datetime.now(UTC) - result).total_seconds()) assert delta < 5 def test_empty_string(self) -> None: @@ -53,7 +51,7 @@ def test_empty_string(self) -> None: assert isinstance(result, datetime) def test_datetime_passthrough(self) -> None: - dt = datetime(2026, 1, 1, tzinfo=timezone.utc) + dt = datetime(2026, 1, 1, tzinfo=UTC) result = safe_parse_date(dt) assert result == dt