From ea2fc80e0c7acd369ffd90c65f7c091b2fd0c0e4 Mon Sep 17 00:00:00 2001 From: Alec Khoury Date: Mon, 27 Jul 2026 16:30:57 -0500 Subject: [PATCH 1/6] feat: extract Eval Author into nemo-eval-author-plugin Move Eval Author, the Harbor evaluator stack, staging, and trace analysis into a library plugin so Experimentalist depends on it one-way like Insights, without Eval Author depending on Experimentalist. Signed-off-by: Alec Khoury --- plugins/README.md | 1 + plugins/nemo-eval-author/README.md | 36 ++ plugins/nemo-eval-author/pyproject.toml | 30 ++ .../src/nemo_eval_author_plugin/backend.py | 122 +++++ .../src/nemo_eval_author_plugin/cache.py | 121 +++++ .../src/nemo_eval_author_plugin/client.py | 51 ++ .../dataset_staging.py | 2 +- .../eval_author/README.md | 8 +- .../eval_author/REFERENCE.md | 0 .../eval_author/agent.py | 18 +- .../eval_author/config.yaml | 0 .../eval_author/materialization.py | 4 +- .../eval_author/models.py | 2 +- .../eval_author/run.py | 22 +- .../evaluator/__init__.py | 4 +- .../evaluator/base.py | 2 +- .../evaluator/factory.py | 6 +- .../evaluator/harbor.py | 4 +- .../evaluator/models.py | 0 .../nemo_eval_author_plugin/holdout_utils.py | 96 ++++ .../nemo_eval_author_plugin/model_config.py | 107 ++++ .../src/nemo_eval_author_plugin/py.typed | 0 .../src/nemo_eval_author_plugin/rationale.py | 21 + .../src/nemo_eval_author_plugin/repository.py | 438 +++++++++++++++ .../src/nemo_eval_author_plugin/tools.py | 221 ++++++++ .../trace_analyzer.py | 17 +- .../trace_explorer.py | 2 +- .../src/nemo_eval_author_plugin/util.py | 19 + .../workspace_entities.py | 35 ++ .../tests}/test_dataset_staging.py | 98 +--- .../tests/test_eval_author_agent.py | 27 +- .../tests/test_eval_author_materialization.py | 8 +- .../tests/test_eval_author_run.py | 14 +- .../tests}/test_evaluator_base.py | 2 +- .../tests}/test_evaluator_factory.py | 10 +- .../tests}/test_evaluator_harbor.py | 54 +- .../tests/test_trace_explorer_from_ref.py | 4 +- plugins/nemo-experimentalist/AGENTS.md | 13 +- .../nemo-experimentalist/benchmarks/run.py | 4 +- plugins/nemo-experimentalist/pyproject.toml | 1 + .../nemo_experimentalist_plugin/entities.py | 2 +- .../experimentalist/components/analyzer.py | 6 +- .../experimentalist/components/coder.py | 2 +- .../experimentalist/components/goal_tree.py | 2 +- .../components/insight_promotion.py | 6 +- .../experimentalist/components/loop.py | 14 +- .../components/rationalizer.py | 4 +- .../components/trace_scorer.py | 4 +- .../experimentalist/deps.py | 4 +- .../experimentalist_backend.py | 4 +- .../experimentalist/otlp.py | 2 +- .../experimentalist/run.py | 2 +- .../nemo_experimentalist_plugin/resolve.py | 4 +- .../test_dataset_staging_loop.py | 110 ++++ .../test_eval_author_repair_e2e.py | 10 +- .../test_loop_insight_suite.py | 10 +- .../experimentalist/test_loop_reporting.py | 6 +- .../nemo-experimentalist/tests/test_deps.py | 2 +- .../tests/test_eval_author_config.py | 25 + .../tests/test_experiment_cli.py | 2 +- .../tests/test_experimentalist_analyzer.py | 2 +- .../tests/test_experimentalist_backend.py | 2 +- .../tests/test_experimentalist_benchmark.py | 2 +- .../tests/test_experimentalist_run.py | 2 +- .../nemo-experimentalist/tests/test_otlp.py | 2 +- pyproject.toml | 4 + uv.lock | 510 ++++++++++++++++++ 67 files changed, 2107 insertions(+), 262 deletions(-) create mode 100644 plugins/nemo-eval-author/README.md create mode 100644 plugins/nemo-eval-author/pyproject.toml create mode 100644 plugins/nemo-eval-author/src/nemo_eval_author_plugin/backend.py create mode 100644 plugins/nemo-eval-author/src/nemo_eval_author_plugin/cache.py create mode 100644 plugins/nemo-eval-author/src/nemo_eval_author_plugin/client.py rename plugins/{nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components => nemo-eval-author/src/nemo_eval_author_plugin}/dataset_staging.py (96%) rename plugins/{nemo-experimentalist/src/nemo_experimentalist_plugin => nemo-eval-author/src/nemo_eval_author_plugin}/eval_author/README.md (93%) rename plugins/{nemo-experimentalist/src/nemo_experimentalist_plugin => nemo-eval-author/src/nemo_eval_author_plugin}/eval_author/REFERENCE.md (100%) rename plugins/{nemo-experimentalist/src/nemo_experimentalist_plugin => nemo-eval-author/src/nemo_eval_author_plugin}/eval_author/agent.py (95%) rename plugins/{nemo-experimentalist/src/nemo_experimentalist_plugin => nemo-eval-author/src/nemo_eval_author_plugin}/eval_author/config.yaml (100%) rename plugins/{nemo-experimentalist/src/nemo_experimentalist_plugin => nemo-eval-author/src/nemo_eval_author_plugin}/eval_author/materialization.py (98%) rename plugins/{nemo-experimentalist/src/nemo_experimentalist_plugin => nemo-eval-author/src/nemo_eval_author_plugin}/eval_author/models.py (94%) rename plugins/{nemo-experimentalist/src/nemo_experimentalist_plugin => nemo-eval-author/src/nemo_eval_author_plugin}/eval_author/run.py (82%) rename plugins/{nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components => nemo-eval-author/src/nemo_eval_author_plugin}/evaluator/__init__.py (83%) rename plugins/{nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components => nemo-eval-author/src/nemo_eval_author_plugin}/evaluator/base.py (98%) rename plugins/{nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components => nemo-eval-author/src/nemo_eval_author_plugin}/evaluator/factory.py (93%) rename plugins/{nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components => nemo-eval-author/src/nemo_eval_author_plugin}/evaluator/harbor.py (99%) rename plugins/{nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components => nemo-eval-author/src/nemo_eval_author_plugin}/evaluator/models.py (100%) create mode 100644 plugins/nemo-eval-author/src/nemo_eval_author_plugin/holdout_utils.py create mode 100644 plugins/nemo-eval-author/src/nemo_eval_author_plugin/model_config.py create mode 100644 plugins/nemo-eval-author/src/nemo_eval_author_plugin/py.typed create mode 100644 plugins/nemo-eval-author/src/nemo_eval_author_plugin/rationale.py create mode 100644 plugins/nemo-eval-author/src/nemo_eval_author_plugin/repository.py create mode 100644 plugins/nemo-eval-author/src/nemo_eval_author_plugin/tools.py rename plugins/{nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components => nemo-eval-author/src/nemo_eval_author_plugin}/trace_analyzer.py (95%) rename plugins/{nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components => nemo-eval-author/src/nemo_eval_author_plugin}/trace_explorer.py (99%) create mode 100644 plugins/nemo-eval-author/src/nemo_eval_author_plugin/util.py create mode 100644 plugins/nemo-eval-author/src/nemo_eval_author_plugin/workspace_entities.py rename plugins/{nemo-experimentalist/tests/experimentalist => nemo-eval-author/tests}/test_dataset_staging.py (63%) rename plugins/{nemo-experimentalist => nemo-eval-author}/tests/test_eval_author_agent.py (95%) rename plugins/{nemo-experimentalist => nemo-eval-author}/tests/test_eval_author_materialization.py (97%) rename plugins/{nemo-experimentalist => nemo-eval-author}/tests/test_eval_author_run.py (94%) rename plugins/{nemo-experimentalist/tests/experimentalist => nemo-eval-author/tests}/test_evaluator_base.py (98%) rename plugins/{nemo-experimentalist/tests/experimentalist => nemo-eval-author/tests}/test_evaluator_factory.py (92%) rename plugins/{nemo-experimentalist/tests/experimentalist => nemo-eval-author/tests}/test_evaluator_harbor.py (95%) rename plugins/{nemo-experimentalist => nemo-eval-author}/tests/test_trace_explorer_from_ref.py (93%) create mode 100644 plugins/nemo-experimentalist/tests/experimentalist/test_dataset_staging_loop.py create mode 100644 plugins/nemo-experimentalist/tests/test_eval_author_config.py diff --git a/plugins/README.md b/plugins/README.md index 0e23f0fafa0..9452a437b92 100644 --- a/plugins/README.md +++ b/plugins/README.md @@ -61,6 +61,7 @@ The package name is the `name` field in the plugin's `pyproject.toml`, not the d | `nemo-agents/` | `nemo-agents-plugin` | | `nemo-anonymizer/` | `nemo-anonymizer-plugin` | | `nemo-data-designer/` | `nemo-data-designer-plugin` | +| `nemo-eval-author/` | `nemo-eval-author-plugin` | | `nemo-evaluator/` | `nemo-evaluator-plugin` | | `nemo-guardrails/` | `nemo-guardrails-plugin` | | `nemo-switchyard/` | `nemo-switchyard` | diff --git a/plugins/nemo-eval-author/README.md b/plugins/nemo-eval-author/README.md new file mode 100644 index 00000000000..f672588ef9e --- /dev/null +++ b/plugins/nemo-eval-author/README.md @@ -0,0 +1,36 @@ +# NeMo Eval Author Plugin + +Library-only plugin that owns the Eval Author agent, Harbor evaluator stack, trace analysis helpers, and dataset staging used to author evaluation suites from Insights. + +Experimentalist depends on this plugin the same way it depends on `nemo-insights-plugin`. + +## Public API + +```python +from nemo_eval_author_plugin.eval_author.agent import EvalAuthor, build_eval_author_agent +from nemo_eval_author_plugin.eval_author.models import EvalAuthorConfig, EvalAuthorResult +from nemo_eval_author_plugin.eval_author.run import run_eval_author +from nemo_eval_author_plugin.evaluator import Dataset, DatasetRef, Evaluator +from nemo_eval_author_plugin.evaluator.factory import DatasetFactory, EvaluatorFactory +from nemo_eval_author_plugin.dataset_staging import stage_eval_author_inputs, stage_task_template +from nemo_eval_author_plugin.trace_analyzer import TraceAnalyzer, TraceAnalyzerConfig, Diagnostic +from nemo_eval_author_plugin.trace_explorer import TraceExplorer +``` + +## Install + +From the repository root: + +```bash +uv sync --group experimentalist +``` + +## TODO(shared-module) + +The following modules are exact copies of Experimentalist helpers and should eventually live in a shared package: + +- `tools.py` +- `model_config.py` +- `cache.py` +- `client.py` +- `repository.py` (agent clone helpers used by `backend.py`) diff --git a/plugins/nemo-eval-author/pyproject.toml b/plugins/nemo-eval-author/pyproject.toml new file mode 100644 index 00000000000..d773c32f79d --- /dev/null +++ b/plugins/nemo-eval-author/pyproject.toml @@ -0,0 +1,30 @@ +[project] +name = "nemo-eval-author-plugin" +version = "0.1.0" +description = "Eval Author agent and Harbor evaluator stack for NeMo Platform." +requires-python = ">=3.11,<3.14" +dependencies = [ + "pydantic>=2", + "httpx", + "harbor>=0.16 ; python_full_version >= '3.12'", + "opentelemetry-proto>=1.42.1", + "protobuf>=6.0.0", + "nooa ; python_full_version >= '3.12'", + "pyyaml>=6.0.3", + "nemo-insights-plugin", + "nemo-platform", + "nemo-platform-plugin", + "tomlkit>=0.13.3", +] + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.hatch.build.targets.wheel] +packages = ["src/nemo_eval_author_plugin"] + +[tool.pytest.ini_options] +asyncio_mode = "auto" +pythonpath = ["src"] +testpaths = ["tests"] diff --git a/plugins/nemo-eval-author/src/nemo_eval_author_plugin/backend.py b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/backend.py new file mode 100644 index 00000000000..3ff2eeba752 --- /dev/null +++ b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/backend.py @@ -0,0 +1,122 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Minimal backend for standalone Eval Author runs. + +Replicates the ``get_insight`` and ``get_agent_code`` behavior from +``LocalExperimentalistBackend`` so ``run_eval_author`` does not depend on +Experimentalist. + +TODO(shared-module): unify agent materialization with Experimentalist backend. +""" + +from __future__ import annotations + +import asyncio +import json +import shutil +import subprocess +from pathlib import Path +from typing import Any, Literal, TypeVar, cast + +import httpx +from nemo_eval_author_plugin.repository import ( + AgentSource, + clone_agent_repo, + looks_like_git, + split_agent_spec, + split_git_ref, +) +from nemo_insights_plugin.entities import Insight +from nemo_platform import AsyncNeMoPlatform + +_ModelT = TypeVar("_ModelT") + +_AGENT_COPY_EXCLUDE_NAMES = { + ".git", + ".venv", + "artifacts", + "dataset", + "eval-and-optimize", + "scratch", +} + + +def _ignore_agent_copy(directory: str, contents: list[str]) -> set[str]: + del directory + return {name for name in contents if name in _AGENT_COPY_EXCLUDE_NAMES} + + +def _load_entity(cls: type[_ModelT], path: Path) -> _ModelT: + """Deserialize *path* as JSON into *cls*, restoring the private ``_id`` field.""" + data = json.loads(path.read_text()) + entity_id = data.get("id", "") + obj = cls.model_validate(data) + if entity_id: + cast(Any, obj)._id = entity_id + return obj + + +class EvalAuthorBackend: + """Insight and agent-code materialization for Eval Author runs.""" + + def __init__(self, *, client: AsyncNeMoPlatform | None, path: Path) -> None: + self.client = client + self.path = path + + async def get_insight(self, *, workspace: str, insight_id: str) -> Insight: + p = Path(insight_id) + if p.exists(): + return _load_entity(Insight, p) + if self.client is None: + raise ValueError( + f"Insight {insight_id!r} is not an existing local file and no platform " + "client is available to fetch it from the platform." + ) + try: + return await self.client.insights.insights.get(workspace=workspace, insight_id=insight_id) + except httpx.HTTPStatusError as exc: + if exc.response.status_code == 404: + raise ValueError(f"Insight not found on the platform: {insight_id!r}") from exc + raise ValueError(f"Failed to fetch insight {insight_id!r} from the platform: {exc}") from exc + + async def get_agent_code( + self, *, workspace: str, agent: str | Path, dest: Path, clone_depth: int | None = None + ) -> AgentSource | None: + del workspace + if looks_like_git(str(agent)): + return await self._clone_git_agent(str(agent), dest, clone_depth=clone_depth) + src = Path(agent) + if not src.exists(): + raise FileNotFoundError(f"Local agent path not found: {src}") + if dest.resolve() != src.resolve(): + if dest.exists(): + shutil.rmtree(dest) + shutil.copytree(src, dest, ignore=_ignore_agent_copy) + return None + + async def _clone_git_agent(self, agent: str, dest: Path, *, clone_depth: int | None = None) -> AgentSource: + try: + return await asyncio.to_thread(clone_agent_repo, agent, dest, clone_depth=clone_depth) + except subprocess.CalledProcessError: + remote, _ = split_git_ref(split_agent_spec(agent)[0]) + from nemo_eval_author_plugin.repository import _redact_url + + raise ValueError(f"failed to fetch --agent {_redact_url(remote)!r}") from None + + +def make_eval_author_backend( + *, + client: AsyncNeMoPlatform | None, + experiments_output: str, + mode: Literal["local", "remote"], +) -> EvalAuthorBackend: + """Select the Eval Author backend for *mode*. + + Both local and remote modes use the same insight/agent materialization + implementation; remote mode requires a platform client for platform-backed + insight ids. + """ + if client is None and mode == "remote": + raise ValueError("remote Eval Author backend requires a platform client") + return EvalAuthorBackend(client=client, path=Path(experiments_output)) diff --git a/plugins/nemo-eval-author/src/nemo_eval_author_plugin/cache.py b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/cache.py new file mode 100644 index 00000000000..25716cd5174 --- /dev/null +++ b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/cache.py @@ -0,0 +1,121 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +# TODO(shared-module): exact copy of experimentalist components/cache.py; unify into a shared package. +from __future__ import annotations + +import hashlib +import json +import logging +from pathlib import Path +from typing import TypeVar +from uuid import uuid4 + +from pydantic import BaseModel, ValidationError + +_logger = logging.getLogger(__name__) + +# PEP 695 syntax would read better, but the workspace lints against Python 3.11. +T = TypeVar("T", bound=BaseModel) + + +def task_hash(task_name: str) -> str: + """Return a namespaced sha256 hex digest for a task key. + + Args: + task_name: the task identifier to hash. + + Returns: + str: a ``task-`` prefixed digest string. + + """ + digest = hashlib.sha256(task_name.encode()).hexdigest() + return f"task-{digest}" + + +def agent_hash(agent_id: str) -> str: + """Return a namespaced sha256 hex digest for an agent key. + + Args: + agent_id: the agent identifier to hash. + + Returns: + str: an ``agent-`` prefixed digest string. + + """ + digest = hashlib.sha256(agent_id.encode()).hexdigest() + return f"agent-{digest}" + + +def trace_hash(trace_path: str | Path) -> str: + """Return a namespaced sha256 hex digest of a trace file's contents. + + Args: + trace_path: path to the trace file to hash. + + Returns: + str: a ``trace-`` prefixed digest string. + + """ + h = hashlib.sha256() + with open(trace_path, "rb") as f: + for chunk in iter(lambda: f.read(1 << 20), b""): + h.update(chunk) + digest = h.hexdigest() + return f"trace-{digest}" + + +def _cache_path(workspace: Path, key: str) -> Path: + return workspace / "eval-and-optimize" / "cache" / f"{key}.json" + + +def load(workspace: Path, key: str, model: type[T]) -> T | None: + """Return a previously-stored model instance for this key, or None. + + Args: + workspace: root workspace directory containing the cache. + key: cache key returned by one of the ``*_hash`` functions. + model: Pydantic model class used to deserialise the stored payload. + + Returns: + T | None: the deserialised model instance, or None if the entry is + missing, unreadable, or fails validation. + + """ + path = _cache_path(workspace, key) + if not path.exists(): + return None + try: + payload = json.loads(path.read_text()) + except (OSError, ValueError) as exc: + _logger.warning(f"Cache read failed at {path}: {exc} -- ignoring") + return None + try: + return model.model_validate(payload) + except ValidationError as exc: + _logger.warning(f"Cache payload at {path} does not validate against {model.__name__}: {exc} -- ignoring") + return None + + +def store(workspace: Path, key: str, value: BaseModel) -> None: + """Persist a model instance under the given key, overwriting any prior entry. + + Args: + workspace: root workspace directory containing the cache. + key: cache key returned by one of the ``*_hash`` functions. + value: Pydantic model instance to serialise and store. + + """ + path = _cache_path(workspace, key) + path.parent.mkdir(parents=True, exist_ok=True) + payload = value.model_dump(mode="json") + tmp = path.parent / f"{path.name}.{uuid4().hex}.tmp" + try: + tmp.write_text(json.dumps(payload, indent=2)) + tmp.replace(path) + except OSError as exc: + _logger.warning(f"Cache write failed for key {key}: {exc} -- skipping") + try: + tmp.unlink(missing_ok=True) + except OSError: + pass diff --git a/plugins/nemo-eval-author/src/nemo_eval_author_plugin/client.py b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/client.py new file mode 100644 index 00000000000..e7335cdeb2d --- /dev/null +++ b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/client.py @@ -0,0 +1,51 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +# TODO(shared-module): exact copy of experimentalist client.py; unify into a shared package. + +"""Shared NeMo Platform SDK client construction for Insight consumers. + +Auth lives in the active ``nemo auth login`` context in +``~/.config/nmp/config.yaml``. The SDK only wires up that context (and the +transparent OIDC token refresh that comes with it) when it runs its config +bootstrap. Passing ``base_url`` *alone* puts the SDK in "direct mode", which +skips the bootstrap and injects **no** auth headers — fine for an +unauthenticated local ``nemo services run``, but it 401s against a remote +deployment. To authenticate against a remote URL we must trigger the bootstrap +(by also passing ``config_path``) so the explicit ``base_url`` is combined with +the context's credentials. + +Every Platform Insight consumer takes ``base_url`` from its workflow context, +so this helper is the one place that branch lives. +""" + +from urllib.parse import urlparse + +from nemo_platform import AsyncNeMoPlatform +from nemo_platform.config.config import Config + +# Loopback hosts are served by an unauthenticated local platform; attaching +# (and refreshing) OAuth tokens there is both unnecessary and a failure mode +# when the cached token is stale and OIDC discovery against localhost fails. +LOOPBACK_HOSTS = frozenset({"localhost", "127.0.0.1", "::1", "0.0.0.0"}) + + +def make_client(base_url: str | None) -> AsyncNeMoPlatform: + """Construct an :class:`AsyncNeMoPlatform` honoring an optional ``base_url``. + + - No ``base_url``: use the active nmp context for both URL and auth. + - Loopback ``base_url``: direct mode (local platform is unauthenticated). + - Remote ``base_url`` with an nmp config present: combine the URL with the + context's auth so the SDK injects and refreshes a Bearer token. + - Remote ``base_url`` without an nmp config: direct mode (no credentials to + use; the request will surface a clear auth error). + """ + if not base_url: + return AsyncNeMoPlatform() + + host = (urlparse(base_url).hostname or "").lower() + config_path = Config.get_default_config_path() + if host in LOOPBACK_HOSTS or not config_path.exists(): + return AsyncNeMoPlatform(base_url=base_url) + + return AsyncNeMoPlatform(base_url=base_url, config_path=config_path) diff --git a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/dataset_staging.py b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/dataset_staging.py similarity index 96% rename from plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/dataset_staging.py rename to plugins/nemo-eval-author/src/nemo_eval_author_plugin/dataset_staging.py index cf99a77b47c..36a3d29fd9c 100644 --- a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/dataset_staging.py +++ b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/dataset_staging.py @@ -8,7 +8,7 @@ from pathlib import Path from urllib.parse import urlparse -from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import DatasetRef, local_path_from_uri +from nemo_eval_author_plugin.evaluator.models import DatasetRef, local_path_from_uri from nemo_platform import AsyncNeMoPlatform diff --git a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/eval_author/README.md b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/eval_author/README.md similarity index 93% rename from plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/eval_author/README.md rename to plugins/nemo-eval-author/src/nemo_eval_author_plugin/eval_author/README.md index 09acb7f7061..838d5cd1cad 100644 --- a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/eval_author/README.md +++ b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/eval_author/README.md @@ -5,7 +5,7 @@ SPDX-License-Identifier: Apache-2.0 # Eval Author -The top-level `nemo_experimentalist_plugin.eval_author` package is the canonical Eval Author +The top-level `nemo_eval_author_plugin.eval_author` package is the canonical Eval Author implementation. It turns an Experimentalist Insight and its production trace refs into evaluator dataset changes, creating or augmenting regression signals that capture the failure mode before optimization begins. @@ -89,9 +89,9 @@ directly: import asyncio from pathlib import Path -from nemo_experimentalist_plugin.eval_author.models import EvalAuthorConfig -from nemo_experimentalist_plugin.eval_author.run import run_eval_author -from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import DatasetRef +from nemo_eval_author_plugin.eval_author.models import EvalAuthorConfig +from nemo_eval_author_plugin.eval_author.run import run_eval_author +from nemo_eval_author_plugin.evaluator.models import DatasetRef async def main() -> None: diff --git a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/eval_author/REFERENCE.md b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/eval_author/REFERENCE.md similarity index 100% rename from plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/eval_author/REFERENCE.md rename to plugins/nemo-eval-author/src/nemo_eval_author_plugin/eval_author/REFERENCE.md diff --git a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/eval_author/agent.py b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/eval_author/agent.py similarity index 95% rename from plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/eval_author/agent.py rename to plugins/nemo-eval-author/src/nemo_eval_author_plugin/eval_author/agent.py index 33fe71da8c4..68537381cc0 100644 --- a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/eval_author/agent.py +++ b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/eval_author/agent.py @@ -12,24 +12,24 @@ from pathlib import Path from typing import Any -from nemo_experimentalist_plugin.eval_author.materialization import InsightSuite -from nemo_experimentalist_plugin.eval_author.models import EvalAuthorConfig, EvalAuthorResult -from nemo_experimentalist_plugin.experimentalist.components import cache -from nemo_experimentalist_plugin.experimentalist.components.evaluator import ( +from nemo_eval_author_plugin import cache +from nemo_eval_author_plugin.eval_author.materialization import InsightSuite +from nemo_eval_author_plugin.eval_author.models import EvalAuthorConfig, EvalAuthorResult +from nemo_eval_author_plugin.evaluator import ( Dataset, DatasetValidationError, Task, TrialResult, ) -from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import ResourceRef -from nemo_experimentalist_plugin.experimentalist.components.model_config import get_fast_model, get_smart_model -from nemo_experimentalist_plugin.experimentalist.components.tools import GuardedShellTools -from nemo_experimentalist_plugin.experimentalist.components.trace_analyzer import ( +from nemo_eval_author_plugin.evaluator.models import ResourceRef +from nemo_eval_author_plugin.model_config import get_fast_model, get_smart_model +from nemo_eval_author_plugin.tools import GuardedShellTools +from nemo_eval_author_plugin.trace_analyzer import ( Diagnostic, TraceAnalyzer, TraceAnalyzerConfig, ) -from nemo_experimentalist_plugin.experimentalist.components.trace_explorer import ( +from nemo_eval_author_plugin.trace_explorer import ( SearchResult, SessionData, SessionSummary, diff --git a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/eval_author/config.yaml b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/eval_author/config.yaml similarity index 100% rename from plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/eval_author/config.yaml rename to plugins/nemo-eval-author/src/nemo_eval_author_plugin/eval_author/config.yaml diff --git a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/eval_author/materialization.py b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/eval_author/materialization.py similarity index 98% rename from plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/eval_author/materialization.py rename to plugins/nemo-eval-author/src/nemo_eval_author_plugin/eval_author/materialization.py index 4e084dccb88..783ca481c81 100644 --- a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/eval_author/materialization.py +++ b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/eval_author/materialization.py @@ -18,8 +18,8 @@ import tomlkit from harbor.models.task.task import Task as HarborTask -from nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor import HarborDataset -from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import Task, local_path_from_uri +from nemo_eval_author_plugin.evaluator.harbor import HarborDataset +from nemo_eval_author_plugin.evaluator.models import Task, local_path_from_uri _MANIFEST_SCHEMA_VERSION = 3 _CONTENT_HASH_SCHEMA_VERSION = 1 diff --git a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/eval_author/models.py b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/eval_author/models.py similarity index 94% rename from plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/eval_author/models.py rename to plugins/nemo-eval-author/src/nemo_eval_author_plugin/eval_author/models.py index 83f5e6f86be..40b9ae15196 100644 --- a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/eval_author/models.py +++ b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/eval_author/models.py @@ -3,7 +3,7 @@ """Shared models for the top-level Eval Author.""" -from nemo_experimentalist_plugin.experimentalist.components.evaluator import Dataset +from nemo_eval_author_plugin.evaluator import Dataset from pydantic import BaseModel, ConfigDict, Field diff --git a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/eval_author/run.py b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/eval_author/run.py similarity index 82% rename from plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/eval_author/run.py rename to plugins/nemo-eval-author/src/nemo_eval_author_plugin/eval_author/run.py index aad8158bb5c..d966aafbb7c 100644 --- a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/eval_author/run.py +++ b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/eval_author/run.py @@ -7,16 +7,14 @@ from pathlib import Path from typing import Literal, Protocol, cast -from nemo_experimentalist_plugin.client import make_client -from nemo_experimentalist_plugin.eval_author.models import EvalAuthorConfig, EvalAuthorResult -from nemo_experimentalist_plugin.experimentalist.components.dataset_staging import stage_task_template -from nemo_experimentalist_plugin.experimentalist.components.evaluator import Dataset, Task -from nemo_experimentalist_plugin.experimentalist.components.evaluator.base import EvaluatorType -from nemo_experimentalist_plugin.experimentalist.components.evaluator.factory import DatasetFactory -from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import DatasetRef -from nemo_experimentalist_plugin.experimentalist.experimentalist_backend import ( - make_experimentalist_backend, -) +from nemo_eval_author_plugin.backend import make_eval_author_backend +from nemo_eval_author_plugin.client import make_client +from nemo_eval_author_plugin.dataset_staging import stage_task_template +from nemo_eval_author_plugin.eval_author.models import EvalAuthorConfig, EvalAuthorResult +from nemo_eval_author_plugin.evaluator import Dataset, Task +from nemo_eval_author_plugin.evaluator.base import EvaluatorType +from nemo_eval_author_plugin.evaluator.factory import DatasetFactory +from nemo_eval_author_plugin.evaluator.models import DatasetRef from nemo_insights_plugin.entities import Insight from nemo_platform import AsyncNeMoPlatform @@ -78,7 +76,7 @@ async def run_eval_author( client = make_client(base_url) try: - backend = make_experimentalist_backend( + backend = make_eval_author_backend( client=client, experiments_output=str(experiment_dir), mode=mode, @@ -113,7 +111,7 @@ async def run_eval_author( def build_eval_author_agent(*, experiment_dir: Path, config: EvalAuthorConfig) -> _EvalAuthorAgent: """Build the LLM-backed Eval Author agent lazily.""" - from nemo_experimentalist_plugin.eval_author.agent import build_eval_author_agent as _build_eval_author_agent + from nemo_eval_author_plugin.eval_author.agent import build_eval_author_agent as _build_eval_author_agent return _build_eval_author_agent(experiment_dir=experiment_dir, config=config) diff --git a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/evaluator/__init__.py b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/evaluator/__init__.py similarity index 83% rename from plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/evaluator/__init__.py rename to plugins/nemo-eval-author/src/nemo_eval_author_plugin/evaluator/__init__.py index 59fee57c2ff..f293250efed 100644 --- a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/evaluator/__init__.py +++ b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/evaluator/__init__.py @@ -1,12 +1,12 @@ # SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 -from nemo_experimentalist_plugin.experimentalist.components.evaluator.base import ( +from nemo_eval_author_plugin.evaluator.base import ( Evaluator, EvaluatorConfig, EvaluatorType, ) -from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import ( +from nemo_eval_author_plugin.evaluator.models import ( CommandSpec, Dataset, DatasetRef, diff --git a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/evaluator/base.py b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/evaluator/base.py similarity index 98% rename from plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/evaluator/base.py rename to plugins/nemo-eval-author/src/nemo_eval_author_plugin/evaluator/base.py index 25261b4e214..48a0e568631 100644 --- a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/evaluator/base.py +++ b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/evaluator/base.py @@ -10,7 +10,7 @@ from pathlib import Path from typing import Literal, TypeAlias -from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import ( +from nemo_eval_author_plugin.evaluator.models import ( Dataset, EvaluationResult, TrialResult, diff --git a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/evaluator/factory.py b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/evaluator/factory.py similarity index 93% rename from plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/evaluator/factory.py rename to plugins/nemo-eval-author/src/nemo_eval_author_plugin/evaluator/factory.py index 3b59226cec2..f3a3959ded7 100644 --- a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/evaluator/factory.py +++ b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/evaluator/factory.py @@ -6,17 +6,17 @@ from pathlib import Path from typing import Any -from nemo_experimentalist_plugin.experimentalist.components.evaluator.base import ( +from nemo_eval_author_plugin.evaluator.base import ( Evaluator, EvaluatorConfig, EvaluatorType, ) -from nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor import ( +from nemo_eval_author_plugin.evaluator.harbor import ( HarborDataset, HarborEvaluator, HarborEvaluatorConfig, ) -from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import Dataset, DatasetRef, Task +from nemo_eval_author_plugin.evaluator.models import Dataset, DatasetRef, Task _SUPPORTED_EVALUATOR_TYPES = { "harbor": (HarborDataset, HarborEvaluator, HarborEvaluatorConfig), diff --git a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/evaluator/harbor.py b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/evaluator/harbor.py similarity index 99% rename from plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/evaluator/harbor.py rename to plugins/nemo-eval-author/src/nemo_eval_author_plugin/evaluator/harbor.py index 24a60ffa373..99556972bff 100644 --- a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/evaluator/harbor.py +++ b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/evaluator/harbor.py @@ -32,12 +32,12 @@ from harbor.models.task.task import Task as HarborTaskModel from harbor.models.trial.config import ServiceVolumeConfig from harbor.models.trial.paths import EnvironmentPaths, TrialPaths -from nemo_experimentalist_plugin.experimentalist.components.evaluator.base import ( +from nemo_eval_author_plugin.evaluator.base import ( Evaluator, EvaluatorConfig, EvaluatorType, ) -from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import ( +from nemo_eval_author_plugin.evaluator.models import ( Dataset, DatasetRef, DatasetValidationError, diff --git a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/evaluator/models.py b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/evaluator/models.py similarity index 100% rename from plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/evaluator/models.py rename to plugins/nemo-eval-author/src/nemo_eval_author_plugin/evaluator/models.py diff --git a/plugins/nemo-eval-author/src/nemo_eval_author_plugin/holdout_utils.py b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/holdout_utils.py new file mode 100644 index 00000000000..e314556cd34 --- /dev/null +++ b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/holdout_utils.py @@ -0,0 +1,96 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +# TODO(shared-module): exact copy of experimentalist components/holdout_utils.py; unify into a shared package. + +from __future__ import annotations + +import shutil +from pathlib import Path + +HELD_OUT_SPLITS = frozenset({"validation"}) +HELD_OUT_STORAGE_DIR = ".aad-heldout" + +# Path tokens GuardedShellTools refuses: a tripwire for direct shell access while a +# split is briefly restored for scoring. Relocation is the main obfuscation layer. +DEFAULT_BLOCKED_PATHS: tuple[str, ...] = ( + HELD_OUT_STORAGE_DIR, + *(f"dataset/{s}" for s in sorted(HELD_OUT_SPLITS)), +) + +BLOCKED_MESSAGE = ( + "blocked: the validation split is held out for scoring only. " + "Its contents are off-limits; diagnose and fix using the train split." +) + + +class HeldOutAccessError(PermissionError): + """Raised when held-out split contents or debugging artifacts are requested.""" + + +def heldout_storage_root(workspace: Path) -> Path: + """Return the workspace-local directory used to store held-out splits. + + Args: + workspace: absolute path to the eval-and-optimize workspace root. + + Returns: + Path: the hidden storage directory for held-out splits. + + """ + workspace = Path(workspace).resolve() + return workspace / HELD_OUT_STORAGE_DIR + + +def _split_paths(workspace: Path, split: str) -> tuple[Path, Path]: + """Return the ``(visible, hidden)`` locations for ``split``.""" + workspace = Path(workspace).resolve() + return workspace / "dataset" / split, heldout_storage_root(workspace) / split + + +def _move(src: Path, dst: Path, split: str) -> None: + """Delete any existing ``dst``, then rename ``src`` to ``dst``.""" + if dst.exists(): + shutil.rmtree(dst) + dst.parent.mkdir(parents=True, exist_ok=True) + src.rename(dst) + + +def ensure_heldout_hidden(workspace: Path, *, splits: frozenset[str] = HELD_OUT_SPLITS) -> None: + """Move any visible held-out splits into hidden storage. + + Idempotent — safe to call before every optimizer phase. + + Args: + workspace: absolute path to the eval-and-optimize workspace root. + splits: set of split names to hide; defaults to ``HELD_OUT_SPLITS``. + + """ + for split in splits: + visible, hidden = _split_paths(workspace, split) + if visible.exists(): + _move(visible, hidden, split) + + +def restore_heldout_splits(workspace: Path, *, splits: frozenset[str] = HELD_OUT_SPLITS) -> None: + """Restore hidden held-out splits back into ``dataset/``. + + Args: + workspace: absolute path to the eval-and-optimize workspace root. + splits: set of split names to restore; defaults to ``HELD_OUT_SPLITS``. + + """ + for split in splits: + visible, hidden = _split_paths(workspace, split) + if hidden.exists(): + _move(hidden, visible, split) + _prune_empty_storage(workspace) + + +def _prune_empty_storage(workspace: Path) -> None: + """Remove the hidden storage dir if empty.""" + root = heldout_storage_root(workspace) + try: + root.rmdir() + except OSError: + pass diff --git a/plugins/nemo-eval-author/src/nemo_eval_author_plugin/model_config.py b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/model_config.py new file mode 100644 index 00000000000..865e085be39 --- /dev/null +++ b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/model_config.py @@ -0,0 +1,107 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +# TODO(shared-module): exact copy of experimentalist components/model_config.py; unify into a shared package. +import functools +import os +from urllib.parse import urlparse + +from nooa.unifiedllm import CompletionClient + + +def _required_env(name: str) -> str: + value = os.environ.get(name, "").strip() + if not value: + raise ValueError(f"{name} must be set") + return value + + +def _optional_env(name: str, default: str) -> str: + return os.environ.get(name, "").strip() or default + + +@functools.cache +def get_smart_model() -> CompletionClient: + """Return the cached smart (high-capability) LLM client configured from environment variables. + + Returns: + CompletionClient: the singleton smart-model client. + + Raises: + ValueError: if ``EXPERIMENTALIST_API_BASE`` or ``EXPERIMENTALIST_API_KEY`` are unset or empty. + + """ + api_base = _required_env("EXPERIMENTALIST_API_BASE") + api_key = _required_env("EXPERIMENTALIST_API_KEY") + name = _optional_env("EXPERIMENTALIST_SMART_MODEL_NAME", "openai/openai/openai/gpt-5.5") + return CompletionClient(name, api_base=api_base, api_key=api_key) + + +@functools.cache +def get_mid_model() -> CompletionClient: + """Return the cached mid-tier LLM client configured from environment variables. + + Returns: + CompletionClient: the singleton mid-model client. + + Raises: + ValueError: if ``EXPERIMENTALIST_API_BASE`` or ``EXPERIMENTALIST_API_KEY`` are unset or empty. + + """ + api_base = _required_env("EXPERIMENTALIST_API_BASE") + api_key = _required_env("EXPERIMENTALIST_API_KEY") + name = _optional_env("EXPERIMENTALIST_MID_MODEL_NAME", "openai/gcp/google/gemini-3.5-flash") + return CompletionClient(name, api_base=api_base, api_key=api_key) + + +@functools.cache +def get_fast_model() -> CompletionClient: + """Return the cached fast (low-latency) LLM client configured from environment variables. + + Returns: + CompletionClient: the singleton fast-model client. + + Raises: + ValueError: if ``EXPERIMENTALIST_API_BASE`` or ``EXPERIMENTALIST_API_KEY`` are unset or empty. + + """ + api_base = _required_env("EXPERIMENTALIST_API_BASE") + api_key = _required_env("EXPERIMENTALIST_API_KEY") + name = _optional_env("EXPERIMENTALIST_FAST_MODEL_NAME", "openai/openai/openai/gpt-5-mini") + return CompletionClient(name, api_base=api_base, api_key=api_key) + + +def _mask_key(value: str) -> str: + if len(value) <= 4: + return "*" * len(value) + return f"*****{value[-4:]}" + + +def _sanitize_url(url: str) -> str: + """Strip userinfo, query, and fragment so credentials never leak via the log banner.""" + parsed = urlparse(url) + host = parsed.hostname or "" + port = f":{parsed.port}" if parsed.port else "" + return f"{parsed.scheme}://{host}{port}{parsed.path}" + + +def log_model_config() -> str: + """Return a formatted string summarising the current optimizer model configuration. + + Returns: + str: a multi-line summary of smart model, fast model, API base, and masked API key. + + """ + smart = _optional_env("EXPERIMENTALIST_SMART_MODEL_NAME", "openai/openai/openai/gpt-5.5") + mid = _optional_env("EXPERIMENTALIST_MID_MODEL_NAME", "openai/gcp/google/gemini-3.5-flash") + fast = _optional_env("EXPERIMENTALIST_FAST_MODEL_NAME", "openai/openai/openai/gpt-5-mini") + api_base = _required_env("EXPERIMENTALIST_API_BASE") + api_key = _required_env("EXPERIMENTALIST_API_KEY") + return ( + "Experimentalist model config:\n" + f" smart model: {smart}\n" + f" mid model: {mid}\n" + f" fast model: {fast}\n" + f" api base: {_sanitize_url(api_base)}\n" + f" api key: {_mask_key(api_key)}" + ) diff --git a/plugins/nemo-eval-author/src/nemo_eval_author_plugin/py.typed b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/py.typed new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/nemo-eval-author/src/nemo_eval_author_plugin/rationale.py b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/rationale.py new file mode 100644 index 00000000000..03b7bd9b61a --- /dev/null +++ b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/rationale.py @@ -0,0 +1,21 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +# TODO(shared-module): Rationale models copied from experimentalist rationalizer.py; unify with rationalizer module. + +from pydantic import BaseModel, Field + + +class RationaleStep(BaseModel): + """One reasoning step in a task rationale.""" + + thought: str + action: str + observation: str = "" + + +class Rationale(BaseModel): + """Task-level context produced by the Rationalizer before trace analysis.""" + + task_name: str + steps: list[RationaleStep] = Field(default_factory=list) diff --git a/plugins/nemo-eval-author/src/nemo_eval_author_plugin/repository.py b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/repository.py new file mode 100644 index 00000000000..1c135bb1f3c --- /dev/null +++ b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/repository.py @@ -0,0 +1,438 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +# TODO(shared-module): exact copy of experimentalist components/repository.py (clone helpers for backend); unify into a shared package. + +"""Fetch an agent from a git repo and publish a candidate as a draft PR/MR. + +Fetching clones ``[@]`` into a local checkout (retaining ``.git`` so +it can serve as a push target). Publishing takes that checkout plus a directory of +updated agent files, creates a branch off the source ref, commits the files, pushes, +and opens a draft pull request (GitHub) or merge request (GitLab) via the ``gh`` / +``glab`` CLIs. Branching from the exact source ref keeps the published change +reproducible against the commit the agent came from. +""" + +from __future__ import annotations + +import logging +import re +import shutil +import subprocess +from fnmatch import fnmatch +from pathlib import Path +from urllib.parse import urlsplit + +from pydantic import BaseModel + +logger = logging.getLogger(__name__) + +# Upper bound (seconds) for any single git/gh/glab call during publishing, so a +# network stall or auth prompt can't hang the optimizer's awaited publish path. +_COMMAND_TIMEOUT = 120 + +# Matches the ``userinfo@`` of a URL (e.g. ``https://oauth2:@host``). Git clone URLs +# can embed credentials there, so we strip them before logging a URL. +_CREDENTIAL_RE = re.compile(r"://[^/@]+@") + +_AGENT_PATH_ERROR = "agent path must be a normalized relative POSIX path" +_AGENT_PATH_CHECKOUT_ERROR = "agent path must stay within the checkout without symlinks" + + +def _redact_url(url: str) -> str: + """Return *url* with any embedded ``userinfo@`` credentials masked, safe for logging.""" + return _CREDENTIAL_RE.sub("://***@", url) + + +def _validated_agent_path(agent_path: str) -> str: + """Return a safe relative POSIX agent path or raise a controlled error.""" + agent_path = agent_path.removesuffix("/").removeprefix("./") + if agent_path == ".": + return agent_path + components = agent_path.split("/") + if ( + not agent_path + or agent_path.startswith(("/", ":")) + or "\\" in agent_path + or any(ord(char) < 32 or ord(char) == 127 for char in agent_path) + or any(component in {"", ".", ".."} for component in components) + or any(component.casefold() == ".git" for component in components) + ): + raise ValueError(_AGENT_PATH_ERROR) + return agent_path + + +def pr_cli_for_repo_url(url: str) -> tuple[str, str] | None: + """Return the PR CLI and hostname for a supported git remote. + + Substring match on the host family so both github.com and self-hosted + GitLab (e.g. gitlab.example.com) are recognized. + """ + if "github" in url: + cli = "gh" + elif "gitlab" in url: + cli = "glab" + else: + return None + hostname = urlsplit(url).hostname if "://" in url else url.partition("@")[2].partition(":")[0] + return cli, hostname or "" + + +def split_agent_spec(spec: str) -> tuple[str, str]: + """Split an agent spec ``#`` into ``(core_spec, agent_path)``. + + The ``#`` fragment carries the agent's location within the repo (monorepo + support), so repo, rev, and sub-path all travel together on the one ``--agent`` value. + A spec with no fragment has agent_path ``"."`` (whole-repo agent). + """ + core, separator, fragment = spec.partition("#") + agent_path = fragment if separator else "." + return core, _validated_agent_path(agent_path) + + +# Glob patterns for files never copied into the published branch: generated/run-local +# files, VCS/tool dirs, and build artifacts. Matched with fnmatch against each name. +_EXCLUDE_GLOBS = ( + "metadata.json", + "harbor_wrapper.py", + "dind_environment.py", + "architecture.md", + ".git", + "__pycache__", + ".pytest_cache", + ".mypy_cache", + ".ruff_cache", + "*.pyc", + "*.pyo", +) + + +class AgentSource(BaseModel): + """Git provenance of the agent under test. + + Set when ``--agent`` is given as ``@[#]``. ``repo_url`` is the + clone URL (the PR/MR target remote), ``ref`` is the base branch/tag/commit the agent was + checked out from, and ``agent_path`` is the agent's location within the repo (``"."`` for + a whole-repo agent) — the subtree that archival/PR overlays and commits. + """ + + repo_url: str + ref: str + agent_path: str = "." + + +class AgentCloneError(ValueError): + """A git source could not be materialized.""" + + +def split_git_ref(spec: str) -> tuple[str, str | None]: + """Split a ``.git@`` spec into ``(url, ref)``. + + Locates the first ``.git@`` marker in the repository path, excluding URL + authority/userinfo. A spec without a path marker has no ref. + """ + marker = ".git@" + if "://" in spec: + try: + parsed = urlsplit(spec) + except (ValueError, UnicodeError): + return spec, None + idx = parsed.path.find(marker) + if idx == -1: + return spec, None + repo_path = parsed.path[: idx + len(".git")] + ref = parsed.path[idx + len(marker) :] + return parsed._replace(path=repo_path).geturl(), ref + + user_end = spec.find("@") + path_start = spec.find(":", user_end + 1) if user_end >= 0 else -1 + search_start = path_start + 1 if path_start >= 0 else 0 + idx = spec.find(marker, search_start) + if idx == -1: + return spec, None + return spec[: idx + len(".git")], spec[idx + len(marker) :] + + +def looks_like_git(spec: str) -> bool: + """Return True if *spec* looks like a git URL (vs a local path).""" + url, _ = split_git_ref(spec) + return "://" in url or url.startswith("git@") or url.endswith(".git") + + +def clone_agent_repo(spec: str, dest: Path, *, clone_depth: int | None = None) -> AgentSource: + """Clone ``[@][#]`` into *dest* (retaining ``.git``) and return provenance. + + The clone keeps ``.git``/``origin`` so *dest* can serve as the PR/MR push target. The whole + repo is cloned; ``agent_path`` (the ``#`` fragment, ``"."`` when omitted) records the agent's + sub-path within it for downstream overlay/commit. When no ``@ref`` is given, the checked-out + default branch is resolved and recorded. When ``clone_depth`` is given a shallow clone is made + (it must be deep enough to branch from ``ref``). Raises on git failure (callers decide how to + handle). + """ + core, agent_path = split_agent_spec(spec) + url, ref = split_git_ref(core) + dest = Path(dest) + if dest.exists(): + shutil.rmtree(dest) + dest.parent.mkdir(parents=True, exist_ok=True) + safe_url = _redact_url(url) + logger.info(f"[AGENT] cloning {safe_url} -> {dest}") + clone_cmd = ["git", "clone", "--quiet"] + if clone_depth is not None: + clone_cmd += ["--depth", str(clone_depth)] + clone_cmd += [url, str(dest)] + + def run_git(command: list[str], operation: str) -> subprocess.CompletedProcess[str]: + try: + return subprocess.run( # noqa: S603, S607 + command, + capture_output=True, + text=True, + check=True, + stdin=subprocess.DEVNULL, + timeout=_COMMAND_TIMEOUT, + ) + except subprocess.CalledProcessError as exc: + details = _redact_url((exc.stderr or exc.stdout or "").strip()) + suffix = f": {details}" if details else "" + raise AgentCloneError(f"{operation} failed for {safe_url} (exit status {exc.returncode}){suffix}") from None + except subprocess.TimeoutExpired: + raise AgentCloneError(f"{operation} timed out after {_COMMAND_TIMEOUT}s for {safe_url}") from None + except OSError as exc: + raise AgentCloneError(f"could not run 'git' during {operation} for {safe_url}: {exc}") from None + + run_git(clone_cmd, "git clone") + if ref: + run_git(["git", "-C", str(dest), "checkout", "--quiet", ref], "git checkout") + else: + ref = run_git( + ["git", "-C", str(dest), "rev-parse", "--abbrev-ref", "HEAD"], + "git rev-parse", + ).stdout.strip() + # repo_url is provenance (it becomes the candidate's source_link), so keep it + # credential-free; the conventional ssh ``git`` user is not a secret. + repo_url = url if url.startswith("ssh://git@") or "://" not in url else _redact_url(url) + return AgentSource(repo_url=repo_url, ref=ref, agent_path=agent_path) + + +class PRPublisherError(RuntimeError): + """Raised when publishing the winner PR/MR fails.""" + + +class PRPublisher: + """Open a draft PR/MR for the winning candidate against the agent's source repo. + + Args: + agent_dir: The local git checkout of the agent under test (its ``origin`` + remote is the PR/MR target). Must be a git work tree. + """ + + def __init__(self, *, agent_dir: Path) -> None: + self.agent_dir = Path(agent_dir).resolve() + + def _run(self, cmd: list[str], *, cwd: Path | None = None) -> str: + """Run a command and return stdout; raise :class:`PRPublisherError` on failure. + + Bounded by ``_COMMAND_TIMEOUT`` so a git/gh/glab call that blocks on the + network or an auth prompt can never hang the awaited publish path. + """ + command = _redact_url(" ".join(cmd)) + try: + result = subprocess.run( # noqa: S603 + cmd, + cwd=str(cwd or self.agent_dir), + capture_output=True, + text=True, + timeout=_COMMAND_TIMEOUT, + stdin=subprocess.DEVNULL, # never block on an interactive auth prompt + ) + except subprocess.TimeoutExpired: + raise PRPublisherError(f"command timed out after {_COMMAND_TIMEOUT}s: {command}") from None + except OSError as exc: # e.g. git/gh/glab not installed -> FileNotFoundError + raise PRPublisherError(f"could not run {cmd[0]!r}: {exc}") from None + if result.returncode != 0: + stderr = _redact_url(result.stderr.strip()) + raise PRPublisherError(f"command failed ({result.returncode}): {command}\n{stderr}") + return result.stdout.strip() + + # -- helpers -------------------------------------------------------------- + + def _origin_url(self) -> str: + return self._run(["git", "remote", "get-url", "origin"]) + + def _is_remote_branch(self, ref: str) -> bool: + """Return True if *ref* is a branch on origin (vs a tag or bare commit).""" + return bool(self._run(["git", "ls-remote", "--heads", "origin", ref])) + + def _default_branch(self) -> str: + """Resolve origin's default branch (e.g. ``main``) from its local symbolic HEAD. + + Reads ``refs/remotes/origin/HEAD``, which the fresh clone in + ``clone_agent_repo`` sets, so no extra network round-trip is needed. Strips + only the ``origin/`` prefix so slashed branch names (e.g. ``release/2026.07``) + survive intact. + """ + ref = self._run(["git", "rev-parse", "--abbrev-ref", "origin/HEAD"]) # e.g. "origin/main" + prefix = "origin/" + if not ref.startswith(prefix) or ref == "origin/HEAD": + raise PRPublisherError(f"could not resolve origin default branch (got {ref!r})") + return ref[len(prefix) :] + + @staticmethod + def _overlay(winner_dir: Path, dest: Path) -> None: + """Copy the winner's agent files over *dest*, excluding files matching ``_EXCLUDE_GLOBS``. + + Exclusions apply at the top level and recursively (via ``shutil.ignore_patterns``), + so nested ``__pycache__``/``*.pyc`` and the like never reach the published branch. + """ + dest.mkdir(parents=True, exist_ok=True) + ignore = shutil.ignore_patterns(*_EXCLUDE_GLOBS) + for item in winner_dir.iterdir(): + if any(fnmatch(item.name, pat) for pat in _EXCLUDE_GLOBS): + continue + target = dest / item.name + if item.is_dir(): + shutil.copytree(item, target, dirs_exist_ok=True, ignore=ignore) + else: + shutil.copy2(item, target) + + def _validated_subtree(self, agent_path: str) -> Path: + """Resolve an agent subtree without following a path component symlink.""" + agent_path = _validated_agent_path(agent_path) + if agent_path == ".": + return self.agent_dir + subtree = self.agent_dir + try: + for component in agent_path.split("/"): + subtree /= component + if subtree.is_symlink(): + raise ValueError(_AGENT_PATH_CHECKOUT_ERROR) + if not subtree.resolve(strict=False).is_relative_to(self.agent_dir): + raise ValueError(_AGENT_PATH_CHECKOUT_ERROR) + except OSError: + raise ValueError(_AGENT_PATH_CHECKOUT_ERROR) from None + return subtree + + def _snapshot_subtree(self, src_dir: Path, agent_path: str) -> None: + """Replace the ``agent_path`` subtree with *src_dir*'s files (deletions captured). + + ``git rm`` the existing subtree first so files the candidate removed don't linger, + using literal pathspec semantics, then overlay the candidate's files. ``agent_path`` + ``"."`` targets the whole repo. + """ + self._validated_subtree(agent_path) + self._run(["git", "--literal-pathspecs", "rm", "-r", "-q", "-f", "--ignore-unmatch", "--", agent_path]) + subtree = self._validated_subtree(agent_path) + stale_items = ( + (item for item in subtree.iterdir() if item.name.casefold() != ".git") if agent_path == "." else (subtree,) + ) + for item in stale_items: + if item.is_dir() and not item.is_symlink(): + shutil.rmtree(item) + else: + item.unlink(missing_ok=True) + self._overlay(Path(src_dir), subtree) + self._run(["git", "--literal-pathspecs", "add", "-A", "--", agent_path]) + + # -- public API ----------------------------------------------------------- + + def push_branch( + self, + *, + src_dir: Path, + branch: str, + base_ref: str, + message: str, + agent_path: str = ".", + ) -> bool: + """Archive one candidate: push *src_dir* as *branch* cut from *base_ref*. + + Snapshots the ``agent_path`` subtree (``git rm`` + overlay, capturing deletions). + Skip-if-exists and idempotent without force-pushing: returns ``False`` when *branch* + already exists on origin or the result is byte-identical to *base_ref* (empty diff), + ``True`` when a new branch was pushed. Used to archive every candidate to its own + branch during a run. + """ + agent_path = _validated_agent_path(agent_path) + if self._is_remote_branch(branch): + logger.info(f"[PUSH] branch {branch} already exists on origin; skipping") + return False + self._run(["git", "fetch", "origin", base_ref]) + self._run(["git", "checkout", "-B", branch, "FETCH_HEAD"]) + self._snapshot_subtree(Path(src_dir), agent_path) + if not self._run(["git", "--literal-pathspecs", "status", "--porcelain", "--", agent_path]): + logger.info(f"[PUSH] {branch} identical to {base_ref}; skipping") + return False + self._run(["git", "--literal-pathspecs", "commit", "--only", "-m", message, "--", agent_path]) + self._run(["git", "push", "-u", "origin", branch]) + return True + + def publish( + self, + *, + winner_dir: Path, + branch: str, + base_ref: str, + title: str, + body: str, + draft: bool = True, + agent_path: str = ".", + base_branch_override: str | None = None, + labels: list[str] | None = None, + ) -> str: + """Create a branch with the winner's code and open a draft PR/MR. + + Args: + winner_dir: Directory holding the winning candidate's agent files. + branch: Name of the branch to create and push. + base_ref: Source ref the branch is created from. Used as the PR/MR base + when it is a branch; for a tag/commit source the base falls back to + the remote's default branch (a PR/MR base must be a branch). + title: PR/MR title. + body: PR/MR description. + draft: Open as a draft (default True). + agent_path: In-repo sub-path the winner's files overlay (``"."`` = whole repo). + base_branch_override: Explicit PR/MR base branch; overrides the ``base_ref``/default. + labels: Optional labels applied to the PR/MR. + + Returns: + The created PR/MR URL. + """ + agent_path = _validated_agent_path(agent_path) + origin = self._origin_url() + target = pr_cli_for_repo_url(origin) + if target is None: + raise PRPublisherError(f"unsupported remote host for PR creation: {_redact_url(origin)}") + cli, _ = target + + # Branch from the exact source ref (branch, tag, or commit) via FETCH_HEAD so + # the change is reproducible against the commit the agent came from. + self._run(["git", "fetch", "origin", base_ref]) + self._run(["git", "checkout", "-B", branch, "FETCH_HEAD"]) + self._snapshot_subtree(Path(winner_dir), agent_path) + # Nothing to commit → no PR (winner identical to base). + status = self._run(["git", "--literal-pathspecs", "status", "--porcelain", "--", agent_path]) + if not status: + logger.info("[TERMINATOR] winner is identical to base ref; skipping PR creation") + return "" + self._run(["git", "--literal-pathspecs", "commit", "--only", "-m", title, "--", agent_path]) + self._run(["git", "push", "--force-with-lease", "-u", "origin", branch]) + + # A PR/MR base must be a branch: an explicit override wins; else target base_ref when it + # is a branch, else the remote's default branch (the source may be a tag/commit). + pr_base = base_branch_override or (base_ref if self._is_remote_branch(base_ref) else self._default_branch()) + if cli == "gh": + cmd = ["gh", "pr", "create", "--base", pr_base, "--head", branch, "--title", title, "--body", body] + if draft: + cmd.append("--draft") + for label in labels or []: + cmd += ["--label", label] + return self._run(cmd) + # gitlab + cmd = ["glab", "mr", "create", "--source-branch", branch, "--target-branch", pr_base] + cmd += ["--title", title, "--description", body, "--yes"] + if draft: + cmd.append("--draft") + if labels: + cmd += ["--label", ",".join(labels)] + return self._run(cmd) diff --git a/plugins/nemo-eval-author/src/nemo_eval_author_plugin/tools.py b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/tools.py new file mode 100644 index 00000000000..7e31562d5a4 --- /dev/null +++ b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/tools.py @@ -0,0 +1,221 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +# TODO(shared-module): exact copy of experimentalist components/tools.py; unify into a shared package. + +import json +import logging +from collections.abc import Sequence +from pathlib import Path + +from nemo_eval_author_plugin.holdout_utils import ( + BLOCKED_MESSAGE, + DEFAULT_BLOCKED_PATHS, +) +from nemo_eval_author_plugin.workspace_entities import Candidate +from nooa.tools import ShellResult, ShellTools + +logger = logging.getLogger(__name__) + + +class GuardedShellTools(ShellTools): + """``ShellTools`` that refuses shell access to held-out dataset splits. + + The optimizer's sub-agents run CodeAct with a raw shell rooted at the + workspace, so they could read the held-out validation ground truth straight off disk + (``cat dataset/validation/.../tests/test.sh``) and overfit the selection + signal. This intercepts the ``ShellTools.run`` chokepoint and refuses any command that + references a held-out split. + + It is a tripwire, not a wall: a substring match can be bypassed by a + determined caller (globs, shell variables, ``find / -name test.sh``). It + defeats casual and accidental access and logs every attempt. Pair it with the + ``DatasetTool`` split guard, which covers the separate ``Path.read_text`` + channel that never touches bash. + + Args: + cwd: Working directory forwarded to ``ShellTools``. + blocked_paths: Token substrings whose presence in a command causes the + command to be rejected; defaults to ``DEFAULT_BLOCKED_PATHS``. + init_command: Optional command run once when the shell session starts. + + """ + + def __init__( + self, + cwd: str | Path = ".", + *, + blocked_paths: Sequence[str] = DEFAULT_BLOCKED_PATHS, + init_command: str | None = None, + ) -> None: + """Initialize with a working directory and an optional set of blocked path tokens.""" + super().__init__(cwd=str(cwd), init_command=init_command) + self._blocked_paths = tuple(blocked_paths) + + def is_blocked(self, command: str) -> bool: + """Return True if the command references any held-out split path token. + + Args: + command: shell command string to inspect. + + Returns: + bool: True if any blocked path token appears in the command. + + """ + return any(token in command for token in self._blocked_paths) + + async def run( + self, + command: str, + *, + stdin: str | None = None, + timeout: float = 30.0, + ) -> ShellResult: + """Execute a shell command, blocking access to held-out dataset splits. + + Args: + command: Shell command string to execute. + stdin: Optional text piped to stdin. + timeout: Timeout in seconds; forwarded to ``ShellTools.run``. + + Returns: + ShellResult: The result of the command, or a synthetic failure result + with ``returncode=1`` and ``stderr=BLOCKED_MESSAGE`` when the command + references a held-out split path. + + """ + if self.is_blocked(command): + logger.warning(f"GuardedShellTools blocked held-out access: {command}") + return ShellResult(stdout="", stderr=BLOCKED_MESSAGE, returncode=1) + return await super().run(command, stdin=stdin, timeout=timeout) + + +class WorkspaceTool: + """Navigate the eval-and-optimize workspace: agents and analysis files. + + Args: + workspace: Absolute path to the eval-and-optimize workspace root. + + """ + + def __init__(self, workspace: Path) -> None: + """Initialize the workspace tool for the given workspace.""" + self.workspace = Path(workspace).resolve() + self._eval_root = self.workspace / "eval-and-optimize" + self._agents_root = self._eval_root / "agents" + self._analysis_root = self._eval_root / "analysis" + + # ── Navigation ──────────────────────────────────────────────────────────── + + def list_agents(self) -> list[str]: + """Return all agent IDs that have a ``metadata.json``, sorted naturally. + + Returns: + list[str]: agent IDs in natural numeric order. + + """ + if not self._agents_root.exists(): + return [] + ids = [d.name for d in self._agents_root.iterdir() if d.is_dir() and (d / "metadata.json").exists()] + return sorted( + ids, + key=lambda s: int(s.split("-")[1]) if s.split("-")[1].isdigit() else 0, + ) + + def get_agent_path(self, agent_id: str) -> str: + """Return the absolute path to an agent's directory. + + Args: + agent_id: the agent to locate. + + Returns: + str: absolute path to the agent's directory under ``eval-and-optimize/agents/``. + + """ + return str(self._agents_root / agent_id) + + # ── File listing ────────────────────────────────────────────────────────── + + def list_agent_files(self, agent_id: str) -> list[str]: + """Return all files in an agent's directory (relative paths). + + Args: + agent_id: the agent to inspect. + + Returns: + list[str]: sorted relative paths of all files in the agent directory. + + """ + agent_dir = self._agents_root / agent_id + if not agent_dir.exists(): + return [] + return sorted(str(f.relative_to(agent_dir)) for f in agent_dir.rglob("*") if f.is_file()) + + # ── File reading ────────────────────────────────────────────────────────── + + def read_agent_file(self, agent_id: str, relative_path: str, limit: int | None = 8000) -> str: + """Read any file inside an agent's directory by relative path. + + Args: + agent_id: e.g. "agent-2" + relative_path: e.g. "main.py", "metadata.json" + limit: max characters (default 8000, None = no truncation) + + Returns: + str: file contents, truncated to ``limit`` characters if needed, or empty + string if the file is not found. + + """ + return self._read_file(self._agents_root / agent_id / relative_path, limit=limit) + + def read_analysis_file(self, path_or_round: "str | int", limit: int | None = 8000) -> str: + """Read a file from eval-and-optimize/analysis/. + + Args: + path_or_round: round number (e.g. 1 → round-1.md) or filename. + limit: max characters (default 8000, None = no truncation) + + Returns: + str: file contents, truncated to ``limit`` characters if needed, or empty + string if the file is not found. + + """ + if isinstance(path_or_round, int): + path = self._analysis_root / f"round-{path_or_round}.md" + else: + path = self._analysis_root / path_or_round + return self._read_file(path, limit=limit) + + def get_metadata(self, agent_id: str) -> Candidate: + """Read ``agents/{agent_id}/metadata.json`` as a :class:`Candidate`. + + Args: + agent_id: the agent whose metadata to read. + + Returns: + Candidate: parsed candidate, or a minimal candidate with default + values if the file is missing or unreadable. + + """ + path = self._agents_root / agent_id / "metadata.json" + if not path.exists(): + raise FileNotFoundError(f"Metadata file not found: {path}") + data = json.loads(path.read_text()) + candidate = Candidate.model_validate(data) + entity_id = data.get("id", "") + if entity_id: + candidate._id = entity_id # type: ignore[attr-defined] + return candidate + + # ── Private helpers ─────────────────────────────────────────────────────── + + def _read_file(self, path: Path, limit: int | None = 4000) -> str: + if not path.exists(): + return "" + try: + text = path.read_text().strip() + if limit is not None and len(text) > limit: + text = text[:limit] + f"... [truncated, {len(text)} chars total]" + return text + except (OSError, UnicodeDecodeError): + return "" diff --git a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/trace_analyzer.py b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/trace_analyzer.py similarity index 95% rename from plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/trace_analyzer.py rename to plugins/nemo-eval-author/src/nemo_eval_author_plugin/trace_analyzer.py index 319d0b4dfb1..e171391cf04 100644 --- a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/trace_analyzer.py +++ b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/trace_analyzer.py @@ -11,8 +11,14 @@ from typing import Any, Literal from urllib.parse import unquote, urlparse -from nemo_experimentalist_plugin.experimentalist.components.evaluator import MetricResult, Task, TrialResult -from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import DependencyRuntime +from nemo_eval_author_plugin import cache +from nemo_eval_author_plugin.evaluator import MetricResult, Task, TrialResult +from nemo_eval_author_plugin.evaluator.models import DependencyRuntime +from nemo_eval_author_plugin.model_config import get_fast_model, get_smart_model +from nemo_eval_author_plugin.rationale import Rationale +from nemo_eval_author_plugin.tools import GuardedShellTools, WorkspaceTool +from nemo_eval_author_plugin.trace_explorer import SearchResult, SessionData, SessionSummary, TraceExplorer, TurnInfo +from nemo_eval_author_plugin.util import load_framework_skills from nemo_insights_plugin.entities import Insight from nemo_platform import AsyncNeMoPlatform from nooa import Agent, CodeActStrategy, strategy @@ -24,13 +30,6 @@ from nooa.tools import Match, TodoManager from pydantic import BaseModel, Field -from . import cache -from .model_config import get_fast_model, get_smart_model -from .rationalizer import Rationale -from .tools import GuardedShellTools, WorkspaceTool -from .trace_explorer import SearchResult, SessionData, SessionSummary, TraceExplorer, TurnInfo -from .util import load_framework_skills - class TraceAnalyzerConfig(BaseModel): """Configuration for TraceAnalyzer tuning parameters.""" diff --git a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/trace_explorer.py b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/trace_explorer.py similarity index 99% rename from plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/trace_explorer.py rename to plugins/nemo-eval-author/src/nemo_eval_author_plugin/trace_explorer.py index a7f26fa2994..2ef9cac105f 100644 --- a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/trace_explorer.py +++ b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/trace_explorer.py @@ -12,7 +12,7 @@ from pathlib import Path from typing import Any, Literal, cast -from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import ResourceRef +from nemo_eval_author_plugin.evaluator.models import ResourceRef from nemo_platform import AsyncNeMoPlatform from pydantic import BaseModel, ConfigDict, Field diff --git a/plugins/nemo-eval-author/src/nemo_eval_author_plugin/util.py b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/util.py new file mode 100644 index 00000000000..6e9f41793c9 --- /dev/null +++ b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/util.py @@ -0,0 +1,19 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +from pathlib import Path + +from nooa import TextSkill +from nooa.skill_registry import SkillRegistry + + +def load_framework_skills(registry: SkillRegistry, dirs: list[Path]) -> None: + """Register TextSkills from user-provided framework skill directories.""" + for skill_dir in dirs: + if not skill_dir.is_dir(): + continue + if (skill_dir / "SKILL.md").exists() or (skill_dir / "skill.md").exists(): + skill = TextSkill(path=skill_dir) + registry.register(f"ext.{skill.id}", skill) + registry.load(["cmd.*", "ext.*"]) + registry.activate(["cmd.*", "ext.*"]) diff --git a/plugins/nemo-eval-author/src/nemo_eval_author_plugin/workspace_entities.py b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/workspace_entities.py new file mode 100644 index 00000000000..6fc7b60bcd1 --- /dev/null +++ b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/workspace_entities.py @@ -0,0 +1,35 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +# TODO(shared-module): exact copy of experimentalist Candidate entity for WorkspaceTool; unify with experimentalist entities. + +from typing import Any, Sequence + +from nemo_eval_author_plugin.evaluator.models import TrialResult +from nemo_platform_plugin.entity import NemoEntity +from pydantic import Field + + +class Candidate(NemoEntity, entity_type="candidate"): + """Candidate metadata used by WorkspaceTool when reading agent metadata.json.""" + + workspace: str = Field(default="default") + run_id: str = Field(...) + label: str = Field(...) + ancestor: str | None = Field(default=None) + round: int = Field(...) + optimization: str = Field(...) + optimization_type: str | None = Field(default=None) + task_ids: list[str] = Field(default_factory=list) + train_reward: dict[str, float] | None = Field(default=None) + train_reward_details: Sequence[TrialResult] | None = Field(default=None) + validation_reward: dict[str, float] | None = Field(default=None) + validation_reward_details: Sequence[TrialResult] | None = Field(default=None) + insight_reward: dict[str, float] | None = Field(default=None) + insight_reward_details: Sequence[TrialResult] | None = Field(default=None) + insight_suite_identity: str | None = Field(default=None) + insight_metric_keys: list[str] | None = Field(default=None) + validation_trajectory_reward: dict[str, float] | None = Field(default=None) + validation_trajectory_reward_details: dict[str, Any] | None = Field(default=None) + killed_round: int | None = Field(default=None) + artifacts: dict[str, Any] = Field(default_factory=dict, exclude=True) diff --git a/plugins/nemo-experimentalist/tests/experimentalist/test_dataset_staging.py b/plugins/nemo-eval-author/tests/test_dataset_staging.py similarity index 63% rename from plugins/nemo-experimentalist/tests/experimentalist/test_dataset_staging.py rename to plugins/nemo-eval-author/tests/test_dataset_staging.py index eb813d2c6a0..2fcb1de1f89 100644 --- a/plugins/nemo-experimentalist/tests/experimentalist/test_dataset_staging.py +++ b/plugins/nemo-eval-author/tests/test_dataset_staging.py @@ -4,17 +4,13 @@ from pathlib import Path from types import SimpleNamespace from typing import cast -from unittest.mock import AsyncMock import pytest -from nemo_experimentalist_plugin.experimentalist.components import loop as loop_module -from nemo_experimentalist_plugin.experimentalist.components.dataset_staging import ( +from nemo_eval_author_plugin.dataset_staging import ( stage_eval_author_inputs, stage_task_template, ) -from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import DatasetRef -from nemo_experimentalist_plugin.experimentalist.components.loop import EvolutionaryOptimizer -from nemo_experimentalist_plugin.resolve import EvolutionaryOptimizerConfig +from nemo_eval_author_plugin.evaluator.models import DatasetRef from nemo_platform import AsyncNeMoPlatform @@ -157,93 +153,3 @@ async def download(self, *, remote_path: str, local_path: str, workspace: str) - "workspace": "workspace-a", }, ] - - -@pytest.mark.asyncio -async def test_insight_run_stages_inputs_before_eval_author( - monkeypatch: pytest.MonkeyPatch, - tmp_path: Path, -) -> None: - source = tmp_path / "source" - train = source / "train" - validation = source / "validation" - template = source / "task-template" - _write_tree(train, "train source") - _write_tree(validation, "validation source") - _write_tree(template, "template source") - experiment = tmp_path / "experiment" - captured: dict[str, Path] = {} - - class RecordingDatasetFactory: - def build_dataset(self, evaluator_type: str, ref: DatasetRef) -> SimpleNamespace: - return SimpleNamespace(ref=ref) - - def build_task_template(self, evaluator_type: str, ref: DatasetRef) -> SimpleNamespace: - return SimpleNamespace(uri=ref.uri) - - class MutatingEvalAuthor: - def __init__(self, **kwargs: object) -> None: - pass - - async def run( - self, - *, - task_template: SimpleNamespace, - train_dataset: SimpleNamespace, - validation_dataset: SimpleNamespace, - **kwargs: object, - ) -> None: - captured["train"] = Path(train_dataset.ref.uri) - captured["validation"] = Path(validation_dataset.ref.uri) - captured["template"] = Path(task_template.uri) - (captured["train"] / "task-1" / "tests" / "test.sh").write_text("curated", encoding="utf-8") - (captured["template"].parent / "generated-task").mkdir() - raise RuntimeError("stop after eval_author") - - monkeypatch.setattr( - loop_module, - "EvaluatorFactory", - lambda: SimpleNamespace(build_evaluator=lambda *args, **kwargs: object()), - ) - monkeypatch.setattr(loop_module, "DatasetFactory", RecordingDatasetFactory) - monkeypatch.setattr(loop_module, "EvalAuthor", MutatingEvalAuthor) - monkeypatch.setattr( - EvolutionaryOptimizer, - "_init_structure", - lambda self: (experiment / "agents", experiment / "analysis", experiment / "results"), - ) - - backend = SimpleNamespace( - client=object(), - get_insight=AsyncMock(return_value=SimpleNamespace(agent=str(tmp_path / "agent"))), - get_agent_code=AsyncMock(), - ) - config = EvolutionaryOptimizerConfig() - optimizer = object.__new__(EvolutionaryOptimizer) - optimizer.working_dir = experiment - optimizer.config = config - optimizer.shell = SimpleNamespace(close=AsyncMock()) - deps = SimpleNamespace( - backend=backend, - workspace="default", - config=config, - evaluator_type="harbor", - train_dataset=DatasetRef(uri=str(train)), - validation_dataset=DatasetRef(uri=str(validation)), - task_template=DatasetRef(uri=str(template)), - insight="insight-1", - agent=tmp_path / "agent", - agent_spec=None, - ) - - with pytest.raises(RuntimeError, match="stop after eval_author"): - await optimizer.run(deps) - - optimizer.shell.close.assert_awaited_once() - assert captured == { - "train": experiment / "dataset" / "train", - "validation": experiment / "dataset" / "validation", - "template": experiment / "dataset" / "task-template", - } - assert (train / "task-1" / "tests" / "test.sh").read_text(encoding="utf-8") == "train source" - assert not (template.parent / "generated-task").exists() diff --git a/plugins/nemo-experimentalist/tests/test_eval_author_agent.py b/plugins/nemo-eval-author/tests/test_eval_author_agent.py similarity index 95% rename from plugins/nemo-experimentalist/tests/test_eval_author_agent.py rename to plugins/nemo-eval-author/tests/test_eval_author_agent.py index 943b9775bbc..4a721c10576 100644 --- a/plugins/nemo-experimentalist/tests/test_eval_author_agent.py +++ b/plugins/nemo-eval-author/tests/test_eval_author_agent.py @@ -12,17 +12,16 @@ from typing import Any, cast import pytest -from nemo_experimentalist_plugin.eval_author import agent as eval_author_module -from nemo_experimentalist_plugin.eval_author.agent import EvalAuthor -from nemo_experimentalist_plugin.eval_author.models import EvalAuthorConfig -from nemo_experimentalist_plugin.experimentalist.components.evaluator import ( +from nemo_eval_author_plugin.eval_author import agent as eval_author_module +from nemo_eval_author_plugin.eval_author.agent import EvalAuthor +from nemo_eval_author_plugin.eval_author.models import EvalAuthorConfig +from nemo_eval_author_plugin.evaluator import ( Dataset, DatasetValidationError, Task, TrialResult, ) -from nemo_experimentalist_plugin.experimentalist.components.loop import EvolutionaryOptimizerConfig -from nemo_experimentalist_plugin.experimentalist.components.trace_analyzer import ( +from nemo_eval_author_plugin.trace_analyzer import ( Diagnostic, TraceAnalyzerConfig, ) @@ -679,21 +678,7 @@ async def test_run_raises_after_validation_repair_budget_is_exhausted( assert insight_dataset.validate_calls == 2 -def test_evolutionary_optimizer_uses_top_level_eval_author_config() -> None: - config = EvolutionaryOptimizerConfig().eval_author - - assert type(config) is EvalAuthorConfig - assert config.max_validation_repair_attempts == 5 +def test_eval_author_config_bounds_validation_repair_attempts() -> None: assert EvalAuthorConfig(max_validation_repair_attempts=10).max_validation_repair_attempts == 10 with pytest.raises(ValueError, match="less than or equal to 10"): EvalAuthorConfig(max_validation_repair_attempts=11) - - -def test_evolutionary_optimizer_tolerates_unknown_eval_author_config() -> None: - config = EvolutionaryOptimizerConfig.model_validate({"eval_author": {"bogus": 1}}) - assert type(config.eval_author) is EvalAuthorConfig - - -def test_evolutionary_optimizer_rejects_legacy_curator_config() -> None: - with pytest.raises(ValueError, match="'curator' was renamed to 'eval_author'"): - EvolutionaryOptimizerConfig.model_validate({"curator": {"max_traces": 1}}) diff --git a/plugins/nemo-experimentalist/tests/test_eval_author_materialization.py b/plugins/nemo-eval-author/tests/test_eval_author_materialization.py similarity index 97% rename from plugins/nemo-experimentalist/tests/test_eval_author_materialization.py rename to plugins/nemo-eval-author/tests/test_eval_author_materialization.py index da415afd0ba..0e3a7bb1c77 100644 --- a/plugins/nemo-experimentalist/tests/test_eval_author_materialization.py +++ b/plugins/nemo-eval-author/tests/test_eval_author_materialization.py @@ -10,10 +10,10 @@ from pathlib import Path import pytest -from nemo_experimentalist_plugin.eval_author import materialization as materialization_module -from nemo_experimentalist_plugin.eval_author.materialization import InsightSuite -from nemo_experimentalist_plugin.experimentalist.components.evaluator import Task -from nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor import HarborDataset +from nemo_eval_author_plugin.eval_author import materialization as materialization_module +from nemo_eval_author_plugin.eval_author.materialization import InsightSuite +from nemo_eval_author_plugin.evaluator import Task +from nemo_eval_author_plugin.evaluator.harbor import HarborDataset def _write_template(root: Path) -> Task: diff --git a/plugins/nemo-experimentalist/tests/test_eval_author_run.py b/plugins/nemo-eval-author/tests/test_eval_author_run.py similarity index 94% rename from plugins/nemo-experimentalist/tests/test_eval_author_run.py rename to plugins/nemo-eval-author/tests/test_eval_author_run.py index cc5625765fd..4dae6117d40 100644 --- a/plugins/nemo-experimentalist/tests/test_eval_author_run.py +++ b/plugins/nemo-eval-author/tests/test_eval_author_run.py @@ -6,9 +6,9 @@ from typing import Any, Literal import pytest -from nemo_experimentalist_plugin.eval_author import run as eval_author_run -from nemo_experimentalist_plugin.eval_author.models import EvalAuthorConfig, EvalAuthorResult -from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import Dataset, DatasetRef, Task +from nemo_eval_author_plugin.eval_author import run as eval_author_run +from nemo_eval_author_plugin.eval_author.models import EvalAuthorConfig, EvalAuthorResult +from nemo_eval_author_plugin.evaluator.models import Dataset, DatasetRef, Task from nemo_insights_plugin.entities import Insight @@ -147,7 +147,7 @@ def build_eval_author_agent(*, experiment_dir: Path, config: EvalAuthorConfig) - return eval_author monkeypatch.setattr(eval_author_run, "make_client", lambda base_url: client) - monkeypatch.setattr(eval_author_run, "make_experimentalist_backend", make_backend) + monkeypatch.setattr(eval_author_run, "make_eval_author_backend", make_backend) monkeypatch.setattr(eval_author_run, "DatasetFactory", lambda: dataset_factory) monkeypatch.setattr(eval_author_run, "build_eval_author_agent", build_eval_author_agent) monkeypatch.setattr(eval_author_run, "_enable_litellm_drop_params", lambda: litellm_calls.append(True)) @@ -227,7 +227,7 @@ async def download(self, *, remote_path: str, local_path: str, workspace: str) - eval_author = FakeEvalAuthor() monkeypatch.setattr(eval_author_run, "make_client", lambda base_url: client) - monkeypatch.setattr(eval_author_run, "make_experimentalist_backend", lambda **_: backend) + monkeypatch.setattr(eval_author_run, "make_eval_author_backend", lambda **_: backend) monkeypatch.setattr(eval_author_run, "DatasetFactory", lambda: dataset_factory) monkeypatch.setattr(eval_author_run, "build_eval_author_agent", lambda **_: eval_author) monkeypatch.setattr(eval_author_run, "_enable_litellm_drop_params", lambda: None) @@ -269,7 +269,7 @@ async def test_run_eval_author_uses_agent_override( eval_author = FakeEvalAuthor() monkeypatch.setattr(eval_author_run, "make_client", lambda base_url: client) - monkeypatch.setattr(eval_author_run, "make_experimentalist_backend", lambda **_: backend) + monkeypatch.setattr(eval_author_run, "make_eval_author_backend", lambda **_: backend) monkeypatch.setattr(eval_author_run, "DatasetFactory", lambda: dataset_factory) monkeypatch.setattr(eval_author_run, "build_eval_author_agent", lambda **_: eval_author) monkeypatch.setattr(eval_author_run, "_enable_litellm_drop_params", lambda: None) @@ -304,7 +304,7 @@ def fail_backend_creation(**_: object) -> object: raise RuntimeError("backend creation failed") monkeypatch.setattr(eval_author_run, "make_client", lambda base_url: client) - monkeypatch.setattr(eval_author_run, "make_experimentalist_backend", fail_backend_creation) + monkeypatch.setattr(eval_author_run, "make_eval_author_backend", fail_backend_creation) with pytest.raises(RuntimeError, match="backend creation failed"): await eval_author_run.run_eval_author( diff --git a/plugins/nemo-experimentalist/tests/experimentalist/test_evaluator_base.py b/plugins/nemo-eval-author/tests/test_evaluator_base.py similarity index 98% rename from plugins/nemo-experimentalist/tests/experimentalist/test_evaluator_base.py rename to plugins/nemo-eval-author/tests/test_evaluator_base.py index 9f3fc4b3863..979dfc9d165 100644 --- a/plugins/nemo-experimentalist/tests/experimentalist/test_evaluator_base.py +++ b/plugins/nemo-eval-author/tests/test_evaluator_base.py @@ -6,7 +6,7 @@ from typing import Sequence import pytest -from nemo_experimentalist_plugin.experimentalist.components.evaluator import ( +from nemo_eval_author_plugin.evaluator import ( Dataset, Evaluator, EvaluatorConfig, diff --git a/plugins/nemo-experimentalist/tests/experimentalist/test_evaluator_factory.py b/plugins/nemo-eval-author/tests/test_evaluator_factory.py similarity index 92% rename from plugins/nemo-experimentalist/tests/experimentalist/test_evaluator_factory.py rename to plugins/nemo-eval-author/tests/test_evaluator_factory.py index eb7fea03535..bdbf811813d 100644 --- a/plugins/nemo-experimentalist/tests/experimentalist/test_evaluator_factory.py +++ b/plugins/nemo-eval-author/tests/test_evaluator_factory.py @@ -6,19 +6,19 @@ from typing import Sequence import pytest -from nemo_experimentalist_plugin.experimentalist.components.evaluator import DatasetRef -from nemo_experimentalist_plugin.experimentalist.components.evaluator.base import ( +from nemo_eval_author_plugin.evaluator import DatasetRef +from nemo_eval_author_plugin.evaluator.base import ( Dataset, Evaluator, EvaluatorConfig, TrialResult, ) -from nemo_experimentalist_plugin.experimentalist.components.evaluator.factory import DatasetFactory, EvaluatorFactory -from nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor import ( +from nemo_eval_author_plugin.evaluator.factory import DatasetFactory, EvaluatorFactory +from nemo_eval_author_plugin.evaluator.harbor import ( HarborEvaluator, HarborEvaluatorConfig, ) -from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import Task +from nemo_eval_author_plugin.evaluator.models import Task _UNSUPPORTED_TYPE = "unsupported" _SUPPORTED_TYPE = "concrete" diff --git a/plugins/nemo-experimentalist/tests/experimentalist/test_evaluator_harbor.py b/plugins/nemo-eval-author/tests/test_evaluator_harbor.py similarity index 95% rename from plugins/nemo-experimentalist/tests/experimentalist/test_evaluator_harbor.py rename to plugins/nemo-eval-author/tests/test_evaluator_harbor.py index f869a74586a..952b2056d65 100644 --- a/plugins/nemo-experimentalist/tests/experimentalist/test_evaluator_harbor.py +++ b/plugins/nemo-eval-author/tests/test_evaluator_harbor.py @@ -10,7 +10,7 @@ from types import SimpleNamespace import pytest -from nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor import ( +from nemo_eval_author_plugin.evaluator.harbor import ( _TRACE_ARTIFACT_DESTINATION, _TRACE_ARTIFACT_SOURCE, HarborDataset, @@ -32,7 +32,7 @@ _trial_resources, _with_trace_artifact, ) -from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import ( +from nemo_eval_author_plugin.evaluator.models import ( CommandSpec, Dataset, DatasetRef, @@ -720,7 +720,7 @@ async def create(cls, config): async def run(self): return SimpleNamespace(id="job-id", stats=FakeStats()) - monkeypatch.setattr("nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor.Job", FakeJob) + monkeypatch.setattr("nemo_eval_author_plugin.evaluator.harbor.Job", FakeJob) result = await evaluator.run( agent=tmp_path / "agent", @@ -845,7 +845,7 @@ async def test_harbor_evaluator_rejects_invalid_python_verifiers_before_job_crea dataset = HarborDataset.from_path(dataset_dir) fake_job = _recording_job(tmp_path / "jobs" / "preflight") - monkeypatch.setattr("nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor.Job", fake_job) + monkeypatch.setattr("nemo_eval_author_plugin.evaluator.harbor.Job", fake_job) compile_calls = 0 original_compile = compile @@ -900,7 +900,7 @@ async def create_process(*_args, **_kwargs): monkeypatch.setenv("NVIDIA_API_KEY", "secret") monkeypatch.setattr("asyncio.create_subprocess_exec", create_process) monkeypatch.setattr( - "nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor._SHELL_SYNTAX_TIMEOUT_SEC", + "nemo_eval_author_plugin.evaluator.harbor._SHELL_SYNTAX_TIMEOUT_SEC", 0.01, ) @@ -966,7 +966,7 @@ async def test_harbor_evaluator_accepts_valid_python_verifier( _write(task_dir / "tests" / "check.py", "def check():\n return True\n") dataset = HarborDataset.from_path(task_dir) fake_job = _recording_job(tmp_path / "jobs" / "valid-python") - monkeypatch.setattr("nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor.Job", fake_job) + monkeypatch.setattr("nemo_eval_author_plugin.evaluator.harbor.Job", fake_job) trials = await HarborEvaluator()._run(agent_dir, dataset, HarborEvaluatorConfig()) @@ -988,7 +988,7 @@ async def test_harbor_evaluator_rejects_invalid_configured_test_sh_before_job_cr _write(task_dir / "test" / "test.sh", "if true; then\n echo broken\n") dataset = HarborDataset.from_path(task_dir) fake_job = _recording_job(tmp_path / "jobs" / "invalid-shell") - monkeypatch.setattr("nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor.Job", fake_job) + monkeypatch.setattr("nemo_eval_author_plugin.evaluator.harbor.Job", fake_job) with pytest.raises(HarborVerifierValidationError) as exc_info: await HarborEvaluator()._run(agent_dir, dataset, HarborEvaluatorConfig()) @@ -1013,7 +1013,7 @@ async def test_harbor_evaluator_accepts_valid_legacy_test_sh( _write(task_dir / "test" / "test.sh", "if true; then\n echo valid\nfi\n") dataset = HarborDataset.from_path(task_dir) fake_job = _recording_job(tmp_path / "jobs" / "valid-shell") - monkeypatch.setattr("nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor.Job", fake_job) + monkeypatch.setattr("nemo_eval_author_plugin.evaluator.harbor.Job", fake_job) trials = await HarborEvaluator()._run(agent_dir, dataset, HarborEvaluatorConfig()) @@ -1180,19 +1180,19 @@ class FakeHarborTask: config = SimpleNamespace(environment=SimpleNamespace(os="linux", healthcheck=None, build_timeout_sec=None)) monkeypatch.setattr( - "nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor.HarborTaskModel", + "nemo_eval_author_plugin.evaluator.harbor.HarborTaskModel", lambda p: FakeHarborTask(), ) monkeypatch.setattr( - "nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor.TrialPaths", + "nemo_eval_author_plugin.evaluator.harbor.TrialPaths", FakeTrialPaths, ) monkeypatch.setattr( - "nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor.EnvironmentPaths", + "nemo_eval_author_plugin.evaluator.harbor.EnvironmentPaths", SimpleNamespace(for_os=staticmethod(lambda os: FakeEnvPaths())), ) monkeypatch.setattr( - "nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor.EnvironmentFactory", + "nemo_eval_author_plugin.evaluator.harbor.EnvironmentFactory", SimpleNamespace(create_environment=staticmethod(lambda *a, **kw: FakeEnv())), ) @@ -1252,19 +1252,19 @@ class FakeHarborTask: config = SimpleNamespace(environment=SimpleNamespace(os="linux", healthcheck=None, build_timeout_sec=None)) monkeypatch.setattr( - "nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor.HarborTaskModel", + "nemo_eval_author_plugin.evaluator.harbor.HarborTaskModel", lambda p: FakeHarborTask(), ) monkeypatch.setattr( - "nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor.TrialPaths", + "nemo_eval_author_plugin.evaluator.harbor.TrialPaths", FakeTrialPaths, ) monkeypatch.setattr( - "nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor.EnvironmentPaths", + "nemo_eval_author_plugin.evaluator.harbor.EnvironmentPaths", SimpleNamespace(for_os=staticmethod(lambda os: FakeEnvPaths())), ) monkeypatch.setattr( - "nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor.EnvironmentFactory", + "nemo_eval_author_plugin.evaluator.harbor.EnvironmentFactory", SimpleNamespace(create_environment=staticmethod(lambda *a, **kw: FakeEnv())), ) @@ -1400,9 +1400,7 @@ async def test_harbor_evaluator_force_rerun(tmp_path, monkeypatch): def fake_rmtree(path, **kwargs): rmtree_calls.append(path) - monkeypatch.setattr( - "nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor.shutil.rmtree", fake_rmtree - ) + monkeypatch.setattr("nemo_eval_author_plugin.evaluator.harbor.shutil.rmtree", fake_rmtree) class FakeJob: created_config = None @@ -1419,7 +1417,7 @@ async def create(cls, config): async def run(self): return SimpleNamespace(id="job-id", stats=None) - monkeypatch.setattr("nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor.Job", FakeJob) + monkeypatch.setattr("nemo_eval_author_plugin.evaluator.harbor.Job", FakeJob) trials = await evaluator._run( agent=agent_dir, @@ -1583,7 +1581,7 @@ def from_ref(cls, ref): def test_dataset_from_ref_not_implemented(): - from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import DatasetRef + from nemo_eval_author_plugin.evaluator.models import DatasetRef class ConcreteDataset(Dataset): pass @@ -1821,19 +1819,19 @@ class FakeHarborTask: ) monkeypatch.setattr( - "nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor.HarborTaskModel", + "nemo_eval_author_plugin.evaluator.harbor.HarborTaskModel", lambda p: FakeHarborTask(), ) monkeypatch.setattr( - "nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor.TrialPaths", + "nemo_eval_author_plugin.evaluator.harbor.TrialPaths", FakeTrialPaths, ) monkeypatch.setattr( - "nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor.EnvironmentPaths", + "nemo_eval_author_plugin.evaluator.harbor.EnvironmentPaths", SimpleNamespace(for_os=staticmethod(lambda os: FakeEnvPaths())), ) monkeypatch.setattr( - "nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor.EnvironmentFactory", + "nemo_eval_author_plugin.evaluator.harbor.EnvironmentFactory", SimpleNamespace(create_environment=staticmethod(lambda *a, **kw: FakeEnv())), ) @@ -1853,7 +1851,7 @@ def test_cleanup_scoped_imports_with_sibling_prevents_parent_removal(): def test_trial_task_path_from_config_dict(): - from nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor import _trial_task_path + from nemo_eval_author_plugin.evaluator.harbor import _trial_task_path trial_data = {"config": {"task": {"path": "/tmp/task-x"}}} result = _trial_task_path(trial_data) @@ -1862,7 +1860,7 @@ def test_trial_task_path_from_config_dict(): def test_resolve_trial_task_id_invalid_uri_in_task(tmp_path): - from nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor import _resolve_trial_task_id + from nemo_eval_author_plugin.evaluator.harbor import _resolve_trial_task_id task_map = {"task-x": Task(id="task-x", uri="s3://bad-uri/task-x")} trial_data = {"task_id": {"path": str(tmp_path / "task-x")}} @@ -1871,7 +1869,7 @@ def test_resolve_trial_task_id_invalid_uri_in_task(tmp_path): def test_resolve_trial_task_id_fallback_to_trial_base(): - from nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor import _resolve_trial_task_id + from nemo_eval_author_plugin.evaluator.harbor import _resolve_trial_task_id task_map = {"task-x": Task(id="task-x")} trial_data = {"task_name": "unknown/nonexistent"} diff --git a/plugins/nemo-experimentalist/tests/test_trace_explorer_from_ref.py b/plugins/nemo-eval-author/tests/test_trace_explorer_from_ref.py similarity index 93% rename from plugins/nemo-experimentalist/tests/test_trace_explorer_from_ref.py rename to plugins/nemo-eval-author/tests/test_trace_explorer_from_ref.py index 75d5ff68dbc..bd56d9697b9 100644 --- a/plugins/nemo-experimentalist/tests/test_trace_explorer_from_ref.py +++ b/plugins/nemo-eval-author/tests/test_trace_explorer_from_ref.py @@ -13,8 +13,8 @@ from typing import Any, cast import pytest -from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import ResourceRef -from nemo_experimentalist_plugin.experimentalist.components.trace_explorer import TraceExplorer +from nemo_eval_author_plugin.evaluator.models import ResourceRef +from nemo_eval_author_plugin.trace_explorer import TraceExplorer @pytest.fixture diff --git a/plugins/nemo-experimentalist/AGENTS.md b/plugins/nemo-experimentalist/AGENTS.md index 8cbeb1fbd2f..1367c2f3474 100644 --- a/plugins/nemo-experimentalist/AGENTS.md +++ b/plugins/nemo-experimentalist/AGENTS.md @@ -45,12 +45,23 @@ eventual `nemo agents experimentalist`. The platform's `nemo.cli` entry-point group is flat — only `nemo.jobs` and `nemo.functions` are dot-scoped — so nesting under `nemo agents` needs a Platform-side change first. +### 2026-07-27: Eval Author extracted to `nemo-eval-author-plugin` + +Eval Author, the Harbor evaluator stack, trace analysis, and dataset staging now live in +`plugins/nemo-eval-author/` (`nemo-eval-author-plugin`). Experimentalist is a one-way +consumer: + +- import from `nemo_eval_author_plugin.eval_author`, `nemo_eval_author_plugin.evaluator`, etc. +- do not restore in-tree `eval_author/` or `components/evaluator/` under Experimentalist +- `tools.py`, `model_config.py`, `cache.py`, and `client.py` remain duplicated in both + plugins until a shared module lands (see TODO markers in eval-author) + ### 2026-07-21: Curator renamed to Eval Author The Curator agent was renamed directly to Eval Author in ASE-643. This is a breaking rename with no compatibility aliases or migration layer: -- `nemo_experimentalist_plugin.curator` → `nemo_experimentalist_plugin.eval_author` +- `nemo_experimentalist_plugin.curator` → `nemo_eval_author_plugin.eval_author` (was `nemo_experimentalist_plugin.eval_author` before extraction) - `Curator`, `CuratorConfig`, and `CuratorResult` → `EvalAuthor`, `EvalAuthorConfig`, and `EvalAuthorResult` - `run_curator(...)` and `build_curator_agent(...)` → diff --git a/plugins/nemo-experimentalist/benchmarks/run.py b/plugins/nemo-experimentalist/benchmarks/run.py index 889a696f0a0..a53fd16e6b5 100644 --- a/plugins/nemo-experimentalist/benchmarks/run.py +++ b/plugins/nemo-experimentalist/benchmarks/run.py @@ -15,12 +15,12 @@ import yaml from harbor.registry.client.package import PackageDatasetClient -from nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor import ( +from nemo_eval_author_plugin.evaluator.harbor import ( HarborDataset, HarborEvaluator, HarborEvaluatorConfig, ) -from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import ( +from nemo_eval_author_plugin.evaluator.models import ( DatasetRef, EvaluationResult, TrialResult, diff --git a/plugins/nemo-experimentalist/pyproject.toml b/plugins/nemo-experimentalist/pyproject.toml index e7395b915a3..200f0352083 100644 --- a/plugins/nemo-experimentalist/pyproject.toml +++ b/plugins/nemo-experimentalist/pyproject.toml @@ -16,6 +16,7 @@ dependencies = [ "nooa ; python_full_version >= '3.12'", "pyyaml>=6.0.3", "nemo-insights-plugin", + "nemo-eval-author-plugin", "nemo-platform", "nemo-platform-plugin", "tomlkit>=0.13.3", diff --git a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/entities.py b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/entities.py index 139a3a1e754..9369910c9e8 100644 --- a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/entities.py +++ b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/entities.py @@ -5,7 +5,7 @@ from typing import Any, Literal, Sequence -from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import TrialResult +from nemo_eval_author_plugin.evaluator.models import TrialResult from nemo_platform_plugin.entity import NemoEntity from pydantic import ConfigDict, Field, model_validator diff --git a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/analyzer.py b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/analyzer.py index a65faea12f8..56af0720486 100644 --- a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/analyzer.py +++ b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/analyzer.py @@ -11,12 +11,14 @@ from pathlib import Path from typing import Any, Literal, Sequence -from nemo_experimentalist_plugin.experimentalist.components.evaluator import ( +from nemo_eval_author_plugin.evaluator import ( Dataset, EvaluationResult, Task, TrialResult, ) +from nemo_eval_author_plugin.trace_analyzer import Diagnostic, TraceAnalyzer, TraceAnalyzerConfig # noqa: F401 +from nemo_eval_author_plugin.trace_explorer import TraceExplorer # noqa: F401 from nemo_platform import AsyncNeMoPlatform from nooa import Agent, CodeActStrategy, strategy from nooa.agentdoc import doc, spec @@ -31,8 +33,6 @@ from .model_config import get_fast_model, get_smart_model from .rationalizer import Rationale, Rationalizer, RationalizerConfig # noqa: F401 from .tools import GuardedShellTools -from .trace_analyzer import Diagnostic, TraceAnalyzer, TraceAnalyzerConfig # noqa: F401 -from .trace_explorer import TraceExplorer # noqa: F401 from .util import load_framework_skills diff --git a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/coder.py b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/coder.py index 928359f2d5f..53f595372f0 100644 --- a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/coder.py +++ b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/coder.py @@ -13,6 +13,7 @@ from typing import Any import httpx +from nemo_eval_author_plugin.evaluator import Dataset, EvaluationResult, Evaluator, EvaluatorConfig, Task, TrialResult from nemo_experimentalist_plugin.entities import Candidate from nooa import Agent, CodeActStrategy, strategy from nooa.agentdoc import doc, spec @@ -25,7 +26,6 @@ from pydantic import BaseModel, Field from .cards import Optimize -from .evaluator import Dataset, EvaluationResult, Evaluator, EvaluatorConfig, Task, TrialResult from .model_config import get_fast_model, get_mid_model, get_smart_model from .tools import GuardedShellTools from .util import load_framework_skills diff --git a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/goal_tree.py b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/goal_tree.py index 753ace24198..1231b928bb2 100644 --- a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/goal_tree.py +++ b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/goal_tree.py @@ -8,7 +8,7 @@ from pathlib import Path from typing import Any -from nemo_experimentalist_plugin.experimentalist.components.evaluator import Dataset +from nemo_eval_author_plugin.evaluator import Dataset from nooa import Agent, CodeActStrategy, strategy from nooa.agentdoc import doc, spec from nooa.agents import TokenBudgetSummarizer diff --git a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/insight_promotion.py b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/insight_promotion.py index 660aa94b5a3..a6b27c8615d 100644 --- a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/insight_promotion.py +++ b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/insight_promotion.py @@ -10,14 +10,14 @@ from dataclasses import dataclass, replace from pathlib import Path -from nemo_experimentalist_plugin.entities import Candidate -from nemo_experimentalist_plugin.experimentalist.components.evaluator import ( +from nemo_eval_author_plugin.evaluator import ( Dataset, EvaluationResult, Task, TrialResult, ) -from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import local_path_from_uri +from nemo_eval_author_plugin.evaluator.models import local_path_from_uri +from nemo_experimentalist_plugin.entities import Candidate _GENERIC_METRIC_NAMES = frozenset({"reward", "score"}) _MAX_REPEAT_SPREAD = 0.1 diff --git a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/loop.py b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/loop.py index 80649146f99..a552e3d7599 100644 --- a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/loop.py +++ b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/loop.py @@ -18,18 +18,18 @@ from pathlib import Path from typing import Any, Literal, cast, get_args -from nemo_experimentalist_plugin.entities import Candidate, ExperimentRun -from nemo_experimentalist_plugin.eval_author.agent import EvalAuthor -from nemo_experimentalist_plugin.experimentalist.components.analyzer import AgentAnalyzer, AnalyzerConfig -from nemo_experimentalist_plugin.experimentalist.components.coder import Coder, CoderConfig -from nemo_experimentalist_plugin.experimentalist.components.dataset_staging import stage_eval_author_inputs -from nemo_experimentalist_plugin.experimentalist.components.evaluator import ( +from nemo_eval_author_plugin.dataset_staging import stage_eval_author_inputs +from nemo_eval_author_plugin.eval_author.agent import EvalAuthor +from nemo_eval_author_plugin.evaluator import ( Dataset, EvaluationResult, Evaluator, TrialResult, ) -from nemo_experimentalist_plugin.experimentalist.components.evaluator.factory import DatasetFactory, EvaluatorFactory +from nemo_eval_author_plugin.evaluator.factory import DatasetFactory, EvaluatorFactory +from nemo_experimentalist_plugin.entities import Candidate, ExperimentRun +from nemo_experimentalist_plugin.experimentalist.components.analyzer import AgentAnalyzer, AnalyzerConfig +from nemo_experimentalist_plugin.experimentalist.components.coder import Coder, CoderConfig from nemo_experimentalist_plugin.experimentalist.components.goal_tree import ( GoalTree, GoalTreeConfig, diff --git a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/rationalizer.py b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/rationalizer.py index 7756ffc216c..ba1ce574f1f 100644 --- a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/rationalizer.py +++ b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/rationalizer.py @@ -9,8 +9,8 @@ from pathlib import Path from typing import Any -from nemo_experimentalist_plugin.experimentalist.components.evaluator import Task -from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import DependencyRuntime +from nemo_eval_author_plugin.evaluator import Task +from nemo_eval_author_plugin.evaluator.models import DependencyRuntime from nooa import Agent, CodeActStrategy, strategy from nooa.agentdoc import doc, spec from nooa.agents import TokenBudgetSummarizer diff --git a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/trace_scorer.py b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/trace_scorer.py index 7e73161f8e9..84cd0dfdc5d 100644 --- a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/trace_scorer.py +++ b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/trace_scorer.py @@ -5,7 +5,8 @@ from pathlib import Path from typing import Any -from nemo_experimentalist_plugin.experimentalist.components.evaluator import Dataset, TrialResult +from nemo_eval_author_plugin.evaluator import Dataset, TrialResult +from nemo_eval_author_plugin.trace_explorer import TraceExplorer # noqa: F401 from nemo_platform import AsyncNeMoPlatform from nooa import Agent, CodeActStrategy, strategy from nooa.agentdoc import doc @@ -14,7 +15,6 @@ from .goal_tree import GoalNode from .model_config import get_mid_model -from .trace_explorer import TraceExplorer # noqa: F401 logger = logging.getLogger(__name__) diff --git a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/deps.py b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/deps.py index 2d2cd5a5915..c742c2d53d1 100644 --- a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/deps.py +++ b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/deps.py @@ -13,8 +13,8 @@ from pathlib import Path -from nemo_experimentalist_plugin.experimentalist.components.evaluator.base import EvaluatorType -from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import DatasetRef +from nemo_eval_author_plugin.evaluator.base import EvaluatorType +from nemo_eval_author_plugin.evaluator.models import DatasetRef from nemo_experimentalist_plugin.experimentalist.experimentalist_backend import ( ExperimentalistBackend, ) diff --git a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/experimentalist_backend.py b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/experimentalist_backend.py index a66d2e227aa..a58471cbd58 100644 --- a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/experimentalist_backend.py +++ b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/experimentalist_backend.py @@ -24,12 +24,12 @@ from urllib.parse import urlparse import httpx -from nemo_experimentalist_plugin.entities import Candidate, ExperimentRun -from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import ( +from nemo_eval_author_plugin.evaluator.models import ( EvaluationResult, ResourceRef, TrialResult, ) +from nemo_experimentalist_plugin.entities import Candidate, ExperimentRun from nemo_experimentalist_plugin.experimentalist.components.repository import ( AgentSource, PRPublisher, diff --git a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/otlp.py b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/otlp.py index 58b2dbf16d7..6352f4806d0 100644 --- a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/otlp.py +++ b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/otlp.py @@ -11,7 +11,7 @@ from urllib.parse import urlparse from google.protobuf.json_format import ParseDict -from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import ResourceRef +from nemo_eval_author_plugin.evaluator.models import ResourceRef from opentelemetry.proto.collector.trace.v1.trace_service_pb2 import ExportTraceServiceRequest logger = logging.getLogger(__name__) diff --git a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/run.py b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/run.py index fd5480c5e22..7f6ed7c7db1 100644 --- a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/run.py +++ b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/run.py @@ -7,8 +7,8 @@ from pathlib import Path from typing import Literal, Protocol, cast +from nemo_eval_author_plugin.evaluator.models import DatasetRef from nemo_experimentalist_plugin.experimentalist.agent import build_experimentalist_agent -from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import DatasetRef from nemo_experimentalist_plugin.experimentalist.components.loop import EvolutionaryOptimizerConfig from nemo_experimentalist_plugin.experimentalist.deps import ExperimentalistDeps from nemo_experimentalist_plugin.experimentalist.experimentalist_backend import ( diff --git a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/resolve.py b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/resolve.py index 639ab509b7d..82c24b51a3b 100644 --- a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/resolve.py +++ b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/resolve.py @@ -22,8 +22,8 @@ from typing import Any, Literal import yaml -from nemo_experimentalist_plugin.eval_author.models import EvalAuthorConfig -from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import DatasetRef +from nemo_eval_author_plugin.eval_author.models import EvalAuthorConfig +from nemo_eval_author_plugin.evaluator.models import DatasetRef from nemo_experimentalist_plugin.experimentalist.components.repository import looks_like_git from nemo_experimentalist_plugin.profile import AgentProfile from nemo_insights_plugin.contracts.insights import InsightsFileError, load_insights_document diff --git a/plugins/nemo-experimentalist/tests/experimentalist/test_dataset_staging_loop.py b/plugins/nemo-experimentalist/tests/experimentalist/test_dataset_staging_loop.py new file mode 100644 index 00000000000..e49e81a7d45 --- /dev/null +++ b/plugins/nemo-experimentalist/tests/experimentalist/test_dataset_staging_loop.py @@ -0,0 +1,110 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Loop integration: Experimentalist stages Eval Author inputs before calling Eval Author.""" + +from pathlib import Path +from types import SimpleNamespace +from unittest.mock import AsyncMock + +import pytest +from nemo_eval_author_plugin.evaluator.models import DatasetRef +from nemo_experimentalist_plugin.experimentalist.components import loop as loop_module +from nemo_experimentalist_plugin.experimentalist.components.loop import EvolutionaryOptimizer +from nemo_experimentalist_plugin.resolve import EvolutionaryOptimizerConfig + + +def _write_tree(root: Path, content: str) -> None: + tests = root / "task-1" / "tests" + tests.mkdir(parents=True) + (tests / "test.sh").write_text(content, encoding="utf-8") + + +@pytest.mark.asyncio +async def test_insight_run_stages_inputs_before_eval_author( + monkeypatch: pytest.MonkeyPatch, + tmp_path: Path, +) -> None: + source = tmp_path / "source" + train = source / "train" + validation = source / "validation" + template = source / "task-template" + _write_tree(train, "train source") + _write_tree(validation, "validation source") + _write_tree(template, "template source") + experiment = tmp_path / "experiment" + captured: dict[str, Path] = {} + + class RecordingDatasetFactory: + def build_dataset(self, evaluator_type: str, ref: DatasetRef) -> SimpleNamespace: + return SimpleNamespace(ref=ref) + + def build_task_template(self, evaluator_type: str, ref: DatasetRef) -> SimpleNamespace: + return SimpleNamespace(uri=ref.uri) + + class MutatingEvalAuthor: + def __init__(self, **kwargs: object) -> None: + pass + + async def run( + self, + *, + task_template: SimpleNamespace, + train_dataset: SimpleNamespace, + validation_dataset: SimpleNamespace, + **kwargs: object, + ) -> None: + captured["train"] = Path(train_dataset.ref.uri) + captured["validation"] = Path(validation_dataset.ref.uri) + captured["template"] = Path(task_template.uri) + (captured["train"] / "task-1" / "tests" / "test.sh").write_text("curated", encoding="utf-8") + (captured["template"].parent / "generated-task").mkdir() + raise RuntimeError("stop after eval_author") + + monkeypatch.setattr( + loop_module, + "EvaluatorFactory", + lambda: SimpleNamespace(build_evaluator=lambda *args, **kwargs: object()), + ) + monkeypatch.setattr(loop_module, "DatasetFactory", RecordingDatasetFactory) + monkeypatch.setattr(loop_module, "EvalAuthor", MutatingEvalAuthor) + monkeypatch.setattr( + EvolutionaryOptimizer, + "_init_structure", + lambda self: (experiment / "agents", experiment / "analysis", experiment / "results"), + ) + + backend = SimpleNamespace( + client=object(), + get_insight=AsyncMock(return_value=SimpleNamespace(agent=str(tmp_path / "agent"))), + get_agent_code=AsyncMock(), + ) + config = EvolutionaryOptimizerConfig() + optimizer = object.__new__(EvolutionaryOptimizer) + optimizer.working_dir = experiment + optimizer.config = config + optimizer.shell = SimpleNamespace(close=AsyncMock()) + deps = SimpleNamespace( + backend=backend, + workspace="default", + config=config, + evaluator_type="harbor", + train_dataset=DatasetRef(uri=str(train)), + validation_dataset=DatasetRef(uri=str(validation)), + task_template=DatasetRef(uri=str(template)), + insight="insight-1", + agent=tmp_path / "agent", + agent_spec=None, + ) + + with pytest.raises(RuntimeError, match="stop after eval_author"): + await optimizer.run(deps) + + optimizer.shell.close.assert_awaited_once() + assert captured == { + "train": experiment / "dataset" / "train", + "validation": experiment / "dataset" / "validation", + "template": experiment / "dataset" / "task-template", + } + assert (train / "task-1" / "tests" / "test.sh").read_text(encoding="utf-8") == "train source" + assert not (template.parent / "generated-task").exists() diff --git a/plugins/nemo-experimentalist/tests/experimentalist/test_eval_author_repair_e2e.py b/plugins/nemo-experimentalist/tests/experimentalist/test_eval_author_repair_e2e.py index 6dd3a23b71a..7eea2ee1773 100644 --- a/plugins/nemo-experimentalist/tests/experimentalist/test_eval_author_repair_e2e.py +++ b/plugins/nemo-experimentalist/tests/experimentalist/test_eval_author_repair_e2e.py @@ -9,19 +9,19 @@ from pathlib import Path import pytest -from nemo_experimentalist_plugin.eval_author.agent import EvalAuthor -from nemo_experimentalist_plugin.eval_author.models import EvalAuthorConfig -from nemo_experimentalist_plugin.experimentalist.components.evaluator import ( +from nemo_eval_author_plugin.eval_author.agent import EvalAuthor +from nemo_eval_author_plugin.eval_author.models import EvalAuthorConfig +from nemo_eval_author_plugin.evaluator import ( DatasetValidationError, local_path_from_uri, ) -from nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor import ( +from nemo_eval_author_plugin.evaluator.harbor import ( HarborDataset, HarborEvaluator, HarborEvaluatorConfig, ) +from nemo_eval_author_plugin.trace_analyzer import Diagnostic from nemo_experimentalist_plugin.experimentalist.components.model_config import get_fast_model -from nemo_experimentalist_plugin.experimentalist.components.trace_analyzer import Diagnostic from nemo_insights_plugin.entities import Insight _HAS_EXPERIMENTALIST_LLM = bool( diff --git a/plugins/nemo-experimentalist/tests/experimentalist/test_loop_insight_suite.py b/plugins/nemo-experimentalist/tests/experimentalist/test_loop_insight_suite.py index e959de65be1..8f27ebd2165 100644 --- a/plugins/nemo-experimentalist/tests/experimentalist/test_loop_insight_suite.py +++ b/plugins/nemo-experimentalist/tests/experimentalist/test_loop_insight_suite.py @@ -7,21 +7,21 @@ from unittest.mock import AsyncMock import pytest -from nemo_experimentalist_plugin.entities import Candidate -from nemo_experimentalist_plugin.experimentalist.components import loop as loop_module -from nemo_experimentalist_plugin.experimentalist.components.evaluator import ( +from nemo_eval_author_plugin.evaluator import ( Dataset, EvaluationResult, MetricResult, Task, TrialResult, ) -from nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor import HarborEvaluatorConfig -from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import ( +from nemo_eval_author_plugin.evaluator.harbor import HarborEvaluatorConfig +from nemo_eval_author_plugin.evaluator.models import ( DatasetRef, DataValue, ResourceRef, ) +from nemo_experimentalist_plugin.entities import Candidate +from nemo_experimentalist_plugin.experimentalist.components import loop as loop_module from nemo_experimentalist_plugin.experimentalist.components.loop import EvolutionaryOptimizer from nemo_experimentalist_plugin.resolve import EvolutionaryOptimizerConfig diff --git a/plugins/nemo-experimentalist/tests/experimentalist/test_loop_reporting.py b/plugins/nemo-experimentalist/tests/experimentalist/test_loop_reporting.py index b5cbef7d0d0..d030c590c0e 100644 --- a/plugins/nemo-experimentalist/tests/experimentalist/test_loop_reporting.py +++ b/plugins/nemo-experimentalist/tests/experimentalist/test_loop_reporting.py @@ -7,8 +7,7 @@ from unittest.mock import AsyncMock import pytest -from nemo_experimentalist_plugin.entities import Candidate -from nemo_experimentalist_plugin.experimentalist.components.evaluator import ( +from nemo_eval_author_plugin.evaluator import ( Dataset, EvaluationResult, MetricResult, @@ -16,7 +15,8 @@ TrialResult, TrialStatus, ) -from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import ResourceRef +from nemo_eval_author_plugin.evaluator.models import ResourceRef +from nemo_experimentalist_plugin.entities import Candidate from nemo_experimentalist_plugin.experimentalist.components.insight_promotion import ( _task_evidence, insight_suite_provenance, diff --git a/plugins/nemo-experimentalist/tests/test_deps.py b/plugins/nemo-experimentalist/tests/test_deps.py index cb509f95dde..2c77386c92d 100644 --- a/plugins/nemo-experimentalist/tests/test_deps.py +++ b/plugins/nemo-experimentalist/tests/test_deps.py @@ -6,7 +6,7 @@ from pathlib import Path import pytest -from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import DatasetRef +from nemo_eval_author_plugin.evaluator.models import DatasetRef from nemo_experimentalist_plugin.experimentalist.deps import ExperimentalistDeps diff --git a/plugins/nemo-experimentalist/tests/test_eval_author_config.py b/plugins/nemo-experimentalist/tests/test_eval_author_config.py new file mode 100644 index 00000000000..23eb45b8aa1 --- /dev/null +++ b/plugins/nemo-experimentalist/tests/test_eval_author_config.py @@ -0,0 +1,25 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Experimentalist config wiring for the nested Eval Author settings block.""" + +import pytest +from nemo_eval_author_plugin.eval_author.models import EvalAuthorConfig +from nemo_experimentalist_plugin.experimentalist.components.loop import EvolutionaryOptimizerConfig + + +def test_evolutionary_optimizer_uses_top_level_eval_author_config() -> None: + config = EvolutionaryOptimizerConfig().eval_author + + assert type(config) is EvalAuthorConfig + assert config.max_validation_repair_attempts == 5 + + +def test_evolutionary_optimizer_tolerates_unknown_eval_author_config() -> None: + config = EvolutionaryOptimizerConfig.model_validate({"eval_author": {"bogus": 1}}) + assert type(config.eval_author) is EvalAuthorConfig + + +def test_evolutionary_optimizer_rejects_legacy_curator_config() -> None: + with pytest.raises(ValueError, match="'curator' was renamed to 'eval_author'"): + EvolutionaryOptimizerConfig.model_validate({"curator": {"max_traces": 1}}) diff --git a/plugins/nemo-experimentalist/tests/test_experiment_cli.py b/plugins/nemo-experimentalist/tests/test_experiment_cli.py index f4460ae4a9b..134d3fddb43 100644 --- a/plugins/nemo-experimentalist/tests/test_experiment_cli.py +++ b/plugins/nemo-experimentalist/tests/test_experiment_cli.py @@ -7,8 +7,8 @@ import pytest from click.testing import Result +from nemo_eval_author_plugin.evaluator.models import DatasetRef from nemo_experimentalist_plugin import cli -from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import DatasetRef from nemo_experimentalist_plugin.experimentalist.components.loop import EvolutionaryOptimizerConfig from nemo_experimentalist_plugin.preflight import Probes from nemo_platform import AsyncNeMoPlatform diff --git a/plugins/nemo-experimentalist/tests/test_experimentalist_analyzer.py b/plugins/nemo-experimentalist/tests/test_experimentalist_analyzer.py index 48ab2c4f449..c6f85ce7c1f 100644 --- a/plugins/nemo-experimentalist/tests/test_experimentalist_analyzer.py +++ b/plugins/nemo-experimentalist/tests/test_experimentalist_analyzer.py @@ -21,6 +21,7 @@ from typing import Any, ClassVar, cast import pytest +from nemo_eval_author_plugin.trace_analyzer import Diagnostic from nemo_experimentalist_plugin.experimentalist.components import analyzer as analyzer_module from nemo_experimentalist_plugin.experimentalist.components.analyzer import ( AgentAnalyzer, @@ -29,7 +30,6 @@ PeerComparison, ) from nemo_experimentalist_plugin.experimentalist.components.rationalizer import Rationale -from nemo_experimentalist_plugin.experimentalist.components.trace_analyzer import Diagnostic @dataclass diff --git a/plugins/nemo-experimentalist/tests/test_experimentalist_backend.py b/plugins/nemo-experimentalist/tests/test_experimentalist_backend.py index 331572e8a52..02398d0aeca 100644 --- a/plugins/nemo-experimentalist/tests/test_experimentalist_backend.py +++ b/plugins/nemo-experimentalist/tests/test_experimentalist_backend.py @@ -18,9 +18,9 @@ import httpx import pytest +from nemo_eval_author_plugin.evaluator.models import EvaluationResult from nemo_experimentalist_plugin.entities import Candidate from nemo_experimentalist_plugin.experimentalist import experimentalist_backend as beim -from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import EvaluationResult from nemo_experimentalist_plugin.experimentalist.components.repository import AgentCloneError, AgentSource from nemo_experimentalist_plugin.experimentalist.experimentalist_backend import ( CandidateStorageConfig, diff --git a/plugins/nemo-experimentalist/tests/test_experimentalist_benchmark.py b/plugins/nemo-experimentalist/tests/test_experimentalist_benchmark.py index caab7cc8a65..cddf1d4a76a 100644 --- a/plugins/nemo-experimentalist/tests/test_experimentalist_benchmark.py +++ b/plugins/nemo-experimentalist/tests/test_experimentalist_benchmark.py @@ -7,7 +7,7 @@ from types import ModuleType import pytest -from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import ( +from nemo_eval_author_plugin.evaluator.models import ( EvaluationResult, MetricResult, ResourceRef, diff --git a/plugins/nemo-experimentalist/tests/test_experimentalist_run.py b/plugins/nemo-experimentalist/tests/test_experimentalist_run.py index 35fd4db09e1..90a54f98e46 100644 --- a/plugins/nemo-experimentalist/tests/test_experimentalist_run.py +++ b/plugins/nemo-experimentalist/tests/test_experimentalist_run.py @@ -7,9 +7,9 @@ from typing import Literal, cast import pytest +from nemo_eval_author_plugin.evaluator.models import DatasetRef from nemo_experimentalist_plugin.experimentalist import run as experimentalist_run from nemo_experimentalist_plugin.experimentalist.components import loop as loop_module -from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import DatasetRef from nemo_experimentalist_plugin.experimentalist.components.loop import EvolutionaryOptimizerConfig from nemo_experimentalist_plugin.experimentalist.deps import ExperimentalistDeps from nemo_experimentalist_plugin.experimentalist.experimentalist_backend import LocalExperimentalistBackend diff --git a/plugins/nemo-experimentalist/tests/test_otlp.py b/plugins/nemo-experimentalist/tests/test_otlp.py index 4a0b25915c3..7ea2e38eef2 100644 --- a/plugins/nemo-experimentalist/tests/test_otlp.py +++ b/plugins/nemo-experimentalist/tests/test_otlp.py @@ -14,7 +14,7 @@ from pathlib import Path import pytest -from nemo_experimentalist_plugin.experimentalist.components.evaluator import ResourceRef +from nemo_eval_author_plugin.evaluator import ResourceRef from nemo_experimentalist_plugin.experimentalist.otlp import ( jsonl_to_protobuf, read_trace_id, diff --git a/pyproject.toml b/pyproject.toml index d36d81ff146..a0fb5880aa4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -368,6 +368,7 @@ nmp-testing = { workspace = true } nmp-build-tools = { workspace = true } nemo-platform-plugin = { workspace = true } nemo-insights-plugin = { workspace = true } +nemo-eval-author-plugin = { workspace = true } nemo-experimentalist-plugin = { workspace = true } # Not published to PyPI yet; pinned to an immutable public tag. nooa = { git = "https://github.com/NVIDIA-NeMo/labs-OO-Agents.git", tag = "v0.0.6" } @@ -437,6 +438,7 @@ members = [ "plugins/nemo-agents", "plugins/nemo-deployments", "plugins/nemo-insights", + "plugins/nemo-eval-author", "plugins/nemo-experimentalist", "plugins/nemo-agents/examples/calculator-agent", "plugins/nemo-agents/examples/email-phishing-analyzer", @@ -560,6 +562,8 @@ extra-paths = [ "plugins/nemo-evaluator/src", # The optional Insights analyst is not part of the default environment. "plugins/nemo-insights/src", + # Same for the optional Eval Author plugin. + "plugins/nemo-eval-author/src", # Same for the optional Experimentalist; its tests also import the benchmark runner. "plugins/nemo-experimentalist/src", # Agentic-use tests place both tests/agentic-use and tests/agentic-use/shared diff --git a/uv.lock b/uv.lock index 42abc6acc5e..7f171b20e54 100644 --- a/uv.lock +++ b/uv.lock @@ -33,6 +33,7 @@ members = [ "nemo-customizer-plugin", "nemo-data-designer-plugin", "nemo-deployments-plugin", + "nemo-eval-author-plugin", "nemo-evaluator-plugin", "nemo-evaluator-sdk", "nemo-experimentalist-plugin", @@ -257,14 +258,30 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/26/dd/bf526e6f0a1120dd6f2df2e97bacfe4d358f13d17a0ff5847301a1375a51/aiohttp-3.14.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:aa00140699487bd435fde4342d85c94cb256b7cd3a5b9c3396c67f19922afda2", size = 765225, upload-time = "2026-06-07T21:06:07.957Z" }, { url = "https://files.pythonhosted.org/packages/5b/e7/c60c7b209e509cc787de3cea0550a518538cfc08003e1c1e14c1c63fff71/aiohttp-3.14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d44ec478e713ee7f29b439f7eb8dc2b9d4079e11ae114d2c2ac3d5daf30516c8", size = 514139, upload-time = "2026-06-07T21:06:11.26Z" }, { url = "https://files.pythonhosted.org/packages/5b/8d/614ace2f579702c9840ab1e1447fd8509e35b0b904f7196418fa2f57b25d/aiohttp-3.14.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d3b1a184a9a8f548a6b73f1e26b96b052193e4b3175ed7342aaf1151a1f00a04", size = 1784088, upload-time = "2026-06-07T21:06:12.887Z" }, + { url = "https://files.pythonhosted.org/packages/49/e0/726e90f99542bf292f81a96a12cc4847deb86f3ccf62c6f4014a201f4d33/aiohttp-3.14.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5f2504bc0322437c9a1ff6d3333ca56c7477b727c995f036b976ae17b98372c8", size = 1737835, upload-time = "2026-06-07T21:06:14.564Z" }, + { url = "https://files.pythonhosted.org/packages/0b/4b/d176d5c4db9d33dacf0543102ea59503bc1d528af4cfd0b719949ca49389/aiohttp-3.14.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:73f05ea02013e02512c3bf42714f1208c57168c779cc6fe23516e4543089d0a6", size = 1842801, upload-time = "2026-06-07T21:06:16.228Z" }, + { url = "https://files.pythonhosted.org/packages/dc/d6/5a99b563690ea0cbed912ae94a2ce33993a5709a651a3a4fe761e7dd973a/aiohttp-3.14.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:797457503c2d426bee06eef808d07b31ede30b65e054444e7de64cad0061b7af", size = 1929992, upload-time = "2026-06-07T21:06:17.947Z" }, { url = "https://files.pythonhosted.org/packages/76/7f/a987b14a3859094b3cea3f4825219c3e5536242564af6e3f9c2f6c994eb2/aiohttp-3.14.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b821a1f7dedf7e37450654e620038ac3b2e81e8fa6ea269337e97101978ec730", size = 1786989, upload-time = "2026-06-07T21:06:19.677Z" }, + { url = "https://files.pythonhosted.org/packages/f1/1a/420e5c85a3e73349372ed22ce0b6af86bfa6ce16a4b20a64a2e94608c781/aiohttp-3.14.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4cd96b5ba05d67ed0cf00b5b405c8cd99586d8e3481e8ee0a831057591af7621", size = 1640129, upload-time = "2026-06-07T21:06:22.558Z" }, { url = "https://files.pythonhosted.org/packages/a7/80/18a592ed3be0a402cc03670bd72ee1f8563ddbe1d8d5542dbf868f274136/aiohttp-3.14.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d459b98a932296c6f0e94f87511a0b1b90a8a02c30a50e60a297619cd5a58ee", size = 1756576, upload-time = "2026-06-07T21:06:24.8Z" }, + { url = "https://files.pythonhosted.org/packages/ec/0b/8b3d5713373858ff71a617daf6e3b0e81ad63e79d09a3cf2f6b6b983939c/aiohttp-3.14.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:764457a7be60825fb770a644852ff717bcbb5042f189f2bd16df61a81b3f6573", size = 1754668, upload-time = "2026-06-07T21:06:26.528Z" }, + { url = "https://files.pythonhosted.org/packages/9f/49/fd564575cf225821d7ba5a117cb8bc27213d8a7e1811162afb43ae077039/aiohttp-3.14.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f7a16ef45b081454ef844502d87a848876c490c4cb5c650c230f6ec79ed2c1e7", size = 1817019, upload-time = "2026-06-07T21:06:28.297Z" }, + { url = "https://files.pythonhosted.org/packages/ed/1b/e850c9ae6fc91356552ae668bb6c51e93fa29c8aef13398a10b56678557f/aiohttp-3.14.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:2fbc3ed048b3475b9f0cbcb9978e9d2d3511acd91ead203af26ed9f0056004cf", size = 1631638, upload-time = "2026-06-07T21:06:30.242Z" }, + { url = "https://files.pythonhosted.org/packages/eb/94/3c337ba72451a89806ace6f75bddc92bafc5b8d53d90115a512858024b63/aiohttp-3.14.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:bedb0cd073cc2dc035e30aeb99444389d3cd2113afe4ef9fcd23d439f5bade85", size = 1835660, upload-time = "2026-06-07T21:06:31.943Z" }, { url = "https://files.pythonhosted.org/packages/2b/9c/9c18cf367a0498212d9ba7daf990b504a5e8ae064cda4b504e2647c89c03/aiohttp-3.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b6feea921016eb3d4e04d65fc4e9ca402d1a3801f562aef94989f54694917af3", size = 1775698, upload-time = "2026-06-07T21:06:33.72Z" }, { url = "https://files.pythonhosted.org/packages/1d/21/151624b51cd92553d95424daf4bf19f19ce9be9002d19253e7e7ce67197b/aiohttp-3.14.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d35143e27778b4bb0fb189562d7f275bff79c62ab8e98459717c0ea617ff2480", size = 757402, upload-time = "2026-06-07T21:06:40.311Z" }, { url = "https://files.pythonhosted.org/packages/55/b2/2aac325583aaa1353045f96dffa586d8a34e8322e14a7ba49cffeb103ab4/aiohttp-3.14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:27fd7c91e51729b4f7e1577865fa6d34c9adccbc39aabe9000285b48af9f0ec2", size = 512448, upload-time = "2026-06-07T21:06:43.813Z" }, { url = "https://files.pythonhosted.org/packages/8a/72/a60607cb849faa8af8a356c9329ea2eb6f395d49e82cc82ccba1fd8deb8f/aiohttp-3.14.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:64c567bf9eaf664280116a8688f63016e6b32db2505908e2bdaca1b6438142f2", size = 1766854, upload-time = "2026-06-07T21:06:45.391Z" }, + { url = "https://files.pythonhosted.org/packages/b5/d3/d9fe1c9ec7557ab4d0d82bebaa728c6418f0b93295ec2f4ab015f7710cc7/aiohttp-3.14.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:f5e6ff2bdbb8f4cd3fbe41f99e25bbcd58e3bf9f13d3dd31a11e7917251cc77a", size = 1740884, upload-time = "2026-06-07T21:06:47.413Z" }, + { url = "https://files.pythonhosted.org/packages/c1/dc/f2cecfaf9337ba3e63f181500814ff502aa3d00d9c7ec93a9d23d10a27b2/aiohttp-3.14.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2f73e01dc37122325caf079982621262f96d74823c179038a82fddfc50359264", size = 1810034, upload-time = "2026-06-07T21:06:50.165Z" }, + { url = "https://files.pythonhosted.org/packages/66/d7/2ff65c5e65c0d7476daf7e15c032e0805e36811185b9623e3238ad6c763e/aiohttp-3.14.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bb2c0c80d431c0d03f2c7dbf125150fedd4f0de17366a7ca33f7ccb822391842", size = 1904054, upload-time = "2026-06-07T21:06:52.035Z" }, { url = "https://files.pythonhosted.org/packages/20/9c/d445818389df371f56d141d881153ba23183c4735a03f7356ffb43f7757d/aiohttp-3.14.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3e6fc1a85fa7194a1a7d19f44e8609180f4a8eb5fa4c7ed8b4355f080fad235c", size = 1790278, upload-time = "2026-06-07T21:06:54.049Z" }, + { url = "https://files.pythonhosted.org/packages/4d/aa/bf04cb4d865fc6101c2229a294ad744973b72e513fdc5a6b791e6983d72a/aiohttp-3.14.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:686b6c0d3911ec387b444ddf5dc62fb7f7c0a7d5186a7861626496a5ab4aff95", size = 1591795, upload-time = "2026-06-07T21:06:55.911Z" }, { url = "https://files.pythonhosted.org/packages/dc/b4/4dac0038960427ba832f6609dfb4ea5437d7fd80c72001b9e48f834f428b/aiohttp-3.14.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c6fa4dc7ad6f8109c70bb1499e589f76b0b792baf39f9b017eb92c8a81d0a199", size = 1728397, upload-time = "2026-06-07T21:06:57.777Z" }, + { url = "https://files.pythonhosted.org/packages/2b/f9/7cd4e8ad7aa3b75f17d56bb5498dd604a93d4e6eece822ba0568c413fff0/aiohttp-3.14.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:87a5eea1b2a5e21e1ebdbb33ad4165359189327e63fc4e4894693e7f821ac817", size = 1766504, upload-time = "2026-06-07T21:07:00.009Z" }, + { url = "https://files.pythonhosted.org/packages/f9/df/fc01d9fcad0f73fed3f3d361f1f94f975947b50dff82919f6dc2bf4316cc/aiohttp-3.14.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1c1421eb01d4fd608d88cc8290211d177a58532b55ad94076fb349c5bf467f0a", size = 1777806, upload-time = "2026-06-07T21:07:02.064Z" }, + { url = "https://files.pythonhosted.org/packages/41/09/47e2d090bddcc8fb4ccb4c314aadc32d7c5d9bb55f50f6ad1c92fc15d501/aiohttp-3.14.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:34b257ec41345c1e8f2df68fa908a7952f5de932723871eb633ecbbff396c9a4", size = 1580707, upload-time = "2026-06-07T21:07:03.942Z" }, + { url = "https://files.pythonhosted.org/packages/3d/36/f1a4ce904ae0b6930cfe9afc96d0896f7ec1a620c400405d63783bb95a9c/aiohttp-3.14.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:de538791a80e5d862addbc183f70f0158ac9b9bb872bb147f1fd2a683691e087", size = 1798121, upload-time = "2026-06-07T21:07:05.987Z" }, { url = "https://files.pythonhosted.org/packages/70/0a/e0075ce9ca0279ee1d4f0c0b85f54fea02ebc83c3007651a72bece658fec/aiohttp-3.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6f71173be42d3241d428f760122febb748de0623f44308a6f120d0dd9ec572e3", size = 1767580, upload-time = "2026-06-07T21:07:07.873Z" }, { url = "https://files.pythonhosted.org/packages/fe/22/a73ccbf9dbd6e26dda0b24d5fd5db7da92ee3383a79f47677ffb834c5c5b/aiohttp-3.14.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:915fbb7b41b115192259f8c9ae58f3ddc444d2b5579917270211858e606a4afd", size = 485841, upload-time = "2026-06-07T21:07:19.555Z" }, { url = "https://files.pythonhosted.org/packages/3b/b9/57ed8eaf596321c2ad747bd480fb1700dbd7177c60dfc9e4c187f629662e/aiohttp-3.14.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:7fb4bdf95b0561a79f259f9d28fbc109728c5ee7f27aff6391f0ca703a329abe", size = 492088, upload-time = "2026-06-07T21:07:21.581Z" }, @@ -272,8 +289,16 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/75/7f/8cdaa24fc7983865e0915153b96a9ac5bcdd3548d64c5a27d17cecccad2d/aiohttp-3.14.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:86a6dab78b0e43e2897a3bbe15745aa60dc5423ca437b7b0b164c069bf91b876", size = 751998, upload-time = "2026-06-07T21:07:25.046Z" }, { url = "https://files.pythonhosted.org/packages/ab/01/a2d5f96cd4e74424864d30bc0a7e44d0a12dacdcfa91b5b2d1bd3dca6bf3/aiohttp-3.14.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:317acd9f8602858dc7d59679812c376c7f0b97bcbbf16e0d6237f54141d8a8a6", size = 508657, upload-time = "2026-06-07T21:07:29.252Z" }, { url = "https://files.pythonhosted.org/packages/e8/ed/3c0fb5c500fdd8e7ebc10d1889c04384fffa1a9163eac1356088ca9da1b1/aiohttp-3.14.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd869c427324e5cb15195793de951295710db28be7d818247f3097b4ab5d4b96", size = 1757907, upload-time = "2026-06-07T21:07:31.03Z" }, + { url = "https://files.pythonhosted.org/packages/0b/ab/d4c924d9bd5be3050c226612413ce68cb54c70d2c31b661bfc8d9a5b6a70/aiohttp-3.14.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:93b032b5ec3255473c143627d21a69ac74ae12f7f33974cb587c564d11b1066f", size = 1737565, upload-time = "2026-06-07T21:07:33.031Z" }, + { url = "https://files.pythonhosted.org/packages/19/2a/37326821ff779084020cdc33224d20b19f42f4183a500ff92022a739eda7/aiohttp-3.14.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f234b4deb12f3ad59127e037bc57c40c21e45b45282df7d3a55a0f409f595296", size = 1799018, upload-time = "2026-06-07T21:07:35.003Z" }, + { url = "https://files.pythonhosted.org/packages/b3/4f/6e947ba73e4ce09070761c05ed3a8ceb7c21f5e46798671d8b2aac0e4626/aiohttp-3.14.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:9af6779bfb46abf124068327abcdf9ce95c9ef8287a3e8da76ccf2d0f16c28fa", size = 1894416, upload-time = "2026-06-07T21:07:36.956Z" }, { url = "https://files.pythonhosted.org/packages/9d/6e/dbf1d0625dc711fb2851f4f3c3055c39ed58bae92082d8c627dbe6013736/aiohttp-3.14.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:faccab372e66bc76d5731525e7f1143c922271725b9d38c9f97edcc66266b451", size = 1783881, upload-time = "2026-06-07T21:07:39.063Z" }, + { url = "https://files.pythonhosted.org/packages/44/c2/5e25098a67268ed369483ae7d1a58bd0a13d03aab860d2a0e4a6eb25b046/aiohttp-3.14.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f380468b09d2a81633ee863b0ec5648d364bd17bb8ecfb8c2f387f7ac1faf42c", size = 1587572, upload-time = "2026-06-07T21:07:41.058Z" }, { url = "https://files.pythonhosted.org/packages/2a/bd/cf9cee17e140f942a3de73e658a543aa8fbf35a5fc67a9d2538d52d77f0b/aiohttp-3.14.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:97e704dcd26271f5bda3fa07c3ce0fb76d6d3f8659f4baa1a24442cc9ba177ca", size = 1722137, upload-time = "2026-06-07T21:07:43.014Z" }, + { url = "https://files.pythonhosted.org/packages/89/6d/5684f8c59045c96f81a18cefbc1fbbd79d25b88f1c622f2a5c5c08fcb632/aiohttp-3.14.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:269b76ac5394092b95bc4a098f4fc6c191c083c3bd12775d1e30e663132f6a09", size = 1755953, upload-time = "2026-06-07T21:07:45.933Z" }, + { url = "https://files.pythonhosted.org/packages/a8/40/35caf3170f8359760740a7d9aa0fff2e344bef98e1d1186f5a0f6dec17e6/aiohttp-3.14.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5c0b3e614340c889d575451696374c9d17affd54cd607ca0babed8f8c37b9397", size = 1766479, upload-time = "2026-06-07T21:07:48.047Z" }, + { url = "https://files.pythonhosted.org/packages/6d/a1/b0c61e7a137f0d81de49a82023a6df73c3c16d6fefb0f8e4a93d21639002/aiohttp-3.14.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:5663ee9257cfa1add7253a7da3035a02f31b6600ec48261585e1800a81533080", size = 1580077, upload-time = "2026-06-07T21:07:50.069Z" }, + { url = "https://files.pythonhosted.org/packages/0b/41/194ea4623693009fcefebef7aef63c141754f153e9cd0d39d3b9e36c175c/aiohttp-3.14.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:603a2c834142172ffddc054067f5ec0ca65d57a0aa98a71bc81952573208e345", size = 1791688, upload-time = "2026-06-07T21:07:52.106Z" }, { url = "https://files.pythonhosted.org/packages/ba/45/4de841f005cfe1fd63e2a2fe011262c515e2a62aa6994b15947e7d717ac9/aiohttp-3.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:cb21957bb8aca671c1765e32f58164cf0c50e6bf41c0bbbd16da20732ecaf588", size = 1761094, upload-time = "2026-06-07T21:07:54.113Z" }, ] @@ -712,16 +737,22 @@ sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8 wheels = [ { url = "https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", size = 180560, upload-time = "2025-09-08T23:22:28.197Z" }, { url = "https://files.pythonhosted.org/packages/b8/56/6033f5e86e8cc9bb629f0077ba71679508bdf54a9a5e112a3c0b91870332/cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", size = 216476, upload-time = "2025-09-08T23:22:31.063Z" }, + { url = "https://files.pythonhosted.org/packages/dc/7f/55fecd70f7ece178db2f26128ec41430d8720f2d12ca97bf8f0a628207d5/cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", size = 203374, upload-time = "2025-09-08T23:22:32.507Z" }, + { url = "https://files.pythonhosted.org/packages/84/ef/a7b77c8bdc0f77adc3b46888f1ad54be8f3b7821697a7b89126e829e676a/cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", size = 202597, upload-time = "2025-09-08T23:22:34.132Z" }, { url = "https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", size = 215574, upload-time = "2025-09-08T23:22:35.443Z" }, { url = "https://files.pythonhosted.org/packages/44/64/58f6255b62b101093d5df22dcb752596066c7e89dd725e0afaed242a61be/cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", size = 218971, upload-time = "2025-09-08T23:22:36.805Z" }, { url = "https://files.pythonhosted.org/packages/0b/28/dd0967a76aab36731b6ebfe64dec4e981aff7e0608f60c2d46b46982607d/cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", size = 217078, upload-time = "2025-09-08T23:22:39.776Z" }, { url = "https://files.pythonhosted.org/packages/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/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/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/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" }, @@ -753,18 +784,42 @@ sdist = { url = "https://files.pythonhosted.org/packages/7b/60/e3bec1881450851b0 wheels = [ { url = "https://files.pythonhosted.org/packages/62/28/ff6f234e628a2de61c458be2779cb182bc03f6eec12200d4a525bbfc9741/charset_normalizer-3.4.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:82060f995ab5003a2d6e0f4ad29065b7672b6593c8c63559beefe5b443242c3e", size = 293582, upload-time = "2026-03-15T18:50:25.454Z" }, { url = "https://files.pythonhosted.org/packages/1c/b7/b1a117e5385cbdb3205f6055403c2a2a220c5ea80b8716c324eaf75c5c95/charset_normalizer-3.4.6-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:60c74963d8350241a79cb8feea80e54d518f72c26db618862a8f53e5023deaf9", size = 197240, upload-time = "2026-03-15T18:50:27.196Z" }, + { url = "https://files.pythonhosted.org/packages/a1/5f/2574f0f09f3c3bc1b2f992e20bce6546cb1f17e111c5be07308dc5427956/charset_normalizer-3.4.6-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6e4333fb15c83f7d1482a76d45a0818897b3d33f00efd215528ff7c51b8e35d", size = 217363, upload-time = "2026-03-15T18:50:28.601Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d1/0ae20ad77bc949ddd39b51bf383b6ca932f2916074c95cad34ae465ab71f/charset_normalizer-3.4.6-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bc72863f4d9aba2e8fd9085e63548a324ba706d2ea2c83b260da08a59b9482de", size = 212994, upload-time = "2026-03-15T18:50:30.102Z" }, { url = "https://files.pythonhosted.org/packages/60/ac/3233d262a310c1b12633536a07cde5ddd16985e6e7e238e9f3f9423d8eb9/charset_normalizer-3.4.6-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9cc4fc6c196d6a8b76629a70ddfcd4635a6898756e2d9cac5565cf0654605d73", size = 204697, upload-time = "2026-03-15T18:50:31.654Z" }, + { url = "https://files.pythonhosted.org/packages/25/3c/8a18fc411f085b82303cfb7154eed5bd49c77035eb7608d049468b53f87c/charset_normalizer-3.4.6-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:0c173ce3a681f309f31b87125fecec7a5d1347261ea11ebbb856fa6006b23c8c", size = 191673, upload-time = "2026-03-15T18:50:33.433Z" }, + { url = "https://files.pythonhosted.org/packages/ff/a7/11cfe61d6c5c5c7438d6ba40919d0306ed83c9ab957f3d4da2277ff67836/charset_normalizer-3.4.6-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c907cdc8109f6c619e6254212e794d6548373cc40e1ec75e6e3823d9135d29cc", size = 201120, upload-time = "2026-03-15T18:50:35.105Z" }, { url = "https://files.pythonhosted.org/packages/b5/10/cf491fa1abd47c02f69687046b896c950b92b6cd7337a27e6548adbec8e4/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:404a1e552cf5b675a87f0651f8b79f5f1e6fd100ee88dc612f89aa16abd4486f", size = 200911, upload-time = "2026-03-15T18:50:36.819Z" }, + { url = "https://files.pythonhosted.org/packages/28/70/039796160b48b18ed466fde0af84c1b090c4e288fae26cd674ad04a2d703/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:e3c701e954abf6fc03a49f7c579cc80c2c6cc52525340ca3186c41d3f33482ef", size = 192516, upload-time = "2026-03-15T18:50:38.228Z" }, + { url = "https://files.pythonhosted.org/packages/ff/34/c56f3223393d6ff3124b9e78f7de738047c2d6bc40a4f16ac0c9d7a1cb3c/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:7a6967aaf043bceabab5412ed6bd6bd26603dae84d5cb75bf8d9a74a4959d398", size = 218795, upload-time = "2026-03-15T18:50:39.664Z" }, + { url = "https://files.pythonhosted.org/packages/e8/3b/ce2d4f86c5282191a041fdc5a4ce18f1c6bd40a5bd1f74cf8625f08d51c1/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:5feb91325bbceade6afab43eb3b508c63ee53579fe896c77137ded51c6b6958e", size = 201833, upload-time = "2026-03-15T18:50:41.552Z" }, + { url = "https://files.pythonhosted.org/packages/3b/9b/b6a9f76b0fd7c5b5ec58b228ff7e85095370282150f0bd50b3126f5506d6/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:f820f24b09e3e779fe84c3c456cb4108a7aa639b0d1f02c28046e11bfcd088ed", size = 213920, upload-time = "2026-03-15T18:50:43.33Z" }, { url = "https://files.pythonhosted.org/packages/ae/98/7bc23513a33d8172365ed30ee3a3b3fe1ece14a395e5fc94129541fc6003/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b35b200d6a71b9839a46b9b7fff66b6638bb52fc9658aa58796b0326595d3021", size = 206951, upload-time = "2026-03-15T18:50:44.789Z" }, { url = "https://files.pythonhosted.org/packages/e5/62/c0815c992c9545347aeea7859b50dc9044d147e2e7278329c6e02ac9a616/charset_normalizer-3.4.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2ef7fedc7a6ecbe99969cd09632516738a97eeb8bd7258bf8a0f23114c057dab", size = 295154, upload-time = "2026-03-15T18:50:50.88Z" }, { url = "https://files.pythonhosted.org/packages/a8/37/bdca6613c2e3c58c7421891d80cc3efa1d32e882f7c4a7ee6039c3fc951a/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a4ea868bc28109052790eb2b52a9ab33f3aa7adc02f96673526ff47419490e21", size = 199191, upload-time = "2026-03-15T18:50:52.658Z" }, + { url = "https://files.pythonhosted.org/packages/6c/92/9934d1bbd69f7f398b38c5dae1cbf9cc672e7c34a4adf7b17c0a9c17d15d/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:836ab36280f21fc1a03c99cd05c6b7af70d2697e374c7af0b61ed271401a72a2", size = 218674, upload-time = "2026-03-15T18:50:54.102Z" }, + { url = "https://files.pythonhosted.org/packages/af/90/25f6ab406659286be929fd89ab0e78e38aa183fc374e03aa3c12d730af8a/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f1ce721c8a7dfec21fcbdfe04e8f68174183cf4e8188e0645e92aa23985c57ff", size = 215259, upload-time = "2026-03-15T18:50:55.616Z" }, { url = "https://files.pythonhosted.org/packages/4e/ef/79a463eb0fff7f96afa04c1d4c51f8fc85426f918db467854bfb6a569ce3/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e28d62a8fc7a1fa411c43bd65e346f3bce9716dc51b897fbe930c5987b402d5", size = 207276, upload-time = "2026-03-15T18:50:57.054Z" }, + { url = "https://files.pythonhosted.org/packages/f7/72/d0426afec4b71dc159fa6b4e68f868cd5a3ecd918fec5813a15d292a7d10/charset_normalizer-3.4.6-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:530d548084c4a9f7a16ed4a294d459b4f229db50df689bfe92027452452943a0", size = 195161, upload-time = "2026-03-15T18:50:58.686Z" }, + { url = "https://files.pythonhosted.org/packages/bf/18/c82b06a68bfcb6ce55e508225d210c7e6a4ea122bfc0748892f3dc4e8e11/charset_normalizer-3.4.6-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:30f445ae60aad5e1f8bdbb3108e39f6fbc09f4ea16c815c66578878325f8f15a", size = 203452, upload-time = "2026-03-15T18:51:00.196Z" }, { url = "https://files.pythonhosted.org/packages/44/d6/0c25979b92f8adafdbb946160348d8d44aa60ce99afdc27df524379875cb/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ac2393c73378fea4e52aa56285a3d64be50f1a12395afef9cce47772f60334c2", size = 202272, upload-time = "2026-03-15T18:51:01.703Z" }, + { url = "https://files.pythonhosted.org/packages/2e/3d/7fea3e8fe84136bebbac715dd1221cc25c173c57a699c030ab9b8900cbb7/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:90ca27cd8da8118b18a52d5f547859cc1f8354a00cd1e8e5120df3e30d6279e5", size = 195622, upload-time = "2026-03-15T18:51:03.526Z" }, + { url = "https://files.pythonhosted.org/packages/57/8a/d6f7fd5cb96c58ef2f681424fbca01264461336d2a7fc875e4446b1f1346/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8e5a94886bedca0f9b78fecd6afb6629142fd2605aa70a125d49f4edc6037ee6", size = 220056, upload-time = "2026-03-15T18:51:05.269Z" }, + { url = "https://files.pythonhosted.org/packages/16/50/478cdda782c8c9c3fb5da3cc72dd7f331f031e7f1363a893cdd6ca0f8de0/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:695f5c2823691a25f17bc5d5ffe79fa90972cc34b002ac6c843bb8a1720e950d", size = 203751, upload-time = "2026-03-15T18:51:06.858Z" }, + { url = "https://files.pythonhosted.org/packages/75/fc/cc2fcac943939c8e4d8791abfa139f685e5150cae9f94b60f12520feaa9b/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:231d4da14bcd9301310faf492051bee27df11f2bc7549bc0bb41fef11b82daa2", size = 216563, upload-time = "2026-03-15T18:51:08.564Z" }, { url = "https://files.pythonhosted.org/packages/a8/b7/a4add1d9a5f68f3d037261aecca83abdb0ab15960a3591d340e829b37298/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a056d1ad2633548ca18ffa2f85c202cfb48b68615129143915b8dc72a806a923", size = 209265, upload-time = "2026-03-15T18:51:10.312Z" }, { url = "https://files.pythonhosted.org/packages/1e/1d/4fdabeef4e231153b6ed7567602f3b68265ec4e5b76d6024cf647d43d981/charset_normalizer-3.4.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:11afb56037cbc4b1555a34dd69151e8e069bee82e613a73bef6e714ce733585f", size = 294823, upload-time = "2026-03-15T18:51:15.755Z" }, { url = "https://files.pythonhosted.org/packages/47/7b/20e809b89c69d37be748d98e84dce6820bf663cf19cf6b942c951a3e8f41/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:423fb7e748a08f854a08a222b983f4df1912b1daedce51a72bd24fe8f26a1843", size = 198527, upload-time = "2026-03-15T18:51:17.177Z" }, + { url = "https://files.pythonhosted.org/packages/37/a6/4f8d27527d59c039dce6f7622593cdcd3d70a8504d87d09eb11e9fdc6062/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d73beaac5e90173ac3deb9928a74763a6d230f494e4bfb422c217a0ad8e629bf", size = 218388, upload-time = "2026-03-15T18:51:18.934Z" }, + { url = "https://files.pythonhosted.org/packages/f6/9b/4770ccb3e491a9bacf1c46cc8b812214fe367c86a96353ccc6daf87b01ec/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d60377dce4511655582e300dc1e5a5f24ba0cb229005a1d5c8d0cb72bb758ab8", size = 214563, upload-time = "2026-03-15T18:51:20.374Z" }, { url = "https://files.pythonhosted.org/packages/2b/58/a199d245894b12db0b957d627516c78e055adc3a0d978bc7f65ddaf7c399/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:530e8cebeea0d76bdcf93357aa5e41336f48c3dc709ac52da2bb167c5b8271d9", size = 206587, upload-time = "2026-03-15T18:51:21.807Z" }, + { url = "https://files.pythonhosted.org/packages/7e/70/3def227f1ec56f5c69dfc8392b8bd63b11a18ca8178d9211d7cc5e5e4f27/charset_normalizer-3.4.6-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:a26611d9987b230566f24a0a125f17fe0de6a6aff9f25c9f564aaa2721a5fb88", size = 194724, upload-time = "2026-03-15T18:51:23.508Z" }, + { url = "https://files.pythonhosted.org/packages/58/ab/9318352e220c05efd31c2779a23b50969dc94b985a2efa643ed9077bfca5/charset_normalizer-3.4.6-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:34315ff4fc374b285ad7f4a0bf7dcbfe769e1b104230d40f49f700d4ab6bbd84", size = 202956, upload-time = "2026-03-15T18:51:25.239Z" }, { url = "https://files.pythonhosted.org/packages/75/13/f3550a3ac25b70f87ac98c40d3199a8503676c2f1620efbf8d42095cfc40/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5f8ddd609f9e1af8c7bd6e2aca279c931aefecd148a14402d4e368f3171769fd", size = 201923, upload-time = "2026-03-15T18:51:26.682Z" }, + { url = "https://files.pythonhosted.org/packages/1b/db/c5c643b912740b45e8eec21de1bbab8e7fc085944d37e1e709d3dcd9d72f/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:80d0a5615143c0b3225e5e3ef22c8d5d51f3f72ce0ea6fb84c943546c7b25b6c", size = 195366, upload-time = "2026-03-15T18:51:28.129Z" }, + { url = "https://files.pythonhosted.org/packages/5a/67/3b1c62744f9b2448443e0eb160d8b001c849ec3fef591e012eda6484787c/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:92734d4d8d187a354a556626c221cd1a892a4e0802ccb2af432a1d85ec012194", size = 219752, upload-time = "2026-03-15T18:51:29.556Z" }, + { url = "https://files.pythonhosted.org/packages/f6/98/32ffbaf7f0366ffb0445930b87d103f6b406bc2c271563644bde8a2b1093/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:613f19aa6e082cf96e17e3ffd89383343d0d589abda756b7764cf78361fd41dc", size = 203296, upload-time = "2026-03-15T18:51:30.921Z" }, + { url = "https://files.pythonhosted.org/packages/41/12/5d308c1bbe60cabb0c5ef511574a647067e2a1f631bc8634fcafaccd8293/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:2b1a63e8224e401cafe7739f77efd3f9e7f5f2026bda4aead8e59afab537784f", size = 215956, upload-time = "2026-03-15T18:51:32.399Z" }, { url = "https://files.pythonhosted.org/packages/53/e9/5f85f6c5e20669dbe56b165c67b0260547dea97dba7e187938833d791687/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6cceb5473417d28edd20c6c984ab6fee6c6267d38d906823ebfe20b03d607dc2", size = 208652, upload-time = "2026-03-15T18:51:34.214Z" }, { url = "https://files.pythonhosted.org/packages/2a/68/687187c7e26cb24ccbd88e5069f5ef00eba804d36dde11d99aad0838ab45/charset_normalizer-3.4.6-py3-none-any.whl", hash = "sha256:947cf925bc916d90adba35a64c82aace04fa39b46b52d4630ece166655905a69", size = 61455, upload-time = "2026-03-15T18:53:23.833Z" }, ] @@ -829,18 +884,30 @@ sdist = { url = "https://files.pythonhosted.org/packages/46/9e/d8e40b29b6269a845 wheels = [ { url = "https://files.pythonhosted.org/packages/ec/e0/1ae285f4d5bb61bb62016deb38dc175a2b8cbe578dffdad5e1a5a02a176c/clickhouse_driver-0.2.10-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3572e74cd65828f72284bf607de259c059178b44b19a93cb67766b7e7458cf8e", size = 208477, upload-time = "2025-11-10T22:47:34.925Z" }, { url = "https://files.pythonhosted.org/packages/28/04/e2fb47a4aaf9653c9ed872e1505e997d42f834b0891351ef54169e100c5c/clickhouse_driver-0.2.10-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:767af2b2d2e02fb7abd8fc9619f8aa2de65be010d3024029d68bc9b1be564466", size = 1006131, upload-time = "2025-11-10T22:47:36.386Z" }, + { url = "https://files.pythonhosted.org/packages/e3/52/626cf3a908dde51638213a6c414e86bc66c47e384cb379c4a0533930a329/clickhouse_driver-0.2.10-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b00005a89f0e40ec0bc313f7e131d958aa17e6af5c3260dbb5c7daf5370c5443", size = 1059838, upload-time = "2025-11-10T22:47:38.266Z" }, + { url = "https://files.pythonhosted.org/packages/12/57/a5917930760e4032e98017916bd6770308e146d44c58e450da6fa87f2d4b/clickhouse_driver-0.2.10-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a62ad120dab6bcc68b6413b7ef0dbaef75ab5ca985d490a9e1ec13d93bf33dc3", size = 1069504, upload-time = "2025-11-10T22:47:40.681Z" }, { url = "https://files.pythonhosted.org/packages/f5/08/4419ce43b27b6349fd14af0d8f5d8594d270b9bb24cbaca575bacfec630e/clickhouse_driver-0.2.10-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a9f65e71c99f0c8a64afa348977793967f897c4f731984ed54fed4eca8d375a0", size = 998284, upload-time = "2025-11-10T22:47:42.713Z" }, { url = "https://files.pythonhosted.org/packages/36/10/edbe55be3554e2cea7c68ed1761aaa2bea0153474d81a467a0ac862b3478/clickhouse_driver-0.2.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c120b182ea7e9713b119ba2b518dd75503c18f26cb001a6e436326765fddd123", size = 971989, upload-time = "2025-11-10T22:47:44.642Z" }, + { url = "https://files.pythonhosted.org/packages/2f/18/d0b883af04067c70e99c22dbab1f085062ae764a063f22d4e144e833048d/clickhouse_driver-0.2.10-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d3da6f6d05f14780f3183f8b7b23ed9826d1e0f2f73c2471037d5335b474782e", size = 1022107, upload-time = "2025-11-10T22:47:46.148Z" }, + { url = "https://files.pythonhosted.org/packages/fe/40/11446c52c5330123354f2f88151c620e1b38ca6d5131b2cc71786ec3c067/clickhouse_driver-0.2.10-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9617c6ef154e58c6693be6e1169000957238f83d21fe20d57bb492412cc6128d", size = 1015750, upload-time = "2025-11-10T22:47:47.702Z" }, { url = "https://files.pythonhosted.org/packages/36/9b/32abe3c76fe8494ad1642febbe4c59dfd46477e27c401d2ac8cc8a0a6117/clickhouse_driver-0.2.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b8c2dbee2083295c6d9789a10cb0c967c4e123e6315e3192e4ad935cd55967c3", size = 975901, upload-time = "2025-11-10T22:47:49.136Z" }, { url = "https://files.pythonhosted.org/packages/32/7b/8e526f6ffb9983c0c6d082e358df4b20fe1a9e95f453e704bc7a25ef4aab/clickhouse_driver-0.2.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:188775d38ff7cb36e7045441aabf3a6a8751127d8b37b6eb1b1518494eaac5bd", size = 207193, upload-time = "2025-11-10T22:47:55.146Z" }, { url = "https://files.pythonhosted.org/packages/65/96/40f274896abf287c378575f025c602fa4e834278930dd63574ff548815c4/clickhouse_driver-0.2.10-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ff5cba860df61845d6ae12f31d4a70ff4ae3be4e6a8a876e68af8aa4b0e45bc", size = 1046187, upload-time = "2025-11-10T22:47:58.246Z" }, + { url = "https://files.pythonhosted.org/packages/0c/80/7b6e110c3b803fa8b3f8cdba0e08553a62c5f64e5ad57e56de3ea95cd9e1/clickhouse_driver-0.2.10-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fad865009d96de44d548f1691ed92adee971f72c001cf4466b3ba2ac7d9db47b", size = 1088806, upload-time = "2025-11-10T22:47:59.834Z" }, + { url = "https://files.pythonhosted.org/packages/c5/d6/7f77bd00fc01df9db2e573de21bbc1f66083549d004864b892877dee8a76/clickhouse_driver-0.2.10-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:cf0fe791e7c2adc0ab41d4770953c00f8a88bdd7e3ee83bb849a661a6c93d4ef", size = 1109839, upload-time = "2025-11-10T22:48:01.405Z" }, { url = "https://files.pythonhosted.org/packages/55/f7/57a80ff9cc44a333021e2caf8d35fc23da6ec7b602bbc3bf8dfac0253a6e/clickhouse_driver-0.2.10-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5744daafdd0ff7520c6ae95a78211a0ff5c2cfb3513a20f5602d2bc7eed580d", size = 1049773, upload-time = "2025-11-10T22:48:03.089Z" }, { url = "https://files.pythonhosted.org/packages/f6/3e/fcf8e9cb9edc717ce6c467a9ec7c96b4495d5f8ec4859175952149fbdaa8/clickhouse_driver-0.2.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f02f6c9f71ae5c06e3b760d3d9f4f758b32acf6f71504b6d90bacca9abbfec18", size = 1006817, upload-time = "2025-11-10T22:48:05.038Z" }, + { url = "https://files.pythonhosted.org/packages/95/ab/1bc25a385012c03595b91311d8341205a5790375207d80425e2285055d42/clickhouse_driver-0.2.10-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:6df571410f149e16e0a0e5529f1c2a9e41bb62b9357a3c8b0bd0647d6bb0fd1e", size = 1051047, upload-time = "2025-11-10T22:48:07.115Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e1/9dd7331d08495beacf4291a6fbe5514fd0f6f8d53014121a8d70d8bd6c1e/clickhouse_driver-0.2.10-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:1e891162226a44fa169bdc996efd49b22bcf59372c35118ec5785e936fe97178", size = 1052014, upload-time = "2025-11-10T22:48:08.608Z" }, { url = "https://files.pythonhosted.org/packages/ee/e9/af10e0ddbbd90c4ead933effff1b8914bc687bd52a70d244404db4c91529/clickhouse_driver-0.2.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3a261947ba0cf0034d044c30563ad151d1cf8156a5ff419b017c423b4235e0ac", size = 1020937, upload-time = "2025-11-10T22:48:10.993Z" }, { url = "https://files.pythonhosted.org/packages/34/92/ee5a2d7a812b65d9690e46222218f33064c4bd44f3535b1ba564fb4b528b/clickhouse_driver-0.2.10-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8be64c77d58d4a33b3c957cdb7c5a4deeac56bf93f4188dbfb5c5454eb04c985", size = 205158, upload-time = "2025-11-10T22:48:17.745Z" }, { url = "https://files.pythonhosted.org/packages/03/00/6c532a0aea89e3d09dd4150b1df0b92e787a306b8711d54d003d18fd1ddd/clickhouse_driver-0.2.10-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:23abafd0c883ccc1baea527c1d05a6bc0c59aae6c29ae65e1b84d498b265f8c0", size = 1033476, upload-time = "2025-11-10T22:48:19.239Z" }, + { url = "https://files.pythonhosted.org/packages/ea/9b/137ea1ff9539da77cd022331ec4fa079cbefbd4ebbcb5c51bdd7dcd0bca0/clickhouse_driver-0.2.10-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:77ffe2063469c637c5e57bf0713ca1b617b612d55a8392799f97e34c353e6908", size = 1079495, upload-time = "2025-11-10T22:48:20.744Z" }, + { url = "https://files.pythonhosted.org/packages/ec/1c/e13766af7e4e174c6f17b1fbc5a078b28584f53adc91f103caacc73f569b/clickhouse_driver-0.2.10-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:df2d77779fcc1ddb68614b75bf45b8db61cf63f42a03d5624ce6922a305e609f", size = 1100658, upload-time = "2025-11-10T22:48:22.277Z" }, { url = "https://files.pythonhosted.org/packages/41/e5/0686ad3ef1b594c16e8b13394c73ee4860fd025d70211a360f797dd7a28a/clickhouse_driver-0.2.10-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85e46e31e4b14626571819e669341a3017376ce935d25b2cc0bfea9343b1b562", size = 1034175, upload-time = "2025-11-10T22:48:24.117Z" }, { url = "https://files.pythonhosted.org/packages/d8/32/fea4e971297b50e5af3318fd90d400269ae1c74ad4d83a9453b89f578d3c/clickhouse_driver-0.2.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d7435d1ff2bc577aeedf8f01d94b5777af382484f8973a9c5018d5afd0dd175c", size = 995963, upload-time = "2025-11-10T22:48:25.824Z" }, + { url = "https://files.pythonhosted.org/packages/02/c4/d42f2b69ab5903e5bc9119b179f55c9aef79fe667f77cab4d8ae90492dcd/clickhouse_driver-0.2.10-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b60c7e3321214eec4568811bcd953836671fa078c57f6607f236414447636de2", size = 1044626, upload-time = "2025-11-10T22:48:27.927Z" }, + { url = "https://files.pythonhosted.org/packages/78/36/043b6b2d967396172a60f10bf26de2c83248857f9a1e75b481f02218d1d7/clickhouse_driver-0.2.10-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:13fdf6571e20ac79992605ad65058296ac0f2437c1e7428a98dd6d173753119e", size = 1045772, upload-time = "2025-11-10T22:48:29.439Z" }, { url = "https://files.pythonhosted.org/packages/0c/cf/bc5c807cbe68ce9eeac6a1997b937c81774ca86b2ab593c6efb9121a9f08/clickhouse_driver-0.2.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:06b6b683af086f9049d0c5e7e660fb76013439efa640e6c8ff6673622c3838fa", size = 1006716, upload-time = "2025-11-10T22:48:31.086Z" }, { url = "https://files.pythonhosted.org/packages/40/7d/9abdd95b0da0dcf6dc644336459f132575bfbdee1a4ba377195c2032c03a/clickhouse_driver-0.2.10-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:68e96e04282d126486b3820391a8ebf1d7c32b61e5fcbd701aeeda79017349e5", size = 216933, upload-time = "2025-11-10T22:49:47.474Z" }, { url = "https://files.pythonhosted.org/packages/5a/a4/33d4b6f1650847280265756e4d54f94730cdac082ab3f9e6518ba97502bf/clickhouse_driver-0.2.10-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0fd9f72fcfe86e0fef0681cd124325714e92e96ad1fd675dfbeaccdfb7bd2f64", size = 219670, upload-time = "2025-11-10T22:49:49.401Z" }, @@ -891,22 +958,38 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/35/8b/cd129b0ca4afe886a6ce9d183c44d8301acbd4ef248622e7c49a23145605/coverage-7.13.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:145ede53ccbafb297c1c9287f788d1bc3efd6c900da23bf6931b09eafc931587", size = 219880, upload-time = "2026-03-17T10:30:16.231Z" }, { url = "https://files.pythonhosted.org/packages/92/be/b1afb692be85b947f3401375851484496134c5554e67e822c35f28bf2fbc/coverage-7.13.5-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ec10e2a42b41c923c2209b846126c6582db5e43a33157e9870ba9fb70dc7854b", size = 252218, upload-time = "2026-03-17T10:30:19.804Z" }, { url = "https://files.pythonhosted.org/packages/da/69/2f47bb6fa1b8d1e3e5d0c4be8ccb4313c63d742476a619418f85740d597b/coverage-7.13.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be3d4bbad9d4b037791794ddeedd7d64a56f5933a2c1373e18e9e568b9141686", size = 254326, upload-time = "2026-03-17T10:30:21.321Z" }, + { url = "https://files.pythonhosted.org/packages/d5/d0/79db81da58965bd29dabc8f4ad2a2af70611a57cba9d1ec006f072f30a54/coverage-7.13.5-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4d2afbc5cc54d286bfb54541aa50b64cdb07a718227168c87b9e2fb8f25e1743", size = 256267, upload-time = "2026-03-17T10:30:23.094Z" }, + { url = "https://files.pythonhosted.org/packages/e5/32/d0d7cc8168f91ddab44c0ce4806b969df5f5fdfdbb568eaca2dbc2a04936/coverage-7.13.5-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3ad050321264c49c2fa67bb599100456fc51d004b82534f379d16445da40fb75", size = 250430, upload-time = "2026-03-17T10:30:25.311Z" }, { url = "https://files.pythonhosted.org/packages/4d/06/a055311d891ddbe231cd69fdd20ea4be6e3603ffebddf8704b8ca8e10a3c/coverage-7.13.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7300c8a6d13335b29bb76d7651c66af6bd8658517c43499f110ddc6717bfc209", size = 252017, upload-time = "2026-03-17T10:30:27.284Z" }, + { url = "https://files.pythonhosted.org/packages/4e/ab/0d7fb2efc2e9a5eb7ddcc6e722f834a69b454b7e6e5888c3a8567ecffb31/coverage-7.13.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:9adb6688e3b53adffefd4a52d72cbd8b02602bfb8f74dcd862337182fd4d1a4e", size = 253843, upload-time = "2026-03-17T10:30:31.301Z" }, + { url = "https://files.pythonhosted.org/packages/ba/6f/7467b917bbf5408610178f62a49c0ed4377bb16c1657f689cc61470da8ce/coverage-7.13.5-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7c8d4bc913dd70b93488d6c496c77f3aff5ea99a07e36a18f865bca55adef8bd", size = 249802, upload-time = "2026-03-17T10:30:33.358Z" }, { url = "https://files.pythonhosted.org/packages/75/2c/1172fb689df92135f5bfbbd69fc83017a76d24ea2e2f3a1154007e2fb9f8/coverage-7.13.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0e3c426ffc4cd952f54ee9ffbdd10345709ecc78a3ecfd796a57236bfad0b9b8", size = 250707, upload-time = "2026-03-17T10:30:35.2Z" }, { url = "https://files.pythonhosted.org/packages/a6/16/a68a19e5384e93f811dccc51034b1fd0b865841c390e3c931dcc4699e035/coverage-7.13.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0e223ce4b4ed47f065bfb123687686512e37629be25cc63728557ae7db261422", size = 219908, upload-time = "2026-03-17T10:30:43.906Z" }, { url = "https://files.pythonhosted.org/packages/8c/49/cd14b789536ac6a4778c453c6a2338bc0a2fb60c5a5a41b4008328b9acc1/coverage-7.13.5-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:03ccc709a17a1de074fb1d11f217342fb0d2b1582ed544f554fc9fc3f07e95f5", size = 254159, upload-time = "2026-03-17T10:30:47.204Z" }, { url = "https://files.pythonhosted.org/packages/9d/00/7b0edcfe64e2ed4c0340dac14a52ad0f4c9bd0b8b5e531af7d55b703db7c/coverage-7.13.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3f4818d065964db3c1c66dc0fbdac5ac692ecbc875555e13374fdbe7eedb4376", size = 255270, upload-time = "2026-03-17T10:30:48.812Z" }, + { url = "https://files.pythonhosted.org/packages/93/89/7ffc4ba0f5d0a55c1e84ea7cee39c9fc06af7b170513d83fbf3bbefce280/coverage-7.13.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:012d5319e66e9d5a218834642d6c35d265515a62f01157a45bcc036ecf947256", size = 257538, upload-time = "2026-03-17T10:30:50.77Z" }, + { url = "https://files.pythonhosted.org/packages/81/bd/73ddf85f93f7e6fa83e77ccecb6162d9415c79007b4bc124008a4995e4a7/coverage-7.13.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8dd02af98971bdb956363e4827d34425cb3df19ee550ef92855b0acb9c7ce51c", size = 251821, upload-time = "2026-03-17T10:30:52.5Z" }, { url = "https://files.pythonhosted.org/packages/a0/81/278aff4e8dec4926a0bcb9486320752811f543a3ce5b602cc7a29978d073/coverage-7.13.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f08fd75c50a760c7eb068ae823777268daaf16a80b918fa58eea888f8e3919f5", size = 253191, upload-time = "2026-03-17T10:30:54.543Z" }, + { url = "https://files.pythonhosted.org/packages/37/a6/f79fb37aa104b562207cc23cb5711ab6793608e246cae1e93f26b2236ed9/coverage-7.13.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:9d44d7aa963820b1b971dbecd90bfe5fe8f81cff79787eb6cca15750bd2f79b9", size = 255404, upload-time = "2026-03-17T10:30:58.427Z" }, + { url = "https://files.pythonhosted.org/packages/75/f0/ed15262a58ec81ce457ceb717b7f78752a1713556b19081b76e90896e8d4/coverage-7.13.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:7132bed4bd7b836200c591410ae7d97bf7ae8be6fc87d160b2bd881df929e7bf", size = 250903, upload-time = "2026-03-17T10:31:00.093Z" }, { url = "https://files.pythonhosted.org/packages/0f/e9/9129958f20e7e9d4d56d51d42ccf708d15cac355ff4ac6e736e97a9393d2/coverage-7.13.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a698e363641b98843c517817db75373c83254781426e94ada3197cabbc2c919c", size = 252780, upload-time = "2026-03-17T10:31:01.916Z" }, { url = "https://files.pythonhosted.org/packages/0c/c9/44fb661c55062f0818a6ffd2685c67aa30816200d5f2817543717d4b92eb/coverage-7.13.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:941617e518602e2d64942c88ec8499f7fbd49d3f6c4327d3a71d43a1973032f3", size = 219942, upload-time = "2026-03-17T10:31:10.708Z" }, { url = "https://files.pythonhosted.org/packages/ac/68/1666e3a4462f8202d836920114fa7a5ee9275d1fa45366d336c551a162dd/coverage-7.13.5-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:78e696e1cc714e57e8b25760b33a8b1026b7048d270140d25dafe1b0a1ee05a3", size = 253541, upload-time = "2026-03-17T10:31:14.247Z" }, { url = "https://files.pythonhosted.org/packages/4e/5e/3ee3b835647be646dcf3c65a7c6c18f87c27326a858f72ab22c12730773d/coverage-7.13.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:02ca0eed225b2ff301c474aeeeae27d26e2537942aa0f87491d3e147e784a82b", size = 254780, upload-time = "2026-03-17T10:31:16.193Z" }, + { url = "https://files.pythonhosted.org/packages/44/b3/cb5bd1a04cfcc49ede6cd8409d80bee17661167686741e041abc7ee1b9a9/coverage-7.13.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:04690832cbea4e4663d9149e05dba142546ca05cb1848816760e7f58285c970a", size = 256912, upload-time = "2026-03-17T10:31:17.89Z" }, + { url = "https://files.pythonhosted.org/packages/1b/66/c1dceb7b9714473800b075f5c8a84f4588f887a90eb8645282031676e242/coverage-7.13.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0590e44dd2745c696a778f7bab6aa95256de2cbc8b8cff4f7db8ff09813d6969", size = 251165, upload-time = "2026-03-17T10:31:19.605Z" }, { url = "https://files.pythonhosted.org/packages/b7/62/5502b73b97aa2e53ea22a39cf8649ff44827bef76d90bf638777daa27a9d/coverage-7.13.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d7cfad2d6d81dd298ab6b89fe72c3b7b05ec7544bdda3b707ddaecff8d25c161", size = 252908, upload-time = "2026-03-17T10:31:21.312Z" }, + { url = "https://files.pythonhosted.org/packages/a3/23/bc866fb6163be52a8a9e5d708ba0d3b1283c12158cefca0a8bbb6e247a43/coverage-7.13.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:48c39bc4a04d983a54a705a6389512883d4a3b9862991b3617d547940e9f52b1", size = 255030, upload-time = "2026-03-17T10:31:25.58Z" }, + { url = "https://files.pythonhosted.org/packages/7d/8b/ef67e1c222ef49860701d346b8bbb70881bef283bd5f6cbba68a39a086c7/coverage-7.13.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2d3807015f138ffea1ed9afeeb8624fd781703f2858b62a8dd8da5a0994c57b6", size = 250694, upload-time = "2026-03-17T10:31:27.316Z" }, { url = "https://files.pythonhosted.org/packages/46/0d/866d1f74f0acddbb906db212e096dee77a8e2158ca5e6bb44729f9d93298/coverage-7.13.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ee2aa19e03161671ec964004fb74b2257805d9710bf14a5c704558b9d8dbaf17", size = 252469, upload-time = "2026-03-17T10:31:29.472Z" }, { url = "https://files.pythonhosted.org/packages/5b/4c/d20e554f988c8f91d6a02c5118f9abbbf73a8768a3048cb4962230d5743f/coverage-7.13.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e0b216a19534b2427cc201a26c25da4a48633f29a487c61258643e89d28200c0", size = 220617, upload-time = "2026-03-17T10:31:39.245Z" }, { url = "https://files.pythonhosted.org/packages/d5/f6/7f1ab39393eeb50cfe4747ae8ef0e4fc564b989225aa1152e13a180d74f8/coverage-7.13.5-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4b59148601efcd2bac8c4dbf1f0ad6391693ccf7a74b8205781751637076aee3", size = 263987, upload-time = "2026-03-17T10:31:43.724Z" }, { url = "https://files.pythonhosted.org/packages/a0/d7/62c084fb489ed9c6fbdf57e006752e7c516ea46fd690e5ed8b8617c7d52e/coverage-7.13.5-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:505d7083c8b0c87a8fa8c07370c285847c1f77739b22e299ad75a6af6c32c5c9", size = 266416, upload-time = "2026-03-17T10:31:45.769Z" }, + { url = "https://files.pythonhosted.org/packages/a9/f6/df63d8660e1a0bff6125947afda112a0502736f470d62ca68b288ea762d8/coverage-7.13.5-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:60365289c3741e4db327e7baff2a4aaacf22f788e80fa4683393891b70a89fbd", size = 267558, upload-time = "2026-03-17T10:31:48.293Z" }, + { url = "https://files.pythonhosted.org/packages/5b/02/353ca81d36779bd108f6d384425f7139ac3c58c750dcfaafe5d0bee6436b/coverage-7.13.5-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1b88c69c8ef5d4b6fe7dea66d6636056a0f6a7527c440e890cf9259011f5e606", size = 261163, upload-time = "2026-03-17T10:31:50.125Z" }, { url = "https://files.pythonhosted.org/packages/2c/16/2e79106d5749bcaf3aee6d309123548e3276517cd7851faa8da213bc61bf/coverage-7.13.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5b13955d31d1633cf9376908089b7cebe7d15ddad7aeaabcbe969a595a97e95e", size = 263981, upload-time = "2026-03-17T10:31:51.961Z" }, + { url = "https://files.pythonhosted.org/packages/40/48/097cdc3db342f34006a308ab41c3a7c11c3f0d84750d340f45d88a782e00/coverage-7.13.5-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:084b84a8c63e8d6fc7e3931b316a9bcafca1458d753c539db82d31ed20091a87", size = 265321, upload-time = "2026-03-17T10:31:55.997Z" }, + { url = "https://files.pythonhosted.org/packages/bb/1f/4994af354689e14fd03a75f8ec85a9a68d94e0188bbdab3fc1516b55e512/coverage-7.13.5-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ad14385487393e386e2ea988b09d62dd42c397662ac2dabc3832d71253eee479", size = 260502, upload-time = "2026-03-17T10:31:58.308Z" }, { url = "https://files.pythonhosted.org/packages/22/c6/9bb9ef55903e628033560885f5c31aa227e46878118b63ab15dc7ba87797/coverage-7.13.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:7f2c47b36fe7709a6e83bfadf4eefb90bd25fbe4014d715224c4316f808e59a2", size = 262688, upload-time = "2026-03-17T10:32:00.141Z" }, { url = "https://files.pythonhosted.org/packages/9e/ee/a4cf96b8ce1e566ed238f0659ac2d3f007ed1d14b181bcb684e19561a69a/coverage-7.13.5-py3-none-any.whl", hash = "sha256:34b02417cf070e173989b3db962f7ed56d2f644307b2cf9d5a0f258e13084a61", size = 211346, upload-time = "2026-03-17T10:33:15.691Z" }, ] @@ -929,8 +1012,11 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d5/85/6379d42181bfc713094f081360fc5784d6c816b599d45e7f082502d173ce/cryptography-48.0.1-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:32143b24adb918f078134e1e230f1eb8cc04886b92c28b5f0041aaf3e5699225", size = 4696243, upload-time = "2026-06-09T22:32:33.446Z" }, { url = "https://files.pythonhosted.org/packages/9c/87/c85d147b53323c7eb4d850920c8901377323c2a0ff8d79c262d4fee89aa2/cryptography-48.0.1-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f0d27a5696721ef7a672b8c810f6aded391058e0b9486e63e6d93baf765da691", size = 4713235, upload-time = "2026-06-09T22:31:40.141Z" }, { url = "https://files.pythonhosted.org/packages/79/58/67cbf8cf1ee7c54b439ca07bbecf8362c07afc11a3724fea70f745784add/cryptography-48.0.1-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:eb86ce1af36fe65041b6db9a8bb064ee621a7e5fded0f80d475ec243477cd242", size = 4702323, upload-time = "2026-06-09T22:31:42.191Z" }, + { url = "https://files.pythonhosted.org/packages/89/c6/24266ac10c47f6cd2a865f4446062b466da1d1f10b27189eac00e61bf0c9/cryptography-48.0.1-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:b024e784ad6c077ee0147b35ea9cbfc1e34e1fd4c1dcca214c2794d73a12df08", size = 5300085, upload-time = "2026-06-09T22:31:58.703Z" }, { url = "https://files.pythonhosted.org/packages/d2/bb/cc4b78784f97efc8c5874c2a9743708d172be6663024b34a0467885ae0c8/cryptography-48.0.1-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3752f2dbc8f07a30aad2932c986cea495b03bb554887828225da104f732852b6", size = 4746137, upload-time = "2026-06-09T22:31:31.01Z" }, + { url = "https://files.pythonhosted.org/packages/1f/52/0c44de3f5267f8fbe8e835138017522a333436166e406f0db9b9e6e3033f/cryptography-48.0.1-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:bd81490cd5801d755cf97bb68ac191f14b708470b1c7cf4580f669b9c9264cd8", size = 4333867, upload-time = "2026-06-09T22:32:28.096Z" }, { url = "https://files.pythonhosted.org/packages/9a/2e/772d7adbfa931537bc401640b7cac9976bff689bda187833e5d63b428e49/cryptography-48.0.1-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:66fd0771e7b9c6dcd44cf1120690d2338d16d72795cf40cae2786a39eba65429", size = 4701805, upload-time = "2026-06-09T22:31:38.284Z" }, + { url = "https://files.pythonhosted.org/packages/f8/a3/b06844f303873493c963caf581c04df31c7035e0c1b0f02c4814d319ec80/cryptography-48.0.1-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:3fd2ca57062b241c856670b073487d2e86c4637937ca5601e48f97bf8e11fc8f", size = 5258461, upload-time = "2026-06-09T22:31:04.187Z" }, { url = "https://files.pythonhosted.org/packages/9f/13/8b765e2e12b07c74941caadb9d1c8fdc006c4dfbf2b8f2d610519758954d/cryptography-48.0.1-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:0ee6ea481db1ab889cba043ec1eda17bb9c1ea79db6722f779c3667f9f70322f", size = 4745488, upload-time = "2026-06-09T22:32:30.07Z" }, { url = "https://files.pythonhosted.org/packages/2e/aa/48972bce55049b32a94f4907eda4d75fa385aad8a39506cc2fc72196ecf0/cryptography-48.0.1-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:f2ceef93cb096aa3c4cc4b5c94ca6131f9196d28c64d6111533402a9b2054d41", size = 4830256, upload-time = "2026-06-09T22:31:43.868Z" }, { url = "https://files.pythonhosted.org/packages/47/a2/e5079a032fb85cf6005046ca92bbd78b0c82dad2b5751ab8c311659da06f/cryptography-48.0.1-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9bd3f92d76217892b15df84ca256c2c113d386fdda7a7d8691aeeced976507c6", size = 4979117, upload-time = "2026-06-09T22:31:05.845Z" }, @@ -938,8 +1024,11 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b0/d9/45f309a7e4e5f3f8f121d6d3be9e94024a7726ec598d6e08ae04edb2f04d/cryptography-48.0.1-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:48fe40804d4caa2288f24e70ca8c64c42dd826da0ad7e4f1b41b2128d679e6c8", size = 4690196, upload-time = "2026-06-09T22:31:54.74Z" }, { url = "https://files.pythonhosted.org/packages/5f/9f/a1bc8bcc798811b8527eb374bbccf30a3f3e806829d967118222bf1125eb/cryptography-48.0.1-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:86be3b1b0b6bf09482fb50a979c508d2950ed95f5621ec77f4e385962006b83a", size = 4696782, upload-time = "2026-06-09T22:31:45.615Z" }, { url = "https://files.pythonhosted.org/packages/66/c2/81a4fb4e4373c500bb526bc337ac5719dd31dd15b970b84a238168c6aa08/cryptography-48.0.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:4ab0a343c807bbcd90c971cd1ecf072937cd01847a9e002bef88fb47ac6be577", size = 4696618, upload-time = "2026-06-09T22:31:11.564Z" }, + { url = "https://files.pythonhosted.org/packages/e5/0b/aa68b221dde92d09cb29a024ede17550ee21e77a404e59fc093c82bb51e1/cryptography-48.0.1-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:9621de99d2da096006b629979efd8ae7eb2d8b822488d0c89ee4000c306c59b1", size = 5289970, upload-time = "2026-06-09T22:31:20.368Z" }, { url = "https://files.pythonhosted.org/packages/78/13/fba657f958d2af66ea959a4ba01212632089249d34af1ae48054136344d7/cryptography-48.0.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:88c852a0ae366e262e5a1744b685e6a433dc8788dd2a277e418bf4904203609d", size = 4731873, upload-time = "2026-06-09T22:31:22.253Z" }, + { url = "https://files.pythonhosted.org/packages/4c/4c/9a964756d24a26b3e34dfcb16f961b89838786e6700b635b0d1e3adff4b6/cryptography-48.0.1-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:43c5835e2cb98c8733d86f57d6fc879b613f5c3478607281c3e36daffc6dd8a6", size = 4330804, upload-time = "2026-06-09T22:31:36.56Z" }, { url = "https://files.pythonhosted.org/packages/4b/0f/a10f3a6eb12950a10e3a874070283aa2dd5875b2bfd15fad8a3e17b3f13e/cryptography-48.0.1-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:fe0180af5bf9236518a087e35bf2d9a347d5f5f51e63c579d683ddff424e3d46", size = 4696217, upload-time = "2026-06-09T22:31:13.351Z" }, + { url = "https://files.pythonhosted.org/packages/f3/6f/5cd12f951165ea73ef85266775d97e4c763b2474ccfd816dd69d3a18d6f8/cryptography-48.0.1-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:b7a2d1a937a738a881737cec135a38bb61470589b17515b9f73f571d0ae10401", size = 5245252, upload-time = "2026-06-09T22:32:02.193Z" }, { url = "https://files.pythonhosted.org/packages/68/ab/8aaa12e4516ec4464033ab79b6f3b592bd5a92102467c4ace8a0d970203f/cryptography-48.0.1-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:b74ca3b8e5ecdd833bf6a002ca41b4793bb27fb8f1c06ffaf2643c9e9140e31b", size = 4731388, upload-time = "2026-06-09T22:32:04.019Z" }, { url = "https://files.pythonhosted.org/packages/1b/24/50027ea4dca85ec1f40688f3c24fb32ccacd520583c9592c3cc95628e6fb/cryptography-48.0.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2c37f2461406063b417837f5f3daab668652acd82423efcd7f0a9f04be972de1", size = 4824186, upload-time = "2026-06-09T22:32:18.707Z" }, { url = "https://files.pythonhosted.org/packages/52/41/04cb5eb17085ade6f50cc611fb657df6a0f5885350de8764ece89c050197/cryptography-48.0.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:86fe77abb1bd87afb251d4d02ada7ecf53a32cee9b67d976abb2e45a13297475", size = 4964539, upload-time = "2026-06-09T22:31:18.793Z" }, @@ -1566,23 +1655,43 @@ sdist = { url = "https://files.pythonhosted.org/packages/dd/00/dab9ca274cf1fde19 wheels = [ { url = "https://files.pythonhosted.org/packages/11/6a/085b3cae0e04da4d42306dc07e2cc4f95d9c8f27df4dfd1a25d0f80516cb/fastar-0.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c8ac3e8aaee57dfc822b04f570f0a963c2381a9dc8990fe0c6e965efd23fd451", size = 629764, upload-time = "2026-03-20T14:25:19.017Z" }, { url = "https://files.pythonhosted.org/packages/30/d4/4a5a3c341d26197ea3ae6bed79fc9bb4ead8ddc74a93bdb74e4ee0bac18e/fastar-0.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:17e2c3b46408193ea13c1e1177275ca7951e88bd3dce16baccb8de4f5e0dc2e8", size = 762096, upload-time = "2026-03-20T14:23:49.175Z" }, + { url = "https://files.pythonhosted.org/packages/bc/dd/1d346cdfcd3064f6c435eff90a8d7cf0021487e3681453bdd681b9488d81/fastar-0.9.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:52f96a3d4cfbe4f06b376706fa0562f3a1d2329bc37168119af0e47e1ac21cab", size = 759627, upload-time = "2026-03-20T14:24:01.984Z" }, + { url = "https://files.pythonhosted.org/packages/02/a1/e91eb7ae1e41c0d3ead86dc199beb13a0b80101e2948d66adeb578b09e60/fastar-0.9.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:57e9b94e485713c79bb259f7ecff1213527d05e9aa43a157c3fbc88812cf163e", size = 926211, upload-time = "2026-03-20T14:24:15.218Z" }, + { url = "https://files.pythonhosted.org/packages/9b/63/9fea9604e7aecc2f062f0df5729f74712d81615a1b18fa6a1a13106184fa/fastar-0.9.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fb06d0a0cc3cf52a9c07559bb16ab99eb75afe0b3d5ce68f5c299569460851ac", size = 818748, upload-time = "2026-03-20T14:24:40.765Z" }, { url = "https://files.pythonhosted.org/packages/b0/f8/521438041d69873bb68b144b09080ae4f1621cebb8238b1e54821057206b/fastar-0.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c75e779f72d845037d4bf6692d01ac66f014eaef965c9231d41d5cc1276b89fc", size = 822380, upload-time = "2026-03-20T14:25:06.825Z" }, + { url = "https://files.pythonhosted.org/packages/92/05/f33cc3f5f96ffb7d81a7f06c9239d4eea584527292a030a73d3218148f41/fastar-0.9.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:24b13fc4ef3f1e3c9cc2dcf07ad9445900db9d3ce09b73021547a55994d0407f", size = 886569, upload-time = "2026-03-20T14:24:27.567Z" }, { url = "https://files.pythonhosted.org/packages/60/32/6e7cb45dce544f97b0199325084a0a5a895cb903e0539690619e78d8d7cf/fastar-0.9.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ec7852de506d022ad36ad56f4aefb10c259dd59e485bf87af827954d404ba9d5", size = 969993, upload-time = "2026-03-20T14:25:44.222Z" }, + { url = "https://files.pythonhosted.org/packages/6a/ee/04cf9374e5e6a82ddc87073d684c1fa7a9ca368bf85c2786535b1bfc38a9/fastar-0.9.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:a79c53c3003958dca88a7ec3dd805bf9c2fb2a659110039f44571d57e329e3d4", size = 1036738, upload-time = "2026-03-20T14:25:57.551Z" }, { url = "https://files.pythonhosted.org/packages/1f/44/a1c9f6afe93d1cc1abb68a7cda2bada509d756d24e22d5d949ca86b4f45e/fastar-0.9.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5c03fad1ad9ac57cf03a4db9e18c7109c37416ff4eb9ebfca98fcd2b233a26c4", size = 1029251, upload-time = "2026-03-20T14:26:23.215Z" }, { url = "https://files.pythonhosted.org/packages/95/97/f1e34c8224dc373c6fab5b33e33be0d184751fdc27013af3278b1e4e6e6c/fastar-0.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9ec841a69fea73361c6df6d9183915c09e9ce3bd96493763fa46019e79918400", size = 627422, upload-time = "2026-03-20T14:25:20.318Z" }, { url = "https://files.pythonhosted.org/packages/fe/cf/b6ad68b2ab1d7b74b0d38725d817418016bdd64880b36108be80d2460b4d/fastar-0.9.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de264da9e8ef6407aa0b23c7c47ed4e34fde867e7c1f6e3cb98945a93e5f89f2", size = 760583, upload-time = "2026-03-20T14:23:50.447Z" }, + { url = "https://files.pythonhosted.org/packages/b8/96/086116ad46e3b98f6c217919d680e619f2857ffa6b5cc0d7e46e4f214b83/fastar-0.9.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:75c70be3a7da3ff9342f64c15ec3749c13ef56bc28e69075d82d03768532a8d0", size = 758000, upload-time = "2026-03-20T14:24:03.471Z" }, + { url = "https://files.pythonhosted.org/packages/9b/e6/ea642ea61eea98d609343080399a296a9ff132bd0492a6638d6e0d9e41a7/fastar-0.9.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4a734506b071d2a8844771fe735fbd6d67dd0eec80eef5f189bbe763ebe7a0b8", size = 923647, upload-time = "2026-03-20T14:24:16.875Z" }, + { url = "https://files.pythonhosted.org/packages/c6/3e/53874aad61e4a664af555a2aa7a52fe46cfadd423db0e592fa0cfe0fa668/fastar-0.9.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8eac084ab215aaf65fa406c9b9da1ac4e697c3d3a1a183e09c488e555802f62d", size = 816528, upload-time = "2026-03-20T14:24:42.048Z" }, { url = "https://files.pythonhosted.org/packages/41/df/d663214d35380b07a24a796c48d7d7d4dc3a28ec0756edbcb7e2a81dc572/fastar-0.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acb62e2369834fb23d26327157f0a2dbec40b230c709fa85b1ce96cf010e6fbf", size = 819050, upload-time = "2026-03-20T14:25:08.352Z" }, + { url = "https://files.pythonhosted.org/packages/7c/5a/455b53f11527568100ba6d5847635430645bad62d676f0bae4173fc85c90/fastar-0.9.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:f2f399fffb74bcd9e9d4507e253ace2430b5ccf61000596bda41e90414bcf4f2", size = 885257, upload-time = "2026-03-20T14:24:28.86Z" }, { url = "https://files.pythonhosted.org/packages/4f/dd/0a8ea7b910293b07f8c82ef4e6451262ccf2a6f2020e880f184dc4abd6c2/fastar-0.9.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:87006c8770dfc558aefe927590bbcdaf9648ca4472a9ee6d10dfb7c0bda4ce5b", size = 968135, upload-time = "2026-03-20T14:25:45.614Z" }, + { url = "https://files.pythonhosted.org/packages/6b/cb/5c7e9231d6ba00e225623947068db09ddd4e401800b0afaf39eece14bfee/fastar-0.9.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:4d012644421d669d9746157193f4eafd371e8ae56ff7aef97612a4922418664c", size = 1034940, upload-time = "2026-03-20T14:25:58.893Z" }, { url = "https://files.pythonhosted.org/packages/8b/53/6ddda28545b428d54c42f341d797046467c689616a36eae9a43ba56f2545/fastar-0.9.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:59bc500d7b6bdaf2ffb2b632bc6b0f97ddfb3bb7d31b54d61ceb00b5698d6484", size = 1025314, upload-time = "2026-03-20T14:26:24.624Z" }, { url = "https://files.pythonhosted.org/packages/77/52/f3b06867e5ca8d5b2c1c15a1563415e0037b5831f2058ee72b03960296d9/fastar-0.9.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f07c6bdeedfeb30ef459f21fa9ab06e2b6727f7e7653176d3abb7a85f447c400", size = 627615, upload-time = "2026-03-20T14:25:21.608Z" }, { url = "https://files.pythonhosted.org/packages/3f/54/e2e1b4c8512d670373047e5e585b1d1ff9ffd722b0a17647d22c9c9bd248/fastar-0.9.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:108bb46c080ca152bb331f1e0576177d36e9badba51b1d5724d2823542e0dd1f", size = 760246, upload-time = "2026-03-20T14:23:51.964Z" }, + { url = "https://files.pythonhosted.org/packages/fa/7d/1e283dd8dbb3647049594bb477bdc053045c6fff2d3f06386d2dcacce7aa/fastar-0.9.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d17d311cfbb559154ba940972b6d07a3a7ac221a2a01208f119ad03495f01d32", size = 757024, upload-time = "2026-03-20T14:24:04.69Z" }, + { url = "https://files.pythonhosted.org/packages/87/ac/82d3cb64d318ce16c5d1a26a40b8aa570fcc9b23684221aece838c4cbada/fastar-0.9.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d2ef34e7088f308e73460e1b8d9b0479a743f679816782a80db6ae87ee68714a", size = 921630, upload-time = "2026-03-20T14:24:18.155Z" }, + { url = "https://files.pythonhosted.org/packages/f7/b8/3e7892f1a25a1a2054a20de6c846c0794b8fa361e5b9d3d00915b41e97bd/fastar-0.9.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c93bf4732d0dd6adae4a8b3bbebe19af76ee1072b7688bf39c5a1d120425a772", size = 815791, upload-time = "2026-03-20T14:24:43.28Z" }, { url = "https://files.pythonhosted.org/packages/db/5e/8fcc662db1fd0985f4f8a54e79276416565a0d1fcb8da66665b2061ead30/fastar-0.9.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a67b061b1099cf3b8b6234dd3605fa16f5078ab6b51c8d77ad7a5d11c3cf834", size = 818980, upload-time = "2026-03-20T14:25:09.545Z" }, + { url = "https://files.pythonhosted.org/packages/68/ed/37291fbd6c9b5b0905712da6191bdfc25a7dc236efbf130e3a1a7d1b9440/fastar-0.9.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:912efe3121dc1f3c05940cfa1c6b09b8868d702d24566506aa1d0d96e429923a", size = 884578, upload-time = "2026-03-20T14:24:30.584Z" }, { url = "https://files.pythonhosted.org/packages/94/19/7b3b7af978ae4f012664781554716d67549ab19ddbcb6e6d1adc04d7a5e7/fastar-0.9.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2394980cc126a3263e115600bc4ff9e7320cddde83c99fc334ab530be5b7166e", size = 967790, upload-time = "2026-03-20T14:25:46.975Z" }, + { url = "https://files.pythonhosted.org/packages/e6/38/4cce2a8e529a7d3e99e427c9bbcccd7013ff6b3ba295613e6f1c573c9e6c/fastar-0.9.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:d0aff74ea98642784c941d3cd8c35943258d4b9626157858901c5b181683339b", size = 1033892, upload-time = "2026-03-20T14:26:00.22Z" }, { url = "https://files.pythonhosted.org/packages/10/4f/6ec0c123c15bbcb9a9b82e979dc81273789ebbfbb4a2b41a1a6941577c94/fastar-0.9.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c9bd8879ebf05aa247e60e454bb7568cbdd44f016b8c58e31e5398039403e61d", size = 1025768, upload-time = "2026-03-20T14:26:25.957Z" }, { url = "https://files.pythonhosted.org/packages/d0/19/9f8fb5c0e803254c5d535c362102dd604d9bdb206d5a36150f4637cadf09/fastar-0.9.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:76be31936cabce31cbb6381128f851cf0a6da2d5c25357615cd1504b26dc31cf", size = 633000, upload-time = "2026-03-20T14:25:28.496Z" }, { url = "https://files.pythonhosted.org/packages/ef/04/366937320b1cca522570c527a45b1254bd68d057e68956baefc49eacae27/fastar-0.9.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b665c33afcd1d581b82235b690d999c5446ccc2c4d80c4a95f30df3b43d22494", size = 763872, upload-time = "2026-03-20T14:23:59.122Z" }, + { url = "https://files.pythonhosted.org/packages/c8/f2/121c5432bb152da68fc466a0d0206d66383a40a2f9beff5583d9277aceee/fastar-0.9.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d2a9a49f9217f4f60f9ba23fdd1f7f3f04fed97391145eb9460ec83ca0b4bd33", size = 762897, upload-time = "2026-03-20T14:24:11.932Z" }, + { url = "https://files.pythonhosted.org/packages/80/9e/88d3a603b997063e032f94cc0fff74031d76903f38cc30416a400395df03/fastar-0.9.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59d860e82a531e9cc67e7f500a299bffbe6e93d80bbf48401fd8f452a0c58f28", size = 927024, upload-time = "2026-03-20T14:24:24.689Z" }, + { url = "https://files.pythonhosted.org/packages/a6/17/d6dc778c45b0c7d9a279706d7a5d62122dab0a7a0cb39aac6f5ef42f13f6/fastar-0.9.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3feede2d72ec0782b5ccc18568f36cbe33816be396551aa47b3e1b73c322cdd2", size = 821265, upload-time = "2026-03-20T14:24:50.407Z" }, { url = "https://files.pythonhosted.org/packages/e0/e0/cec25d43df7ea4b4e3e875352c6d51c848c855792ba276c546732a7170af/fastar-0.9.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9ac410d32cbb514e966c45f0fedd0f9447b0dea9e734af714648da503603df6", size = 824024, upload-time = "2026-03-20T14:25:16.142Z" }, + { url = "https://files.pythonhosted.org/packages/52/90/c354969770d21d1b07c9281b5e23052392c288d22984a1917d30940e86cb/fastar-0.9.0-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:40b8c08df809e5e58d1839ccb37bafe4485deb6ee56bb7c5f0cbb72d701eb965", size = 888886, upload-time = "2026-03-20T14:24:38.229Z" }, { url = "https://files.pythonhosted.org/packages/8c/ac/eb2a01ed94e79b72003840448d2b69644a54a47f615c7d693432a1337caa/fastar-0.9.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:d62a4fd86eda3bea7cc32efd64d43b6d0fcdbbec009558b750fc362f20142789", size = 972503, upload-time = "2026-03-20T14:25:54.207Z" }, + { url = "https://files.pythonhosted.org/packages/8d/88/f7e28100fa7ff4a26a3493ad7a5d45d70f6de858c05f5c34aca3570c5839/fastar-0.9.0-pp311-pypy311_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:7bf6958bb6f94e5ec522e4a255b8e940d3561ad973f0be5dde6115b5a0854af5", size = 1039106, upload-time = "2026-03-20T14:26:07.686Z" }, { url = "https://files.pythonhosted.org/packages/a4/45/1ea024be428ad9d89e9f738c9379507e97df9f9ed97e50e4a1d10ff90fef/fastar-0.9.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:fad70e257daefb42bab68dcd68beaf2e2a99da056d65f2c9f988449a4e869306", size = 1031304, upload-time = "2026-03-20T14:26:33.294Z" }, ] @@ -1750,25 +1859,49 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6e/ef/0e8f1fe32f8a53dd26bdd1f9347efe0778b0fddf62789ea683f4cc7d787d/frozenlist-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fa47e444b8ba08fffd1c18e8cdb9a75db1b6a27f17507522834ad13ed5922b93", size = 50119, upload-time = "2025-10-06T05:35:48.38Z" }, { url = "https://files.pythonhosted.org/packages/11/b1/71a477adc7c36e5fb628245dfbdea2166feae310757dea848d02bd0689fd/frozenlist-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2552f44204b744fba866e573be4c1f9048d6a324dfe14475103fd51613eb1d1f", size = 231067, upload-time = "2025-10-06T05:35:49.97Z" }, { url = "https://files.pythonhosted.org/packages/45/7e/afe40eca3a2dc19b9904c0f5d7edfe82b5304cb831391edec0ac04af94c2/frozenlist-1.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:957e7c38f250991e48a9a73e6423db1bb9dd14e722a10f6b8bb8e16a0f55f695", size = 233160, upload-time = "2025-10-06T05:35:51.729Z" }, + { url = "https://files.pythonhosted.org/packages/a6/aa/7416eac95603ce428679d273255ffc7c998d4132cfae200103f164b108aa/frozenlist-1.8.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8585e3bb2cdea02fc88ffa245069c36555557ad3609e83be0ec71f54fd4abb52", size = 228544, upload-time = "2025-10-06T05:35:53.246Z" }, + { url = "https://files.pythonhosted.org/packages/8b/3d/2a2d1f683d55ac7e3875e4263d28410063e738384d3adc294f5ff3d7105e/frozenlist-1.8.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:edee74874ce20a373d62dc28b0b18b93f645633c2943fd90ee9d898550770581", size = 243797, upload-time = "2025-10-06T05:35:54.497Z" }, + { url = "https://files.pythonhosted.org/packages/78/1e/2d5565b589e580c296d3bb54da08d206e797d941a83a6fdea42af23be79c/frozenlist-1.8.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c9a63152fe95756b85f31186bddf42e4c02c6321207fd6601a1c89ebac4fe567", size = 247923, upload-time = "2025-10-06T05:35:55.861Z" }, { url = "https://files.pythonhosted.org/packages/aa/c3/65872fcf1d326a7f101ad4d86285c403c87be7d832b7470b77f6d2ed5ddc/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b6db2185db9be0a04fecf2f241c70b63b1a242e2805be291855078f2b404dd6b", size = 230886, upload-time = "2025-10-06T05:35:57.399Z" }, + { url = "https://files.pythonhosted.org/packages/a0/76/ac9ced601d62f6956f03cc794f9e04c81719509f85255abf96e2510f4265/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:f4be2e3d8bc8aabd566f8d5b8ba7ecc09249d74ba3c9ed52e54dc23a293f0b92", size = 245731, upload-time = "2025-10-06T05:35:58.563Z" }, + { url = "https://files.pythonhosted.org/packages/b9/49/ecccb5f2598daf0b4a1415497eba4c33c1e8ce07495eb07d2860c731b8d5/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c8d1634419f39ea6f5c427ea2f90ca85126b54b50837f31497f3bf38266e853d", size = 241544, upload-time = "2025-10-06T05:35:59.719Z" }, + { url = "https://files.pythonhosted.org/packages/53/4b/ddf24113323c0bbcc54cb38c8b8916f1da7165e07b8e24a717b4a12cbf10/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:1a7fa382a4a223773ed64242dbe1c9c326ec09457e6b8428efb4118c685c3dfd", size = 241806, upload-time = "2025-10-06T05:36:00.959Z" }, { url = "https://files.pythonhosted.org/packages/a7/fb/9b9a084d73c67175484ba2789a59f8eebebd0827d186a8102005ce41e1ba/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:11847b53d722050808926e785df837353bd4d75f1d494377e59b23594d834967", size = 229382, upload-time = "2025-10-06T05:36:02.22Z" }, { url = "https://files.pythonhosted.org/packages/69/29/948b9aa87e75820a38650af445d2ef2b6b8a6fab1a23b6bb9e4ef0be2d59/frozenlist-1.8.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:78f7b9e5d6f2fdb88cdde9440dc147259b62b9d3b019924def9f6478be254ac1", size = 87782, upload-time = "2025-10-06T05:36:06.649Z" }, { url = "https://files.pythonhosted.org/packages/2b/94/5c8a2b50a496b11dd519f4a24cb5496cf125681dd99e94c604ccdea9419a/frozenlist-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f833670942247a14eafbb675458b4e61c82e002a148f49e68257b79296e865c4", size = 50448, upload-time = "2025-10-06T05:36:08.78Z" }, { url = "https://files.pythonhosted.org/packages/6a/bd/d91c5e39f490a49df14320f4e8c80161cfcce09f1e2cde1edd16a551abb3/frozenlist-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:494a5952b1c597ba44e0e78113a7266e656b9794eec897b19ead706bd7074383", size = 242411, upload-time = "2025-10-06T05:36:09.801Z" }, { url = "https://files.pythonhosted.org/packages/8f/83/f61505a05109ef3293dfb1ff594d13d64a2324ac3482be2cedc2be818256/frozenlist-1.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96f423a119f4777a4a056b66ce11527366a8bb92f54e541ade21f2374433f6d4", size = 243014, upload-time = "2025-10-06T05:36:11.394Z" }, + { url = "https://files.pythonhosted.org/packages/d8/cb/cb6c7b0f7d4023ddda30cf56b8b17494eb3a79e3fda666bf735f63118b35/frozenlist-1.8.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3462dd9475af2025c31cc61be6652dfa25cbfb56cbbf52f4ccfe029f38decaf8", size = 234909, upload-time = "2025-10-06T05:36:12.598Z" }, + { url = "https://files.pythonhosted.org/packages/31/c5/cd7a1f3b8b34af009fb17d4123c5a778b44ae2804e3ad6b86204255f9ec5/frozenlist-1.8.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4c800524c9cd9bac5166cd6f55285957fcfc907db323e193f2afcd4d9abd69b", size = 250049, upload-time = "2025-10-06T05:36:14.065Z" }, + { url = "https://files.pythonhosted.org/packages/c0/01/2f95d3b416c584a1e7f0e1d6d31998c4a795f7544069ee2e0962a4b60740/frozenlist-1.8.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d6a5df73acd3399d893dafc71663ad22534b5aa4f94e8a2fabfe856c3c1b6a52", size = 256485, upload-time = "2025-10-06T05:36:15.39Z" }, { url = "https://files.pythonhosted.org/packages/ce/03/024bf7720b3abaebcff6d0793d73c154237b85bdf67b7ed55e5e9596dc9a/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:405e8fe955c2280ce66428b3ca55e12b3c4e9c336fb2103a4937e891c69a4a29", size = 237619, upload-time = "2025-10-06T05:36:16.558Z" }, + { url = "https://files.pythonhosted.org/packages/69/fa/f8abdfe7d76b731f5d8bd217827cf6764d4f1d9763407e42717b4bed50a0/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:908bd3f6439f2fef9e85031b59fd4f1297af54415fb60e4254a95f75b3cab3f3", size = 250320, upload-time = "2025-10-06T05:36:17.821Z" }, + { url = "https://files.pythonhosted.org/packages/f5/3c/b051329f718b463b22613e269ad72138cc256c540f78a6de89452803a47d/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:294e487f9ec720bd8ffcebc99d575f7eff3568a08a253d1ee1a0378754b74143", size = 246820, upload-time = "2025-10-06T05:36:19.046Z" }, + { url = "https://files.pythonhosted.org/packages/0f/ae/58282e8f98e444b3f4dd42448ff36fa38bef29e40d40f330b22e7108f565/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:74c51543498289c0c43656701be6b077f4b265868fa7f8a8859c197006efb608", size = 250518, upload-time = "2025-10-06T05:36:20.763Z" }, { url = "https://files.pythonhosted.org/packages/8f/96/007e5944694d66123183845a106547a15944fbbb7154788cbf7272789536/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:776f352e8329135506a1d6bf16ac3f87bc25b28e765949282dcc627af36123aa", size = 239096, upload-time = "2025-10-06T05:36:22.129Z" }, { url = "https://files.pythonhosted.org/packages/2d/40/0832c31a37d60f60ed79e9dfb5a92e1e2af4f40a16a29abcc7992af9edff/frozenlist-1.8.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8d92f1a84bb12d9e56f818b3a746f3efba93c1b63c8387a73dde655e1e42282a", size = 85717, upload-time = "2025-10-06T05:36:27.341Z" }, { url = "https://files.pythonhosted.org/packages/0c/ab/6e5080ee374f875296c4243c381bbdef97a9ac39c6e3ce1d5f7d42cb78d6/frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f21f00a91358803399890ab167098c131ec2ddd5f8f5fd5fe9c9f2c6fcd91e40", size = 49417, upload-time = "2025-10-06T05:36:29.877Z" }, { url = "https://files.pythonhosted.org/packages/d5/4e/e4691508f9477ce67da2015d8c00acd751e6287739123113a9fca6f1604e/frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fb30f9626572a76dfe4293c7194a09fb1fe93ba94c7d4f720dfae3b646b45027", size = 234391, upload-time = "2025-10-06T05:36:31.301Z" }, { url = "https://files.pythonhosted.org/packages/40/76/c202df58e3acdf12969a7895fd6f3bc016c642e6726aa63bd3025e0fc71c/frozenlist-1.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaa352d7047a31d87dafcacbabe89df0aa506abb5b1b85a2fb91bc3faa02d822", size = 233048, upload-time = "2025-10-06T05:36:32.531Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c0/8746afb90f17b73ca5979c7a3958116e105ff796e718575175319b5bb4ce/frozenlist-1.8.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:03ae967b4e297f58f8c774c7eabcce57fe3c2434817d4385c50661845a058121", size = 226549, upload-time = "2025-10-06T05:36:33.706Z" }, + { url = "https://files.pythonhosted.org/packages/7e/eb/4c7eefc718ff72f9b6c4893291abaae5fbc0c82226a32dcd8ef4f7a5dbef/frozenlist-1.8.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6292f1de555ffcc675941d65fffffb0a5bcd992905015f85d0592201793e0e5", size = 239833, upload-time = "2025-10-06T05:36:34.947Z" }, + { url = "https://files.pythonhosted.org/packages/c2/4e/e5c02187cf704224f8b21bee886f3d713ca379535f16893233b9d672ea71/frozenlist-1.8.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29548f9b5b5e3460ce7378144c3010363d8035cea44bc0bf02d57f5a685e084e", size = 245363, upload-time = "2025-10-06T05:36:36.534Z" }, { url = "https://files.pythonhosted.org/packages/1f/96/cb85ec608464472e82ad37a17f844889c36100eed57bea094518bf270692/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ec3cc8c5d4084591b4237c0a272cc4f50a5b03396a47d9caaf76f5d7b38a4f11", size = 229314, upload-time = "2025-10-06T05:36:38.582Z" }, + { url = "https://files.pythonhosted.org/packages/5d/6f/4ae69c550e4cee66b57887daeebe006fe985917c01d0fff9caab9883f6d0/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:517279f58009d0b1f2e7c1b130b377a349405da3f7621ed6bfae50b10adf20c1", size = 243365, upload-time = "2025-10-06T05:36:40.152Z" }, + { url = "https://files.pythonhosted.org/packages/7a/58/afd56de246cf11780a40a2c28dc7cbabbf06337cc8ddb1c780a2d97e88d8/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:db1e72ede2d0d7ccb213f218df6a078a9c09a7de257c2fe8fcef16d5925230b1", size = 237763, upload-time = "2025-10-06T05:36:41.355Z" }, + { url = "https://files.pythonhosted.org/packages/cb/36/cdfaf6ed42e2644740d4a10452d8e97fa1c062e2a8006e4b09f1b5fd7d63/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b4dec9482a65c54a5044486847b8a66bf10c9cb4926d42927ec4e8fd5db7fed8", size = 240110, upload-time = "2025-10-06T05:36:42.716Z" }, { url = "https://files.pythonhosted.org/packages/03/a8/9ea226fbefad669f11b52e864c55f0bd57d3c8d7eb07e9f2e9a0b39502e1/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:21900c48ae04d13d416f0e1e0c4d81f7931f73a9dfa0b7a8746fb2fe7dd970ed", size = 233717, upload-time = "2025-10-06T05:36:44.251Z" }, { url = "https://files.pythonhosted.org/packages/d2/5c/3bbfaa920dfab09e76946a5d2833a7cbdf7b9b4a91c714666ac4855b88b4/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e25ac20a2ef37e91c1b39938b591457666a0fa835c7783c3a8f33ea42870db94", size = 89235, upload-time = "2025-10-06T05:36:48.78Z" }, { url = "https://files.pythonhosted.org/packages/1e/bb/a6d12b7ba4c3337667d0e421f7181c82dda448ce4e7ad7ecd249a16fa806/frozenlist-1.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4e0c11f2cc6717e0a741f84a527c52616140741cd812a50422f83dc31749fb52", size = 51725, upload-time = "2025-10-06T05:36:50.851Z" }, { url = "https://files.pythonhosted.org/packages/bc/71/d1fed0ffe2c2ccd70b43714c6cab0f4188f09f8a67a7914a6b46ee30f274/frozenlist-1.8.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b3210649ee28062ea6099cfda39e147fa1bc039583c8ee4481cb7811e2448c51", size = 284533, upload-time = "2025-10-06T05:36:51.898Z" }, { url = "https://files.pythonhosted.org/packages/c9/1f/fb1685a7b009d89f9bf78a42d94461bc06581f6e718c39344754a5d9bada/frozenlist-1.8.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:581ef5194c48035a7de2aefc72ac6539823bb71508189e5de01d60c9dcd5fa65", size = 292506, upload-time = "2025-10-06T05:36:53.101Z" }, + { url = "https://files.pythonhosted.org/packages/e6/3b/b991fe1612703f7e0d05c0cf734c1b77aaf7c7d321df4572e8d36e7048c8/frozenlist-1.8.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3ef2d026f16a2b1866e1d86fc4e1291e1ed8a387b2c333809419a2f8b3a77b82", size = 274161, upload-time = "2025-10-06T05:36:54.309Z" }, + { url = "https://files.pythonhosted.org/packages/ca/ec/c5c618767bcdf66e88945ec0157d7f6c4a1322f1473392319b7a2501ded7/frozenlist-1.8.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5500ef82073f599ac84d888e3a8c1f77ac831183244bfd7f11eaa0289fb30714", size = 294676, upload-time = "2025-10-06T05:36:55.566Z" }, + { url = "https://files.pythonhosted.org/packages/7c/ce/3934758637d8f8a88d11f0585d6495ef54b2044ed6ec84492a91fa3b27aa/frozenlist-1.8.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50066c3997d0091c411a66e710f4e11752251e6d2d73d70d8d5d4c76442a199d", size = 300638, upload-time = "2025-10-06T05:36:56.758Z" }, { url = "https://files.pythonhosted.org/packages/fc/4f/a7e4d0d467298f42de4b41cbc7ddaf19d3cfeabaf9ff97c20c6c7ee409f9/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5c1c8e78426e59b3f8005e9b19f6ff46e5845895adbde20ece9218319eca6506", size = 283067, upload-time = "2025-10-06T05:36:57.965Z" }, + { url = "https://files.pythonhosted.org/packages/dc/48/c7b163063d55a83772b268e6d1affb960771b0e203b632cfe09522d67ea5/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:eefdba20de0d938cec6a89bd4d70f346a03108a19b9df4248d3cf0d88f1b0f51", size = 292101, upload-time = "2025-10-06T05:36:59.237Z" }, + { url = "https://files.pythonhosted.org/packages/9f/d0/2366d3c4ecdc2fd391e0afa6e11500bfba0ea772764d631bbf82f0136c9d/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cf253e0e1c3ceb4aaff6df637ce033ff6535fb8c70a764a8f46aafd3d6ab798e", size = 289901, upload-time = "2025-10-06T05:37:00.811Z" }, + { url = "https://files.pythonhosted.org/packages/b8/94/daff920e82c1b70e3618a2ac39fbc01ae3e2ff6124e80739ce5d71c9b920/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:032efa2674356903cd0261c4317a561a6850f3ac864a63fc1583147fb05a79b0", size = 289395, upload-time = "2025-10-06T05:37:02.115Z" }, { url = "https://files.pythonhosted.org/packages/e3/20/bba307ab4235a09fdcd3cc5508dbabd17c4634a1af4b96e0f69bfe551ebd/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6da155091429aeba16851ecb10a9104a108bcd32f6c1642867eadaee401c1c41", size = 283659, upload-time = "2025-10-06T05:37:03.711Z" }, { url = "https://files.pythonhosted.org/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d", size = 13409, upload-time = "2025-10-06T05:38:16.721Z" }, ] @@ -1900,16 +2033,22 @@ sdist = { url = "https://files.pythonhosted.org/packages/a3/51/1664f6b78fc6ebbd9 wheels = [ { url = "https://files.pythonhosted.org/packages/f3/47/16400cb42d18d7a6bb46f0626852c1718612e35dcb0dffa16bbaffdf5dd2/greenlet-3.3.2-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:c56692189a7d1c7606cb794be0a8381470d95c57ce5be03fb3d0ef57c7853b86", size = 278890, upload-time = "2026-02-20T20:19:39.263Z" }, { url = "https://files.pythonhosted.org/packages/a3/90/42762b77a5b6aa96cd8c0e80612663d39211e8ae8a6cd47c7f1249a66262/greenlet-3.3.2-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ebd458fa8285960f382841da585e02201b53a5ec2bac6b156fc623b5ce4499f", size = 581120, upload-time = "2026-02-20T20:47:30.161Z" }, + { url = "https://files.pythonhosted.org/packages/bf/6f/f3d64f4fa0a9c7b5c5b3c810ff1df614540d5aa7d519261b53fba55d4df9/greenlet-3.3.2-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a443358b33c4ec7b05b79a7c8b466f5d275025e750298be7340f8fc63dff2a55", size = 594363, upload-time = "2026-02-20T20:55:56.965Z" }, + { url = "https://files.pythonhosted.org/packages/9c/8b/1430a04657735a3f23116c2e0d5eb10220928846e4537a938a41b350bed6/greenlet-3.3.2-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4375a58e49522698d3e70cc0b801c19433021b5c37686f7ce9c65b0d5c8677d2", size = 605046, upload-time = "2026-02-20T21:02:45.234Z" }, { url = "https://files.pythonhosted.org/packages/72/83/3e06a52aca8128bdd4dcd67e932b809e76a96ab8c232a8b025b2850264c5/greenlet-3.3.2-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e2cd90d413acbf5e77ae41e5d3c9b3ac1d011a756d7284d7f3f2b806bbd6358", size = 594156, upload-time = "2026-02-20T20:20:59.955Z" }, { url = "https://files.pythonhosted.org/packages/70/79/0de5e62b873e08fe3cef7dbe84e5c4bc0e8ed0c7ff131bccb8405cd107c8/greenlet-3.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:442b6057453c8cb29b4fb36a2ac689382fc71112273726e2423f7f17dc73bf99", size = 1554649, upload-time = "2026-02-20T20:49:32.293Z" }, { url = "https://files.pythonhosted.org/packages/5a/00/32d30dee8389dc36d42170a9c66217757289e2afb0de59a3565260f38373/greenlet-3.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:45abe8eb6339518180d5a7fa47fa01945414d7cca5ecb745346fc6a87d2750be", size = 1619472, upload-time = "2026-02-20T20:21:07.966Z" }, { url = "https://files.pythonhosted.org/packages/ea/ab/1608e5a7578e62113506740b88066bf09888322a311cff602105e619bd87/greenlet-3.3.2-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:ac8d61d4343b799d1e526db579833d72f23759c71e07181c2d2944e429eb09cd", size = 280358, upload-time = "2026-02-20T20:17:43.971Z" }, { url = "https://files.pythonhosted.org/packages/a5/23/0eae412a4ade4e6623ff7626e38998cb9b11e9ff1ebacaa021e4e108ec15/greenlet-3.3.2-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ceec72030dae6ac0c8ed7591b96b70410a8be370b6a477b1dbc072856ad02bd", size = 601217, upload-time = "2026-02-20T20:47:31.462Z" }, + { url = "https://files.pythonhosted.org/packages/f8/16/5b1678a9c07098ecb9ab2dd159fafaf12e963293e61ee8d10ecb55273e5e/greenlet-3.3.2-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a2a5be83a45ce6188c045bcc44b0ee037d6a518978de9a5d97438548b953a1ac", size = 611792, upload-time = "2026-02-20T20:55:58.423Z" }, + { url = "https://files.pythonhosted.org/packages/5c/c5/cc09412a29e43406eba18d61c70baa936e299bc27e074e2be3806ed29098/greenlet-3.3.2-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ae9e21c84035c490506c17002f5c8ab25f980205c3e61ddb3a2a2a2e6c411fcb", size = 626250, upload-time = "2026-02-20T21:02:46.596Z" }, { url = "https://files.pythonhosted.org/packages/50/1f/5155f55bd71cabd03765a4aac9ac446be129895271f73872c36ebd4b04b6/greenlet-3.3.2-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43e99d1749147ac21dde49b99c9abffcbc1e2d55c67501465ef0930d6e78e070", size = 613875, upload-time = "2026-02-20T20:21:01.102Z" }, { url = "https://files.pythonhosted.org/packages/fc/dd/845f249c3fcd69e32df80cdab059b4be8b766ef5830a3d0aa9d6cad55beb/greenlet-3.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4c956a19350e2c37f2c48b336a3afb4bff120b36076d9d7fb68cb44e05d95b79", size = 1571467, upload-time = "2026-02-20T20:49:33.495Z" }, { url = "https://files.pythonhosted.org/packages/2a/50/2649fe21fcc2b56659a452868e695634722a6655ba245d9f77f5656010bf/greenlet-3.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6c6f8ba97d17a1e7d664151284cb3315fc5f8353e75221ed4324f84eb162b395", size = 1640001, upload-time = "2026-02-20T20:21:09.154Z" }, { url = "https://files.pythonhosted.org/packages/ac/48/f8b875fa7dea7dd9b33245e37f065af59df6a25af2f9561efa8d822fde51/greenlet-3.3.2-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:aa6ac98bdfd716a749b84d4034486863fd81c3abde9aa3cf8eff9127981a4ae4", size = 279120, upload-time = "2026-02-20T20:19:01.9Z" }, { url = "https://files.pythonhosted.org/packages/49/8d/9771d03e7a8b1ee456511961e1b97a6d77ae1dea4a34a5b98eee706689d3/greenlet-3.3.2-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ab0c7e7901a00bc0a7284907273dc165b32e0d109a6713babd04471327ff7986", size = 603238, upload-time = "2026-02-20T20:47:32.873Z" }, + { url = "https://files.pythonhosted.org/packages/59/0e/4223c2bbb63cd5c97f28ffb2a8aee71bdfb30b323c35d409450f51b91e3e/greenlet-3.3.2-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d248d8c23c67d2291ffd47af766e2a3aa9fa1c6703155c099feb11f526c63a92", size = 614219, upload-time = "2026-02-20T20:55:59.817Z" }, + { url = "https://files.pythonhosted.org/packages/94/2b/4d012a69759ac9d77210b8bfb128bc621125f5b20fc398bce3940d036b1c/greenlet-3.3.2-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ccd21bb86944ca9be6d967cf7691e658e43417782bce90b5d2faeda0ff78a7dd", size = 628268, upload-time = "2026-02-20T21:02:48.024Z" }, { url = "https://files.pythonhosted.org/packages/7a/34/259b28ea7a2a0c904b11cd36c79b8cef8019b26ee5dbe24e73b469dea347/greenlet-3.3.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b6997d360a4e6a4e936c0f9625b1c20416b8a0ea18a8e19cabbefc712e7397ab", size = 616774, upload-time = "2026-02-20T20:21:02.454Z" }, { url = "https://files.pythonhosted.org/packages/0a/03/996c2d1689d486a6e199cb0f1cf9e4aa940c500e01bdf201299d7d61fa69/greenlet-3.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:64970c33a50551c7c50491671265d8954046cb6e8e2999aacdd60e439b70418a", size = 1571277, upload-time = "2026-02-20T20:49:34.795Z" }, { url = "https://files.pythonhosted.org/packages/d9/c4/2570fc07f34a39f2caf0bf9f24b0a1a0a47bc2e8e465b2c2424821389dfc/greenlet-3.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1a9172f5bf6bd88e6ba5a84e0a68afeac9dc7b6b412b245dd64f52d83c81e55b", size = 1640455, upload-time = "2026-02-20T20:21:10.261Z" }, @@ -1933,16 +2072,19 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/b7/48/af6173dbca4454f4637a4678b67f52ca7e0c1ed7d5894d89d434fecede05/grpcio-1.80.0.tar.gz", hash = "sha256:29aca15edd0688c22ba01d7cc01cb000d72b2033f4a3c72a81a19b56fd143257", size = 12978905, upload-time = "2026-03-30T08:49:10.502Z" } wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/db/1d56e5f5823257b291962d6c0ce106146c6447f405b60b234c4f222a7cde/grpcio-1.80.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:dfab85db094068ff42e2a3563f60ab3dddcc9d6488a35abf0132daec13209c8a", size = 6055009, upload-time = "2026-03-30T08:46:46.265Z" }, { url = "https://files.pythonhosted.org/packages/6e/18/c83f3cad64c5ca63bca7e91e5e46b0d026afc5af9d0a9972472ceba294b3/grpcio-1.80.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:5c07e82e822e1161354e32da2662f741a4944ea955f9f580ec8fb409dd6f6060", size = 12035295, upload-time = "2026-03-30T08:46:49.099Z" }, { url = "https://files.pythonhosted.org/packages/0f/8e/e14966b435be2dda99fbe89db9525ea436edc79780431a1c2875a3582644/grpcio-1.80.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ba0915d51fd4ced2db5ff719f84e270afe0e2d4c45a7bdb1e8d036e4502928c2", size = 6610297, upload-time = "2026-03-30T08:46:52.123Z" }, { url = "https://files.pythonhosted.org/packages/25/51/bd267c989f85a17a5b3eea65a6feb4ff672af41ca614e5a0279cc0ea381c/grpcio-1.80.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:09e5e478b3d14afd23f12e49e8b44c8684ac3c5f08561c43a5b9691c54d136ab", size = 6813442, upload-time = "2026-03-30T08:46:57.056Z" }, { url = "https://files.pythonhosted.org/packages/9e/d9/d80eef735b19e9169e30164bbf889b46f9df9127598a83d174eb13a48b26/grpcio-1.80.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:00168469238b022500e486c1c33916acf2f2a9b2c022202cf8a1885d2e3073c1", size = 7414743, upload-time = "2026-03-30T08:46:59.682Z" }, { url = "https://files.pythonhosted.org/packages/62/29/73ef0141b4732ff5eacd68430ff2512a65c004696997f70476a83e548e7e/grpcio-1.80.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ce1794f4ea6cc3ca29463f42d665c32ba1b964b48958a66497917fe9069f26e6", size = 7851641, upload-time = "2026-03-30T08:47:05.462Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e8/a2b749265eb3415abc94f2e619bbd9e9707bebdda787e61c593004ec927a/grpcio-1.80.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:c624cc9f1008361014378c9d776de7182b11fe8b2e5a81bc69f23a295f2a1ad0", size = 6015616, upload-time = "2026-03-30T08:47:13.428Z" }, { url = "https://files.pythonhosted.org/packages/3e/97/b1282161a15d699d1e90c360df18d19165a045ce1c343c7f313f5e8a0b77/grpcio-1.80.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:f49eddcac43c3bf350c0385366a58f36bed8cc2c0ec35ef7b74b49e56552c0c2", size = 12014204, upload-time = "2026-03-30T08:47:15.873Z" }, { url = "https://files.pythonhosted.org/packages/6e/5e/d319c6e997b50c155ac5a8cb12f5173d5b42677510e886d250d50264949d/grpcio-1.80.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d334591df610ab94714048e0d5b4f3dd5ad1bee74dfec11eee344220077a79de", size = 6563866, upload-time = "2026-03-30T08:47:18.588Z" }, { url = "https://files.pythonhosted.org/packages/db/f0/a3deb5feba60d9538a962913e37bd2e69a195f1c3376a3dd44fe0427e996/grpcio-1.80.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4e78c4ac0d97dc2e569b2f4bcbbb447491167cb358d1a389fc4af71ab6f70411", size = 6782121, upload-time = "2026-03-30T08:47:23.827Z" }, { url = "https://files.pythonhosted.org/packages/ca/84/36c6dcfddc093e108141f757c407902a05085e0c328007cb090d56646cdf/grpcio-1.80.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2ed770b4c06984f3b47eb0517b1c69ad0b84ef3f40128f51448433be904634cd", size = 7383811, upload-time = "2026-03-30T08:47:26.517Z" }, { url = "https://files.pythonhosted.org/packages/9b/8d/9d4d27ed7f33d109c50d6b5ce578a9914aa68edab75d65869a17e630a8d1/grpcio-1.80.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9a6284a5d907c37db53350645567c522be314bac859a64a7a5ca63b77bb7958f", size = 7830132, upload-time = "2026-03-30T08:47:33.254Z" }, + { url = "https://files.pythonhosted.org/packages/2f/3a/7c3c25789e3f069e581dc342e03613c5b1cb012c4e8c7d9d5cf960a75856/grpcio-1.80.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:e9e408fc016dffd20661f0126c53d8a31c2821b5c13c5d67a0f5ed5de93319ad", size = 6017243, upload-time = "2026-03-30T08:47:40.075Z" }, { url = "https://files.pythonhosted.org/packages/04/19/21a9806eb8240e174fd1ab0cd5b9aa948bb0e05c2f2f55f9d5d7405e6d08/grpcio-1.80.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:92d787312e613754d4d8b9ca6d3297e69994a7912a32fa38c4c4e01c272974b0", size = 12010840, upload-time = "2026-03-30T08:47:43.11Z" }, { url = "https://files.pythonhosted.org/packages/18/3a/23347d35f76f639e807fb7a36fad3068aed100996849a33809591f26eca6/grpcio-1.80.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8ac393b58aa16991a2f1144ec578084d544038c12242da3a215966b512904d0f", size = 6567644, upload-time = "2026-03-30T08:47:46.806Z" }, { url = "https://files.pythonhosted.org/packages/9b/e2/da1506ecea1f34a5e365964644b35edef53803052b763ca214ba3870c856/grpcio-1.80.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:873ff5d17d68992ef6605330127425d2fc4e77e612fa3c3e0ed4e668685e3140", size = 6783216, upload-time = "2026-03-30T08:47:52.817Z" }, @@ -2039,13 +2181,19 @@ sdist = { url = "https://files.pythonhosted.org/packages/1a/eb/8fc64f40388c29ce8 wheels = [ { url = "https://files.pythonhosted.org/packages/ea/2e/3d60b1a9e9f29a2152aa66c823bf5e399ae7be3fef310ff0de86779c5d2d/hf_transfer-0.1.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3ebc4ab9023414880c8b1d3c38174d1c9989eb5022d37e814fa91a3060123eb0", size = 1343558, upload-time = "2025-01-07T10:04:42.313Z" }, { url = "https://files.pythonhosted.org/packages/fb/38/130a5ac3747f104033591bcac1c961cb1faadfdc91704f59b09c0b465ff2/hf_transfer-0.1.9-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8674026f21ed369aa2a0a4b46000aca850fc44cd2b54af33a172ce5325b4fc82", size = 3726676, upload-time = "2025-01-07T10:04:11.539Z" }, + { url = "https://files.pythonhosted.org/packages/15/a1/f4e27c5ad17aac616ae0849e2aede5aae31db8267a948c6b3eeb9fd96446/hf_transfer-0.1.9-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a736dfbb2c84f5a2c975478ad200c0c8bfcb58a25a35db402678fb87ce17fa4", size = 3062920, upload-time = "2025-01-07T10:04:16.297Z" }, + { url = "https://files.pythonhosted.org/packages/50/d0/2b213eb1ea8b1252ccaf1a6c804d0aba03fea38aae4124df6a3acb70511a/hf_transfer-0.1.9-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2c7fc1b85f4d0f76e452765d7648c9f4bfd0aedb9ced2ae1ebfece2d8cfaf8e2", size = 3398837, upload-time = "2025-01-07T10:04:22.778Z" }, { url = "https://files.pythonhosted.org/packages/8c/8a/79dbce9006e0bd6b74516f97451a7b7c64dbbb426df15d901dd438cfeee3/hf_transfer-0.1.9-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d991376f0eac70a60f0cbc95602aa708a6f7c8617f28b4945c1431d67b8e3c8", size = 3546986, upload-time = "2025-01-07T10:04:36.415Z" }, { url = "https://files.pythonhosted.org/packages/a9/f7/9ac239b6ee6fe0bad130325d987a93ea58c4118e50479f0786f1733b37e8/hf_transfer-0.1.9-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e6ac4eddcd99575ed3735ed911ddf9d1697e2bd13aa3f0ad7e3904dd4863842e", size = 4071715, upload-time = "2025-01-07T10:04:53.224Z" }, + { url = "https://files.pythonhosted.org/packages/d8/a3/0ed697279f5eeb7a40f279bd783cf50e6d0b91f24120dcf66ef2cf8822b4/hf_transfer-0.1.9-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:57fd9880da1ee0f47250f735f791fab788f0aa1ee36afc49f761349869c8b4d9", size = 3388081, upload-time = "2025-01-07T10:04:57.818Z" }, { url = "https://files.pythonhosted.org/packages/45/07/6661e43fbee09594a8a5e9bb778107d95fe38dac4c653982afe03d32bd4d/hf_transfer-0.1.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a5b366d34cd449fe9b20ef25941e6eef0460a2f74e7389f02e673e1f88ebd538", size = 3690551, upload-time = "2025-01-07T10:05:09.238Z" }, { url = "https://files.pythonhosted.org/packages/41/ba/8d9fd9f1083525edfcb389c93738c802f3559cb749324090d7109c8bf4c2/hf_transfer-0.1.9-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:8669dbcc7a3e2e8d61d42cd24da9c50d57770bd74b445c65123291ca842a7e7a", size = 1348126, upload-time = "2025-01-07T10:04:45.712Z" }, { url = "https://files.pythonhosted.org/packages/8e/a2/cd7885bc9959421065a6fae0fe67b6c55becdeda4e69b873e52976f9a9f0/hf_transfer-0.1.9-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8fd0167c4407a3bc4cdd0307e65ada2294ec04f1813d8a69a5243e379b22e9d8", size = 3728604, upload-time = "2025-01-07T10:04:14.173Z" }, + { url = "https://files.pythonhosted.org/packages/f6/2e/a072cf196edfeda3310c9a5ade0a0fdd785e6154b3ce24fc738c818da2a7/hf_transfer-0.1.9-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ee8b10afedcb75f71091bcc197c526a6ebf5c58bbbadb34fdeee6160f55f619f", size = 3064995, upload-time = "2025-01-07T10:04:18.663Z" }, + { url = "https://files.pythonhosted.org/packages/29/63/b560d39651a56603d64f1a0212d0472a44cbd965db2fa62b99d99cb981bf/hf_transfer-0.1.9-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc6bd19e1cc177c66bdef15ef8636ad3bde79d5a4f608c158021153b4573509d", size = 3400839, upload-time = "2025-01-07T10:04:26.122Z" }, { url = "https://files.pythonhosted.org/packages/d6/d8/f87ea6f42456254b48915970ed98e993110521e9263472840174d32c880d/hf_transfer-0.1.9-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdca9bfb89e6f8f281890cc61a8aff2d3cecaff7e1a4d275574d96ca70098557", size = 3552664, upload-time = "2025-01-07T10:04:40.123Z" }, { url = "https://files.pythonhosted.org/packages/d6/56/1267c39b65fc8f4e2113b36297320f102718bf5799b544a6cbe22013aa1d/hf_transfer-0.1.9-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:89a23f58b7b7effbc047b8ca286f131b17728c99a9f972723323003ffd1bb916", size = 4073732, upload-time = "2025-01-07T10:04:55.624Z" }, + { url = "https://files.pythonhosted.org/packages/82/1a/9c748befbe3decf7cb415e34f8a0c3789a0a9c55910dea73d581e48c0ce5/hf_transfer-0.1.9-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:dc7fff1345980d6c0ebb92c811d24afa4b98b3e07ed070c8e38cc91fd80478c5", size = 3390096, upload-time = "2025-01-07T10:04:59.98Z" }, { url = "https://files.pythonhosted.org/packages/e7/6e/e597b04f753f1b09e6893075d53a82a30c13855cbaa791402695b01e369f/hf_transfer-0.1.9-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:d2fde99d502093ade3ab1b53f80da18480e9902aa960dab7f74fb1b9e5bc5746", size = 3695243, upload-time = "2025-01-07T10:05:11.411Z" }, ] @@ -2470,16 +2618,25 @@ sdist = { url = "https://files.pythonhosted.org/packages/ee/9d/ae7ddb4b8ab3fb1b5 wheels = [ { url = "https://files.pythonhosted.org/packages/be/cf/fc33f5159ce132be1d8dd57251a1ec7a631c7df4bd11e1cd198308c6ae32/jiter-0.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:558cc7e44fd8e507a236bee6a02fa17199ba752874400a0ca6cd6e2196cdb7dc", size = 321971, upload-time = "2025-05-18T19:03:27.255Z" }, { url = "https://files.pythonhosted.org/packages/68/a4/da3f150cf1d51f6c472616fb7650429c7ce053e0c962b41b68557fdf6379/jiter-0.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d613e4b379a07d7c8453c5712ce7014e86c6ac93d990a0b8e7377e18505e98d", size = 345574, upload-time = "2025-05-18T19:03:28.63Z" }, + { url = "https://files.pythonhosted.org/packages/84/34/6e8d412e60ff06b186040e77da5f83bc158e9735759fcae65b37d681f28b/jiter-0.10.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f62cf8ba0618eda841b9bf61797f21c5ebd15a7a1e19daab76e4e4b498d515b2", size = 371028, upload-time = "2025-05-18T19:03:30.292Z" }, + { url = "https://files.pythonhosted.org/packages/fb/d9/9ee86173aae4576c35a2f50ae930d2ccb4c4c236f6cb9353267aa1d626b7/jiter-0.10.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:919d139cdfa8ae8945112398511cb7fca58a77382617d279556b344867a37e61", size = 491083, upload-time = "2025-05-18T19:03:31.654Z" }, + { url = "https://files.pythonhosted.org/packages/d9/2c/f955de55e74771493ac9e188b0f731524c6a995dffdcb8c255b89c6fb74b/jiter-0.10.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:13ddbc6ae311175a3b03bd8994881bc4635c923754932918e18da841632349db", size = 388821, upload-time = "2025-05-18T19:03:33.184Z" }, { url = "https://files.pythonhosted.org/packages/81/5a/0e73541b6edd3f4aada586c24e50626c7815c561a7ba337d6a7eb0a915b4/jiter-0.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c440ea003ad10927a30521a9062ce10b5479592e8a70da27f21eeb457b4a9c5", size = 352174, upload-time = "2025-05-18T19:03:34.965Z" }, { url = "https://files.pythonhosted.org/packages/41/22/5beb5ee4ad4ef7d86f5ea5b4509f680a20706c4a7659e74344777efb7739/jiter-0.10.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:13252b58c1f4d8c5b63ab103c03d909e8e1e7842d302473f482915d95fefd605", size = 523741, upload-time = "2025-05-18T19:03:38.168Z" }, { url = "https://files.pythonhosted.org/packages/ea/10/768e8818538e5817c637b0df52e54366ec4cebc3346108a4457ea7a98f32/jiter-0.10.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7d1bbf3c465de4a24ab12fb7766a0003f6f9bce48b8b6a886158c4d569452dc5", size = 514527, upload-time = "2025-05-18T19:03:39.577Z" }, { url = "https://files.pythonhosted.org/packages/9c/4a/6a2397096162b21645162825f058d1709a02965606e537e3304b02742e9b/jiter-0.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7202ae396446c988cb2a5feb33a543ab2165b786ac97f53b59aafb803fef0744", size = 320124, upload-time = "2025-05-18T19:03:46.341Z" }, { url = "https://files.pythonhosted.org/packages/2a/85/1ce02cade7516b726dd88f59a4ee46914bf79d1676d1228ef2002ed2f1c9/jiter-0.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23ba7722d6748b6920ed02a8f1726fb4b33e0fd2f3f621816a8b486c66410ab2", size = 345330, upload-time = "2025-05-18T19:03:47.596Z" }, + { url = "https://files.pythonhosted.org/packages/75/d0/bb6b4f209a77190ce10ea8d7e50bf3725fc16d3372d0a9f11985a2b23eff/jiter-0.10.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:371eab43c0a288537d30e1f0b193bc4eca90439fc08a022dd83e5e07500ed026", size = 369670, upload-time = "2025-05-18T19:03:49.334Z" }, + { url = "https://files.pythonhosted.org/packages/a0/f5/a61787da9b8847a601e6827fbc42ecb12be2c925ced3252c8ffcb56afcaf/jiter-0.10.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c675736059020365cebc845a820214765162728b51ab1e03a1b7b3abb70f74c", size = 489057, upload-time = "2025-05-18T19:03:50.66Z" }, + { url = "https://files.pythonhosted.org/packages/12/e4/6f906272810a7b21406c760a53aadbe52e99ee070fc5c0cb191e316de30b/jiter-0.10.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0c5867d40ab716e4684858e4887489685968a47e3ba222e44cde6e4a2154f959", size = 389372, upload-time = "2025-05-18T19:03:51.98Z" }, { url = "https://files.pythonhosted.org/packages/e2/ba/77013b0b8ba904bf3762f11e0129b8928bff7f978a81838dfcc958ad5728/jiter-0.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:395bb9a26111b60141757d874d27fdea01b17e8fac958b91c20128ba8f4acc8a", size = 352038, upload-time = "2025-05-18T19:03:53.703Z" }, { url = "https://files.pythonhosted.org/packages/c0/72/0d6b7e31fc17a8fdce76164884edef0698ba556b8eb0af9546ae1a06b91d/jiter-0.10.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:62755d1bcea9876770d4df713d82606c8c1a3dca88ff39046b85a048566d56ea", size = 523557, upload-time = "2025-05-18T19:03:56.386Z" }, { url = "https://files.pythonhosted.org/packages/2f/09/bc1661fbbcbeb6244bd2904ff3a06f340aa77a2b94e5a7373fd165960ea3/jiter-0.10.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:533efbce2cacec78d5ba73a41756beff8431dfa1694b6346ce7af3a12c42202b", size = 514202, upload-time = "2025-05-18T19:03:57.675Z" }, { url = "https://files.pythonhosted.org/packages/91/e3/0916334936f356d605f54cc164af4060e3e7094364add445a3bc79335d46/jiter-0.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cafc4628b616dc32530c20ee53d71589816cf385dd9449633e910d596b1f5c8a", size = 318947, upload-time = "2025-05-18T19:04:03.347Z" }, { url = "https://files.pythonhosted.org/packages/6a/8e/fd94e8c02d0e94539b7d669a7ebbd2776e51f329bb2c84d4385e8063a2ad/jiter-0.10.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:520ef6d981172693786a49ff5b09eda72a42e539f14788124a07530f785c3ad6", size = 344618, upload-time = "2025-05-18T19:04:04.709Z" }, + { url = "https://files.pythonhosted.org/packages/6f/b0/f9f0a2ec42c6e9c2e61c327824687f1e2415b767e1089c1d9135f43816bd/jiter-0.10.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:554dedfd05937f8fc45d17ebdf298fe7e0c77458232bcb73d9fbbf4c6455f5b3", size = 368829, upload-time = "2025-05-18T19:04:06.912Z" }, + { url = "https://files.pythonhosted.org/packages/e8/57/5bbcd5331910595ad53b9fd0c610392ac68692176f05ae48d6ce5c852967/jiter-0.10.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5bc299da7789deacf95f64052d97f75c16d4fc8c4c214a22bf8d859a4288a1c2", size = 491034, upload-time = "2025-05-18T19:04:08.222Z" }, + { url = "https://files.pythonhosted.org/packages/9b/be/c393df00e6e6e9e623a73551774449f2f23b6ec6a502a3297aeeece2c65a/jiter-0.10.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5161e201172de298a8a1baad95eb85db4fb90e902353b1f6a41d64ea64644e25", size = 388529, upload-time = "2025-05-18T19:04:09.566Z" }, { url = "https://files.pythonhosted.org/packages/42/3e/df2235c54d365434c7f150b986a6e35f41ebdc2f95acea3036d99613025d/jiter-0.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e2227db6ba93cb3e2bf67c87e594adde0609f146344e8207e8730364db27041", size = 350671, upload-time = "2025-05-18T19:04:10.98Z" }, { url = "https://files.pythonhosted.org/packages/6a/d3/ef774b6969b9b6178e1d1e7a89a3bd37d241f3d3ec5f8deb37bbd203714a/jiter-0.10.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:901b92f2e2947dc6dfcb52fd624453862e16665ea909a08398dde19c0731b7f4", size = 522989, upload-time = "2025-05-18T19:04:14.261Z" }, { url = "https://files.pythonhosted.org/packages/0c/41/9becdb1d8dd5d854142f45a9d71949ed7e87a8e312b0bede2de849388cb9/jiter-0.10.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d0cb9a125d5a3ec971a094a845eadde2db0de85b33c9f13eb94a0c63d463879e", size = 513495, upload-time = "2025-05-18T19:04:15.603Z" }, @@ -2555,23 +2712,39 @@ sdist = { url = "https://files.pythonhosted.org/packages/ad/1d/68607c574dd78f030 wheels = [ { url = "https://files.pythonhosted.org/packages/8f/90/8391d14ac97e253d2637dab0eab370903510e0ba3a48510eff33df026742/jsonpath_rust_bindings-1.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0ca169ac219bc141775fb19df8165d4d0162e6ed77102e1ab19a74a80c1f9051", size = 814574, upload-time = "2025-11-16T19:01:53.739Z" }, { url = "https://files.pythonhosted.org/packages/b0/bd/1fb1e4c6635cfcc2936d9bfd8870c47ae2b1351d0bbd3ac241494e42446e/jsonpath_rust_bindings-1.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13446ad021abe05d622a01eaa648c238ef3b98e9fc0bd837a589bafb246ca3bc", size = 832886, upload-time = "2025-11-16T19:00:15.109Z" }, + { url = "https://files.pythonhosted.org/packages/cc/7d/77479e07955e1808390faa24b1ec6d57c3970bf96584bbe7a3f285a2c43a/jsonpath_rust_bindings-1.1.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b9583e965fe5f8f21cd0d047244db9716a119e0e82a06f2336e6b14c9a9637af", size = 837021, upload-time = "2025-11-16T19:00:31.979Z" }, + { url = "https://files.pythonhosted.org/packages/1c/fb/e8254b8c0bc112914b05d91c09ddc83f393d421c238ad5f25dcbc92b174d/jsonpath_rust_bindings-1.1.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:36a40ed04d2db70897cde2ac92f6c9aae2ed1b426aa4c97a47f3e2be911ea4ba", size = 932671, upload-time = "2025-11-16T19:00:48.349Z" }, + { url = "https://files.pythonhosted.org/packages/c9/de/d71c31e2fcdc9a82bb6390ee804aee21a84237cd8284ca2767085957179a/jsonpath_rust_bindings-1.1.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:50f16c3dd6eb572dda74731508d2fca1abbb927ab4f6511fb65eeba6e59fd041", size = 960293, upload-time = "2025-11-16T19:01:05.211Z" }, { url = "https://files.pythonhosted.org/packages/3c/9e/159ab37a111f4a8ef7a781b50e6b9fe39663bb9e69e720f1827858f45e76/jsonpath_rust_bindings-1.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d656507b5913f9515ff136797c5850df907c5040fa1368baa428f7e829e33f0", size = 947441, upload-time = "2025-11-16T19:01:36.917Z" }, { url = "https://files.pythonhosted.org/packages/17/c8/ff82ee574f5508793599481f2632a02ceb25da23223b81d0a5d080de2396/jsonpath_rust_bindings-1.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a43107f6efc4e66ee046c338741429a268fd972e887721b01bf0f32e47387e30", size = 1067748, upload-time = "2025-11-16T19:02:30.136Z" }, + { url = "https://files.pythonhosted.org/packages/89/e5/97a4e4f3ed1bd069feba3f9810c94ae0ea1001a52c243ecf655b667bd14b/jsonpath_rust_bindings-1.1.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:dc0c3488f04dbd318fa876fb880e8cb7d1e53abcf8b0d9e697e10a0a15ac3158", size = 1149298, upload-time = "2025-11-16T19:02:46.232Z" }, { url = "https://files.pythonhosted.org/packages/a5/8f/613120a36b281619a13394eb8ede093941d939c530c6595d62070fa10f3d/jsonpath_rust_bindings-1.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:fbfeb05c7a6854104e97a0e3234f312004b3f4e678d14b68180a6a4f33f4d7c3", size = 1134382, upload-time = "2025-11-16T19:03:19.881Z" }, { url = "https://files.pythonhosted.org/packages/9e/f6/02301a17826e0f5d253146918e52436831f43fdf018031819ab4dc2af8e4/jsonpath_rust_bindings-1.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:44de7464ad227028c36e8d713653b4bfe5eb7524ac1a4b0a71e8bcb3bd4f4f3a", size = 814583, upload-time = "2025-11-16T19:01:55.251Z" }, { url = "https://files.pythonhosted.org/packages/7b/63/8860fc926e25ef3dfbc61d6366932f3e106a089308e3ad6a36987fac3efe/jsonpath_rust_bindings-1.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c220c2d27ab6a0791e3af10e2a7c53ccd1dc2dfc8681999fed4458392aa0372", size = 831964, upload-time = "2025-11-16T19:00:17.016Z" }, + { url = "https://files.pythonhosted.org/packages/84/2d/5f16683333b298969c24b6a09b5cb071fe4d603e4e8788e5db6b82231618/jsonpath_rust_bindings-1.1.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e423363b47080830bbb4d8257c0f26bda8ee655a18c4f934952bfe4c46e8d510", size = 836196, upload-time = "2025-11-16T19:00:33.647Z" }, + { url = "https://files.pythonhosted.org/packages/da/c9/5c75bad74f27eca1853d9d58fabc4ed838e94997d65177d9f29cc9bb3229/jsonpath_rust_bindings-1.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:366cba544c080c08530cef0cc19922f0380f0caab6e7e5a0ddfb70de288d5abc", size = 931327, upload-time = "2025-11-16T19:00:50.823Z" }, + { url = "https://files.pythonhosted.org/packages/b7/5f/8e3a65a8053945d0c63ea8e5c11832b051e0919b342f29e1365108165472/jsonpath_rust_bindings-1.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7bf30e27a81d07c79cc58c86600687e5adfe0f7b1aaf8069a737085bebfaea71", size = 959445, upload-time = "2025-11-16T19:01:06.891Z" }, { url = "https://files.pythonhosted.org/packages/2f/5a/f44c4b55cecc6eb1a4b22dc2aacb9cf9f434b600706527ab619f6076ced0/jsonpath_rust_bindings-1.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c390c33582cd268d35b86eb0f550229e0cf26f03bb06c470db4712d6fa4dc0f", size = 947132, upload-time = "2025-11-16T19:01:38.408Z" }, { url = "https://files.pythonhosted.org/packages/5f/9d/e35fdaea0a065584d4864af8711a9be501015d4354d3eb9f61de0fedccc6/jsonpath_rust_bindings-1.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0017af7054fb6bce55863a7065ae465a9c47fd93fb94f002ca98bb8adf15101a", size = 1066860, upload-time = "2025-11-16T19:02:31.654Z" }, + { url = "https://files.pythonhosted.org/packages/8b/25/8ca3c1b67435f3a29d121c86867afdb86e02ec932c7a5343af61554c5788/jsonpath_rust_bindings-1.1.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:9212d3746a57015fc3722488f61c4afc465d993f68371d864be8fa5b0c58d635", size = 1148553, upload-time = "2025-11-16T19:02:47.91Z" }, { url = "https://files.pythonhosted.org/packages/c6/be/708f5c15718e796d3d3fb3d139fe5dfa8aa6b0eff44adadc0bf66822d388/jsonpath_rust_bindings-1.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d21101114514d34b21ab216eef1d7bb41155311fa61284e8f2dbdb93bde41c78", size = 1133969, upload-time = "2025-11-16T19:03:21.839Z" }, { url = "https://files.pythonhosted.org/packages/7b/4c/2a7995761e247610551cb218b5fcfa9c95a542d8a38915a91579178eba73/jsonpath_rust_bindings-1.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f55ee1e7fdb6bb2363c40a6d6ce0285e53bd52b4ecae7bef3909eeb11a9b4cd2", size = 815031, upload-time = "2025-11-16T19:01:56.972Z" }, { url = "https://files.pythonhosted.org/packages/f1/fb/f1375e4f254fdf088ebbb397cfb42f3bdd5c7fed3349ad140f09d052ae09/jsonpath_rust_bindings-1.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:734eee89754c829a0fb55a30467c8a33081976375b763c907f71f7018682c26c", size = 832342, upload-time = "2025-11-16T19:00:18.79Z" }, + { url = "https://files.pythonhosted.org/packages/17/5f/bccd6178fc9655e03b01917531c08a25951b55455189a98faa13f7125d4a/jsonpath_rust_bindings-1.1.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6716caa0855dbf9d021509a3caa00a9fa7cc241930f40830c24e85d0e17a6246", size = 836616, upload-time = "2025-11-16T19:00:35.477Z" }, + { url = "https://files.pythonhosted.org/packages/e9/e6/e809c31962c161230ef136646e7a6bc1783ab9299255f043923af1d55a90/jsonpath_rust_bindings-1.1.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:02373d581a093d0640e60858884d67ec93259e7b6d6bd8e5874400ad99558e00", size = 931852, upload-time = "2025-11-16T19:00:52.271Z" }, + { url = "https://files.pythonhosted.org/packages/e1/51/9d29b9f642012d545233416138c96562aefdab78d3602b61e19267fc4098/jsonpath_rust_bindings-1.1.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:146b69ce20cb9869e05a6d369f4a10b52f98e1f8575f1ac5b49e285fa2032380", size = 959626, upload-time = "2025-11-16T19:01:08.348Z" }, { url = "https://files.pythonhosted.org/packages/1c/95/696e02d5af89b95da829b79473cde3e7a1c0d73c571d1dc7c32e886e04e0/jsonpath_rust_bindings-1.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe44737c6c72079ef30c85f975c19fa0114c13039fe538d8c5b259007a35a0ff", size = 947152, upload-time = "2025-11-16T19:01:41.146Z" }, { url = "https://files.pythonhosted.org/packages/66/5c/b7eb6647de1721b632cccbfe3de777f1030fa0525a89b119ed70ebafc2c6/jsonpath_rust_bindings-1.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:40c23781d28a8b126c8a2b337e4fe275cc8f35a149bda769e3ec2760dfb58b91", size = 1067196, upload-time = "2025-11-16T19:02:33.289Z" }, + { url = "https://files.pythonhosted.org/packages/c5/80/1c56c148c92f43aca6799716f549ff2463ee328c377b9c1e630d4057a607/jsonpath_rust_bindings-1.1.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:4eacb98f80fff7d43956503ca7b42e491f7084c7b9bd8b5b6bad3f50d08480df", size = 1148940, upload-time = "2025-11-16T19:02:49.647Z" }, { url = "https://files.pythonhosted.org/packages/7e/df/b0c2fd033c5f5714a7ea4c03dabe8ab66dbaabab4fa7d9385344ab7a16e7/jsonpath_rust_bindings-1.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7f2a526c87a245f708dc1d8d4988c471384c369a5909b8b730e63b6a7f0c2d60", size = 1134078, upload-time = "2025-11-16T19:03:23.799Z" }, { url = "https://files.pythonhosted.org/packages/93/75/47695316d55a13d475490ca5aa41e02b8eab8b4eea696cc08536e2f05694/jsonpath_rust_bindings-1.1.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ddbf025592bf88fc5395d9d023d7bcc8fab977898c406e0a5722925c3b887c71", size = 814128, upload-time = "2025-11-16T19:02:08.847Z" }, { url = "https://files.pythonhosted.org/packages/69/0b/bbe0f2ba599a3aa59bcf44589188641528be507a2b7e45e8c2edfb17f77f/jsonpath_rust_bindings-1.1.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce1c6804706012c3c7a194903ef20befafa3cc913a4ef553696bc837ac738a66", size = 832484, upload-time = "2025-11-16T19:00:27.089Z" }, + { url = "https://files.pythonhosted.org/packages/67/2f/fc61ad93957f01cbb683c78e842348f11832da98274574ff28b886da2cbe/jsonpath_rust_bindings-1.1.1-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:aa7e9d25b00c227c51e7a916a13fbf22cf483df622699dbc3ef051861ec1de85", size = 836786, upload-time = "2025-11-16T19:00:43.535Z" }, + { url = "https://files.pythonhosted.org/packages/11/e9/3859c3c118f02b5413ef6a1ddfd1b9f2ecdaf2d1a2eaa58e656bc8d4a887/jsonpath_rust_bindings-1.1.1-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ebb9a05a2b80195ac47aec0ce98d861c102459d16225fefb0f7e0158196c4a58", size = 932463, upload-time = "2025-11-16T19:01:00.129Z" }, + { url = "https://files.pythonhosted.org/packages/3d/f5/56e48adb9dad2a97172b905e305c2de478ae8748a0467996d9aae72f4667/jsonpath_rust_bindings-1.1.1-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1ff4cd052f733d5f270329c552a04e08a1520053355d35f0be886714dff46955", size = 959317, upload-time = "2025-11-16T19:01:16.435Z" }, { url = "https://files.pythonhosted.org/packages/6f/77/d4ddd5710121ffa18f270d0af2c906786db0d5fd914ed47ce704beba9a75/jsonpath_rust_bindings-1.1.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce7039a2f497674785a423076e803a1fa547c2f9cf568b25e2ac83ff5890b98f", size = 947180, upload-time = "2025-11-16T19:01:49.08Z" }, { url = "https://files.pythonhosted.org/packages/3c/38/7f3e03ae9655d1f7d97f34e2f8e95d55aa3f0790ba4743f648844048fab4/jsonpath_rust_bindings-1.1.1-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:a239166bd1418897de327c952a9d9ff912d1fabc9da82e688204ccfcd7b22584", size = 1067329, upload-time = "2025-11-16T19:02:41.053Z" }, + { url = "https://files.pythonhosted.org/packages/ca/a6/2a96e232a6f0164320801c68bd99b407aea22d1a101892ccb4fc8a2d2198/jsonpath_rust_bindings-1.1.1-pp311-pypy311_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:330f457556d06abc1ea36b6738eb172288afff6bd251350eaba42bed2f459fd3", size = 1149213, upload-time = "2025-11-16T19:02:57.714Z" }, { url = "https://files.pythonhosted.org/packages/c9/c1/4f7b7f5f78dcf23c7a2a208b3088875ae3596f22db5f0612367d95bdb5f7/jsonpath_rust_bindings-1.1.1-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:26955685acf0208b6061419cab4bd79fe869ebce57f3cec1e9b20f0e0af56b35", size = 1133989, upload-time = "2025-11-16T19:03:32.485Z" }, ] @@ -3104,21 +3277,37 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a7/23/851cfa33b6b38adb628e45ad51fb27105fa34b2b3ba9d1d4aa7a9428dfe0/lxml-6.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d036ee7b99d5148072ac7c9b847193decdfeac633db350363f7bce4fff108f0e", size = 5068101, upload-time = "2026-04-18T04:32:21.437Z" }, { url = "https://files.pythonhosted.org/packages/b0/38/41bf99c2023c6b79916ba057d83e9db21d642f473cac210201222882d38b/lxml-6.1.0-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ae5d8d5427f3cc317e7950f2da7ad276df0cfa37b8de2f5658959e618ea8512", size = 5002573, upload-time = "2026-04-18T04:32:25.373Z" }, { url = "https://files.pythonhosted.org/packages/c2/20/053aa10bdc39747e1e923ce2d45413075e84f70a136045bb09e5eaca41d3/lxml-6.1.0-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:363e47283bde87051b821826e71dde47f107e08614e1aa312ba0c5711e77738c", size = 5202816, upload-time = "2026-04-18T04:32:29.393Z" }, + { url = "https://files.pythonhosted.org/packages/b3/cb/bf035dedbdf7fab49411aa52e4236f3445e98d38647d85419e6c0d2806b9/lxml-6.1.0-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:23a5dc68e08ed13331d61815c08f260f46b4a60fdd1640bbeb82cf89a9d90289", size = 4659643, upload-time = "2026-04-18T04:32:37.932Z" }, + { url = "https://files.pythonhosted.org/packages/5c/4f/22be31f33727a5e4c7b01b0a874503026e50329b259d3587e0b923cf964b/lxml-6.1.0-cp311-cp311-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f15401d8d3dbf239e23c818afc10c7207f7b95f9a307e092122b6f86dd43209a", size = 5265963, upload-time = "2026-04-18T04:32:41.881Z" }, { url = "https://files.pythonhosted.org/packages/c8/2b/d44d0e5c79226017f4ab8c87a802ebe4f89f97e6585a8e4166dffcdd7b6e/lxml-6.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fcf3da95e93349e0647d48d4b36a12783105bcc74cb0c416952f9988410846a3", size = 5045444, upload-time = "2026-04-18T04:32:44.512Z" }, + { url = "https://files.pythonhosted.org/packages/d3/c3/3f034fec1594c331a6dbf9491238fdcc9d66f68cc529e109ec75b97197e1/lxml-6.1.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:0d082495c5fcf426e425a6e28daaba1fcb6d8f854a4ff01effb1f1f381203eb9", size = 4712703, upload-time = "2026-04-18T04:32:47.16Z" }, + { url = "https://files.pythonhosted.org/packages/12/16/0b83fccc158218aca75a7aa33e97441df737950734246b9fffa39301603d/lxml-6.1.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:e3c4f84b24a1fcba435157d111c4b755099c6ff00a3daee1ad281817de75ed11", size = 5252745, upload-time = "2026-04-18T04:32:50.427Z" }, { url = "https://files.pythonhosted.org/packages/dd/ee/12e6c1b39a77666c02eaa77f94a870aaf63c4ac3a497b2d52319448b01c6/lxml-6.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:976a6b39b1b13e8c354ad8d3f261f3a4ac6609518af91bdb5094760a08f132c4", size = 5226822, upload-time = "2026-04-18T04:32:53.437Z" }, { url = "https://files.pythonhosted.org/packages/d2/d4/9326838b59dc36dfae42eec9656b97520f9997eee1de47b8316aaeed169c/lxml-6.1.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d2f17a16cd8751e8eb233a7e41aecdf8e511712e00088bf9be455f604cd0d28d", size = 8570663, upload-time = "2026-04-18T04:27:48.253Z" }, { url = "https://files.pythonhosted.org/packages/90/97/a517944b20f8fd0932ad2109482bee4e29fe721416387a363306667941f6/lxml-6.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fc46da94826188ed45cb53bd8e3fc076ae22675aea2087843d4735627f867c6d", size = 4930895, upload-time = "2026-04-18T04:32:56.29Z" }, { url = "https://files.pythonhosted.org/packages/94/7c/e08a970727d556caa040a44773c7b7e3ad0f0d73dedc863543e9a8b931f2/lxml-6.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9147d8e386ec3b82c3b15d88927f734f565b0aaadef7def562b853adca45784a", size = 5093820, upload-time = "2026-04-18T04:32:58.94Z" }, { url = "https://files.pythonhosted.org/packages/88/ee/2a5c2aa2c32016a226ca25d3e1056a8102ea6e1fe308bf50213586635400/lxml-6.1.0-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5715e0e28736a070f3f34a7ccc09e2fdcba0e3060abbcf61a1a5718ff6d6b105", size = 5005790, upload-time = "2026-04-18T04:33:01.272Z" }, + { url = "https://files.pythonhosted.org/packages/e3/38/a0db9be8f38ad6043ab9429487c128dd1d30f07956ef43040402f8da49e8/lxml-6.1.0-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4937460dc5df0cdd2f06a86c285c28afda06aefa3af949f9477d3e8df430c485", size = 5630827, upload-time = "2026-04-18T04:33:04.036Z" }, { url = "https://files.pythonhosted.org/packages/31/ba/3c13d3fc24b7cacf675f808a3a1baabf43a30d0cd24c98f94548e9aa58eb/lxml-6.1.0-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bc783ee3147e60a25aa0445ea82b3e8aabb83b240f2b95d32cb75587ff781814", size = 5240445, upload-time = "2026-04-18T04:33:06.87Z" }, + { url = "https://files.pythonhosted.org/packages/7e/01/1da87c7b587c38d0cbe77a01aae3b9c1c49ed47d76918ef3db8fc151b1ca/lxml-6.1.0-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:05b9b8787e35bec69e68daf4952b2e6dfcfb0db7ecf1a06f8cdfbbac4eb71aad", size = 4694949, upload-time = "2026-04-18T04:33:11.628Z" }, + { url = "https://files.pythonhosted.org/packages/a1/88/7db0fe66d5aaf128443ee1623dec3db1576f3e4c17751ec0ef5866468590/lxml-6.1.0-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0f0f08beb0182e3e9a86fae124b3c47a7b41b7b69b225e1377db983802404e54", size = 5243901, upload-time = "2026-04-18T04:33:13.95Z" }, { url = "https://files.pythonhosted.org/packages/00/a8/1346726af7d1f6fca1f11223ba34001462b0a3660416986d37641708d57c/lxml-6.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73becf6d8c81d4c76b1014dbd3584cb26d904492dcf73ca85dc8bff08dcd6d2d", size = 5048054, upload-time = "2026-04-18T04:33:16.965Z" }, + { url = "https://files.pythonhosted.org/packages/2e/b7/85057012f035d1a0c87e02f8c723ca3c3e6e0728bcf4cb62080b21b1c1e3/lxml-6.1.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1ae225f66e5938f4fa29d37e009a3bb3b13032ac57eb4eb42afa44f6e4054e69", size = 4777324, upload-time = "2026-04-18T04:33:19.832Z" }, + { url = "https://files.pythonhosted.org/packages/75/6c/ad2f94a91073ef570f33718040e8e160d5fb93331cf1ab3ca1323f939e2d/lxml-6.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:690022c7fae793b0489aa68a658822cea83e0d5933781811cabbf5ea3bcfe73d", size = 5645702, upload-time = "2026-04-18T04:33:22.436Z" }, + { url = "https://files.pythonhosted.org/packages/3b/89/0bb6c0bd549c19004c60eea9dc554dd78fd647b72314ef25d460e0d208c6/lxml-6.1.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:63aeafc26aac0be8aff14af7871249e87ea1319be92090bfd632ec68e03b16a5", size = 5232901, upload-time = "2026-04-18T04:33:26.21Z" }, { url = "https://files.pythonhosted.org/packages/a1/d9/d609a11fb567da9399f525193e2b49847b5a409cdebe737f06a8b7126bdc/lxml-6.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:264c605ab9c0e4aa1a679636f4582c4d3313700009fac3ec9c3412ed0d8f3e1d", size = 5261333, upload-time = "2026-04-18T04:33:28.984Z" }, { url = "https://files.pythonhosted.org/packages/08/03/69347590f1cf4a6d5a4944bb6099e6d37f334784f16062234e1f892fdb1d/lxml-6.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a0092f2b107b69601adf562a57c956fbb596e05e3e6651cabd3054113b007e45", size = 8559689, upload-time = "2026-04-18T04:31:57.785Z" }, { url = "https://files.pythonhosted.org/packages/f5/54/92ad98a94ac318dc4f97aaac22ff8d1b94212b2ae8af5b6e9b354bf825f7/lxml-6.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:419c58fc92cc3a2c3fa5f78c63dbf5da70c1fa9c1b25f25727ecee89a96c7de2", size = 4923489, upload-time = "2026-04-18T04:33:31.401Z" }, { url = "https://files.pythonhosted.org/packages/15/3b/a20aecfab42bdf4f9b390590d345857ad3ffd7c51988d1c89c53a0c73faf/lxml-6.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:37fabd1452852636cf38ecdcc9dd5ca4bba7a35d6c53fa09725deeb894a87491", size = 5082162, upload-time = "2026-04-18T04:33:34.262Z" }, { url = "https://files.pythonhosted.org/packages/45/26/2cdb3d281ac1bd175603e290cbe4bad6eff127c0f8de90bafd6f8548f0fd/lxml-6.1.0-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2853c8b2170cc6cd54a6b4d50d2c1a8a7aeca201f23804b4898525c7a152cfc", size = 4993247, upload-time = "2026-04-18T04:33:36.674Z" }, + { url = "https://files.pythonhosted.org/packages/f6/05/d735aef963740022a08185c84821f689fc903acb3d50326e6b1e9886cc22/lxml-6.1.0-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8e369cbd690e788c8d15e56222d91a09c6a417f49cbc543040cba0fe2e25a79e", size = 5613042, upload-time = "2026-04-18T04:33:39.205Z" }, { url = "https://files.pythonhosted.org/packages/ee/b8/ead7c10efff731738c72e59ed6eb5791854879fbed7ae98781a12006263a/lxml-6.1.0-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e69aa6805905807186eb00e66c6d97a935c928275182eb02ee40ba00da9623b2", size = 5228304, upload-time = "2026-04-18T04:33:41.647Z" }, + { url = "https://files.pythonhosted.org/packages/89/54/40d9403d7c2775fa7301d3ddd3464689bfe9ba71acc17dfff777071b4fdc/lxml-6.1.0-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:cbd7b79cdcb4986ad78a2662625882747f09db5e4cd7b2ae178a88c9c51b3dfe", size = 4700209, upload-time = "2026-04-18T04:33:47.552Z" }, + { url = "https://files.pythonhosted.org/packages/85/b2/bbdcc2cf45dfc7dfffef4fd97e5c47b15919b6a365247d95d6f684ef5e82/lxml-6.1.0-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:43e4d297f11080ec9d64a4b1ad7ac02b4484c9f0e2179d9c4ef78e886e747b88", size = 5232365, upload-time = "2026-04-18T04:33:50.249Z" }, { url = "https://files.pythonhosted.org/packages/48/5a/b06875665e53aaba7127611a7bed3b7b9658e20b22bc2dd217a0b7ab0091/lxml-6.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cc16682cc987a3da00aa56a3aa3075b08edb10d9b1e476938cfdbee8f3b67181", size = 5043654, upload-time = "2026-04-18T04:33:52.71Z" }, + { url = "https://files.pythonhosted.org/packages/e9/9c/e71a069d09641c1a7abeb30e693f828c7c90a41cbe3d650b2d734d876f85/lxml-6.1.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:d6d8efe71429635f0559579092bb5e60560d7b9115ee38c4adbea35632e7fa24", size = 4769326, upload-time = "2026-04-18T04:33:55.244Z" }, + { url = "https://files.pythonhosted.org/packages/cc/06/7a9cd84b3d4ed79adf35f874750abb697dec0b4a81a836037b36e47c091a/lxml-6.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7e39ab3a28af7784e206d8606ec0e4bcad0190f63a492bca95e94e5a4aef7f6e", size = 5635879, upload-time = "2026-04-18T04:33:58.509Z" }, + { url = "https://files.pythonhosted.org/packages/cc/f0/9d57916befc1e54c451712c7ee48e9e74e80ae4d03bdce49914e0aee42cd/lxml-6.1.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:9eb667bf50856c4a58145f8ca2d5e5be160191e79eb9e30855a476191b3c3495", size = 5224048, upload-time = "2026-04-18T04:34:00.943Z" }, { url = "https://files.pythonhosted.org/packages/99/75/90c4eefda0c08c92221fe0753db2d6699a4c628f76ff4465ec20dea84cc1/lxml-6.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7f4a77d6f7edf9230cee3e1f7f6764722a41604ee5681844f18db9a81ea0ec33", size = 5250241, upload-time = "2026-04-18T04:34:03.365Z" }, { url = "https://files.pythonhosted.org/packages/b5/97/28b985c2983938d3cb696dd5501423afb90a8c3e869ef5d3c62569282c0f/lxml-6.1.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5cfa1a34df366d9dc0d5eaf420f4cf2bb1e1bebe1066d1c2fc28c179f8a4004c", size = 4210749, upload-time = "2026-04-18T04:36:03.626Z" }, { url = "https://files.pythonhosted.org/packages/29/67/dfab2b7d58214921935ccea7ce9b3df9b7d46f305d12f0f532ac7cf6b804/lxml-6.1.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:db88156fcf544cdbf0d95588051515cfdfd4c876fc66444eb98bceb5d6db76de", size = 4318463, upload-time = "2026-04-18T04:36:06.309Z" }, @@ -3188,22 +3377,30 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058, upload-time = "2025-09-27T18:36:19.444Z" }, { url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287, upload-time = "2025-09-27T18:36:20.768Z" }, { url = "https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", size = 22940, upload-time = "2025-09-27T18:36:22.249Z" }, + { url = "https://files.pythonhosted.org/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", size = 21887, upload-time = "2025-09-27T18:36:23.535Z" }, { url = "https://files.pythonhosted.org/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", size = 23692, upload-time = "2025-09-27T18:36:24.823Z" }, + { url = "https://files.pythonhosted.org/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", size = 21471, upload-time = "2025-09-27T18:36:25.95Z" }, { url = "https://files.pythonhosted.org/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", size = 22923, upload-time = "2025-09-27T18:36:27.109Z" }, { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, + { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, + { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, ] @@ -3431,13 +3628,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c1/b4/9cd284bd6062d711e13d26c04d4778ab3f690c1c38a4563e3c767ec8802e/mmh3-5.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0634581290e6714c068f4aa24020acf7880927d1f0084fa753d9799ae9610082", size = 40079, upload-time = "2026-03-05T15:54:02.743Z" }, { url = "https://files.pythonhosted.org/packages/ee/93/723e317dd9e041c4dc4566a2eb53b01ad94de31750e0b834f1643905e97c/mmh3-5.2.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:db0562c5f71d18596dcd45e854cf2eeba27d7543e1a3acdafb7eef728f7fe85d", size = 103082, upload-time = "2026-03-05T15:54:06.387Z" }, { url = "https://files.pythonhosted.org/packages/61/b5/f96121e69cc48696075071531cf574f112e1ffd08059f4bffb41210e6fc5/mmh3-5.2.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d9f9a3ce559a5267014b04b82956993270f63ec91765e13e9fd73daf2d2738e", size = 106054, upload-time = "2026-03-05T15:54:07.506Z" }, + { url = "https://files.pythonhosted.org/packages/82/49/192b987ec48d0b2aecf8ac285a9b11fbc00030f6b9c694664ae923458dde/mmh3-5.2.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:960b1b3efa39872ac8b6cc3a556edd6fb90ed74f08c9c45e028f1005b26aa55d", size = 112910, upload-time = "2026-03-05T15:54:09.403Z" }, + { url = "https://files.pythonhosted.org/packages/cf/a1/03e91fd334ed0144b83343a76eb11f17434cd08f746401488cfeafb2d241/mmh3-5.2.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d30b650595fdbe32366b94cb14f30bb2b625e512bd4e1df00611f99dc5c27fd4", size = 120551, upload-time = "2026-03-05T15:54:10.587Z" }, { url = "https://files.pythonhosted.org/packages/93/b9/b89a71d2ff35c3a764d1c066c7313fc62c7cc48fa48a4b3b0304a4a0146f/mmh3-5.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:82f3802bfc4751f420d591c5c864de538b71cea117fce67e4595c2afede08a15", size = 99096, upload-time = "2026-03-05T15:54:11.76Z" }, + { url = "https://files.pythonhosted.org/packages/5e/0e/1524566fe8eaf871e4f7bc44095929fcd2620488f402822d848df19d679c/mmh3-5.2.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:fc78739b5ec6e4fb02301984a3d442a91406e7700efbe305071e7fd1c78278f2", size = 106239, upload-time = "2026-03-05T15:54:14.601Z" }, + { url = "https://files.pythonhosted.org/packages/04/94/21adfa7d90a7a697137ad6de33eeff6445420ca55e433a5d4919c79bc3b5/mmh3-5.2.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:41aac7002a749f08727cb91babff1daf8deac317c0b1f317adc69be0e6c375d1", size = 109797, upload-time = "2026-03-05T15:54:15.819Z" }, { url = "https://files.pythonhosted.org/packages/b5/e6/1aacc3a219e1aa62fa65669995d4a3562b35be5200ec03680c7e4bec9676/mmh3-5.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9d8089d853c7963a8ce87fff93e2a67075c0bc08684a08ea6ad13577c38ffc38", size = 97228, upload-time = "2026-03-05T15:54:16.992Z" }, { url = "https://files.pythonhosted.org/packages/92/94/bc5c3b573b40a328c4d141c20e399039ada95e5e2a661df3425c5165fd84/mmh3-5.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0cc21533878e5586b80d74c281d7f8da7932bc8ace50b8d5f6dbf7e3935f63f1", size = 56087, upload-time = "2026-03-05T15:54:21.92Z" }, { url = "https://files.pythonhosted.org/packages/8b/72/e6d6602ce18adf4ddcd0e48f2e13590cc92a536199e52109f46f259d3c46/mmh3-5.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:eee884572b06bbe8a2b54f424dbd996139442cf83c76478e1ec162512e0dd2c7", size = 40034, upload-time = "2026-03-05T15:54:23.943Z" }, { url = "https://files.pythonhosted.org/packages/e5/e2/51ed62063b44d10b06d975ac87af287729eeb5e3ed9772f7584a17983e90/mmh3-5.2.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8e6c219e375f6341d0959af814296372d265a8ca1af63825f65e2e87c618f006", size = 103274, upload-time = "2026-03-05T15:54:26.44Z" }, { url = "https://files.pythonhosted.org/packages/75/ce/12a7524dca59eec92e5b31fdb13ede1e98eda277cf2b786cf73bfbc24e81/mmh3-5.2.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:26fb5b9c3946bf7f1daed7b37e0c03898a6f062149127570f8ede346390a0825", size = 106158, upload-time = "2026-03-05T15:54:28.578Z" }, + { url = "https://files.pythonhosted.org/packages/86/1f/d3ba6dd322d01ab5d44c46c8f0c38ab6bbbf9b5e20e666dfc05bf4a23604/mmh3-5.2.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3c38d142c706201db5b2345166eeef1e7740e3e2422b470b8ba5c8727a9b4c7a", size = 113005, upload-time = "2026-03-05T15:54:29.767Z" }, + { url = "https://files.pythonhosted.org/packages/b6/a9/15d6b6f913294ea41b44d901741298e3718e1cb89ee626b3694625826a43/mmh3-5.2.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50885073e2909251d4718634a191c49ae5f527e5e1736d738e365c3e8be8f22b", size = 120744, upload-time = "2026-03-05T15:54:30.931Z" }, { url = "https://files.pythonhosted.org/packages/76/b3/70b73923fd0284c439860ff5c871b20210dfdbe9a6b9dd0ee6496d77f174/mmh3-5.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b3f99e1756fc48ad507b95e5d86f2fb21b3d495012ff13e6592ebac14033f166", size = 99111, upload-time = "2026-03-05T15:54:32.353Z" }, + { url = "https://files.pythonhosted.org/packages/fd/68/6e292c0853e204c44d2f03ea5f090be3317a0e2d9417ecb62c9eb27687df/mmh3-5.2.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8f767ba0911602ddef289404e33835a61168314ebd3c729833db2ed685824211", size = 106437, upload-time = "2026-03-05T15:54:35.177Z" }, + { url = "https://files.pythonhosted.org/packages/dd/c6/fedd7284c459cfb58721d461fcf5607a4c1f5d9ab195d113d51d10164d16/mmh3-5.2.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:67e41a497bac88cc1de96eeba56eeb933c39d54bc227352f8455aa87c4ca4000", size = 110002, upload-time = "2026-03-05T15:54:36.673Z" }, { url = "https://files.pythonhosted.org/packages/3b/ac/ca8e0c19a34f5b71390171d2ff0b9f7f187550d66801a731bb68925126a4/mmh3-5.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3d74a03fb57757ece25aa4b3c1c60157a1cece37a020542785f942e2f827eed5", size = 97507, upload-time = "2026-03-05T15:54:37.804Z" }, { url = "https://files.pythonhosted.org/packages/62/fb/648bfddb74a872004b6ee751551bfdda783fe6d70d2e9723bad84dbe5311/mmh3-5.2.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:e48d4dbe0f88e53081da605ae68644e5182752803bbc2beb228cca7f1c4454d6", size = 39114, upload-time = "2026-03-05T15:54:45.205Z" }, { url = "https://files.pythonhosted.org/packages/95/c2/ab7901f87af438468b496728d11264cb397b3574d41506e71b92128e0373/mmh3-5.2.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:a482ac121de6973897c92c2f31defc6bafb11c83825109275cffce54bb64933f", size = 39819, upload-time = "2026-03-05T15:54:46.509Z" }, @@ -3446,7 +3651,11 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d4/4c/8e3af1b6d85a299767ec97bd923f12b06267089c1472c27c1696870d1175/mmh3-5.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be77c402d5e882b6fbacfd90823f13da8e0a69658405a39a569c6b58fdb17b03", size = 40033, upload-time = "2026-03-05T15:54:50.994Z" }, { url = "https://files.pythonhosted.org/packages/bb/0d/2c5f9893b38aeb6b034d1a44ecd55a010148054f6a516abe53b5e4057297/mmh3-5.2.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:707151644085dd0f20fe4f4b573d28e5130c4aaa5f587e95b60989c5926653b5", size = 103299, upload-time = "2026-03-05T15:54:53.569Z" }, { url = "https://files.pythonhosted.org/packages/1c/fc/2ebaef4a4d4376f89761274dc274035ffd96006ab496b4ee5af9b08f21a9/mmh3-5.2.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3737303ca9ea0f7cb83028781148fcda4f1dac7821db0c47672971dabcf63593", size = 106222, upload-time = "2026-03-05T15:54:55.092Z" }, + { url = "https://files.pythonhosted.org/packages/57/09/ea7ffe126d0ba0406622602a2d05e1e1a6841cc92fc322eb576c95b27fad/mmh3-5.2.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2778fed822d7db23ac5008b181441af0c869455b2e7d001f4019636ac31b6fe4", size = 113048, upload-time = "2026-03-05T15:54:56.305Z" }, + { url = "https://files.pythonhosted.org/packages/85/57/9447032edf93a64aa9bef4d9aa596400b1756f40411890f77a284f6293ca/mmh3-5.2.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d57dea657357230cc780e13920d7fa7db059d58fe721c80020f94476da4ca0a1", size = 120742, upload-time = "2026-03-05T15:54:57.453Z" }, { url = "https://files.pythonhosted.org/packages/53/82/a86cc87cc88c92e9e1a598fee509f0409435b57879a6129bf3b3e40513c7/mmh3-5.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:169e0d178cb59314456ab30772429a802b25d13227088085b0d49b9fe1533104", size = 99132, upload-time = "2026-03-05T15:54:58.583Z" }, + { url = "https://files.pythonhosted.org/packages/e8/88/a601e9f32ad1410f438a6d0544298ea621f989bd34a0731a7190f7dec799/mmh3-5.2.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:2bd9f19f7f1fcebd74e830f4af0f28adad4975d40d80620be19ffb2b2af56c9f", size = 106479, upload-time = "2026-03-05T15:55:01.532Z" }, + { url = "https://files.pythonhosted.org/packages/d6/5c/ce29ae3dfc4feec4007a437a1b7435fb9507532a25147602cd5b52be86db/mmh3-5.2.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:c88653877aeb514c089d1b3d473451677b8b9a6d1497dbddf1ae7934518b06d2", size = 110030, upload-time = "2026-03-05T15:55:02.934Z" }, { url = "https://files.pythonhosted.org/packages/13/30/ae444ef2ff87c805d525da4fa63d27cda4fe8a48e77003a036b8461cfd5c/mmh3-5.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fceef7fe67c81e1585198215e42ad3fdba3a25644beda8fbdaf85f4d7b93175a", size = 97536, upload-time = "2026-03-05T15:55:04.135Z" }, ] @@ -3518,26 +3727,50 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ce/f1/a90635c4f88fb913fbf4ce660b83b7445b7a02615bda034b2f8eb38fd597/multidict-6.7.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7ff981b266af91d7b4b3793ca3382e53229088d193a85dfad6f5f4c27fc73e5d", size = 76626, upload-time = "2026-01-26T02:43:26.485Z" }, { url = "https://files.pythonhosted.org/packages/dd/a4/d45caf2b97b035c57267791ecfaafbd59c68212004b3842830954bb4b02e/multidict-6.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f2a0a924d4c2e9afcd7ec64f9de35fcd96915149b2216e1cb2c10a56df483855", size = 44356, upload-time = "2026-01-26T02:43:28.661Z" }, { url = "https://files.pythonhosted.org/packages/5d/16/8c65be997fd7dd311b7d39c7b6e71a0cb449bad093761481eccbbe4b42a2/multidict-6.7.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2e2d2ed645ea29f31c4c7ea1552fcfd7cb7ba656e1eafd4134a6620c9f5fdd9e", size = 246433, upload-time = "2026-01-26T02:43:32.581Z" }, + { url = "https://files.pythonhosted.org/packages/01/fb/4dbd7e848d2799c6a026ec88ad39cf2b8416aa167fcc903baa55ecaa045c/multidict-6.7.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:95922cee9a778659e91db6497596435777bd25ed116701a4c034f8e46544955a", size = 225376, upload-time = "2026-01-26T02:43:34.417Z" }, + { url = "https://files.pythonhosted.org/packages/b6/8a/4a3a6341eac3830f6053062f8fbc9a9e54407c80755b3f05bc427295c2d0/multidict-6.7.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6b83cabdc375ffaaa15edd97eb7c0c672ad788e2687004990074d7d6c9b140c8", size = 257365, upload-time = "2026-01-26T02:43:35.741Z" }, + { url = "https://files.pythonhosted.org/packages/f7/a2/dd575a69c1aa206e12d27d0770cdf9b92434b48a9ef0cd0d1afdecaa93c4/multidict-6.7.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:38fb49540705369bab8484db0689d86c0a33a0a9f2c1b197f506b71b4b6c19b0", size = 254747, upload-time = "2026-01-26T02:43:36.976Z" }, { url = "https://files.pythonhosted.org/packages/5a/56/21b27c560c13822ed93133f08aa6372c53a8e067f11fbed37b4adcdac922/multidict-6.7.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:439cbebd499f92e9aa6793016a8acaa161dfa749ae86d20960189f5398a19144", size = 246293, upload-time = "2026-01-26T02:43:38.258Z" }, { url = "https://files.pythonhosted.org/packages/5a/a4/23466059dc3854763423d0ad6c0f3683a379d97673b1b89ec33826e46728/multidict-6.7.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6d3bc717b6fe763b8be3f2bee2701d3c8eb1b2a8ae9f60910f1b2860c82b6c49", size = 242962, upload-time = "2026-01-26T02:43:40.034Z" }, + { url = "https://files.pythonhosted.org/packages/1f/67/51dd754a3524d685958001e8fa20a0f5f90a6a856e0a9dcabff69be3dbb7/multidict-6.7.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:619e5a1ac57986dbfec9f0b301d865dddf763696435e2962f6d9cf2fdff2bb71", size = 237360, upload-time = "2026-01-26T02:43:41.752Z" }, + { url = "https://files.pythonhosted.org/packages/3d/20/6214d3c105928ebc353a1c644a6ef1408bc5794fcb4f170bb524a3c16311/multidict-6.7.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:10ae39c9cfe6adedcdb764f5e8411d4a92b055e35573a2eaa88d3323289ef93c", size = 253502, upload-time = "2026-01-26T02:43:44.371Z" }, + { url = "https://files.pythonhosted.org/packages/b1/e2/c653bc4ae1be70a0f836b82172d643fcf1dade042ba2676ab08ec08bff0f/multidict-6.7.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:25167cc263257660290fba06b9318d2026e3c910be240a146e1f66dd114af2b0", size = 247065, upload-time = "2026-01-26T02:43:45.745Z" }, { url = "https://files.pythonhosted.org/packages/c8/11/a854b4154cd3bd8b1fd375e8a8ca9d73be37610c361543d56f764109509b/multidict-6.7.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:128441d052254f42989ef98b7b6a6ecb1e6f708aa962c7984235316db59f50fa", size = 241870, upload-time = "2026-01-26T02:43:47.054Z" }, { url = "https://files.pythonhosted.org/packages/8d/9c/f20e0e2cf80e4b2e4b1c365bf5fe104ee633c751a724246262db8f1a0b13/multidict-6.7.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a90f75c956e32891a4eda3639ce6dd86e87105271f43d43442a3aedf3cddf172", size = 76893, upload-time = "2026-01-26T02:43:52.754Z" }, { url = "https://files.pythonhosted.org/packages/a9/65/1caac9d4cd32e8433908683446eebc953e82d22b03d10d41a5f0fefe991b/multidict-6.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0fa96985700739c4c7853a43c0b3e169360d6855780021bfc6d0f1ce7c123e7", size = 43872, upload-time = "2026-01-26T02:43:55.041Z" }, { url = "https://files.pythonhosted.org/packages/fd/80/c959c5933adedb9ac15152e4067c702a808ea183a8b64cf8f31af8ad3155/multidict-6.7.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eb0ce7b2a32d09892b3dd6cc44877a0d02a33241fafca5f25c8b6b62374f8b75", size = 258883, upload-time = "2026-01-26T02:43:57.499Z" }, + { url = "https://files.pythonhosted.org/packages/86/85/7ed40adafea3d4f1c8b916e3b5cc3a8e07dfcdcb9cd72800f4ed3ca1b387/multidict-6.7.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c3a32d23520ee37bf327d1e1a656fec76a2edd5c038bf43eddfa0572ec49c60b", size = 242413, upload-time = "2026-01-26T02:43:58.755Z" }, + { url = "https://files.pythonhosted.org/packages/d2/57/b8565ff533e48595503c785f8361ff9a4fde4d67de25c207cd0ba3befd03/multidict-6.7.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9c90fed18bffc0189ba814749fdcc102b536e83a9f738a9003e569acd540a733", size = 268404, upload-time = "2026-01-26T02:44:00.216Z" }, + { url = "https://files.pythonhosted.org/packages/e0/50/9810c5c29350f7258180dfdcb2e52783a0632862eb334c4896ac717cebcb/multidict-6.7.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:da62917e6076f512daccfbbde27f46fed1c98fee202f0559adec8ee0de67f71a", size = 269456, upload-time = "2026-01-26T02:44:02.202Z" }, { url = "https://files.pythonhosted.org/packages/f3/8d/5e5be3ced1d12966fefb5c4ea3b2a5b480afcea36406559442c6e31d4a48/multidict-6.7.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bfde23ef6ed9db7eaee6c37dcec08524cb43903c60b285b172b6c094711b3961", size = 256322, upload-time = "2026-01-26T02:44:03.56Z" }, { url = "https://files.pythonhosted.org/packages/31/6e/d8a26d81ac166a5592782d208dd90dfdc0a7a218adaa52b45a672b46c122/multidict-6.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3758692429e4e32f1ba0df23219cd0b4fc0a52f476726fff9337d1a57676a582", size = 253955, upload-time = "2026-01-26T02:44:04.845Z" }, + { url = "https://files.pythonhosted.org/packages/59/4c/7c672c8aad41534ba619bcd4ade7a0dc87ed6b8b5c06149b85d3dd03f0cd/multidict-6.7.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:398c1478926eca669f2fd6a5856b6de9c0acf23a2cb59a14c0ba5844fa38077e", size = 251254, upload-time = "2026-01-26T02:44:06.133Z" }, + { url = "https://files.pythonhosted.org/packages/fa/ba/f5449385510825b73d01c2d4087bf6d2fccc20a2d42ac34df93191d3dd03/multidict-6.7.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a088b62bd733e2ad12c50dad01b7d0166c30287c166e137433d3b410add807a6", size = 263588, upload-time = "2026-01-26T02:44:09.382Z" }, + { url = "https://files.pythonhosted.org/packages/d7/11/afc7c677f68f75c84a69fe37184f0f82fce13ce4b92f49f3db280b7e92b3/multidict-6.7.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3d51ff4785d58d3f6c91bdbffcb5e1f7ddfda557727043aa20d20ec4f65e324a", size = 259642, upload-time = "2026-01-26T02:44:10.73Z" }, { url = "https://files.pythonhosted.org/packages/2b/17/ebb9644da78c4ab36403739e0e6e0e30ebb135b9caf3440825001a0bddcb/multidict-6.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc5907494fccf3e7d3f94f95c91d6336b092b5fc83811720fae5e2765890dfba", size = 251377, upload-time = "2026-01-26T02:44:12.042Z" }, { url = "https://files.pythonhosted.org/packages/f2/22/929c141d6c0dba87d3e1d38fbdf1ba8baba86b7776469f2bc2d3227a1e67/multidict-6.7.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2b41f5fed0ed563624f1c17630cb9941cf2309d4df00e494b551b5f3e3d67a23", size = 76174, upload-time = "2026-01-26T02:44:18.509Z" }, { url = "https://files.pythonhosted.org/packages/79/76/55cd7186f498ed080a18440c9013011eb548f77ae1b297206d030eb1180a/multidict-6.7.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:935434b9853c7c112eee7ac891bc4cb86455aa631269ae35442cb316790c1445", size = 43524, upload-time = "2026-01-26T02:44:21.571Z" }, { url = "https://files.pythonhosted.org/packages/f6/32/befed7f74c458b4a525e60519fe8d87eef72bb1e99924fa2b0f9d97a221e/multidict-6.7.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e82d14e3c948952a1a85503817e038cba5905a3352de76b9a465075d072fba23", size = 256952, upload-time = "2026-01-26T02:44:24.306Z" }, + { url = "https://files.pythonhosted.org/packages/03/d6/c878a44ba877f366630c860fdf74bfb203c33778f12b6ac274936853c451/multidict-6.7.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4cfb48c6ea66c83bcaaf7e4dfa7ec1b6bbcf751b7db85a328902796dfde4c060", size = 240317, upload-time = "2026-01-26T02:44:25.772Z" }, + { url = "https://files.pythonhosted.org/packages/68/49/57421b4d7ad2e9e60e25922b08ceb37e077b90444bde6ead629095327a6f/multidict-6.7.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1d540e51b7e8e170174555edecddbd5538105443754539193e3e1061864d444d", size = 267132, upload-time = "2026-01-26T02:44:27.648Z" }, + { url = "https://files.pythonhosted.org/packages/b7/fe/ec0edd52ddbcea2a2e89e174f0206444a61440b40f39704e64dc807a70bd/multidict-6.7.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:273d23f4b40f3dce4d6c8a821c741a86dec62cded82e1175ba3d99be128147ed", size = 268140, upload-time = "2026-01-26T02:44:29.588Z" }, { url = "https://files.pythonhosted.org/packages/b0/73/6e1b01cbeb458807aa0831742232dbdd1fa92bfa33f52a3f176b4ff3dc11/multidict-6.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d624335fd4fa1c08a53f8b4be7676ebde19cd092b3895c421045ca87895b429", size = 254277, upload-time = "2026-01-26T02:44:30.902Z" }, { url = "https://files.pythonhosted.org/packages/6a/b2/5fb8c124d7561a4974c342bc8c778b471ebbeb3cc17df696f034a7e9afe7/multidict-6.7.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:12fad252f8b267cc75b66e8fc51b3079604e8d43a75428ffe193cd9e2195dfd6", size = 252291, upload-time = "2026-01-26T02:44:32.31Z" }, + { url = "https://files.pythonhosted.org/packages/5a/96/51d4e4e06bcce92577fcd488e22600bd38e4fd59c20cb49434d054903bd2/multidict-6.7.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:03ede2a6ffbe8ef936b92cb4529f27f42be7f56afcdab5ab739cd5f27fb1cbf9", size = 250156, upload-time = "2026-01-26T02:44:33.734Z" }, + { url = "https://files.pythonhosted.org/packages/44/a3/ec5b5bd98f306bc2aa297b8c6f11a46714a56b1e6ef5ebda50a4f5d7c5fb/multidict-6.7.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5c4b9bfc148f5a91be9244d6264c53035c8a0dcd2f51f1c3c6e30e30ebaa1c84", size = 262221, upload-time = "2026-01-26T02:44:36.604Z" }, + { url = "https://files.pythonhosted.org/packages/cd/f7/e8c0d0da0cd1e28d10e624604e1a36bcc3353aaebdfdc3a43c72bc683a12/multidict-6.7.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:401c5a650f3add2472d1d288c26deebc540f99e2fb83e9525007a74cd2116f1d", size = 258664, upload-time = "2026-01-26T02:44:38.008Z" }, { url = "https://files.pythonhosted.org/packages/52/da/151a44e8016dd33feed44f730bd856a66257c1ee7aed4f44b649fb7edeb3/multidict-6.7.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:97891f3b1b3ffbded884e2916cacf3c6fc87b66bb0dde46f7357404750559f33", size = 249490, upload-time = "2026-01-26T02:44:39.386Z" }, { url = "https://files.pythonhosted.org/packages/6d/b3/e6b21c6c4f314bb956016b0b3ef2162590a529b84cb831c257519e7fde44/multidict-6.7.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:c76c4bec1538375dad9d452d246ca5368ad6e1c9039dadcf007ae59c70619ea1", size = 83175, upload-time = "2026-01-26T02:44:44.894Z" }, { url = "https://files.pythonhosted.org/packages/c4/57/a0ed92b23f3a042c36bc4227b72b97eca803f5f1801c1ab77c8a212d455e/multidict-6.7.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e954b24433c768ce78ab7929e84ccf3422e46deb45a4dc9f93438f8217fa2d34", size = 46930, upload-time = "2026-01-26T02:44:47.278Z" }, { url = "https://files.pythonhosted.org/packages/58/18/64f5a795e7677670e872673aca234162514696274597b3708b2c0d276cce/multidict-6.7.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:253282d70d67885a15c8a7716f3a73edf2d635793ceda8173b9ecc21f2fb8292", size = 250031, upload-time = "2026-01-26T02:44:50.544Z" }, + { url = "https://files.pythonhosted.org/packages/c8/ed/e192291dbbe51a8290c5686f482084d31bcd9d09af24f63358c3d42fd284/multidict-6.7.1-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0b4c48648d7649c9335cf1927a8b87fa692de3dcb15faa676c6a6f1f1aabda43", size = 228596, upload-time = "2026-01-26T02:44:51.951Z" }, + { url = "https://files.pythonhosted.org/packages/1e/7e/3562a15a60cf747397e7f2180b0a11dc0c38d9175a650e75fa1b4d325e15/multidict-6.7.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:98bc624954ec4d2c7cb074b8eefc2b5d0ce7d482e410df446414355d158fe4ca", size = 257492, upload-time = "2026-01-26T02:44:53.902Z" }, + { url = "https://files.pythonhosted.org/packages/24/02/7d0f9eae92b5249bb50ac1595b295f10e263dd0078ebb55115c31e0eaccd/multidict-6.7.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1b99af4d9eec0b49927b4402bcbb58dea89d3e0db8806a4086117019939ad3dd", size = 255899, upload-time = "2026-01-26T02:44:55.316Z" }, { url = "https://files.pythonhosted.org/packages/00/e3/9b60ed9e23e64c73a5cde95269ef1330678e9c6e34dd4eb6b431b85b5a10/multidict-6.7.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6aac4f16b472d5b7dc6f66a0d49dd57b0e0902090be16594dc9ebfd3d17c47e7", size = 247970, upload-time = "2026-01-26T02:44:56.783Z" }, { url = "https://files.pythonhosted.org/packages/3e/06/538e58a63ed5cfb0bd4517e346b91da32fde409d839720f664e9a4ae4f9d/multidict-6.7.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:21f830fe223215dffd51f538e78c172ed7c7f60c9b96a2bf05c4848ad49921c3", size = 245060, upload-time = "2026-01-26T02:44:58.195Z" }, + { url = "https://files.pythonhosted.org/packages/b2/2f/d743a3045a97c895d401e9bd29aaa09b94f5cbdf1bd561609e5a6c431c70/multidict-6.7.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f5dd81c45b05518b9aa4da4aa74e1c93d715efa234fd3e8a179df611cc85e5f4", size = 235888, upload-time = "2026-01-26T02:44:59.57Z" }, + { url = "https://files.pythonhosted.org/packages/20/1f/9d2327086bd15da2725ef6aae624208e2ef828ed99892b17f60c344e57ed/multidict-6.7.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c9035dde0f916702850ef66460bc4239d89d08df4d02023a5926e7446724212c", size = 252341, upload-time = "2026-01-26T02:45:02.484Z" }, + { url = "https://files.pythonhosted.org/packages/e8/2c/2a1aa0280cf579d0f6eed8ee5211c4f1730bd7e06c636ba2ee6aafda302e/multidict-6.7.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:af959b9beeb66c822380f222f0e0a1889331597e81f1ded7f374f3ecb0fd6c52", size = 246391, upload-time = "2026-01-26T02:45:03.862Z" }, { url = "https://files.pythonhosted.org/packages/e5/03/7ca022ffc36c5a3f6e03b179a5ceb829be9da5783e6fe395f347c0794680/multidict-6.7.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:41f2952231456154ee479651491e94118229844dd7226541788be783be2b5108", size = 243422, upload-time = "2026-01-26T02:45:05.296Z" }, { url = "https://files.pythonhosted.org/packages/81/08/7036c080d7117f28a4af526d794aab6a84463126db031b007717c1a6676e/multidict-6.7.1-py3-none-any.whl", hash = "sha256:55d97cc6dae627efa6a6e548885712d4864b81110ac76fa4e534c03819fa4a56", size = 12319, upload-time = "2026-01-26T02:46:44.004Z" }, ] @@ -4050,6 +4283,39 @@ dev = [ { name = "pytest-asyncio", specifier = ">=0.25.3" }, ] +[[package]] +name = "nemo-eval-author-plugin" +version = "0.1.0" +source = { editable = "plugins/nemo-eval-author" } +dependencies = [ + { name = "harbor", marker = "(python_full_version >= '3.12' and platform_machine == 'arm64' and sys_platform == 'darwin') or (python_full_version >= '3.12' and platform_machine == 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.12' and platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "httpx", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nemo-insights-plugin", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nemo-platform", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nemo-platform-plugin", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nooa", marker = "(python_full_version >= '3.12' and platform_machine == 'arm64' and sys_platform == 'darwin') or (python_full_version >= '3.12' and platform_machine == 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.12' and platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "opentelemetry-proto", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "protobuf", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "pydantic", extra = ["email"], marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "pyyaml", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "tomlkit", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, +] + +[package.metadata] +requires-dist = [ + { name = "harbor", marker = "python_full_version >= '3.12'", specifier = ">=0.16" }, + { name = "httpx" }, + { name = "nemo-insights-plugin", editable = "plugins/nemo-insights" }, + { name = "nemo-platform", editable = "packages/nemo_platform" }, + { name = "nemo-platform-plugin", editable = "packages/nemo_platform_plugin" }, + { name = "nooa", marker = "python_full_version >= '3.12'", git = "https://github.com/NVIDIA-NeMo/labs-OO-Agents.git?tag=v0.0.6" }, + { name = "opentelemetry-proto", specifier = ">=1.42.1" }, + { name = "protobuf", specifier = ">=6.0.0" }, + { name = "pydantic", specifier = ">=2" }, + { name = "pyyaml", specifier = ">=6.0.3" }, + { name = "tomlkit", specifier = ">=0.13.3" }, +] + [[package]] name = "nemo-evaluator-plugin" version = "0.0.0" @@ -4160,6 +4426,7 @@ source = { editable = "plugins/nemo-experimentalist" } dependencies = [ { name = "harbor", marker = "(python_full_version >= '3.12' and platform_machine == 'arm64' and sys_platform == 'darwin') or (python_full_version >= '3.12' and platform_machine == 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.12' and platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "httpx", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nemo-eval-author-plugin", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-insights-plugin", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-platform", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-platform-plugin", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, @@ -4175,6 +4442,7 @@ dependencies = [ requires-dist = [ { name = "harbor", marker = "python_full_version >= '3.12'", specifier = ">=0.16" }, { name = "httpx" }, + { name = "nemo-eval-author-plugin", editable = "plugins/nemo-eval-author" }, { name = "nemo-insights-plugin", editable = "plugins/nemo-insights" }, { name = "nemo-platform", editable = "packages/nemo_platform" }, { name = "nemo-platform-plugin", editable = "packages/nemo_platform_plugin" }, @@ -8371,20 +8639,32 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/67/41/5aa7fa3b0f4dc6b47dcafc3cea909299c37e40e9972feabc8b6a74e2730d/orjson-3.11.8-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:003646067cc48b7fcab2ae0c562491c9b5d2cbd43f1e5f16d98fd118c5522d34", size = 229229, upload-time = "2026-03-31T16:14:50.424Z" }, { url = "https://files.pythonhosted.org/packages/0a/d7/57e7f2458e0a2c41694f39fc830030a13053a84f837a5b73423dca1f0938/orjson-3.11.8-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:ed193ce51d77a3830cad399a529cd4ef029968761f43ddc549e1bc62b40d88f8", size = 128871, upload-time = "2026-03-31T16:14:51.888Z" }, { url = "https://files.pythonhosted.org/packages/53/4a/e0fdb9430983e6c46e0299559275025075568aad5d21dd606faee3703924/orjson-3.11.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f30491bc4f862aa15744b9738517454f1e46e56c972a2be87d70d727d5b2a8f8", size = 132104, upload-time = "2026-03-31T16:14:53.142Z" }, + { url = "https://files.pythonhosted.org/packages/08/4a/2025a60ff3f5c8522060cda46612d9b1efa653de66ed2908591d8d82f22d/orjson-3.11.8-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6eda5b8b6be91d3f26efb7dc6e5e68ee805bc5617f65a328587b35255f138bf4", size = 130483, upload-time = "2026-03-31T16:14:54.605Z" }, + { url = "https://files.pythonhosted.org/packages/ff/f2/a8238e7734de7cb589fed319857a8025d509c89dc52fdcc88f39c6d03d5a/orjson-3.11.8-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5d8b5231de76c528a46b57010bbd83fb51e056aa0220a372fd5065e978406f1c", size = 146819, upload-time = "2026-03-31T16:14:57.548Z" }, + { url = "https://files.pythonhosted.org/packages/db/10/dbf1e2a3cafea673b1b4350e371877b759060d6018a998643b7040e5de48/orjson-3.11.8-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:58a4a208a6fbfdb7a7327b8f201c6014f189f721fd55d047cafc4157af1bc62a", size = 132846, upload-time = "2026-03-31T16:14:58.91Z" }, { url = "https://files.pythonhosted.org/packages/f8/fc/55e667ec9c85694038fcff00573d221b085d50777368ee3d77f38668bf3c/orjson-3.11.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f8952d6d2505c003e8f0224ff7858d341fa4e33fef82b91c4ff0ef070f2393c", size = 133580, upload-time = "2026-03-31T16:15:00.519Z" }, { url = "https://files.pythonhosted.org/packages/7e/a6/c08c589a9aad0cb46c4831d17de212a2b6901f9d976814321ff8e69e8785/orjson-3.11.8-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0022bb50f90da04b009ce32c512dc1885910daa7cb10b7b0cba4505b16db82a8", size = 142042, upload-time = "2026-03-31T16:15:01.906Z" }, + { url = "https://files.pythonhosted.org/packages/5c/cc/2f78ea241d52b717d2efc38878615fe80425bf2beb6e68c984dde257a766/orjson-3.11.8-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:ff51f9d657d1afb6f410cb435792ce4e1fe427aab23d2fcd727a2876e21d4cb6", size = 423845, upload-time = "2026-03-31T16:15:03.703Z" }, { url = "https://files.pythonhosted.org/packages/90/6c/0fb6e8a24e682e0958d71711ae6f39110e4b9cd8cab1357e2a89cb8e1951/orjson-3.11.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a5c370674ebabe16c6ccac33ff80c62bf8a6e59439f5e9d40c1f5ab8fd2215b7", size = 136425, upload-time = "2026-03-31T16:15:07.052Z" }, { url = "https://files.pythonhosted.org/packages/01/f6/8d58b32ab32d9215973a1688aebd098252ee8af1766c0e4e36e7831f0295/orjson-3.11.8-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:1cd0b77e77c95758f8e1100139844e99f3ccc87e71e6fc8e1c027e55807c549f", size = 229233, upload-time = "2026-03-31T16:15:12.762Z" }, { url = "https://files.pythonhosted.org/packages/a9/8b/2ffe35e71f6b92622e8ea4607bf33ecf7dfb51b3619dcfabfd36cbe2d0a5/orjson-3.11.8-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:6a3d159d5ffa0e3961f353c4b036540996bf8b9697ccc38261c0eac1fd3347a6", size = 128772, upload-time = "2026-03-31T16:15:14.237Z" }, { url = "https://files.pythonhosted.org/packages/27/d2/1f8682ae50d5c6897a563cb96bc106da8c9cb5b7b6e81a52e4cc086679b9/orjson-3.11.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76070a76e9c5ae661e2d9848f216980d8d533e0f8143e6ed462807b242e3c5e8", size = 131946, upload-time = "2026-03-31T16:15:15.607Z" }, + { url = "https://files.pythonhosted.org/packages/52/4b/5500f76f0eece84226e0689cb48dcde081104c2fa6e2483d17ca13685ffb/orjson-3.11.8-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:54153d21520a71a4c82a0dbb4523e468941d549d221dc173de0f019678cf3813", size = 130368, upload-time = "2026-03-31T16:15:17.066Z" }, + { url = "https://files.pythonhosted.org/packages/56/7c/ba7cb871cba1bcd5cd02ee34f98d894c6cea96353ad87466e5aef2429c60/orjson-3.11.8-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:14778ffd0f6896aa613951a7fbf4690229aa7a543cb2bfbe9f358e08aafa9546", size = 146877, upload-time = "2026-03-31T16:15:19.833Z" }, + { url = "https://files.pythonhosted.org/packages/0b/5d/eb9c25fc1386696c6a342cd361c306452c75e0b55e86ad602dd4827a7fd7/orjson-3.11.8-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea56a955056a6d6c550cf18b3348656a9d9a4f02e2d0c02cabf3c73f1055d506", size = 132837, upload-time = "2026-03-31T16:15:21.282Z" }, { url = "https://files.pythonhosted.org/packages/37/87/5ddeb7fc1fbd9004aeccab08426f34c81a5b4c25c7061281862b015fce2b/orjson-3.11.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53a0f57e59a530d18a142f4d4ba6dfc708dc5fdedce45e98ff06b44930a2a48f", size = 133624, upload-time = "2026-03-31T16:15:22.641Z" }, { url = "https://files.pythonhosted.org/packages/22/09/90048793db94ee4b2fcec4ac8e5ddb077367637d6650be896b3494b79bb7/orjson-3.11.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9b48e274f8824567d74e2158199e269597edf00823a1b12b63d48462bbf5123e", size = 141904, upload-time = "2026-03-31T16:15:24.435Z" }, + { url = "https://files.pythonhosted.org/packages/c0/cf/eb284847487821a5d415e54149a6449ba9bfc5872ce63ab7be41b8ec401c/orjson-3.11.8-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:3f262401086a3960586af06c054609365e98407151f5ea24a62893a40d80dbbb", size = 423742, upload-time = "2026-03-31T16:15:26.155Z" }, { url = "https://files.pythonhosted.org/packages/b3/6d/37c2589ba864e582ffe7611643314785c6afb1f83c701654ef05daa8fcc7/orjson-3.11.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:093d489fa039ddade2db541097dbb484999fcc65fc2b0ff9819141e2ab364f25", size = 136485, upload-time = "2026-03-31T16:15:29.749Z" }, { url = "https://files.pythonhosted.org/packages/66/7f/95fba509bb2305fab0073558f1e8c3a2ec4b2afe58ed9fcb7d3b8beafe94/orjson-3.11.8-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:3f23426851d98478c8970da5991f84784a76682213cd50eb73a1da56b95239dc", size = 229180, upload-time = "2026-03-31T16:15:36.426Z" }, { url = "https://files.pythonhosted.org/packages/f6/9d/b237215c743ca073697d759b5503abd2cb8a0d7b9c9e21f524bcf176ab66/orjson-3.11.8-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:ebaed4cef74a045b83e23537b52ef19a367c7e3f536751e355a2a394f8648559", size = 128754, upload-time = "2026-03-31T16:15:38.049Z" }, { url = "https://files.pythonhosted.org/packages/42/3d/27d65b6d11e63f133781425f132807aef793ed25075fec686fc8e46dd528/orjson-3.11.8-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97c8f5d3b62380b70c36ffacb2a356b7c6becec86099b177f73851ba095ef623", size = 131877, upload-time = "2026-03-31T16:15:39.484Z" }, + { url = "https://files.pythonhosted.org/packages/dd/cc/faee30cd8f00421999e40ef0eba7332e3a625ce91a58200a2f52c7fef235/orjson-3.11.8-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:436c4922968a619fb7fef1ccd4b8b3a76c13b67d607073914d675026e911a65c", size = 130361, upload-time = "2026-03-31T16:15:41.274Z" }, + { url = "https://files.pythonhosted.org/packages/9c/7c/ca3a3525aa32ff636ebb1778e77e3587b016ab2edb1b618b36ba96f8f2c0/orjson-3.11.8-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f89b6d0b3a8d81e1929d3ab3d92bbc225688bd80a770c49432543928fe09ac55", size = 146862, upload-time = "2026-03-31T16:15:44.341Z" }, + { url = "https://files.pythonhosted.org/packages/3c/0c/18a9d7f18b5edd37344d1fd5be17e94dc652c67826ab749c6e5948a78112/orjson-3.11.8-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:29c009e7a2ca9ad0ed1376ce20dd692146a5d9fe4310848904b6b4fee5c5c137", size = 132847, upload-time = "2026-03-31T16:15:46.368Z" }, { url = "https://files.pythonhosted.org/packages/23/91/7e722f352ad67ca573cee44de2a58fb810d0f4eb4e33276c6a557979fd8a/orjson-3.11.8-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:705b895b781b3e395c067129d8551655642dfe9437273211d5404e87ac752b53", size = 133637, upload-time = "2026-03-31T16:15:48.123Z" }, { url = "https://files.pythonhosted.org/packages/af/04/32845ce13ac5bd1046ddb02ac9432ba856cc35f6d74dde95864fe0ad5523/orjson-3.11.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:88006eda83858a9fdf73985ce3804e885c2befb2f506c9a3723cdeb5a2880e3e", size = 141906, upload-time = "2026-03-31T16:15:49.626Z" }, + { url = "https://files.pythonhosted.org/packages/02/5e/c551387ddf2d7106d9039369862245c85738b828844d13b99ccb8d61fd06/orjson-3.11.8-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:55120759e61309af7fcf9e961c6f6af3dde5921cdb3ee863ef63fd9db126cae6", size = 423722, upload-time = "2026-03-31T16:15:51.176Z" }, { url = "https://files.pythonhosted.org/packages/18/6d/0dce10b9f6643fdc59d99333871a38fa5a769d8e2fc34a18e5d2bfdee900/orjson-3.11.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:708c95f925a43ab9f34625e45dcdadf09ec8a6e7b664a938f2f8d5650f6c090b", size = 136460, upload-time = "2026-03-31T16:15:54.431Z" }, ] @@ -8396,18 +8676,24 @@ sdist = { url = "https://files.pythonhosted.org/packages/12/0c/f1761e21486942ab9 wheels = [ { url = "https://files.pythonhosted.org/packages/4b/08/8b68f24b18e69d92238aa8f258218e6dfeacf4381d9d07ab8df303f524a9/ormsgpack-1.12.2-cp311-cp311-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:bd5f4bf04c37888e864f08e740c5a573c4017f6fd6e99fa944c5c935fabf2dd9", size = 378266, upload-time = "2026-01-18T20:55:59.876Z" }, { url = "https://files.pythonhosted.org/packages/0d/24/29fc13044ecb7c153523ae0a1972269fcd613650d1fa1a9cec1044c6b666/ormsgpack-1.12.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34d5b28b3570e9fed9a5a76528fc7230c3c76333bc214798958e58e9b79cc18a", size = 203035, upload-time = "2026-01-18T20:55:30.59Z" }, + { url = "https://files.pythonhosted.org/packages/ad/c2/00169fb25dd8f9213f5e8a549dfb73e4d592009ebc85fbbcd3e1dcac575b/ormsgpack-1.12.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3708693412c28f3538fb5a65da93787b6bbab3484f6bc6e935bfb77a62400ae5", size = 210539, upload-time = "2026-01-18T20:55:48.569Z" }, { url = "https://files.pythonhosted.org/packages/1b/33/543627f323ff3c73091f51d6a20db28a1a33531af30873ea90c5ac95a9b5/ormsgpack-1.12.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:43013a3f3e2e902e1d05e72c0f1aeb5bedbb8e09240b51e26792a3c89267e181", size = 212401, upload-time = "2026-01-18T20:56:10.101Z" }, { url = "https://files.pythonhosted.org/packages/e8/5d/f70e2c3da414f46186659d24745483757bcc9adccb481a6eb93e2b729301/ormsgpack-1.12.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7c8b1667a72cbba74f0ae7ecf3105a5e01304620ed14528b2cb4320679d2869b", size = 387082, upload-time = "2026-01-18T20:56:12.047Z" }, + { url = "https://files.pythonhosted.org/packages/c0/d6/06e8dc920c7903e051f30934d874d4afccc9bb1c09dcaf0bc03a7de4b343/ormsgpack-1.12.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:df6961442140193e517303d0b5d7bc2e20e69a879c2d774316125350c4a76b92", size = 482346, upload-time = "2026-01-18T20:56:05.152Z" }, { url = "https://files.pythonhosted.org/packages/66/c4/f337ac0905eed9c393ef990c54565cd33644918e0a8031fe48c098c71dbf/ormsgpack-1.12.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c6a4c34ddef109647c769d69be65fa1de7a6022b02ad45546a69b3216573eb4a", size = 425181, upload-time = "2026-01-18T20:55:37.83Z" }, { url = "https://files.pythonhosted.org/packages/4c/36/16c4b1921c308a92cef3bf6663226ae283395aa0ff6e154f925c32e91ff5/ormsgpack-1.12.2-cp312-cp312-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:7a29d09b64b9694b588ff2f80e9826bdceb3a2b91523c5beae1fab27d5c940e7", size = 378618, upload-time = "2026-01-18T20:55:50.835Z" }, { url = "https://files.pythonhosted.org/packages/c0/68/468de634079615abf66ed13bb5c34ff71da237213f29294363beeeca5306/ormsgpack-1.12.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b39e629fd2e1c5b2f46f99778450b59454d1f901bc507963168985e79f09c5d", size = 203186, upload-time = "2026-01-18T20:56:11.163Z" }, + { url = "https://files.pythonhosted.org/packages/73/a9/d756e01961442688b7939bacd87ce13bfad7d26ce24f910f6028178b2cc8/ormsgpack-1.12.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:958dcb270d30a7cb633a45ee62b9444433fa571a752d2ca484efdac07480876e", size = 210738, upload-time = "2026-01-18T20:56:09.181Z" }, { url = "https://files.pythonhosted.org/packages/7b/ba/795b1036888542c9113269a3f5690ab53dd2258c6fb17676ac4bd44fcf94/ormsgpack-1.12.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58d379d72b6c5e964851c77cfedfb386e474adee4fd39791c2c5d9efb53505cc", size = 212569, upload-time = "2026-01-18T20:56:06.135Z" }, { url = "https://files.pythonhosted.org/packages/6c/aa/bff73c57497b9e0cba8837c7e4bcab584b1a6dbc91a5dd5526784a5030c8/ormsgpack-1.12.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8463a3fc5f09832e67bdb0e2fda6d518dc4281b133166146a67f54c08496442e", size = 387166, upload-time = "2026-01-18T20:55:36.738Z" }, + { url = "https://files.pythonhosted.org/packages/d3/cf/f8283cba44bcb7b14f97b6274d449db276b3a86589bdb363169b51bc12de/ormsgpack-1.12.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:eddffb77eff0bad4e67547d67a130604e7e2dfbb7b0cde0796045be4090f35c6", size = 482498, upload-time = "2026-01-18T20:55:29.626Z" }, { url = "https://files.pythonhosted.org/packages/05/be/71e37b852d723dfcbe952ad04178c030df60d6b78eba26bfd14c9a40575e/ormsgpack-1.12.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fcd55e5f6ba0dbce624942adf9f152062135f991a0126064889f68eb850de0dd", size = 425518, upload-time = "2026-01-18T20:55:49.556Z" }, { url = "https://files.pythonhosted.org/packages/eb/29/bb0eba3288c0449efbb013e9c6f58aea79cf5cb9ee1921f8865f04c1a9d7/ormsgpack-1.12.2-cp313-cp313-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:5ea60cb5f210b1cfbad8c002948d73447508e629ec375acb82910e3efa8ff355", size = 378661, upload-time = "2026-01-18T20:55:57.765Z" }, { url = "https://files.pythonhosted.org/packages/6e/31/5efa31346affdac489acade2926989e019e8ca98129658a183e3add7af5e/ormsgpack-1.12.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3601f19afdbea273ed70b06495e5794606a8b690a568d6c996a90d7255e51c1", size = 203194, upload-time = "2026-01-18T20:56:08.252Z" }, + { url = "https://files.pythonhosted.org/packages/eb/56/d0087278beef833187e0167f8527235ebe6f6ffc2a143e9de12a98b1ce87/ormsgpack-1.12.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:29a9f17a3dac6054c0dce7925e0f4995c727f7c41859adf9b5572180f640d172", size = 210778, upload-time = "2026-01-18T20:55:17.694Z" }, { url = "https://files.pythonhosted.org/packages/1c/a2/072343e1413d9443e5a252a8eb591c2d5b1bffbe5e7bfc78c069361b92eb/ormsgpack-1.12.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39c1bd2092880e413902910388be8715f70b9f15f20779d44e673033a6146f2d", size = 212592, upload-time = "2026-01-18T20:55:32.747Z" }, { url = "https://files.pythonhosted.org/packages/a2/8b/a0da3b98a91d41187a63b02dda14267eefc2a74fcb43cc2701066cf1510e/ormsgpack-1.12.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:50b7249244382209877deedeee838aef1542f3d0fc28b8fe71ca9d7e1896a0d7", size = 387164, upload-time = "2026-01-18T20:55:40.853Z" }, + { url = "https://files.pythonhosted.org/packages/19/bb/6d226bc4cf9fc20d8eb1d976d027a3f7c3491e8f08289a2e76abe96a65f3/ormsgpack-1.12.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:5af04800d844451cf102a59c74a841324868d3f1625c296a06cc655c542a6685", size = 482516, upload-time = "2026-01-18T20:55:42.033Z" }, { url = "https://files.pythonhosted.org/packages/fb/f1/bb2c7223398543dedb3dbf8bb93aaa737b387de61c5feaad6f908841b782/ormsgpack-1.12.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:cec70477d4371cd524534cd16472d8b9cc187e0e3043a8790545a9a9b296c258", size = 425539, upload-time = "2026-01-18T20:55:24.727Z" }, ] @@ -8712,26 +8998,46 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8c/d4/4e2c9aaf7ac2242b9358f98dccd8f90f2605402f5afeff6c578682c2c491/propcache-0.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:60a8fda9644b7dfd5dece8c61d8a85e271cb958075bfc4e01083c148b61a7caf", size = 80208, upload-time = "2025-10-08T19:46:24.597Z" }, { url = "https://files.pythonhosted.org/packages/d3/1d/11605e99ac8ea9435651ee71ab4cb4bf03f0949586246476a25aadfec54a/propcache-0.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6918ecbd897443087a3b7cd978d56546a812517dcaaca51b49526720571fa93e", size = 47647, upload-time = "2025-10-08T19:46:27.304Z" }, { url = "https://files.pythonhosted.org/packages/58/1a/3c62c127a8466c9c843bccb503d40a273e5cc69838805f322e2826509e0d/propcache-0.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3d902a36df4e5989763425a8ab9e98cd8ad5c52c823b34ee7ef307fd50582566", size = 214929, upload-time = "2025-10-08T19:46:28.62Z" }, + { url = "https://files.pythonhosted.org/packages/56/b9/8fa98f850960b367c4b8fe0592e7fc341daa7a9462e925228f10a60cf74f/propcache-0.4.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a9695397f85973bb40427dedddf70d8dc4a44b22f1650dd4af9eedf443d45165", size = 221778, upload-time = "2025-10-08T19:46:30.358Z" }, + { url = "https://files.pythonhosted.org/packages/46/a6/0ab4f660eb59649d14b3d3d65c439421cf2f87fe5dd68591cbe3c1e78a89/propcache-0.4.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2bb07ffd7eaad486576430c89f9b215f9e4be68c4866a96e97db9e97fead85dc", size = 228144, upload-time = "2025-10-08T19:46:32.607Z" }, { url = "https://files.pythonhosted.org/packages/52/6a/57f43e054fb3d3a56ac9fc532bc684fc6169a26c75c353e65425b3e56eef/propcache-0.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd6f30fdcf9ae2a70abd34da54f18da086160e4d7d9251f81f3da0ff84fc5a48", size = 210030, upload-time = "2025-10-08T19:46:33.969Z" }, { url = "https://files.pythonhosted.org/packages/40/e2/27e6feebb5f6b8408fa29f5efbb765cd54c153ac77314d27e457a3e993b7/propcache-0.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fc38cba02d1acba4e2869eef1a57a43dfbd3d49a59bf90dda7444ec2be6a5570", size = 208252, upload-time = "2025-10-08T19:46:35.309Z" }, + { url = "https://files.pythonhosted.org/packages/9e/f8/91c27b22ccda1dbc7967f921c42825564fa5336a01ecd72eb78a9f4f53c2/propcache-0.4.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:67fad6162281e80e882fb3ec355398cf72864a54069d060321f6cd0ade95fe85", size = 202064, upload-time = "2025-10-08T19:46:36.993Z" }, + { url = "https://files.pythonhosted.org/packages/f2/26/7f00bd6bd1adba5aafe5f4a66390f243acab58eab24ff1a08bebb2ef9d40/propcache-0.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f10207adf04d08bec185bae14d9606a1444715bc99180f9331c9c02093e1959e", size = 212429, upload-time = "2025-10-08T19:46:38.398Z" }, + { url = "https://files.pythonhosted.org/packages/84/89/fd108ba7815c1117ddca79c228f3f8a15fc82a73bca8b142eb5de13b2785/propcache-0.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e9b0d8d0845bbc4cfcdcbcdbf5086886bc8157aa963c31c777ceff7846c77757", size = 216727, upload-time = "2025-10-08T19:46:39.732Z" }, { url = "https://files.pythonhosted.org/packages/79/37/3ec3f7e3173e73f1d600495d8b545b53802cbf35506e5732dd8578db3724/propcache-0.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:981333cb2f4c1896a12f4ab92a9cc8f09ea664e9b7dbdc4eff74627af3a11c0f", size = 205097, upload-time = "2025-10-08T19:46:41.025Z" }, { url = "https://files.pythonhosted.org/packages/a2/0f/f17b1b2b221d5ca28b4b876e8bb046ac40466513960646bda8e1853cdfa2/propcache-0.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e153e9cd40cc8945138822807139367f256f89c6810c2634a4f6902b52d3b4e2", size = 80061, upload-time = "2025-10-08T19:46:46.075Z" }, { url = "https://files.pythonhosted.org/packages/0a/b6/5c9a0e42df4d00bfb4a3cbbe5cf9f54260300c88a0e9af1f47ca5ce17ac0/propcache-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f048da1b4f243fc44f205dfd320933a951b8d89e0afd4c7cacc762a8b9165207", size = 47324, upload-time = "2025-10-08T19:46:48.384Z" }, { url = "https://files.pythonhosted.org/packages/9e/d3/6c7ee328b39a81ee877c962469f1e795f9db87f925251efeb0545e0020d0/propcache-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ec17c65562a827bba85e3872ead335f95405ea1674860d96483a02f5c698fa72", size = 225505, upload-time = "2025-10-08T19:46:50.055Z" }, + { url = "https://files.pythonhosted.org/packages/01/5d/1c53f4563490b1d06a684742cc6076ef944bc6457df6051b7d1a877c057b/propcache-0.4.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:405aac25c6394ef275dee4c709be43745d36674b223ba4eb7144bf4d691b7367", size = 230242, upload-time = "2025-10-08T19:46:51.815Z" }, + { url = "https://files.pythonhosted.org/packages/20/e1/ce4620633b0e2422207c3cb774a0ee61cac13abc6217763a7b9e2e3f4a12/propcache-0.4.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0013cb6f8dde4b2a2f66903b8ba740bdfe378c943c4377a200551ceb27f379e4", size = 238474, upload-time = "2025-10-08T19:46:53.208Z" }, { url = "https://files.pythonhosted.org/packages/46/4b/3aae6835b8e5f44ea6a68348ad90f78134047b503765087be2f9912140ea/propcache-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15932ab57837c3368b024473a525e25d316d8353016e7cc0e5ba9eb343fbb1cf", size = 221575, upload-time = "2025-10-08T19:46:54.511Z" }, { url = "https://files.pythonhosted.org/packages/6e/a5/8a5e8678bcc9d3a1a15b9a29165640d64762d424a16af543f00629c87338/propcache-0.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:031dce78b9dc099f4c29785d9cf5577a3faf9ebf74ecbd3c856a7b92768c3df3", size = 216736, upload-time = "2025-10-08T19:46:56.212Z" }, + { url = "https://files.pythonhosted.org/packages/f1/63/b7b215eddeac83ca1c6b934f89d09a625aa9ee4ba158338854c87210cc36/propcache-0.4.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ab08df6c9a035bee56e31af99be621526bd237bea9f32def431c656b29e41778", size = 213019, upload-time = "2025-10-08T19:46:57.595Z" }, + { url = "https://files.pythonhosted.org/packages/57/74/f580099a58c8af587cac7ba19ee7cb418506342fbbe2d4a4401661cca886/propcache-0.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4d7af63f9f93fe593afbf104c21b3b15868efb2c21d07d8732c0c4287e66b6a6", size = 220376, upload-time = "2025-10-08T19:46:59.067Z" }, + { url = "https://files.pythonhosted.org/packages/c4/ee/542f1313aff7eaf19c2bb758c5d0560d2683dac001a1c96d0774af799843/propcache-0.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cfc27c945f422e8b5071b6e93169679e4eb5bf73bbcbf1ba3ae3a83d2f78ebd9", size = 226988, upload-time = "2025-10-08T19:47:00.544Z" }, { url = "https://files.pythonhosted.org/packages/8f/18/9c6b015dd9c6930f6ce2229e1f02fb35298b847f2087ea2b436a5bfa7287/propcache-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:35c3277624a080cc6ec6f847cbbbb5b49affa3598c4535a0a4682a697aaa5c75", size = 215615, upload-time = "2025-10-08T19:47:01.968Z" }, { url = "https://files.pythonhosted.org/packages/bf/df/6d9c1b6ac12b003837dde8a10231a7344512186e87b36e855bef32241942/propcache-0.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:43eedf29202c08550aac1d14e0ee619b0430aaef78f85864c1a892294fbc28cf", size = 77750, upload-time = "2025-10-08T19:47:07.648Z" }, { url = "https://files.pythonhosted.org/packages/89/a4/92380f7ca60f99ebae761936bc48a72a639e8a47b29050615eef757cb2a7/propcache-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cae65ad55793da34db5f54e4029b89d3b9b9490d8abe1b4c7ab5d4b8ec7ebf74", size = 46308, upload-time = "2025-10-08T19:47:09.982Z" }, { url = "https://files.pythonhosted.org/packages/2d/48/c5ac64dee5262044348d1d78a5f85dd1a57464a60d30daee946699963eb3/propcache-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:333ddb9031d2704a301ee3e506dc46b1fe5f294ec198ed6435ad5b6a085facfe", size = 208182, upload-time = "2025-10-08T19:47:11.319Z" }, + { url = "https://files.pythonhosted.org/packages/c6/0c/cd762dd011a9287389a6a3eb43aa30207bde253610cca06824aeabfe9653/propcache-0.4.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fd0858c20f078a32cf55f7e81473d96dcf3b93fd2ccdb3d40fdf54b8573df3af", size = 211215, upload-time = "2025-10-08T19:47:13.146Z" }, + { url = "https://files.pythonhosted.org/packages/30/3e/49861e90233ba36890ae0ca4c660e95df565b2cd15d4a68556ab5865974e/propcache-0.4.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:678ae89ebc632c5c204c794f8dab2837c5f159aeb59e6ed0539500400577298c", size = 218112, upload-time = "2025-10-08T19:47:14.913Z" }, { url = "https://files.pythonhosted.org/packages/f1/8b/544bc867e24e1bd48f3118cecd3b05c694e160a168478fa28770f22fd094/propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d472aeb4fbf9865e0c6d622d7f4d54a4e101a89715d8904282bb5f9a2f476c3f", size = 204442, upload-time = "2025-10-08T19:47:16.277Z" }, { url = "https://files.pythonhosted.org/packages/50/a6/4282772fd016a76d3e5c0df58380a5ea64900afd836cec2c2f662d1b9bb3/propcache-0.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4d3df5fa7e36b3225954fba85589da77a0fe6a53e3976de39caf04a0db4c36f1", size = 199398, upload-time = "2025-10-08T19:47:17.962Z" }, + { url = "https://files.pythonhosted.org/packages/3e/ec/d8a7cd406ee1ddb705db2139f8a10a8a427100347bd698e7014351c7af09/propcache-0.4.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ee17f18d2498f2673e432faaa71698032b0127ebf23ae5974eeaf806c279df24", size = 196920, upload-time = "2025-10-08T19:47:19.355Z" }, + { url = "https://files.pythonhosted.org/packages/f6/6c/f38ab64af3764f431e359f8baf9e0a21013e24329e8b85d2da32e8ed07ca/propcache-0.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:580e97762b950f993ae618e167e7be9256b8353c2dcd8b99ec100eb50f5286aa", size = 203748, upload-time = "2025-10-08T19:47:21.338Z" }, + { url = "https://files.pythonhosted.org/packages/d6/e3/fa846bd70f6534d647886621388f0a265254d30e3ce47e5c8e6e27dbf153/propcache-0.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:501d20b891688eb8e7aa903021f0b72d5a55db40ffaab27edefd1027caaafa61", size = 205877, upload-time = "2025-10-08T19:47:23.059Z" }, { url = "https://files.pythonhosted.org/packages/e2/39/8163fc6f3133fea7b5f2827e8eba2029a0277ab2c5beee6c1db7b10fc23d/propcache-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a0bd56e5b100aef69bd8562b74b46254e7c8812918d3baa700c8a8009b0af66", size = 199437, upload-time = "2025-10-08T19:47:24.445Z" }, { url = "https://files.pythonhosted.org/packages/83/ce/a31bbdfc24ee0dcbba458c8175ed26089cf109a55bbe7b7640ed2470cfe9/propcache-0.4.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:92d1935ee1f8d7442da9c0c4fa7ac20d07e94064184811b685f5c4fada64553b", size = 81451, upload-time = "2025-10-08T19:47:29.445Z" }, { url = "https://files.pythonhosted.org/packages/f4/bf/b1d5e21dbc3b2e889ea4327044fb16312a736d97640fb8b6aa3f9c7b3b65/propcache-0.4.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c0ef0aaafc66fbd87842a3fe3902fd889825646bc21149eafe47be6072725835", size = 48396, upload-time = "2025-10-08T19:47:31.79Z" }, { url = "https://files.pythonhosted.org/packages/f4/04/5b4c54a103d480e978d3c8a76073502b18db0c4bc17ab91b3cb5092ad949/propcache-0.4.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f95393b4d66bfae908c3ca8d169d5f79cd65636ae15b5e7a4f6e67af675adb0e", size = 275950, upload-time = "2025-10-08T19:47:33.481Z" }, + { url = "https://files.pythonhosted.org/packages/b4/c1/86f846827fb969c4b78b0af79bba1d1ea2156492e1b83dea8b8a6ae27395/propcache-0.4.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c07fda85708bc48578467e85099645167a955ba093be0a2dcba962195676e859", size = 273856, upload-time = "2025-10-08T19:47:34.906Z" }, + { url = "https://files.pythonhosted.org/packages/36/1d/fc272a63c8d3bbad6878c336c7a7dea15e8f2d23a544bda43205dfa83ada/propcache-0.4.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:af223b406d6d000830c6f65f1e6431783fc3f713ba3e6cc8c024d5ee96170a4b", size = 280420, upload-time = "2025-10-08T19:47:36.338Z" }, { url = "https://files.pythonhosted.org/packages/07/0c/01f2219d39f7e53d52e5173bcb09c976609ba30209912a0680adfb8c593a/propcache-0.4.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a78372c932c90ee474559c5ddfffd718238e8673c340dc21fe45c5b8b54559a0", size = 263254, upload-time = "2025-10-08T19:47:37.692Z" }, { url = "https://files.pythonhosted.org/packages/2d/18/cd28081658ce597898f0c4d174d4d0f3c5b6d4dc27ffafeef835c95eb359/propcache-0.4.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:564d9f0d4d9509e1a870c920a89b2fec951b44bf5ba7d537a9e7c1ccec2c18af", size = 261205, upload-time = "2025-10-08T19:47:39.659Z" }, + { url = "https://files.pythonhosted.org/packages/7a/71/1f9e22eb8b8316701c2a19fa1f388c8a3185082607da8e406a803c9b954e/propcache-0.4.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:17612831fda0138059cc5546f4d12a2aacfb9e47068c06af35c400ba58ba7393", size = 247873, upload-time = "2025-10-08T19:47:41.084Z" }, + { url = "https://files.pythonhosted.org/packages/4a/65/3d4b61f36af2b4eddba9def857959f1016a51066b4f1ce348e0cf7881f58/propcache-0.4.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:41a89040cb10bd345b3c1a873b2bf36413d48da1def52f268a055f7398514874", size = 262739, upload-time = "2025-10-08T19:47:42.51Z" }, + { url = "https://files.pythonhosted.org/packages/2a/42/26746ab087faa77c1c68079b228810436ccd9a5ce9ac85e2b7307195fd06/propcache-0.4.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e35b88984e7fa64aacecea39236cee32dd9bd8c55f57ba8a75cf2399553f9bd7", size = 263514, upload-time = "2025-10-08T19:47:43.927Z" }, { url = "https://files.pythonhosted.org/packages/94/13/630690fe201f5502d2403dd3cfd451ed8858fe3c738ee88d095ad2ff407b/propcache-0.4.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f8b465489f927b0df505cbe26ffbeed4d6d8a2bbc61ce90eb074ff129ef0ab1", size = 257781, upload-time = "2025-10-08T19:47:45.448Z" }, { url = "https://files.pythonhosted.org/packages/5b/5a/bc7b4a4ef808fa59a816c17b20c4bef6884daebbdf627ff2a161da67da19/propcache-0.4.1-py3-none-any.whl", hash = "sha256:af2a6052aeb6cf17d3e46ee169099044fd8224cbaf75c76a2ef596e8163e2237", size = 13305, upload-time = "2025-10-08T19:49:00.792Z" }, ] @@ -8744,6 +9050,7 @@ sdist = { url = "https://files.pythonhosted.org/packages/66/70/e908e9c5e52ef7c3a wheels = [ { url = "https://files.pythonhosted.org/packages/5c/01/a3c3ed5cd186f39e7880f8303cc51385a198a81469d53d0fdecf1f64d929/protobuf-6.33.6-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:9720e6961b251bde64edfdab7d500725a2af5280f3f4c87e57c0208376aa8c3a", size = 427737, upload-time = "2026-03-18T19:04:51.866Z" }, { url = "https://files.pythonhosted.org/packages/ee/90/b3c01fdec7d2f627b3a6884243ba328c1217ed2d978def5c12dc50d328a3/protobuf-6.33.6-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:e2afbae9b8e1825e3529f88d514754e094278bb95eadc0e199751cdd9a2e82a2", size = 324610, upload-time = "2026-03-18T19:04:53.096Z" }, + { url = "https://files.pythonhosted.org/packages/9b/ca/25afc144934014700c52e05103c2421997482d561f3101ff352e1292fb81/protobuf-6.33.6-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:c96c37eec15086b79762ed265d59ab204dabc53056e3443e702d2681f4b39ce3", size = 339381, upload-time = "2026-03-18T19:04:54.616Z" }, { url = "https://files.pythonhosted.org/packages/16/92/d1e32e3e0d894fe00b15ce28ad4944ab692713f2e7f0a99787405e43533a/protobuf-6.33.6-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:e9db7e292e0ab79dd108d7f1a94fe31601ce1ee3f7b79e0692043423020b0593", size = 323436, upload-time = "2026-03-18T19:04:55.768Z" }, { url = "https://files.pythonhosted.org/packages/c4/72/02445137af02769918a93807b2b7890047c32bfb9f90371cbc12688819eb/protobuf-6.33.6-py3-none-any.whl", hash = "sha256:77179e006c476e69bf8e8ce866640091ec42e1beb80b213c3900006ecfba6901", size = 170656, upload-time = "2026-03-18T19:04:59.826Z" }, ] @@ -8772,18 +9079,30 @@ sdist = { url = "https://files.pythonhosted.org/packages/ac/6c/8767aaa597ba42464 wheels = [ { url = "https://files.pythonhosted.org/packages/4b/34/aa03d327739c1be70e09d01182619aca8ebab5970cd0cfa50dd8b9cec2ac/psycopg2_binary-2.9.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:763c93ef1df3da6d1a90f86ea7f3f806dc06b21c198fa87c3c25504abec9404a", size = 3863957, upload-time = "2025-10-10T11:11:16.932Z" }, { url = "https://files.pythonhosted.org/packages/48/89/3fdb5902bdab8868bbedc1c6e6023a4e08112ceac5db97fc2012060e0c9a/psycopg2_binary-2.9.11-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2e164359396576a3cc701ba8af4751ae68a07235d7a380c631184a611220d9a4", size = 4410955, upload-time = "2025-10-10T11:11:21.21Z" }, + { url = "https://files.pythonhosted.org/packages/ce/24/e18339c407a13c72b336e0d9013fbbbde77b6fd13e853979019a1269519c/psycopg2_binary-2.9.11-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:d57c9c387660b8893093459738b6abddbb30a7eab058b77b0d0d1c7d521ddfd7", size = 4468007, upload-time = "2025-10-10T11:11:24.831Z" }, { url = "https://files.pythonhosted.org/packages/91/7e/b8441e831a0f16c159b5381698f9f7f7ed54b77d57bc9c5f99144cc78232/psycopg2_binary-2.9.11-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2c226ef95eb2250974bf6fa7a842082b31f68385c4f3268370e3f3870e7859ee", size = 4165012, upload-time = "2025-10-10T11:11:29.51Z" }, + { url = "https://files.pythonhosted.org/packages/0d/61/4aa89eeb6d751f05178a13da95516c036e27468c5d4d2509bb1e15341c81/psycopg2_binary-2.9.11-cp311-cp311-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a311f1edc9967723d3511ea7d2708e2c3592e3405677bf53d5c7246753591fbb", size = 3981881, upload-time = "2025-10-30T02:55:07.332Z" }, { url = "https://files.pythonhosted.org/packages/76/a1/2f5841cae4c635a9459fe7aca8ed771336e9383b6429e05c01267b0774cf/psycopg2_binary-2.9.11-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ebb415404821b6d1c47353ebe9c8645967a5235e6d88f914147e7fd411419e6f", size = 3650985, upload-time = "2025-10-10T11:11:34.975Z" }, + { url = "https://files.pythonhosted.org/packages/84/74/4defcac9d002bca5709951b975173c8c2fa968e1a95dc713f61b3a8d3b6a/psycopg2_binary-2.9.11-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f07c9c4a5093258a03b28fab9b4f151aa376989e7f35f855088234e656ee6a94", size = 3296039, upload-time = "2025-10-10T11:11:40.432Z" }, + { url = "https://files.pythonhosted.org/packages/6d/c2/782a3c64403d8ce35b5c50e1b684412cf94f171dc18111be8c976abd2de1/psycopg2_binary-2.9.11-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:00ce1830d971f43b667abe4a56e42c1e2d594b32da4802e44a73bacacb25535f", size = 3043477, upload-time = "2025-10-30T02:55:11.182Z" }, { url = "https://files.pythonhosted.org/packages/c8/31/36a1d8e702aa35c38fc117c2b8be3f182613faa25d794b8aeaab948d4c03/psycopg2_binary-2.9.11-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:cffe9d7697ae7456649617e8bb8d7a45afb71cd13f7ab22af3e5c61f04840908", size = 3345842, upload-time = "2025-10-10T11:11:45.366Z" }, { url = "https://files.pythonhosted.org/packages/27/fa/cae40e06849b6c9a95eb5c04d419942f00d9eaac8d81626107461e268821/psycopg2_binary-2.9.11-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f090b7ddd13ca842ebfe301cd587a76a4cf0913b1e429eb92c1be5dbeb1a19bc", size = 3864509, upload-time = "2025-10-10T11:11:56.452Z" }, { url = "https://files.pythonhosted.org/packages/2d/75/364847b879eb630b3ac8293798e380e441a957c53657995053c5ec39a316/psycopg2_binary-2.9.11-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ab8905b5dcb05bf3fb22e0cf90e10f469563486ffb6a96569e51f897c750a76a", size = 4411159, upload-time = "2025-10-10T11:12:00.49Z" }, + { url = "https://files.pythonhosted.org/packages/6f/a0/567f7ea38b6e1c62aafd58375665a547c00c608a471620c0edc364733e13/psycopg2_binary-2.9.11-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:bf940cd7e7fec19181fdbc29d76911741153d51cab52e5c21165f3262125685e", size = 4468234, upload-time = "2025-10-10T11:12:04.892Z" }, { url = "https://files.pythonhosted.org/packages/30/da/4e42788fb811bbbfd7b7f045570c062f49e350e1d1f3df056c3fb5763353/psycopg2_binary-2.9.11-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fa0f693d3c68ae925966f0b14b8edda71696608039f4ed61b1fe9ffa468d16db", size = 4166236, upload-time = "2025-10-10T11:12:11.674Z" }, + { url = "https://files.pythonhosted.org/packages/3c/94/c1777c355bc560992af848d98216148be5f1be001af06e06fc49cbded578/psycopg2_binary-2.9.11-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a1cf393f1cdaf6a9b57c0a719a1068ba1069f022a59b8b1fe44b006745b59757", size = 3983083, upload-time = "2025-10-30T02:55:15.73Z" }, { url = "https://files.pythonhosted.org/packages/bd/42/c9a21edf0e3daa7825ed04a4a8588686c6c14904344344a039556d78aa58/psycopg2_binary-2.9.11-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ef7a6beb4beaa62f88592ccc65df20328029d721db309cb3250b0aae0fa146c3", size = 3652281, upload-time = "2025-10-10T11:12:17.713Z" }, + { url = "https://files.pythonhosted.org/packages/12/22/dedfbcfa97917982301496b6b5e5e6c5531d1f35dd2b488b08d1ebc52482/psycopg2_binary-2.9.11-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:31b32c457a6025e74d233957cc9736742ac5a6cb196c6b68499f6bb51390bd6a", size = 3298010, upload-time = "2025-10-10T11:12:22.671Z" }, + { url = "https://files.pythonhosted.org/packages/66/ea/d3390e6696276078bd01b2ece417deac954dfdd552d2edc3d03204416c0c/psycopg2_binary-2.9.11-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:edcb3aeb11cb4bf13a2af3c53a15b3d612edeb6409047ea0b5d6a21a9d744b34", size = 3044641, upload-time = "2025-10-30T02:55:19.929Z" }, { url = "https://files.pythonhosted.org/packages/12/9a/0402ded6cbd321da0c0ba7d34dc12b29b14f5764c2fc10750daa38e825fc/psycopg2_binary-2.9.11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:62b6d93d7c0b61a1dd6197d208ab613eb7dcfdcca0a49c42ceb082257991de9d", size = 3347940, upload-time = "2025-10-10T11:12:26.529Z" }, { url = "https://files.pythonhosted.org/packages/62/e1/c2b38d256d0dafd32713e9f31982a5b028f4a3651f446be70785f484f472/psycopg2_binary-2.9.11-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:366df99e710a2acd90efed3764bb1e28df6c675d33a7fb40df9b7281694432ee", size = 3864529, upload-time = "2025-10-10T11:12:36.791Z" }, { url = "https://files.pythonhosted.org/packages/11/32/b2ffe8f3853c181e88f0a157c5fb4e383102238d73c52ac6d93a5c8bffe6/psycopg2_binary-2.9.11-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8c55b385daa2f92cb64b12ec4536c66954ac53654c7f15a203578da4e78105c0", size = 4411242, upload-time = "2025-10-10T11:12:42.388Z" }, + { url = "https://files.pythonhosted.org/packages/10/04/6ca7477e6160ae258dc96f67c371157776564679aefd247b66f4661501a2/psycopg2_binary-2.9.11-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:c0377174bf1dd416993d16edc15357f6eb17ac998244cca19bc67cdc0e2e5766", size = 4468258, upload-time = "2025-10-10T11:12:48.654Z" }, { url = "https://files.pythonhosted.org/packages/3c/7e/6a1a38f86412df101435809f225d57c1a021307dd0689f7a5e7fe83588b1/psycopg2_binary-2.9.11-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5c6ff3335ce08c75afaed19e08699e8aacf95d4a260b495a4a8545244fe2ceb3", size = 4166295, upload-time = "2025-10-10T11:12:52.525Z" }, + { url = "https://files.pythonhosted.org/packages/f2/7d/c07374c501b45f3579a9eb761cbf2604ddef3d96ad48679112c2c5aa9c25/psycopg2_binary-2.9.11-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:84011ba3109e06ac412f95399b704d3d6950e386b7994475b231cf61eec2fc1f", size = 3983133, upload-time = "2025-10-30T02:55:24.329Z" }, { url = "https://files.pythonhosted.org/packages/82/56/993b7104cb8345ad7d4516538ccf8f0d0ac640b1ebd8c754a7b024e76878/psycopg2_binary-2.9.11-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ba34475ceb08cccbdd98f6b46916917ae6eeb92b5ae111df10b544c3a4621dc4", size = 3652383, upload-time = "2025-10-10T11:12:56.387Z" }, + { url = "https://files.pythonhosted.org/packages/2d/ac/eaeb6029362fd8d454a27374d84c6866c82c33bfc24587b4face5a8e43ef/psycopg2_binary-2.9.11-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b31e90fdd0f968c2de3b26ab014314fe814225b6c324f770952f7d38abf17e3c", size = 3298168, upload-time = "2025-10-10T11:13:00.403Z" }, + { url = "https://files.pythonhosted.org/packages/2b/39/50c3facc66bded9ada5cbc0de867499a703dc6bca6be03070b4e3b65da6c/psycopg2_binary-2.9.11-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:d526864e0f67f74937a8fce859bd56c979f5e2ec57ca7c627f5f1071ef7fee60", size = 3044712, upload-time = "2025-10-30T02:55:27.975Z" }, { url = "https://files.pythonhosted.org/packages/9c/8e/b7de019a1f562f72ada81081a12823d3c1590bedc48d7d2559410a2763fe/psycopg2_binary-2.9.11-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:04195548662fa544626c8ea0f06561eb6203f1984ba5b4562764fbeb4c3d14b1", size = 3347549, upload-time = "2025-10-10T11:13:03.971Z" }, ] @@ -8838,21 +9157,27 @@ sdist = { url = "https://files.pythonhosted.org/packages/8e/63/4fbc14810c32d2a88 wheels = [ { url = "https://files.pythonhosted.org/packages/f2/d1/e16b587dc0ebc42916b1caad994bc37fbb19ad2c7e3f5f3a586ba2630c16/py_rust_stemmers-0.1.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:910d87d39ba75da1fe3d65df88b926b4b454ada8d73893cbd36e258a8a648158", size = 272019, upload-time = "2025-02-19T13:55:10.268Z" }, { url = "https://files.pythonhosted.org/packages/41/66/8777f125720acb896b336e6f8153e3ec39754563bc9b89523cfe06ba63da/py_rust_stemmers-0.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:31ff4fb9417cec35907c18a6463e3d5a4941a5aa8401f77fbb4156b3ada69e3f", size = 310547, upload-time = "2025-02-19T13:55:11.521Z" }, + { url = "https://files.pythonhosted.org/packages/f1/f5/b79249c787c59b9ce2c5d007c0a0dc0fc1ecccfcf98a546c131cca55899e/py_rust_stemmers-0.1.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:07b3b8582313ef8a7f544acf2c887f27c3dd48c5ddca028fa0f498de7380e24f", size = 315238, upload-time = "2025-02-19T13:55:13.39Z" }, { url = "https://files.pythonhosted.org/packages/62/4c/c05c266ed74c063ae31dc5633ed63c48eb3b78034afcc80fe755d0cb09e7/py_rust_stemmers-0.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:804944eeb5c5559443d81f30c34d6e83c6292d72423f299e42f9d71b9d240941", size = 324420, upload-time = "2025-02-19T13:55:15.292Z" }, { url = "https://files.pythonhosted.org/packages/7f/65/feb83af28095397466e6e031989ff760cc89b01e7da169e76d4cf16a2252/py_rust_stemmers-0.1.5-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:c52c5c326de78c70cfc71813fa56818d1bd4894264820d037d2be0e805b477bd", size = 324791, upload-time = "2025-02-19T13:55:16.45Z" }, { url = "https://files.pythonhosted.org/packages/20/3e/162be2f9c1c383e66e510218d9d4946c8a84ee92c64f6d836746540e915f/py_rust_stemmers-0.1.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8f374c0f26ef35fb87212686add8dff394bcd9a1364f14ce40fe11504e25e30", size = 488014, upload-time = "2025-02-19T13:55:18.486Z" }, + { url = "https://files.pythonhosted.org/packages/a0/ee/ed09ce6fde1eefe50aa13a8a8533aa7ebe3cc096d1a43155cc71ba28d298/py_rust_stemmers-0.1.5-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:0ae0540453843bc36937abb54fdbc0d5d60b51ef47aa9667afd05af9248e09eb", size = 575581, upload-time = "2025-02-19T13:55:19.669Z" }, { url = "https://files.pythonhosted.org/packages/7b/31/2a48960a072e54d7cc244204d98854d201078e1bb5c68a7843a3f6d21ced/py_rust_stemmers-0.1.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:85944262c248ea30444155638c9e148a3adc61fe51cf9a3705b4055b564ec95d", size = 493269, upload-time = "2025-02-19T13:55:21.532Z" }, { url = "https://files.pythonhosted.org/packages/cb/32/fe1cc3d36a19c1ce39792b1ed151ddff5ee1d74c8801f0e93ff36e65f885/py_rust_stemmers-0.1.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4d62410ada44a01e02974b85d45d82f4b4c511aae9121e5f3c1ba1d0bea9126b", size = 272021, upload-time = "2025-02-19T13:55:25.685Z" }, { url = "https://files.pythonhosted.org/packages/0a/38/b8f94e5e886e7ab181361a0911a14fb923b0d05b414de85f427e773bf445/py_rust_stemmers-0.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b28ef729a4c83c7d9418be3c23c0372493fcccc67e86783ff04596ef8a208cdf", size = 310547, upload-time = "2025-02-19T13:55:26.891Z" }, + { url = "https://files.pythonhosted.org/packages/a9/08/62e97652d359b75335486f4da134a6f1c281f38bd3169ed6ecfb276448c3/py_rust_stemmers-0.1.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a979c3f4ff7ad94a0d4cf566ca7bfecebb59e66488cc158e64485cf0c9a7879f", size = 315237, upload-time = "2025-02-19T13:55:28.116Z" }, { url = "https://files.pythonhosted.org/packages/1c/b9/fc0278432f288d2be4ee4d5cc80fd8013d604506b9b0503e8b8cae4ba1c3/py_rust_stemmers-0.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c3593d895453fa06bf70a7b76d6f00d06def0f91fc253fe4260920650c5e078", size = 324419, upload-time = "2025-02-19T13:55:29.211Z" }, { url = "https://files.pythonhosted.org/packages/6b/5b/74e96eaf622fe07e83c5c389d101540e305e25f76a6d0d6fb3d9e0506db8/py_rust_stemmers-0.1.5-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:96ccc7fd042ffc3f7f082f2223bb7082ed1423aa6b43d5d89ab23e321936c045", size = 324792, upload-time = "2025-02-19T13:55:30.948Z" }, { url = "https://files.pythonhosted.org/packages/4f/f7/b76816d7d67166e9313915ad486c21d9e7da0ac02703e14375bb1cb64b5a/py_rust_stemmers-0.1.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ef18cfced2c9c676e0d7d172ba61c3fab2aa6969db64cc8f5ca33a7759efbefe", size = 488014, upload-time = "2025-02-19T13:55:32.066Z" }, + { url = "https://files.pythonhosted.org/packages/b9/ed/7d9bed02f78d85527501f86a867cd5002d97deb791b9a6b1b45b00100010/py_rust_stemmers-0.1.5-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:541d4b5aa911381e3d37ec483abb6a2cf2351b4f16d5e8d77f9aa2722956662a", size = 575582, upload-time = "2025-02-19T13:55:34.005Z" }, { url = "https://files.pythonhosted.org/packages/93/40/eafd1b33688e8e8ae946d1ef25c4dc93f5b685bd104b9c5573405d7e1d30/py_rust_stemmers-0.1.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ffd946a36e9ac17ca96821963663012e04bc0ee94d21e8b5ae034721070b436c", size = 493267, upload-time = "2025-02-19T13:55:35.294Z" }, { url = "https://files.pythonhosted.org/packages/ed/be/0465dcb3a709ee243d464e89231e3da580017f34279d6304de291d65ccb0/py_rust_stemmers-0.1.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4e308fc7687901f0c73603203869908f3156fa9c17c4ba010a7fcc98a7a1c5f2", size = 272019, upload-time = "2025-02-19T13:55:39.183Z" }, { url = "https://files.pythonhosted.org/packages/ab/b6/76ca5b1f30cba36835938b5d9abee0c130c81833d51b9006264afdf8df3c/py_rust_stemmers-0.1.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f9efc4da5e734bdd00612e7506de3d0c9b7abc4b89d192742a0569d0d1fe749", size = 310545, upload-time = "2025-02-19T13:55:40.339Z" }, + { url = "https://files.pythonhosted.org/packages/56/8f/5be87618cea2fe2e70e74115a20724802bfd06f11c7c43514b8288eb6514/py_rust_stemmers-0.1.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cc2cc8d2b36bc05b8b06506199ac63d437360ae38caefd98cd19e479d35afd42", size = 315236, upload-time = "2025-02-19T13:55:41.55Z" }, { url = "https://files.pythonhosted.org/packages/00/02/ea86a316aee0f0a9d1449ad4dbffff38f4cf0a9a31045168ae8b95d8bdf8/py_rust_stemmers-0.1.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a231dc6f0b2a5f12a080dfc7abd9e6a4ea0909290b10fd0a4620e5a0f52c3d17", size = 324419, upload-time = "2025-02-19T13:55:42.693Z" }, { url = "https://files.pythonhosted.org/packages/2a/fd/1612c22545dcc0abe2f30fc08f30a2332f2224dd536fa1508444a9ca0e39/py_rust_stemmers-0.1.5-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:5845709d48afc8b29e248f42f92431155a3d8df9ba30418301c49c6072b181b0", size = 324794, upload-time = "2025-02-19T13:55:43.896Z" }, { url = "https://files.pythonhosted.org/packages/66/18/8a547584d7edac9e7ac9c7bdc53228d6f751c0f70a317093a77c386c8ddc/py_rust_stemmers-0.1.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e48bfd5e3ce9d223bfb9e634dc1425cf93ee57eef6f56aa9a7120ada3990d4be", size = 488014, upload-time = "2025-02-19T13:55:45.088Z" }, + { url = "https://files.pythonhosted.org/packages/3b/87/4619c395b325e26048a6e28a365afed754614788ba1f49b2eefb07621a03/py_rust_stemmers-0.1.5-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:35d32f6e7bdf6fd90e981765e32293a8be74def807147dea9fdc1f65d6ce382f", size = 575582, upload-time = "2025-02-19T13:55:46.436Z" }, { url = "https://files.pythonhosted.org/packages/98/6e/214f1a889142b7df6d716e7f3fea6c41e87bd6c29046aa57e175d452b104/py_rust_stemmers-0.1.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:191ea8bf922c984631ffa20bf02ef0ad7eec0465baeaed3852779e8f97c7e7a3", size = 493269, upload-time = "2025-02-19T13:55:49.057Z" }, ] @@ -8994,18 +9319,30 @@ sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd2 wheels = [ { url = "https://files.pythonhosted.org/packages/12/44/37e403fd9455708b3b942949e1d7febc02167662bf1a7da5b78ee1ea2842/pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b", size = 1899826, upload-time = "2025-11-04T13:39:32.897Z" }, { url = "https://files.pythonhosted.org/packages/33/7f/1d5cab3ccf44c1935a359d51a8a2a9e1a654b744b5e7f80d41b88d501eec/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a", size = 1917869, upload-time = "2025-11-04T13:39:34.469Z" }, + { url = "https://files.pythonhosted.org/packages/6e/6a/30d94a9674a7fe4f4744052ed6c5e083424510be1e93da5bc47569d11810/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8", size = 2063890, upload-time = "2025-11-04T13:39:36.053Z" }, + { url = "https://files.pythonhosted.org/packages/50/be/76e5d46203fcb2750e542f32e6c371ffa9b8ad17364cf94bb0818dbfb50c/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e", size = 2229740, upload-time = "2025-11-04T13:39:37.753Z" }, + { url = "https://files.pythonhosted.org/packages/d3/ee/fed784df0144793489f87db310a6bbf8118d7b630ed07aa180d6067e653a/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1", size = 2350021, upload-time = "2025-11-04T13:39:40.94Z" }, { url = "https://files.pythonhosted.org/packages/c8/be/8fed28dd0a180dca19e72c233cbf58efa36df055e5b9d90d64fd1740b828/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b", size = 2066378, upload-time = "2025-11-04T13:39:42.523Z" }, { url = "https://files.pythonhosted.org/packages/b8/ba/15d537423939553116dea94ce02f9c31be0fa9d0b806d427e0308ec17145/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284", size = 2146303, upload-time = "2025-11-04T13:39:46.238Z" }, + { url = "https://files.pythonhosted.org/packages/58/7f/0de669bf37d206723795f9c90c82966726a2ab06c336deba4735b55af431/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594", size = 2340355, upload-time = "2025-11-04T13:39:48.002Z" }, { url = "https://files.pythonhosted.org/packages/e5/de/e7482c435b83d7e3c3ee5ee4451f6e8973cff0eb6007d2872ce6383f6398/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e", size = 2319875, upload-time = "2025-11-04T13:39:49.705Z" }, { 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/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/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/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/1f/53/8942f884fa33f50794f119012dc6a1a02ac43a56407adaac20463df8e98f/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c", size = 1930291, upload-time = "2025-11-04T13:42:42.169Z" }, { url = "https://files.pythonhosted.org/packages/79/c8/ecb9ed9cd942bce09fc888ee960b52654fbdbede4ba6c2d6e0d3b1d8b49c/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2", size = 1948632, upload-time = "2025-11-04T13:42:44.564Z" }, @@ -9016,6 +9353,7 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a4/ed/d71fefcb4263df0da6a85b5d8a7508360f2f2e9b3bf5814be9c8bccdccc1/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808", size = 1923865, upload-time = "2025-11-04T13:43:28.763Z" }, { url = "https://files.pythonhosted.org/packages/ce/3a/626b38db460d675f873e4444b4bb030453bbe7b4ba55df821d026a0493c4/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc", size = 2134256, upload-time = "2025-11-04T13:43:31.71Z" }, { url = "https://files.pythonhosted.org/packages/55/4c/162d906b8e3ba3a99354e20faa1b49a85206c47de97a639510a0e673f5da/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84", size = 2143141, upload-time = "2025-11-04T13:43:37.701Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f2/f11dd73284122713f5f89fc940f370d035fa8e1e078d446b3313955157fe/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770", size = 2330317, upload-time = "2025-11-04T13:43:40.406Z" }, { url = "https://files.pythonhosted.org/packages/88/9d/b06ca6acfe4abb296110fb1273a4d848a0bfb2ff65f3ee92127b3244e16b/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f", size = 2316992, upload-time = "2025-11-04T13:43:43.602Z" }, ] @@ -9058,16 +9396,25 @@ sdist = { url = "https://files.pythonhosted.org/packages/14/5b/bb6a8bfdf13eb9808 wheels = [ { url = "https://files.pythonhosted.org/packages/6c/8e/b3946ee663349fb35f9dceddf1aed394b8e5df1d8767b840844db9cee515/pydantic_monty-0.0.18-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:421d1b7956e06a22dc13fe6a34bebf3a1bdde8cf78616eded018f7a9ca746295", size = 8718281, upload-time = "2026-05-29T08:31:06.121Z" }, { url = "https://files.pythonhosted.org/packages/d3/5e/cb242ba7bd63985eee94f0dff8864002bb5ded2da7190e73786ddcd5b4e8/pydantic_monty-0.0.18-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd2b37890d8a948606be184bd6e3fa4e445d26e3c7329c6a451a271bfc470f24", size = 8170676, upload-time = "2026-05-29T08:29:38.358Z" }, + { url = "https://files.pythonhosted.org/packages/f1/8d/d144775ea57b813e97aef9edaf5f867fb82960597af517125e1b513c983c/pydantic_monty-0.0.18-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:19b86e4fc4b73c2c925906bbc31488b9e8b99b54101f8ea7bbdccfd60ded38f0", size = 8585337, upload-time = "2026-05-29T08:31:27.206Z" }, + { url = "https://files.pythonhosted.org/packages/b3/81/6291a4871fbdb8dfa66d1b1f2406c11066757caa1c53092320e5d11ea49d/pydantic_monty-0.0.18-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4de83f38b3658152697c524ea87ce307a13701d807801c9be27870e837829a02", size = 9181594, upload-time = "2026-05-29T08:31:36.736Z" }, + { url = "https://files.pythonhosted.org/packages/d7/00/28879cee77e24f70c756c4603b4a21b013e601b7821bd07e06cf6718b75a/pydantic_monty-0.0.18-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ef3aaa4fb7af8fd84f42df5e1e43d7ff3eae7d81314580462c8ca716e7e6e361", size = 9285193, upload-time = "2026-05-29T08:30:58.727Z" }, { url = "https://files.pythonhosted.org/packages/9f/07/52dece571ef47085d2f1053df4c1be8d5b42d8735da4797f5f79d650f81f/pydantic_monty-0.0.18-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d3dd72c195eca243b08c5d68b1f513124feaf22acb87b73cd8bfcdc3f6b4bb7", size = 9264997, upload-time = "2026-05-29T08:29:49.51Z" }, { url = "https://files.pythonhosted.org/packages/38/12/b010315be2927c5be43d3a4036cd6857d9981d5116efda5e40540f43a014/pydantic_monty-0.0.18-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6b0409a5f314af54f704d73cd97fe2a0cc8ef2635952bf57c5879b9313281614", size = 8350432, upload-time = "2026-05-29T08:30:10.984Z" }, { url = "https://files.pythonhosted.org/packages/84/8b/9674a90269dc0f1a080e606cba642b256e138e7fcee1a3a0b55969946f83/pydantic_monty-0.0.18-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:790f169bb5700e3ab24a8d44fd7016d915c701e27bcd2feabe0de917306bbff0", size = 8900736, upload-time = "2026-05-29T08:29:42.508Z" }, { url = "https://files.pythonhosted.org/packages/55/83/8ccf04b2f9642153702c6eb22d0a0abad57014fd85879ab1f6341b5a1946/pydantic_monty-0.0.18-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2988d3e511131680d9de60647bfe5c697b1e4e4cad474fecf1451c53314e8520", size = 8688756, upload-time = "2026-05-29T08:30:24.677Z" }, { url = "https://files.pythonhosted.org/packages/de/b8/c7881620a812850772ae0924863d1399cbecb3e4c8c455a9c7a9c20b06f8/pydantic_monty-0.0.18-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5c688dc7c7b28a2f389a61217bae1d50658e28f960abe33a254328cadc8a17a", size = 8171342, upload-time = "2026-05-29T08:29:44.773Z" }, + { url = "https://files.pythonhosted.org/packages/d3/ea/6d10ea1657e303295a75a3854f6dd6b378cbd501dcd1782844107b932acd/pydantic_monty-0.0.18-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f469293f5a776231b9617787a5dd6c58048c6568833ee6848338be6391f15449", size = 8591152, upload-time = "2026-05-29T08:31:29.944Z" }, + { url = "https://files.pythonhosted.org/packages/93/fb/ab85c4676ccffd0f3b7f509a4c8b396b07c7860577def2f58a22b3fe8aef/pydantic_monty-0.0.18-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e6e0cd4991947b8a47210985836f94c14139bc3ea06253d2f38d0d752b165517", size = 9183064, upload-time = "2026-05-29T08:31:12.776Z" }, + { url = "https://files.pythonhosted.org/packages/5c/12/11292178b487052f9e0a1ea7b3d17e1e3bfcba598fefce8cb9ed8712021e/pydantic_monty-0.0.18-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:58b4b96863abbc0ffa5baf64779b3fbb6376cc763488b027ecaddb5052d5ff17", size = 9285440, upload-time = "2026-05-29T08:29:35.642Z" }, { url = "https://files.pythonhosted.org/packages/79/42/7afb8dde4414d84c042f2cc1b0870a7351cae2e4fbf3fef89b3aa683eca9/pydantic_monty-0.0.18-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1208bd5976c1254b2705559836511c86ea3cc51d9c6f688b1e3e22984715dbc", size = 9233438, upload-time = "2026-05-29T08:31:39.177Z" }, { url = "https://files.pythonhosted.org/packages/5f/46/89124cf146725e354b44685b477da6b0b5dc07a8a3af2aec309e88c55405/pydantic_monty-0.0.18-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:977168253d8f6b49bb64f128d02c4b62ee8b435fd62876268377e1f2b00cc00f", size = 8351900, upload-time = "2026-05-29T08:31:08.348Z" }, { url = "https://files.pythonhosted.org/packages/00/c5/dda512f5a9c68242faea368844aacefb54c2a13f9b40bee5ab48ccdc78c5/pydantic_monty-0.0.18-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c21319a091dc1ff1fccb8647dae5bb543b3f528c556319ed15c7992dfa9b5e87", size = 8901559, upload-time = "2026-05-29T08:29:26.047Z" }, { url = "https://files.pythonhosted.org/packages/c6/9c/7628423f955efb669d2cc1d3a8909bf8271b543ce27036e18229ad0e51e8/pydantic_monty-0.0.18-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d47976a18e3e3da0e86f8cf6068fc8a125930422dc10d3d3bf0b5410a9e9282e", size = 8689116, upload-time = "2026-05-29T08:29:30.906Z" }, { url = "https://files.pythonhosted.org/packages/c2/9c/51f8ffa4340bc1986eb9240b0756724f5fdf3c463d6d66c8cc8450e1446d/pydantic_monty-0.0.18-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb84fe51e3f6e00a0cc9628e0acf5904d982c8cc85d4db42b7532d071602f703", size = 8178458, upload-time = "2026-05-29T08:30:09.015Z" }, + { url = "https://files.pythonhosted.org/packages/c9/ec/7eb84aeb86631571f9acffc91552217dc2b524b00db37b8d10517df467d1/pydantic_monty-0.0.18-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a2e86f3ba67b094d498bc8b071d5e3b8b034bb9f3006216a357c5f71d49d6132", size = 8591295, upload-time = "2026-05-29T08:29:23.357Z" }, + { url = "https://files.pythonhosted.org/packages/da/69/d5210208fa116593bd81789e2e5abb6222d38087c9c1879e18f7e7620275/pydantic_monty-0.0.18-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb8a412aa0336d4e0334a4e05a54b391d91fa334020bd295a280285ba17ab5ca", size = 9184647, upload-time = "2026-05-29T08:29:51.852Z" }, + { url = "https://files.pythonhosted.org/packages/ed/74/4d95c8f65072964c4cb798dbe87d2e1c1349607ab5905874bfa8a0b94de1/pydantic_monty-0.0.18-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1056ce3acef60ab880314caf97775c5ea41b30f9306d0dd28cefeffd42dda366", size = 9291637, upload-time = "2026-05-29T08:31:19.966Z" }, { url = "https://files.pythonhosted.org/packages/d0/40/5817780313a3e089ca6f860fbdc836d3aa33790eb72c2e8fe2edc877820e/pydantic_monty-0.0.18-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9593caa45b68fd07ac67bea9974effbe2a1c5453d8a106b913596b0dff6d8471", size = 9233863, upload-time = "2026-05-29T08:31:42.838Z" }, { url = "https://files.pythonhosted.org/packages/b3/55/f77565c5797502c7ba995dc23a26759cb33023590f9f2926bc4e8ab87afe/pydantic_monty-0.0.18-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:e15e18ed27a17ee607ad3bcbf82f25a9ec4d496ff493ff64cdabb83ca2174cec", size = 8358264, upload-time = "2026-05-29T08:31:32.531Z" }, { url = "https://files.pythonhosted.org/packages/1b/1f/c700eb800868d1be4078a99cb00e23fb7e5d8760c8e83b729bba27b5bf92/pydantic_monty-0.0.18-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:96d75a418d96640ff0c7f354a78fc4470a2d0f40ba69e886c2f32756d594d9a0", size = 8906664, upload-time = "2026-05-29T08:30:04.055Z" }, @@ -9434,16 +9781,19 @@ sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd77 wheels = [ { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" }, { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" }, + { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" }, { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" }, { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" }, { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" }, { url = "https://files.pythonhosted.org/packages/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/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" }, @@ -9457,11 +9807,13 @@ sdist = { url = "https://files.pythonhosted.org/packages/5e/eb/5a0d575de784f9a1f wheels = [ { url = "https://files.pythonhosted.org/packages/ad/c5/a3d2020ce5ccfc6aede0d45bcb870298652ac0cf199f67714d250e0cdf39/pyyaml_ft-8.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:30c5f1751625786c19de751e3130fc345ebcba6a86f6bddd6e1285342f4bbb69", size = 176146, upload-time = "2025-06-10T15:31:50.584Z" }, { url = "https://files.pythonhosted.org/packages/e3/bb/23a9739291086ca0d3189eac7cd92b4d00e9fdc77d722ab610c35f9a82ba/pyyaml_ft-8.0.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3fa992481155ddda2e303fcc74c79c05eddcdbc907b888d3d9ce3ff3e2adcfb0", size = 746792, upload-time = "2025-06-10T15:31:52.304Z" }, + { url = "https://files.pythonhosted.org/packages/5f/c2/e8825f4ff725b7e560d62a3609e31d735318068e1079539ebfde397ea03e/pyyaml_ft-8.0.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cec6c92b4207004b62dfad1f0be321c9f04725e0f271c16247d8b39c3bf3ea42", size = 786772, upload-time = "2025-06-10T15:31:54.712Z" }, { url = "https://files.pythonhosted.org/packages/35/be/58a4dcae8854f2fdca9b28d9495298fd5571a50d8430b1c3033ec95d2d0e/pyyaml_ft-8.0.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06237267dbcab70d4c0e9436d8f719f04a51123f0ca2694c00dd4b68c338e40b", size = 778723, upload-time = "2025-06-10T15:31:56.093Z" }, { url = "https://files.pythonhosted.org/packages/86/ed/fed0da92b5d5d7340a082e3802d84c6dc9d5fa142954404c41a544c1cb92/pyyaml_ft-8.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8a7f332bc565817644cdb38ffe4739e44c3e18c55793f75dddb87630f03fc254", size = 758478, upload-time = "2025-06-10T15:31:58.314Z" }, { url = "https://files.pythonhosted.org/packages/f0/69/ac02afe286275980ecb2dcdc0156617389b7e0c0a3fcdedf155c67be2b80/pyyaml_ft-8.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7d10175a746be65f6feb86224df5d6bc5c049ebf52b89a88cf1cd78af5a367a8", size = 799159, upload-time = "2025-06-10T15:31:59.675Z" }, { url = "https://files.pythonhosted.org/packages/0f/16/2710c252ee04cbd74d9562ebba709e5a284faeb8ada88fcda548c9191b47/pyyaml_ft-8.0.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8d445bf6ea16bb93c37b42fdacfb2f94c8e92a79ba9e12768c96ecde867046d1", size = 182879, upload-time = "2025-06-10T15:32:04.466Z" }, { url = "https://files.pythonhosted.org/packages/9a/40/ae8163519d937fa7bfa457b6f78439cc6831a7c2b170e4f612f7eda71815/pyyaml_ft-8.0.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c56bb46b4fda34cbb92a9446a841da3982cdde6ea13de3fbd80db7eeeab8b49", size = 811277, upload-time = "2025-06-10T15:32:06.214Z" }, + { url = "https://files.pythonhosted.org/packages/f9/66/28d82dbff7f87b96f0eeac79b7d972a96b4980c1e445eb6a857ba91eda00/pyyaml_ft-8.0.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dab0abb46eb1780da486f022dce034b952c8ae40753627b27a626d803926483b", size = 831650, upload-time = "2025-06-10T15:32:08.076Z" }, { url = "https://files.pythonhosted.org/packages/e8/df/161c4566facac7d75a9e182295c223060373d4116dead9cc53a265de60b9/pyyaml_ft-8.0.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd48d639cab5ca50ad957b6dd632c7dd3ac02a1abe0e8196a3c24a52f5db3f7a", size = 815755, upload-time = "2025-06-10T15:32:09.435Z" }, { url = "https://files.pythonhosted.org/packages/05/10/f42c48fa5153204f42eaa945e8d1fd7c10d6296841dcb2447bf7da1be5c4/pyyaml_ft-8.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:052561b89d5b2a8e1289f326d060e794c21fa068aa11255fe71d65baf18a632e", size = 810403, upload-time = "2025-06-10T15:32:11.051Z" }, { url = "https://files.pythonhosted.org/packages/d5/d2/e369064aa51009eb9245399fd8ad2c562bd0bcd392a00be44b2a824ded7c/pyyaml_ft-8.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:3bb4b927929b0cb162fb1605392a321e3333e48ce616cdcfa04a839271373255", size = 835581, upload-time = "2025-06-10T15:32:12.897Z" }, @@ -9561,26 +9913,50 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c2/dc/c1f2df4027e82fc54b5a473e4b250f5139faca49a0fbe29a48668d228f34/regex-2026.5.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ccf5249114cc3e772ecdd88a98a86eca0fd74c61ce32a94743758c083fc05d48", size = 489445, upload-time = "2026-05-09T23:12:06.111Z" }, { url = "https://files.pythonhosted.org/packages/58/b6/14b2c84ff90ddb370c81d27503f4a0fcf071496416f4855f6cc8c5d81c35/regex-2026.5.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ef31cbfe458e21c6122ba8150ff060e0c7789ed0d26eb423f25472584920b555", size = 289212, upload-time = "2026-05-09T23:12:09.266Z" }, { url = "https://files.pythonhosted.org/packages/03/d0/4db86529117320de0c84afd90e70bb47434625875e34fcef9d8c127c5b16/regex-2026.5.9-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:992604d02e6d9c6d786c24a706a71ecffe1020fc1ef264044474cd81fa2c3919", size = 792310, upload-time = "2026-05-09T23:12:11.416Z" }, + { url = "https://files.pythonhosted.org/packages/07/78/fe4800cd322f862ecffd2d553409b20d80650e5ed71b9d178f853d020b82/regex-2026.5.9-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c9411dd64ca95477225734a93dfc8583b51916b8d5942f99d6cac21e09965451", size = 861721, upload-time = "2026-05-09T23:12:13.681Z" }, + { url = "https://files.pythonhosted.org/packages/b5/d0/b3618a895dd8feb897c61bb2954edd265e1767d82a01d53065d5871127a3/regex-2026.5.9-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3dd4a3ff360dfb836fecdb93a4598f9d6e2ac81e3e397125145c6221bf58cf4c", size = 906460, upload-time = "2026-05-09T23:12:15.443Z" }, { url = "https://files.pythonhosted.org/packages/33/6f/1481597e859ef19508b345eec4afd1416ed6e6b459c75a64026ef193aecf/regex-2026.5.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2a661a7d270a61f7cf460caee8b9fa2d5ef9e5c681234bcb9e0fe14f488e7dfc", size = 799843, upload-time = "2026-05-09T23:12:16.892Z" }, + { url = "https://files.pythonhosted.org/packages/73/59/955734c803f59108deccba3597ae440c76b62a652733c0006e6243758420/regex-2026.5.9-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f079e50a0d3cc3cd5091fa9ff45869a2e6b2cd35895731edafb0327901a8d86d", size = 773610, upload-time = "2026-05-09T23:12:19.127Z" }, { url = "https://files.pythonhosted.org/packages/68/8f/70c04a236d651c81881dac42ef8538bddda6121434509d0a22d9e601503b/regex-2026.5.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4ebe8f0b5ec5a5024dc4a4c59f444c4e9afc5f2abdbb8962065b75d27fb971f9", size = 781645, upload-time = "2026-05-09T23:12:20.806Z" }, + { url = "https://files.pythonhosted.org/packages/1d/96/05c7434d88185e5d27fe54aeb74df86bd77cd79f52f0b4eae54faa8fea70/regex-2026.5.9-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:97cf3bc1b7d7d2306772ec07366c80d9df00ff79e79cea32898883a646d2fae2", size = 854473, upload-time = "2026-05-09T23:12:22.465Z" }, + { url = "https://files.pythonhosted.org/packages/4e/c1/6e3d8202d981f3117004bf341ee74893ba4ba8a9fbaf4b94615846550a08/regex-2026.5.9-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0f9eede6a5cbdc02d4978090186390936e1776a7d1359b21e41014c609880bcf", size = 763311, upload-time = "2026-05-09T23:12:24.351Z" }, + { url = "https://files.pythonhosted.org/packages/93/c7/e7737f1526b3fb32bd4c337fd6c71c3ebb5c8296fc34d11197e0955d2e35/regex-2026.5.9-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:01f0f5f55f4b64dacec85dc116d3c05fd23ad3ff037bbc73a2085775953c2611", size = 844593, upload-time = "2026-05-09T23:12:26.341Z" }, { url = "https://files.pythonhosted.org/packages/a5/27/0daffb1a535bb39f422c3d200f4ab023c71110ad66a32b366bee708baba0/regex-2026.5.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1268eddd8486dc561d08eee1156e40aa3a8fe10f4bdec8fa653b455fcbffd12c", size = 789167, upload-time = "2026-05-09T23:12:27.975Z" }, { url = "https://files.pythonhosted.org/packages/50/9b/6550044bc44e17c84d312c031c2ec42fbdb6a4ec4e29093be3a172d08772/regex-2026.5.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:57eeeb05db7979413dec5438f2db21d7ecbba787cde7a711df1a6f6df672aa06", size = 490451, upload-time = "2026-05-09T23:12:34.72Z" }, { url = "https://files.pythonhosted.org/packages/54/4b/ee27938d1b2c443e89a9a10e00d2d19aa5ee300cd3d61140644e93bb083e/regex-2026.5.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f7a7c26137296beba7784de6eba69c6a93a63ccebc385e4962fe67e267a91225", size = 289599, upload-time = "2026-05-09T23:12:38.089Z" }, { url = "https://files.pythonhosted.org/packages/d8/dd/ba103dc19614e25f3880800ca67ce093d6e21b325d72b8383c7bf906e9fa/regex-2026.5.9-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6441cc660d76107934a09c22167200839a0e89604a6297f78a974e66e931d2c0", size = 796732, upload-time = "2026-05-09T23:12:40.062Z" }, + { url = "https://files.pythonhosted.org/packages/cf/e7/f035b4fd858b050b0080bf302968dc0f59ba34e391872d54936758e6844e/regex-2026.5.9-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:91328f1c23d47595ca3ef0a7557fa129c5a23404b775c770697d2f35b33e0107", size = 865440, upload-time = "2026-05-09T23:12:42.059Z" }, + { url = "https://files.pythonhosted.org/packages/0a/51/8cd301ecc899aea28124357f729f4272f44de7806fc7ca02490bfbe253e8/regex-2026.5.9-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:93a7860539414dddaefba2b40f8771765ae17949d4c7182b876ce429e11a8309", size = 912329, upload-time = "2026-05-09T23:12:44.373Z" }, { url = "https://files.pythonhosted.org/packages/cc/1e/3fbe2fa1e8cebd62f3bb7d3321cff1640aca2e240b51d9bd624aad949260/regex-2026.5.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd2810d22146b6d838acc5ec15602cb6b47920aa4e33015df3868eedfd20bab8", size = 801239, upload-time = "2026-05-09T23:12:46.268Z" }, + { url = "https://files.pythonhosted.org/packages/17/2f/6f6008682bf2cf98040a0d3153a8e557b6ab728d7713d045cee4ce544ab8/regex-2026.5.9-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:daff2bdbaf1d23e52fdff7c0b7bc2048b68f978df6a4d107ac981f94caef2e66", size = 777054, upload-time = "2026-05-09T23:12:48.051Z" }, { url = "https://files.pythonhosted.org/packages/19/2b/eee0d20a6842ba04df4b8847a920b57ef56853f14ef85405473e586b605a/regex-2026.5.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4eeb011098fcb77af513dcef521a3dbecbf8849b1e38940759d293b7a93f5026", size = 785098, upload-time = "2026-05-09T23:12:49.851Z" }, + { url = "https://files.pythonhosted.org/packages/4a/98/6fc1e6410feefb92159edaed5041992bfe390e8d26c721865434acbca558/regex-2026.5.9-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:ea9c8ecfa1b73c73b626534d6626e5340d429630943672b8480724f44e84b962", size = 860095, upload-time = "2026-05-09T23:12:51.666Z" }, + { url = "https://files.pythonhosted.org/packages/18/a3/bd855e0f2cb1a978ecf6fa6bb69632dd9c3f6ea3b81cde62fde14c9daec7/regex-2026.5.9-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:cd2846168eb9ee3c513902bc8225409cb1caab31d04728b145171fa1625d9621", size = 765762, upload-time = "2026-05-09T23:12:53.413Z" }, + { url = "https://files.pythonhosted.org/packages/dc/66/0ae8c092e60b14c79d24f8e0b7f0aea5bfbffdcab00b5483d13404d3c3a5/regex-2026.5.9-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:39617fb0cde9c0e6306dc70e3bfc096f3da793219879f7ae7aa341a69fbdcf6d", size = 852100, upload-time = "2026-05-09T23:12:55.256Z" }, { url = "https://files.pythonhosted.org/packages/21/de/8dfde60fc1b21c946a893ba273403b72617edb261370cb1087099a83f088/regex-2026.5.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fd03c4f0e33280d15cae17159b899245d6b7c53d21def19b263b39655061f5ce", size = 789479, upload-time = "2026-05-09T23:12:57.573Z" }, { url = "https://files.pythonhosted.org/packages/aa/da/797e91ecec6f84135da778ddce78c20e0af5d2a15c26f87a81bc3eadb6db/regex-2026.5.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d626b84406444b165fc0ba981604edea39f0588ff1f92baa23fe50799ea9afdb", size = 490303, upload-time = "2026-05-09T23:13:04.382Z" }, { url = "https://files.pythonhosted.org/packages/2d/e7/d0eaf5713828417b9e5648cf81fa9bacd4961f6ab98c380c2034f8716e35/regex-2026.5.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a8820737949116ffff55fe18f9fc644530063ba6ebfcb8314239416e78f1347c", size = 289468, upload-time = "2026-05-09T23:13:08.214Z" }, { url = "https://files.pythonhosted.org/packages/d3/9b/b3fdd62b003baa1a9b593cd8c8699c9651c2e80cc21a5c715707983c42d7/regex-2026.5.9-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa0fbdbac82cb3e4450d0ccde7d7a35607f4cb2dd9fba4b8b69bfaf8c9fa6aed", size = 796749, upload-time = "2026-05-09T23:13:10.573Z" }, + { url = "https://files.pythonhosted.org/packages/d4/30/66ab84588765f5b4b271a9ca09ef7ce2b87caa95176ec3d2ad65d7bc4902/regex-2026.5.9-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:57e8915c7986aa33d25e4d3629cef711cd2863f2961b10409f0c04cb8b7d9020", size = 865445, upload-time = "2026-05-09T23:13:12.523Z" }, + { url = "https://files.pythonhosted.org/packages/1a/89/f05169e8588aac365f35ffc7f3bc3184f095ef4cfded7cfaa3c7fd5dbd89/regex-2026.5.9-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:508f56a89ba9cb26e4168cbc37dbd60a28d82430a9e18ad1d25fe0883c314ca2", size = 912322, upload-time = "2026-05-09T23:13:14.281Z" }, { url = "https://files.pythonhosted.org/packages/30/e1/c93444052cf41581f3c884ab3fb5823daf0992f11cd4388d4275ca610558/regex-2026.5.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b6d189041f15691cfa2b6c4290448ec221244d225b3f5fe9e7771b34ffcdf6e2", size = 801269, upload-time = "2026-05-09T23:13:16.569Z" }, + { url = "https://files.pythonhosted.org/packages/50/fe/0cf96b882f540e62e8b9956599798203d599c44cf4c77917ca27400ff69b/regex-2026.5.9-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e82db382b44d0111b22601c509c89f64434816c9e0eef9d1989cda8cc6ff1c04", size = 777085, upload-time = "2026-05-09T23:13:18.675Z" }, { url = "https://files.pythonhosted.org/packages/23/5c/d78d4924e7fc875557b9e9b768423925fdfaac5549d06da7810019a9bd26/regex-2026.5.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2acfb48634f64996b57f90f39afa692ff362162722581921fe92239a59960f3c", size = 785153, upload-time = "2026-05-09T23:13:20.525Z" }, + { url = "https://files.pythonhosted.org/packages/bf/e0/5214774090e7b4524dcea3e3c4aa74141d43043f8beb49c1599db1c8b53a/regex-2026.5.9-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d29eebfc9525db68cad3c97eedd7f754fa265aa5cd0cf4f863b2421e1b48fc9f", size = 860164, upload-time = "2026-05-09T23:13:22.263Z" }, + { url = "https://files.pythonhosted.org/packages/6e/e1/4a57a83350319b1271f0d7a249b8672513ed928b237a741631270de6caea/regex-2026.5.9-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:debb893095e944091c16e641a6e33c1b0f4cb61ab945ec5afbf53ce7068834d8", size = 765731, upload-time = "2026-05-09T23:13:24.277Z" }, + { url = "https://files.pythonhosted.org/packages/12/f4/499e74a20c156fc75836ee04a72a38d1a063978f600937f9760467beb1b0/regex-2026.5.9-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d659eee77986549c9ea45b861c7567e44d6287c3dc9a4565478853f7b9fe2ff6", size = 852062, upload-time = "2026-05-09T23:13:26.125Z" }, { url = "https://files.pythonhosted.org/packages/5b/92/7eebc0d0a01e78629695f342ba17e0deaff8fb45e79cc0d7b98287da6e3e/regex-2026.5.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2efa205e6d98b24d1f3ab395c11aa15cdf10935bca283d0285e0499c284fba21", size = 789577, upload-time = "2026-05-09T23:13:27.814Z" }, { url = "https://files.pythonhosted.org/packages/e8/e9/d21346f7b60ed58789371358ed66b09d00f832e1bd7c06e55d9da5679882/regex-2026.5.9-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:01f28d868834624c934b8d2e0aa1c8341337e37831f4a012f18a5afcba4cbaf3", size = 494172, upload-time = "2026-05-09T23:13:35.935Z" }, { url = "https://files.pythonhosted.org/packages/f2/7d/9fbf919768368d3f8a4f6c692cf2aa61e482b2b81ec6a298ace4cbf02480/regex-2026.5.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b96350aa424e79d4fd6b567b344dcbe2b2d6bfc48dfe7717587e1fa6d43da6ff", size = 292314, upload-time = "2026-05-09T23:13:40.353Z" }, { url = "https://files.pythonhosted.org/packages/e2/6c/e41bfeecb589716843e7c4df09ba46ff2a42961457afece19059d85caeef/regex-2026.5.9-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8f3af7a4903c5c04a11a196a5aa75cdd7dd3f8508132f9fb3259d9f5908e3b88", size = 811681, upload-time = "2026-05-09T23:13:42.543Z" }, + { url = "https://files.pythonhosted.org/packages/87/83/a5c1c525fba0aa656e88ad0face0b1829788ef4c2fb6b26df58aa1151b84/regex-2026.5.9-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7e87577720152d2caae19fe2baaf1f8d5ca12091e9e229f03915c37d1e4b9178", size = 871135, upload-time = "2026-05-09T23:13:44.326Z" }, + { url = "https://files.pythonhosted.org/packages/18/d4/80882e799e440dd878b0979cbebf8fa4d54624a332c83037c7a701649e3f/regex-2026.5.9-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c8b9b9d294cfea3cd19c718ade7cc93492b2c4991abd9a68d0b3477ae6d8e100", size = 917265, upload-time = "2026-05-09T23:13:47.295Z" }, { url = "https://files.pythonhosted.org/packages/ae/ff/8db60211e2286e396aad7dc7725356c502bff0901ea05bd6cdc2e1a042b9/regex-2026.5.9-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:728d8bfd28a8845c8b6bc5dc7ce010453d206396786c0765c2740cb65f37791e", size = 816311, upload-time = "2026-05-09T23:13:49.885Z" }, + { url = "https://files.pythonhosted.org/packages/4c/47/742ef579c61730f8d268e5cf1f9ce0e37e2ea041ad0f5644724f2378e463/regex-2026.5.9-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7e30b874d341fac767d7df5a0870540541c2c054b80cfaac116e8d367a8a7ff2", size = 785498, upload-time = "2026-05-09T23:13:52.25Z" }, { url = "https://files.pythonhosted.org/packages/7f/ab/cb0999802dcb0fb95b1ab005e8d4163d8afdd67efc2cb6b6630ac13f8cb1/regex-2026.5.9-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:fd190e88a895a8901325fad284a3f74ea52b1da8525b76cc811fa9b1edf0ce2b", size = 801348, upload-time = "2026-05-09T23:13:54.127Z" }, + { url = "https://files.pythonhosted.org/packages/7d/62/8ca59a24c55bc34d166eefaf3717bd77772f329fdbf984d86581e0a3571c/regex-2026.5.9-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:8e76e8161ad00694cfce6767d5dea860c6391ac5b83e5c3a39661e696f11fc7e", size = 866493, upload-time = "2026-05-09T23:13:56.067Z" }, + { url = "https://files.pythonhosted.org/packages/8d/3d/30f2ae62cef3278bb5bb821f467277a55fb73f01032cf85997e15e8289a8/regex-2026.5.9-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ddda5340e6c01a293027dd46232fa79eaff1b48058ce7a98f572b6445b088041", size = 772811, upload-time = "2026-05-09T23:13:57.867Z" }, + { url = "https://files.pythonhosted.org/packages/d8/ae/7d2089bcd78ad0c0161bc684339df50032acb438a7bd3305e7ddb1193cec/regex-2026.5.9-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:205109e96b3cf5adf8f4cd62bedde9487feb282b9497a3535451e5a24cd706a0", size = 856584, upload-time = "2026-05-09T23:13:59.679Z" }, { url = "https://files.pythonhosted.org/packages/a9/29/92ff47f75990131ea4f24ba17819e5a9d141e10819807e09addd73409af6/regex-2026.5.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dfbe4579b9f08036aa7d101d1835437a20783574ac66327e6b29b4018a138081", size = 803453, upload-time = "2026-05-09T23:14:01.978Z" }, ] @@ -9696,23 +10072,39 @@ sdist = { url = "https://files.pythonhosted.org/packages/e5/f5/8bed2310abe4ae04b wheels = [ { url = "https://files.pythonhosted.org/packages/52/66/ba7f561b6062402022887706a7f2b2c2e2e2a28f1e3839202b0a2f77e36d/rignore-0.7.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:182f4e5e4064d947c756819446a7d4cdede8e756b8c81cf9e509683fe38778d7", size = 823882, upload-time = "2025-11-05T20:42:23.488Z" }, { url = "https://files.pythonhosted.org/packages/f5/81/4087453df35a90b07370647b19017029324950c1b9137d54bf1f33843f17/rignore-0.7.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16b63047648a916a87be1e51bb5c009063f1b8b6f5afe4f04f875525507e63dc", size = 899362, upload-time = "2025-11-05T20:40:51.111Z" }, + { url = "https://files.pythonhosted.org/packages/fb/c9/390a8fdfabb76d71416be773bd9f162977bd483084f68daf19da1dec88a6/rignore-0.7.6-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ba5524f5178deca4d7695e936604ebc742acb8958f9395776e1fcb8133f8257a", size = 873633, upload-time = "2025-11-05T20:41:06.193Z" }, + { url = "https://files.pythonhosted.org/packages/df/c9/79404fcb0faa76edfbc9df0901f8ef18568d1104919ebbbad6d608c888d1/rignore-0.7.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:62020dbb89a1dd4b84ab3d60547b3b2eb2723641d5fb198463643f71eaaed57d", size = 1167633, upload-time = "2025-11-05T20:41:22.491Z" }, + { url = "https://files.pythonhosted.org/packages/6e/8d/b3466d32d445d158a0aceb80919085baaae495b1f540fb942f91d93b5e5b/rignore-0.7.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b34acd532769d5a6f153a52a98dcb81615c949ab11697ce26b2eb776af2e174d", size = 941434, upload-time = "2025-11-05T20:41:38.151Z" }, { url = "https://files.pythonhosted.org/packages/e8/40/9cd949761a7af5bc27022a939c91ff622d29c7a0b66d0c13a863097dde2d/rignore-0.7.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c5e53b752f9de44dff7b3be3c98455ce3bf88e69d6dc0cf4f213346c5e3416c", size = 959461, upload-time = "2025-11-05T20:42:08.476Z" }, { url = "https://files.pythonhosted.org/packages/6c/31/1ecff992fc3f59c4fcdcb6c07d5f6c1e6dfb55ccda19c083aca9d86fa1c6/rignore-0.7.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6e01cad2b0b92f6b1993f29fc01f23f2d78caf4bf93b11096d28e9d578eb08ce", size = 1079173, upload-time = "2025-11-05T21:40:12.007Z" }, + { url = "https://files.pythonhosted.org/packages/17/18/162eedadb4c2282fa4c521700dbf93c9b14b8842e8354f7d72b445b8d593/rignore-0.7.6-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:5991e46ab9b4868334c9e372ab0892b0150f3f586ff2b1e314272caeb38aaedb", size = 1139012, upload-time = "2025-11-05T21:40:29.399Z" }, { url = "https://files.pythonhosted.org/packages/9f/22/1c1a65047df864def9a047dbb40bc0b580b8289a4280e62779cd61ae21f2/rignore-0.7.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:aaf938530dcc0b47c4cfa52807aa2e5bfd5ca6d57a621125fe293098692f6345", size = 1128182, upload-time = "2025-11-05T21:41:04.239Z" }, { url = "https://files.pythonhosted.org/packages/93/b0/d4f1f3fe9eb3f8e382d45ce5b0547ea01c4b7e0b4b4eb87bcd66a1d2b888/rignore-0.7.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b9e624f6be6116ea682e76c5feb71ea91255c67c86cb75befe774365b2931961", size = 820411, upload-time = "2025-11-05T20:42:24.782Z" }, { url = "https://files.pythonhosted.org/packages/4a/c8/dea564b36dedac8de21c18e1851789545bc52a0c22ece9843444d5608a6a/rignore-0.7.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bda49950d405aa8d0ebe26af807c4e662dd281d926530f03f29690a2e07d649a", size = 897821, upload-time = "2025-11-05T20:40:52.613Z" }, + { url = "https://files.pythonhosted.org/packages/b3/2b/ee96db17ac1835e024c5d0742eefb7e46de60020385ac883dd3d1cde2c1f/rignore-0.7.6-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b5fd5ab3840b8c16851d327ed06e9b8be6459702a53e5ab1fc4073b684b3789e", size = 873963, upload-time = "2025-11-05T20:41:07.49Z" }, + { url = "https://files.pythonhosted.org/packages/a5/8c/ad5a57bbb9d14d5c7e5960f712a8a0b902472ea3f4a2138cbf70d1777b75/rignore-0.7.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ced2a248352636a5c77504cb755dc02c2eef9a820a44d3f33061ce1bb8a7f2d2", size = 1169216, upload-time = "2025-11-05T20:41:23.73Z" }, + { url = "https://files.pythonhosted.org/packages/80/e6/5b00bc2a6bc1701e6878fca798cf5d9125eb3113193e33078b6fc0d99123/rignore-0.7.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a04a3b73b75ddc12c9c9b21efcdaab33ca3832941d6f1d67bffd860941cd448a", size = 942942, upload-time = "2025-11-05T20:41:39.393Z" }, { url = "https://files.pythonhosted.org/packages/85/e5/7f99bd0cc9818a91d0e8b9acc65b792e35750e3bdccd15a7ee75e64efca4/rignore-0.7.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d24321efac92140b7ec910ac7c53ab0f0c86a41133d2bb4b0e6a7c94967f44dd", size = 959787, upload-time = "2025-11-05T20:42:09.765Z" }, { url = "https://files.pythonhosted.org/packages/41/f7/e80f55dfe0f35787fa482aa18689b9c8251e045076c35477deb0007b3277/rignore-0.7.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1734dc49d1e9501b07852ef44421f84d9f378da9fbeda729e77db71f49cac28b", size = 1078647, upload-time = "2025-11-05T21:40:13.463Z" }, + { url = "https://files.pythonhosted.org/packages/d4/cf/2c64f0b6725149f7c6e7e5a909d14354889b4beaadddaa5fff023ec71084/rignore-0.7.6-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5719ea14ea2b652c0c0894be5dfde954e1853a80dea27dd2fbaa749618d837f5", size = 1139186, upload-time = "2025-11-05T21:40:31.27Z" }, { url = "https://files.pythonhosted.org/packages/7f/5e/13b249613fd5d18d58662490ab910a9f0be758981d1797789913adb4e918/rignore-0.7.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3efdcf1dd84d45f3e2bd2f93303d9be103888f56dfa7c3349b5bf4f0657ec696", size = 1127725, upload-time = "2025-11-05T21:41:05.804Z" }, { url = "https://files.pythonhosted.org/packages/f9/8f/f8daacd177db4bf7c2223bab41e630c52711f8af9ed279be2058d2fe4982/rignore-0.7.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:90f0a00ce0c866c275bf888271f1dc0d2140f29b82fcf33cdbda1e1a6af01010", size = 820150, upload-time = "2025-11-05T20:42:26.545Z" }, { url = "https://files.pythonhosted.org/packages/36/31/b65b837e39c3f7064c426754714ac633b66b8c2290978af9d7f513e14aa9/rignore-0.7.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1ad295537041dc2ed4b540fb1a3906bd9ede6ccdad3fe79770cd89e04e3c73c", size = 897406, upload-time = "2025-11-05T20:40:53.854Z" }, + { url = "https://files.pythonhosted.org/packages/ca/58/1970ce006c427e202ac7c081435719a076c478f07b3a23f469227788dc23/rignore-0.7.6-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f782dbd3a65a5ac85adfff69e5c6b101285ef3f845c3a3cae56a54bebf9fe116", size = 874050, upload-time = "2025-11-05T20:41:08.922Z" }, + { url = "https://files.pythonhosted.org/packages/d4/00/eb45db9f90137329072a732273be0d383cb7d7f50ddc8e0bceea34c1dfdf/rignore-0.7.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65cece3b36e5b0826d946494734c0e6aaf5a0337e18ff55b071438efe13d559e", size = 1167835, upload-time = "2025-11-05T20:41:24.997Z" }, + { url = "https://files.pythonhosted.org/packages/f3/f1/6f1d72ddca41a64eed569680587a1236633587cc9f78136477ae69e2c88a/rignore-0.7.6-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d7e4bb66c13cd7602dc8931822c02dfbbd5252015c750ac5d6152b186f0a8be0", size = 941945, upload-time = "2025-11-05T20:41:40.628Z" }, { url = "https://files.pythonhosted.org/packages/48/6f/2f178af1c1a276a065f563ec1e11e7a9e23d4996fd0465516afce4b5c636/rignore-0.7.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:297e500c15766e196f68aaaa70e8b6db85fa23fdc075b880d8231fdfba738cd7", size = 959067, upload-time = "2025-11-05T20:42:11.09Z" }, { url = "https://files.pythonhosted.org/packages/31/eb/c4f92cc3f2825d501d3c46a244a671eb737fc1bcf7b05a3ecd34abb3e0d7/rignore-0.7.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:181eb2a975a22256a1441a9d2f15eb1292839ea3f05606620bd9e1938302cf79", size = 1078365, upload-time = "2025-11-05T21:40:15.148Z" }, + { url = "https://files.pythonhosted.org/packages/26/09/99442f02794bd7441bfc8ed1c7319e890449b816a7493b2db0e30af39095/rignore-0.7.6-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:7bbcdc52b5bf9f054b34ce4af5269df5d863d9c2456243338bc193c28022bd7b", size = 1139066, upload-time = "2025-11-05T21:40:32.771Z" }, { url = "https://files.pythonhosted.org/packages/e2/25/d37215e4562cda5c13312636393aea0bafe38d54d4e0517520a4cc0753ec/rignore-0.7.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ee4a18b82cbbc648e4aac1510066682fe62beb5dc88e2c67c53a83954e541360", size = 1127550, upload-time = "2025-11-05T21:41:07.648Z" }, { url = "https://files.pythonhosted.org/packages/35/af/c69c0c51b8f9f7914d95c4ea91c29a2ac067572048cae95dd6d2efdbe05d/rignore-0.7.6-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:392dcabfecbe176c9ebbcb40d85a5e86a5989559c4f988c2741da7daf1b5be25", size = 825976, upload-time = "2025-11-05T20:42:35.118Z" }, { url = "https://files.pythonhosted.org/packages/f1/d2/1b264f56132264ea609d3213ab603d6a27016b19559a1a1ede1a66a03dcd/rignore-0.7.6-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22baa462abdc36fdd5a5e2dae423107723351b85ff093762f9261148b9d0a04a", size = 899739, upload-time = "2025-11-05T20:41:01.518Z" }, + { url = "https://files.pythonhosted.org/packages/55/e4/b3c5dfdd8d8a10741dfe7199ef45d19a0e42d0c13aa377c83bd6caf65d90/rignore-0.7.6-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53fb28882d2538cb2d231972146c4927a9d9455e62b209f85d634408c4103538", size = 874843, upload-time = "2025-11-05T20:41:17.687Z" }, + { url = "https://files.pythonhosted.org/packages/cc/10/d6f3750233881a2a154cefc9a6a0a9b19da526b19f7f08221b552c6f827d/rignore-0.7.6-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:87409f7eeb1103d6b77f3472a3a0d9a5953e3ae804a55080bdcb0120ee43995b", size = 1170348, upload-time = "2025-11-05T20:41:34.21Z" }, + { url = "https://files.pythonhosted.org/packages/6e/10/ad98ca05c9771c15af734cee18114a3c280914b6e34fde9ffea2e61e88aa/rignore-0.7.6-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:684014e42e4341ab3ea23a203551857fcc03a7f8ae96ca3aefb824663f55db32", size = 942315, upload-time = "2025-11-05T20:41:48.508Z" }, { url = "https://files.pythonhosted.org/packages/de/00/ab5c0f872acb60d534e687e629c17e0896c62da9b389c66d3aa16b817aa8/rignore-0.7.6-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77356ebb01ba13f8a425c3d30fcad40e57719c0e37670d022d560884a30e4767", size = 961047, upload-time = "2025-11-05T20:42:19.403Z" }, { url = "https://files.pythonhosted.org/packages/33/b8/133aa4002cee0ebbb39362f94e4898eec7fbd09cec9fcbce1cd65b355b7f/rignore-0.7.6-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:2673225dcec7f90497e79438c35e34638d0d0391ccea3cbb79bfb9adc0dc5bd7", size = 1079656, upload-time = "2025-11-05T21:40:24.89Z" }, + { url = "https://files.pythonhosted.org/packages/67/56/36d5d34210e5e7dfcd134eed8335b19e80ae940ee758f493e4f2b344dd70/rignore-0.7.6-pp311-pypy311_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:c081f17290d8a2b96052b79207622aa635686ea39d502b976836384ede3d303c", size = 1139789, upload-time = "2025-11-05T21:40:42.119Z" }, { url = "https://files.pythonhosted.org/packages/ce/8b/a1299085b28a2f6135e30370b126e3c5055b61908622f2488ade67641479/rignore-0.7.6-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:d8955b57e42f2a5434670d5aa7b75eaf6e74602ccd8955dddf7045379cd762fb", size = 1129444, upload-time = "2025-11-05T21:41:17.906Z" }, ] @@ -9745,27 +10137,47 @@ sdist = { url = "https://files.pythonhosted.org/packages/20/af/3f2f423103f1113b3 wheels = [ { url = "https://files.pythonhosted.org/packages/94/ba/24e5ebb7c1c82e74c4e4f33b2112a5573ddc703915b13a073737b59b86e0/rpds_py-0.30.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dc4f992dfe1e2bc3ebc7444f6c7051b4bc13cd8e33e43511e8ffd13bf407010d", size = 359676, upload-time = "2025-11-30T20:21:55.475Z" }, { url = "https://files.pythonhosted.org/packages/84/86/04dbba1b087227747d64d80c3b74df946b986c57af0a9f0c98726d4d7a3b/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:422c3cb9856d80b09d30d2eb255d0754b23e090034e1deb4083f8004bd0761e4", size = 389938, upload-time = "2025-11-30T20:21:57.079Z" }, + { url = "https://files.pythonhosted.org/packages/42/bb/1463f0b1722b7f45431bdd468301991d1328b16cffe0b1c2918eba2c4eee/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:07ae8a593e1c3c6b82ca3292efbe73c30b61332fd612e05abee07c79359f292f", size = 402932, upload-time = "2025-11-30T20:21:58.47Z" }, + { url = "https://files.pythonhosted.org/packages/99/ee/2520700a5c1f2d76631f948b0736cdf9b0acb25abd0ca8e889b5c62ac2e3/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12f90dd7557b6bd57f40abe7747e81e0c0b119bef015ea7726e69fe550e394a4", size = 525830, upload-time = "2025-11-30T20:21:59.699Z" }, + { url = "https://files.pythonhosted.org/packages/e0/ad/bd0331f740f5705cc555a5e17fdf334671262160270962e69a2bdef3bf76/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99b47d6ad9a6da00bec6aabe5a6279ecd3c06a329d4aa4771034a21e335c3a97", size = 412033, upload-time = "2025-11-30T20:22:00.991Z" }, { url = "https://files.pythonhosted.org/packages/f8/1e/372195d326549bb51f0ba0f2ecb9874579906b97e08880e7a65c3bef1a99/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33f559f3104504506a44bb666b93a33f5d33133765b0c216a5bf2f1e1503af89", size = 390828, upload-time = "2025-11-30T20:22:02.723Z" }, + { url = "https://files.pythonhosted.org/packages/ab/2b/d88bb33294e3e0c76bc8f351a3721212713629ffca1700fa94979cb3eae8/rpds_py-0.30.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:946fe926af6e44f3697abbc305ea168c2c31d3e3ef1058cf68f379bf0335a78d", size = 404683, upload-time = "2025-11-30T20:22:04.367Z" }, { url = "https://files.pythonhosted.org/packages/2b/81/e729761dbd55ddf5d84ec4ff1f47857f4374b0f19bdabfcf929164da3e24/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9a0ca5da0386dee0655b4ccdf46119df60e0f10da268d04fe7cc87886872ba7", size = 572496, upload-time = "2025-11-30T20:22:07.713Z" }, { url = "https://files.pythonhosted.org/packages/5f/48/905896b1eb8a05630d20333d1d8ffd162394127b74ce0b0784ae04498d32/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3896fa1be39912cf0757753826bc8bdc8ca331a28a7c4ae46b7a21280b06bb85", size = 561011, upload-time = "2025-11-30T20:22:11.309Z" }, { 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/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/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/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/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/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/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/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/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/57/09/f183df9b8f2d66720d2ef71075c59f7e1b336bec7ee4c48f0a2b06857653/rpds_py-0.30.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ee6af14263f25eedc3bb918a3c04245106a42dfd4f5c2285ea6f997b1fc3f89a", size = 362128, upload-time = "2025-11-30T20:24:18.086Z" }, { url = "https://files.pythonhosted.org/packages/7a/68/5c2594e937253457342e078f0cc1ded3dd7b2ad59afdbf2d354869110a02/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3adbb8179ce342d235c31ab8ec511e66c73faa27a47e076ccc92421add53e2bb", size = 391542, upload-time = "2025-11-30T20:24:20.092Z" }, + { url = "https://files.pythonhosted.org/packages/49/5c/31ef1afd70b4b4fbdb2800249f34c57c64beb687495b10aec0365f53dfc4/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:250fa00e9543ac9b97ac258bd37367ff5256666122c2d0f2bc97577c60a1818c", size = 404004, upload-time = "2025-11-30T20:24:22.231Z" }, + { url = "https://files.pythonhosted.org/packages/e3/63/0cfbea38d05756f3440ce6534d51a491d26176ac045e2707adc99bb6e60a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9854cf4f488b3d57b9aaeb105f06d78e5529d3145b1e4a41750167e8c213c6d3", size = 527063, upload-time = "2025-11-30T20:24:24.302Z" }, + { url = "https://files.pythonhosted.org/packages/42/e6/01e1f72a2456678b0f618fc9a1a13f882061690893c192fcad9f2926553a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:993914b8e560023bc0a8bf742c5f303551992dcb85e247b1e5c7f4a7d145bda5", size = 413099, upload-time = "2025-11-30T20:24:25.916Z" }, { url = "https://files.pythonhosted.org/packages/b8/25/8df56677f209003dcbb180765520c544525e3ef21ea72279c98b9aa7c7fb/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58edca431fb9b29950807e301826586e5bbf24163677732429770a697ffe6738", size = 392177, upload-time = "2025-11-30T20:24:27.834Z" }, + { url = "https://files.pythonhosted.org/packages/4a/b4/0a771378c5f16f8115f796d1f437950158679bcd2a7c68cf251cfb00ed5b/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:dea5b552272a944763b34394d04577cf0f9bd013207bc32323b5a89a53cf9c2f", size = 406015, upload-time = "2025-11-30T20:24:29.457Z" }, { url = "https://files.pythonhosted.org/packages/13/64/b4d76f227d5c45a7e0b796c674fd81b0a6c4fbd48dc29271857d8219571c/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:dff13836529b921e22f15cb099751209a60009731a68519630a24d61f0b1b30a", size = 573981, upload-time = "2025-11-30T20:24:32.934Z" }, { url = "https://files.pythonhosted.org/packages/d1/b7/b95708304cd49b7b6f82fdd039f1748b66ec2b21d6a45180910802f1abf1/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:ac37f9f516c51e5753f27dfdef11a88330f04de2d564be3991384b2f3535d02e", size = 562191, upload-time = "2025-11-30T20:24:36.853Z" }, ] @@ -9785,10 +10197,16 @@ version = "0.15.7" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/a1/22/9e4f66ee588588dc6c9af6a994e12d26e19efbe874d1a909d09a6dac7a59/ruff-0.15.7.tar.gz", hash = "sha256:04f1ae61fc20fe0b148617c324d9d009b5f63412c0b16474f3d5f1a1a665f7ac", size = 4601277, upload-time = "2026-03-19T16:26:22.605Z" } wheels = [ + { url = "https://files.pythonhosted.org/packages/41/2f/0b08ced94412af091807b6119ca03755d651d3d93a242682bf020189db94/ruff-0.15.7-py3-none-linux_armv6l.whl", hash = "sha256:a81cc5b6910fb7dfc7c32d20652e50fa05963f6e13ead3c5915c41ac5d16668e", size = 10489037, upload-time = "2026-03-19T16:26:32.47Z" }, { url = "https://files.pythonhosted.org/packages/ab/10/12586735d0ff42526ad78c049bf51d7428618c8b5c467e72508c694119df/ruff-0.15.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7fbc2448094262552146cbe1b9643a92f66559d3761f1ad0656d4991491af49e", size = 10269302, upload-time = "2026-03-19T16:26:26.183Z" }, { url = "https://files.pythonhosted.org/packages/eb/5d/32b5c44ccf149a26623671df49cbfbd0a0ae511ff3df9d9d2426966a8d57/ruff-0.15.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b39329b60eba44156d138275323cc726bbfbddcec3063da57caa8a8b1d50adf", size = 10607625, upload-time = "2026-03-19T16:27:03.263Z" }, + { url = "https://files.pythonhosted.org/packages/5d/f1/f0001cabe86173aaacb6eb9bb734aa0605f9a6aa6fa7d43cb49cbc4af9c9/ruff-0.15.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:87768c151808505f2bfc93ae44e5f9e7c8518943e5074f76ac21558ef5627c85", size = 10324743, upload-time = "2026-03-19T16:27:09.791Z" }, + { url = "https://files.pythonhosted.org/packages/e4/f2/4fd0d05aab0c5934b2e1464784f85ba2eab9d54bffc53fb5430d1ed8b829/ruff-0.15.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e0d19644f801849229db8345180a71bee5407b429dd217f853ec515e968a6912", size = 11994292, upload-time = "2026-03-19T16:26:48.718Z" }, + { url = "https://files.pythonhosted.org/packages/64/22/fc4483871e767e5e95d1622ad83dad5ebb830f762ed0420fde7dfa9d9b08/ruff-0.15.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4806d8e09ef5e84eb19ba833d0442f7e300b23fe3f0981cae159a248a10f0036", size = 11398981, upload-time = "2026-03-19T16:26:54.513Z" }, { url = "https://files.pythonhosted.org/packages/b0/99/66f0343176d5eab02c3f7fcd2de7a8e0dd7a41f0d982bee56cd1c24db62b/ruff-0.15.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dce0896488562f09a27b9c91b1f58a097457143931f3c4d519690dea54e624c5", size = 11242422, upload-time = "2026-03-19T16:26:29.277Z" }, + { url = "https://files.pythonhosted.org/packages/5d/3a/a7060f145bfdcce4c987ea27788b30c60e2c81d6e9a65157ca8afe646328/ruff-0.15.7-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:1852ce241d2bc89e5dc823e03cff4ce73d816b5c6cdadd27dbfe7b03217d2a12", size = 11232158, upload-time = "2026-03-19T16:26:42.321Z" }, { url = "https://files.pythonhosted.org/packages/a7/53/90fbb9e08b29c048c403558d3cdd0adf2668b02ce9d50602452e187cd4af/ruff-0.15.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5f3e4b221fb4bd293f79912fc5e93a9063ebd6d0dcbd528f91b89172a9b8436c", size = 10577861, upload-time = "2026-03-19T16:26:57.459Z" }, + { url = "https://files.pythonhosted.org/packages/2f/aa/5f486226538fe4d0f0439e2da1716e1acf895e2a232b26f2459c55f8ddad/ruff-0.15.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b15e48602c9c1d9bdc504b472e90b90c97dc7d46c7028011ae67f3861ceba7b4", size = 10327310, upload-time = "2026-03-19T16:26:35.909Z" }, { url = "https://files.pythonhosted.org/packages/bf/29/a4ae78394f76c7759953c47884eb44de271b03a66634148d9f7d11e721bd/ruff-0.15.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:112c1fa316a558bb34319282c1200a8bf0495f1b735aeb78bfcb2991e6087580", size = 11336961, upload-time = "2026-03-19T16:26:39.076Z" }, ] @@ -9829,8 +10247,13 @@ sdist = { url = "https://files.pythonhosted.org/packages/45/06/f955dbbb1859e3bd2 wheels = [ { url = "https://files.pythonhosted.org/packages/f5/b1/fa7c600e7dceae12e9606c7578cbc9ff1e1ed55844883ee5c92205e86226/safetensors-0.8.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:c80201d22cbf405b80647a60ada77bba06c8fba2da2743ba1e89cdcc39a81f25", size = 484562, upload-time = "2026-06-09T07:52:17.518Z" }, { url = "https://files.pythonhosted.org/packages/09/7d/65a7de0af421317bb36a067241e4235fff194eed60b961ed6d3f59a3fc60/safetensors-0.8.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a46e5ff292c356d6991e60942ba7f79817682d3a2cef0702136448cb9c4d235", size = 502844, upload-time = "2026-06-09T07:52:07.624Z" }, + { url = "https://files.pythonhosted.org/packages/91/4f/3175c9d75634e0e0dda0082794193521035edd7c70a6f212bf33ca06ddf4/safetensors-0.8.0-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4124502b78f03534117c848f87a39b8f31e577b15eff423bf8bfb95f2a8c30d0", size = 511823, upload-time = "2026-06-09T07:52:09.565Z" }, + { url = "https://files.pythonhosted.org/packages/20/87/846c289e7aa2299eff406335717cf43ce8777194ece8aad75772e0411615/safetensors-0.8.0-cp310-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7bc0a787ba8a35be368ee3574edfa2b1ad389eebd0a72e482ae275490e3f6c98", size = 633461, upload-time = "2026-06-09T07:52:11.128Z" }, + { url = "https://files.pythonhosted.org/packages/76/22/8d64d9df2c45d5ded401df889d0ad90882804ca172d79ec4f0df8f727fe0/safetensors-0.8.0-cp310-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:040070828e36dc8e122178bbbd5830ff9e97920affb84cbe0f46442497bed358", size = 545148, upload-time = "2026-06-09T07:52:13.603Z" }, { url = "https://files.pythonhosted.org/packages/28/50/f203ff3a3ddfe19308efc83c5a3a29ed02bf786732ec35e68bf9162f3365/safetensors-0.8.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd6f3f93c9a0a7cc2788ee63fb763353d4bd2e89b0751bc78fcf7dda00bea774", size = 516040, upload-time = "2026-06-09T07:52:16.29Z" }, + { url = "https://files.pythonhosted.org/packages/46/fb/cdaed17ceb2948784fd9c36b6fd3e951b608547cea81a48e8ee6f8cfdfcb/safetensors-0.8.0-cp310-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:fcdd41ec4628fee5799f807c73c353629130fbd942aa23d83c623dd6c9d52d78", size = 513832, upload-time = "2026-06-09T07:52:12.37Z" }, { url = "https://files.pythonhosted.org/packages/2a/43/bf38443278eab4b1be1fce2931e2b012ad9cb7df52ada751d0aab8f7659a/safetensors-0.8.0-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:87eec7ffed2b809f05a398a8becb7d013f19f7837cd15d9748580d6cf30dbaf4", size = 678670, upload-time = "2026-06-09T07:52:20.032Z" }, + { url = "https://files.pythonhosted.org/packages/72/e3/68cd3fa5b48488e84add63e04cb12f3bc28ae4638c06d4508c6e88823d0e/safetensors-0.8.0-cp310-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:4a95ae2b05d7726d751da4ebf626a2ca782b706e101bd894c95bc2450b1cffcc", size = 786679, upload-time = "2026-06-09T07:52:21.322Z" }, { url = "https://files.pythonhosted.org/packages/27/43/41c1621732edd934d868a00d1b891584c892a7b62a9aab82ea5a0a5623ee/safetensors-0.8.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:8e080062fcde23be189565e1c3305d16751a218ecf9412c8601e64204eb6f846", size = 722361, upload-time = "2026-06-09T07:52:23.924Z" }, ] @@ -10570,8 +10993,12 @@ sdist = { url = "https://files.pythonhosted.org/packages/73/6f/f80cfef4a312e1fb3 wheels = [ { url = "https://files.pythonhosted.org/packages/2e/47/174dca0502ef88b28f1c9e06b73ce33500eedfac7a7692108aec220464e7/tokenizers-0.22.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:1e418a55456beedca4621dbab65a318981467a2b188e982a23e117f115ce5001", size = 2981472, upload-time = "2026-01-05T10:41:00.276Z" }, { url = "https://files.pythonhosted.org/packages/d6/84/7990e799f1309a8b87af6b948f31edaa12a3ed22d11b352eaf4f4b2e5753/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2249487018adec45d6e3554c71d46eb39fa8ea67156c640f7513eb26f318cec7", size = 3290736, upload-time = "2026-01-05T10:40:32.165Z" }, + { url = "https://files.pythonhosted.org/packages/78/59/09d0d9ba94dcd5f4f1368d4858d24546b4bdc0231c2354aa31d6199f0399/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25b85325d0815e86e0bac263506dd114578953b7b53d7de09a6485e4a160a7dd", size = 3168835, upload-time = "2026-01-05T10:40:38.847Z" }, + { url = "https://files.pythonhosted.org/packages/e0/fa/89f4cb9e08df770b57adb96f8cbb7e22695a4cb6c2bd5f0c4f0ebcf33b66/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c774b1276f71e1ef716e5486f21e76333464f47bece56bbd554485982a9e03e", size = 3724818, upload-time = "2026-01-05T10:40:44.507Z" }, + { url = "https://files.pythonhosted.org/packages/64/04/ca2363f0bfbe3b3d36e95bf67e56a4c88c8e3362b658e616d1ac185d47f2/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df6c4265b289083bf710dff49bc51ef252f9d5be33a45ee2bed151114a56207b", size = 3379195, upload-time = "2026-01-05T10:40:51.139Z" }, { url = "https://files.pythonhosted.org/packages/2e/76/932be4b50ef6ccedf9d3c6639b056a967a86258c6d9200643f01269211ca/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:369cc9fc8cc10cb24143873a0d95438bb8ee257bb80c71989e3ee290e8d72c67", size = 3274982, upload-time = "2026-01-05T10:40:58.331Z" }, { url = "https://files.pythonhosted.org/packages/1d/28/5f9f5a4cc211b69e89420980e483831bcc29dade307955cc9dc858a40f01/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:29c30b83d8dcd061078b05ae0cb94d3c710555fbb44861139f9f83dcca3dc3e4", size = 9478245, upload-time = "2026-01-05T10:41:04.053Z" }, + { url = "https://files.pythonhosted.org/packages/6c/fb/66e2da4704d6aadebf8cb39f1d6d1957df667ab24cff2326b77cda0dcb85/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:37ae80a28c1d3265bb1f22464c856bd23c02a05bb211e56d0c5301a435be6c1a", size = 9560069, upload-time = "2026-01-05T10:45:10.673Z" }, { url = "https://files.pythonhosted.org/packages/05/a1/d62dfe7376beaaf1394917e0f8e93ee5f67fea8fcf4107501db35996586b/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:38337540fbbddff8e999d59970f3c6f35a82de10053206a7562f1ea02d046fa5", size = 10033429, upload-time = "2026-01-05T10:45:14.333Z" }, ] @@ -10835,10 +11262,16 @@ version = "0.0.56" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/55/07/fb29aea5235b0aa8ecfc4d1cc6ddf9fba8b863d67d96c6d345694d644c43/ty-0.0.56.tar.gz", hash = "sha256:84d114dc3796361c0fc72945016eabd74d46b9ee64f198cb0e485719704681e5", size = 6050123, upload-time = "2026-07-01T16:44:56.036Z" } wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/48/bce79e7ca5c1cc529d3e0d37ddd1121aea4b68a4f749974ad1cc77161871/ty-0.0.56-py3-none-linux_armv6l.whl", hash = "sha256:186d4a53e15747c947e1ec3d7eec8e345d8e40a1ca10e634c585db52497e87dd", size = 11643066, upload-time = "2026-07-01T16:44:18.374Z" }, { url = "https://files.pythonhosted.org/packages/cf/2d/b3b7a74ce8bc59ef48843ad80179bb0d9598bbd6cfc0d11d519bdf6b1352/ty-0.0.56-py3-none-macosx_11_0_arm64.whl", hash = "sha256:afd3058c0a6c5f241e814734f133008c93ee805f61c9cf4ce7412b8822b5d9ad", size = 10962270, upload-time = "2026-07-01T16:44:22.959Z" }, { url = "https://files.pythonhosted.org/packages/64/ac/6c2fd7de0304a8a7218a756af74f7e62a5e8540fdb175e0a869e51042345/ty-0.0.56-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:058b52f7a823ac13aae3cae30809dd6b5145794b64d8478f9ef38c75d79b4483", size = 11471406, upload-time = "2026-07-01T16:44:25.327Z" }, + { url = "https://files.pythonhosted.org/packages/50/b6/11d861156861c03c7726b74558f9a0e0092661aff83a4fda1279df28c425/ty-0.0.56-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2c66e00c1522add1f2bbdd2e45828c953b35c306b7bef03ec9169c75a63699a0", size = 11445612, upload-time = "2026-07-01T16:44:27.531Z" }, + { url = "https://files.pythonhosted.org/packages/d7/f7/dbb4b4ccb69cd64c209ae55b1ab788ace8222c2bc1f6845be9e7cbedbf25/ty-0.0.56-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:63fe3947fe0c46c69a7d950e6832ee70a9ec17321fefbff3d2e3c20baf9e5bd0", size = 12666337, upload-time = "2026-07-01T16:44:31.586Z" }, + { url = "https://files.pythonhosted.org/packages/86/e9/73f903fe4a3d9ea02f26f57c1eb07e3b1029ec92b0e8c2364718893440e3/ty-0.0.56-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:71a0c1a72f9854532e710e119b6871ffe4542c8a65146f1f65dcd78fecd885b4", size = 12280247, upload-time = "2026-07-01T16:44:33.637Z" }, { url = "https://files.pythonhosted.org/packages/d6/90/cebd222495832f1a00dcd321ba25f3cab804221a4991b992c2178bec68ee/ty-0.0.56-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70d1665596494e24d8ebd198438872b5a56ec3cae5f2bcf6c673be797acc4e3c", size = 11991107, upload-time = "2026-07-01T16:44:36.122Z" }, + { url = "https://files.pythonhosted.org/packages/b7/07/8f7337a07250f42d975cdb6decf47fc5b421e6c7da5e3e7be1e85f63a7e5/ty-0.0.56-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:778f99e51558afc1dbbe48ee38ab6aae7b31390ed8c1a1ef1499b295e9f1e82f", size = 12298970, upload-time = "2026-07-01T16:44:38.243Z" }, { url = "https://files.pythonhosted.org/packages/3c/b9/a52cd59034a48f5f18c6b155cc2cc36861d874b6d0af204b12c898024c3d/ty-0.0.56-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:867bc5708e0066bb4ff6c7db524bd5deea2676c62bfe71d3303138b3be850af0", size = 11425683, upload-time = "2026-07-01T16:44:40.473Z" }, + { url = "https://files.pythonhosted.org/packages/1d/2e/48e42d33357d52eefb695c0c3fcfc96879b73668a7447d1d1e0ad774fedc/ty-0.0.56-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a6012f4189c928edb330a37deb9930f982380bd4aa7c4b8e0428eec9651c7551", size = 11469258, upload-time = "2026-07-01T16:44:42.513Z" }, { url = "https://files.pythonhosted.org/packages/09/34/9d81967ff240eaa57e9249728ef7b7790747cf6d3c9a98ec86b2cfdcc8ee/ty-0.0.56-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:62619b3b0e2c6248ef30d3f0e2f2217ae9893040585be07f32324242f197cd6f", size = 12100242, upload-time = "2026-07-01T16:44:46.584Z" }, ] @@ -11140,11 +11573,16 @@ sdist = { url = "https://files.pythonhosted.org/packages/7b/d1/38a573f0c631c062c wheels = [ { url = "https://files.pythonhosted.org/packages/43/b7/add4363039a34506a58457d96d4aa2126061df3a143eb4d042aedd6a2e76/uuid_utils-0.14.1-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:93a3b5dc798a54a1feb693f2d1cb4cf08258c32ff05ae4929b5f0a2ca624a4f0", size = 604679, upload-time = "2026-02-20T22:50:27.469Z" }, { url = "https://files.pythonhosted.org/packages/ef/ed/b6d6fd52a6636d7c3eddf97d68da50910bf17cd5ac221992506fb56cf12e/uuid_utils-0.14.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b56b0cacd81583834820588378e432b0696186683b813058b707aedc1e16c4b1", size = 344714, upload-time = "2026-02-20T22:50:42.642Z" }, + { url = "https://files.pythonhosted.org/packages/a8/a7/a19a1719fb626fe0b31882db36056d44fe904dc0cf15b06fdf56b2679cf7/uuid_utils-0.14.1-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb3cf14de789097320a3c56bfdfdd51b1225d11d67298afbedee7e84e3837c96", size = 350914, upload-time = "2026-02-20T22:50:36.487Z" }, + { url = "https://files.pythonhosted.org/packages/1d/fc/f6690e667fdc3bb1a73f57951f97497771c56fe23e3d302d7404be394d4f/uuid_utils-0.14.1-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60e0854a90d67f4b0cc6e54773deb8be618f4c9bad98d3326f081423b5d14fae", size = 482609, upload-time = "2026-02-20T22:50:37.511Z" }, { url = "https://files.pythonhosted.org/packages/54/6e/dcd3fa031320921a12ec7b4672dea3bd1dd90ddffa363a91831ba834d559/uuid_utils-0.14.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce6743ba194de3910b5feb1a62590cd2587e33a73ab6af8a01b642ceb5055862", size = 345699, upload-time = "2026-02-20T22:50:46.87Z" }, { url = "https://files.pythonhosted.org/packages/c7/d9/3d2eb98af94b8dfffc82b6a33b4dfc87b0a5de2c68a28f6dde0db1f8681b/uuid_utils-0.14.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c915d53f22945e55fe0d3d3b0b87fd965a57f5fd15666fd92d6593a73b1dd297", size = 521836, upload-time = "2026-02-20T22:50:23.057Z" }, + { url = "https://files.pythonhosted.org/packages/a8/15/0eb106cc6fe182f7577bc0ab6e2f0a40be247f35c5e297dbf7bbc460bd02/uuid_utils-0.14.1-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:0972488e3f9b449e83f006ead5a0e0a33ad4a13e4462e865b7c286ab7d7566a3", size = 625260, upload-time = "2026-02-20T22:50:25.949Z" }, { url = "https://files.pythonhosted.org/packages/2e/c2/d37a7b2e41f153519367d4db01f0526e0d4b06f1a4a87f1c5dfca5d70a8b/uuid_utils-0.14.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:bec8f8ef627af86abf8298e7ec50926627e29b34fa907fcfbedb45aaa72bca43", size = 551407, upload-time = "2026-02-20T22:50:44.915Z" }, { url = "https://files.pythonhosted.org/packages/91/f9/6c64bdbf71f58ccde7919e00491812556f446a5291573af92c49a5e9aaef/uuid_utils-0.14.1-pp311-pypy311_pp73-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:b197cd5424cf89fb019ca7f53641d05bfe34b1879614bed111c9c313b5574cd8", size = 591617, upload-time = "2026-02-20T22:50:24.532Z" }, { url = "https://files.pythonhosted.org/packages/85/89/d91862b544c695cd58855efe3201f83894ed82fffe34500774238ab8eba7/uuid_utils-0.14.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b5d2ad28063d422ccc2c28d46471d47b61a58de885d35113a8f18cb547e25bf", size = 337678, upload-time = "2026-02-20T22:50:39.768Z" }, + { url = "https://files.pythonhosted.org/packages/ee/6b/cf342ba8a898f1de024be0243fac67c025cad530c79ea7f89c4ce718891a/uuid_utils-0.14.1-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:da2234387b45fde40b0fedfee64a0ba591caeea9c48c7698ab6e2d85c7991533", size = 343711, upload-time = "2026-02-20T22:50:43.965Z" }, + { url = "https://files.pythonhosted.org/packages/b3/20/049418d094d396dfa6606b30af925cc68a6670c3b9103b23e6990f84b589/uuid_utils-0.14.1-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50fffc2827348c1e48972eed3d1c698959e63f9d030aa5dd82ba451113158a62", size = 476731, upload-time = "2026-02-20T22:50:30.589Z" }, { url = "https://files.pythonhosted.org/packages/77/a1/0857f64d53a90321e6a46a3d4cc394f50e1366132dcd2ae147f9326ca98b/uuid_utils-0.14.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1dbe718765f70f5b7f9b7f66b6a937802941b1cc56bcf642ce0274169741e01", size = 338902, upload-time = "2026-02-20T22:50:33.927Z" }, ] @@ -11282,21 +11720,33 @@ sdist = { url = "https://files.pythonhosted.org/packages/c2/c9/8869df9b2a2d6c59d wheels = [ { url = "https://files.pythonhosted.org/packages/fe/cd/f515660b1f32f65df671ddf6f85bfaca621aee177712874dc30a97397977/watchfiles-1.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:421e29339983e1bebc281fab40d812742268ad057db4aee8c4d2bce0af43b741", size = 394384, upload-time = "2025-10-14T15:04:33.761Z" }, { url = "https://files.pythonhosted.org/packages/7b/c3/28b7dc99733eab43fca2d10f55c86e03bd6ab11ca31b802abac26b23d161/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e43d39a741e972bab5d8100b5cdacf69db64e34eb19b6e9af162bccf63c5cc6", size = 448789, upload-time = "2025-10-14T15:04:34.679Z" }, + { url = "https://files.pythonhosted.org/packages/4a/24/33e71113b320030011c8e4316ccca04194bf0cbbaeee207f00cbc7d6b9f5/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f537afb3276d12814082a2e9b242bdcf416c2e8fd9f799a737990a1dbe906e5b", size = 460521, upload-time = "2025-10-14T15:04:35.963Z" }, + { url = "https://files.pythonhosted.org/packages/49/36/506447b73eb46c120169dc1717fe2eff07c234bb3232a7200b5f5bd816e9/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5f3f58818dc0b07f7d9aa7fe9eb1037aecb9700e63e1f6acfed13e9fef648f5d", size = 596088, upload-time = "2025-10-14T15:04:38.39Z" }, + { url = "https://files.pythonhosted.org/packages/82/ab/5f39e752a9838ec4d52e9b87c1e80f1ee3ccdbe92e183c15b6577ab9de16/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9bb9f66367023ae783551042d31b1d7fd422e8289eedd91f26754a66f44d5cff", size = 472923, upload-time = "2025-10-14T15:04:39.666Z" }, { url = "https://files.pythonhosted.org/packages/af/b9/a419292f05e302dea372fa7e6fda5178a92998411f8581b9830d28fb9edb/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aebfd0861a83e6c3d1110b78ad54704486555246e542be3e2bb94195eabb2606", size = 456080, upload-time = "2025-10-14T15:04:40.643Z" }, { url = "https://files.pythonhosted.org/packages/b0/c3/d5932fd62bde1a30c36e10c409dc5d54506726f08cb3e1d8d0ba5e2bc8db/watchfiles-1.1.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5fac835b4ab3c6487b5dbad78c4b3724e26bcc468e886f8ba8cc4306f68f6701", size = 629432, upload-time = "2025-10-14T15:04:41.789Z" }, { url = "https://files.pythonhosted.org/packages/f7/77/16bddd9779fafb795f1a94319dc965209c5641db5bf1edbbccace6d1b3c0/watchfiles-1.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:399600947b170270e80134ac854e21b3ccdefa11a9529a3decc1327088180f10", size = 623046, upload-time = "2025-10-14T15:04:42.718Z" }, { url = "https://files.pythonhosted.org/packages/a5/96/a881a13aa1349827490dab2d363c8039527060cfcc2c92cc6d13d1b1049e/watchfiles-1.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bd404be08018c37350f0d6e34676bd1e2889990117a2b90070b3007f172d0610", size = 391769, upload-time = "2025-10-14T15:04:48.003Z" }, { url = "https://files.pythonhosted.org/packages/4b/5b/d3b460364aeb8da471c1989238ea0e56bec24b6042a68046adf3d9ddb01c/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8526e8f916bb5b9a0a777c8317c23ce65de259422bba5b31325a6fa6029d33af", size = 449374, upload-time = "2025-10-14T15:04:49.179Z" }, + { url = "https://files.pythonhosted.org/packages/b9/44/5769cb62d4ed055cb17417c0a109a92f007114a4e07f30812a73a4efdb11/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2edc3553362b1c38d9f06242416a5d8e9fe235c204a4072e988ce2e5bb1f69f6", size = 459485, upload-time = "2025-10-14T15:04:50.155Z" }, + { url = "https://files.pythonhosted.org/packages/c7/2b/8530ed41112dd4a22f4dcfdb5ccf6a1baad1ff6eed8dc5a5f09e7e8c41c7/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8979280bdafff686ba5e4d8f97840f929a87ed9cdf133cbbd42f7766774d2aa", size = 594816, upload-time = "2025-10-14T15:04:52.031Z" }, + { url = "https://files.pythonhosted.org/packages/ce/d2/f5f9fb49489f184f18470d4f99f4e862a4b3e9ac2865688eb2099e3d837a/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dcc5c24523771db3a294c77d94771abcfcb82a0e0ee8efd910c37c59ec1b31bb", size = 475186, upload-time = "2025-10-14T15:04:53.064Z" }, { url = "https://files.pythonhosted.org/packages/cf/68/5707da262a119fb06fbe214d82dd1fe4a6f4af32d2d14de368d0349eb52a/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db5d7ae38ff20153d542460752ff397fcf5c96090c1230803713cf3147a6803", size = 456812, upload-time = "2025-10-14T15:04:55.174Z" }, { url = "https://files.pythonhosted.org/packages/66/ab/3cbb8756323e8f9b6f9acb9ef4ec26d42b2109bce830cc1f3468df20511d/watchfiles-1.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:28475ddbde92df1874b6c5c8aaeb24ad5be47a11f87cde5a28ef3835932e3e94", size = 630196, upload-time = "2025-10-14T15:04:56.22Z" }, { url = "https://files.pythonhosted.org/packages/78/46/7152ec29b8335f80167928944a94955015a345440f524d2dfe63fc2f437b/watchfiles-1.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:36193ed342f5b9842edd3532729a2ad55c4160ffcfa3700e0d54be496b70dd43", size = 622657, upload-time = "2025-10-14T15:04:57.521Z" }, { url = "https://files.pythonhosted.org/packages/2b/f9/f07a295cde762644aa4c4bb0f88921d2d141af45e735b965fb2e87858328/watchfiles-1.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5f3bde70f157f84ece3765b42b4a52c6ac1a50334903c6eaf765362f6ccca88a", size = 391783, upload-time = "2025-10-14T15:05:03.052Z" }, { url = "https://files.pythonhosted.org/packages/bc/11/fc2502457e0bea39a5c958d86d2cb69e407a4d00b85735ca724bfa6e0d1a/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14e0b1fe858430fc0251737ef3824c54027bedb8c37c38114488b8e131cf8219", size = 449279, upload-time = "2025-10-14T15:05:04.004Z" }, + { url = "https://files.pythonhosted.org/packages/e3/1f/d66bc15ea0b728df3ed96a539c777acfcad0eb78555ad9efcaa1274688f0/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f27db948078f3823a6bb3b465180db8ebecf26dd5dae6f6180bd87383b6b4428", size = 459405, upload-time = "2025-10-14T15:05:04.942Z" }, + { url = "https://files.pythonhosted.org/packages/37/57/ee347af605d867f712be7029bb94c8c071732a4b44792e3176fa3c612d39/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfb5862016acc9b869bb57284e6cb35fdf8e22fe59f7548858e2f971d045f150", size = 595506, upload-time = "2025-10-14T15:05:06.906Z" }, + { url = "https://files.pythonhosted.org/packages/a8/78/cc5ab0b86c122047f75e8fc471c67a04dee395daf847d3e59381996c8707/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:319b27255aacd9923b8a276bb14d21a5f7ff82564c744235fc5eae58d95422ae", size = 474936, upload-time = "2025-10-14T15:05:07.906Z" }, { url = "https://files.pythonhosted.org/packages/62/da/def65b170a3815af7bd40a3e7010bf6ab53089ef1b75d05dd5385b87cf08/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c755367e51db90e75b19454b680903631d41f9e3607fbd941d296a020c2d752d", size = 456147, upload-time = "2025-10-14T15:05:09.138Z" }, { url = "https://files.pythonhosted.org/packages/57/99/da6573ba71166e82d288d4df0839128004c67d2778d3b566c138695f5c0b/watchfiles-1.1.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c22c776292a23bfc7237a98f791b9ad3144b02116ff10d820829ce62dff46d0b", size = 630007, upload-time = "2025-10-14T15:05:10.117Z" }, { url = "https://files.pythonhosted.org/packages/a8/51/7439c4dd39511368849eb1e53279cd3454b4a4dbace80bab88feeb83c6b5/watchfiles-1.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:3a476189be23c3686bc2f4321dd501cb329c0a0469e77b7b534ee10129ae6374", size = 622280, upload-time = "2025-10-14T15:05:11.146Z" }, { url = "https://files.pythonhosted.org/packages/15/49/08732f90ce0fbbc13913f9f215c689cfc9ced345fb1bcd8829a50007cc8d/watchfiles-1.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3ad9fe1dae4ab4212d8c91e80b832425e24f421703b5a42ef2e4a1e215aff051", size = 389964, upload-time = "2025-10-14T15:05:16.85Z" }, { url = "https://files.pythonhosted.org/packages/27/0d/7c315d4bd5f2538910491a0393c56bf70d333d51bc5b34bee8e68e8cea19/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce70f96a46b894b36eba678f153f052967a0d06d5b5a19b336ab0dbbd029f73e", size = 448114, upload-time = "2025-10-14T15:05:17.876Z" }, + { url = "https://files.pythonhosted.org/packages/c3/24/9e096de47a4d11bc4df41e9d1e61776393eac4cb6eb11b3e23315b78b2cc/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cb467c999c2eff23a6417e58d75e5828716f42ed8289fe6b77a7e5a91036ca70", size = 460264, upload-time = "2025-10-14T15:05:18.962Z" }, + { url = "https://files.pythonhosted.org/packages/ac/5b/df24cfc6424a12deb41503b64d42fbea6b8cb357ec62ca84a5a3476f654a/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:743185e7372b7bc7c389e1badcc606931a827112fbbd37f14c537320fca08620", size = 595176, upload-time = "2025-10-14T15:05:21.134Z" }, + { url = "https://files.pythonhosted.org/packages/8f/b5/853b6757f7347de4e9b37e8cc3289283fb983cba1ab4d2d7144694871d9c/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:afaeff7696e0ad9f02cbb8f56365ff4686ab205fcf9c4c5b6fdfaaa16549dd04", size = 473577, upload-time = "2025-10-14T15:05:22.306Z" }, { url = "https://files.pythonhosted.org/packages/e1/f7/0a4467be0a56e80447c8529c9fce5b38eab4f513cb3d9bf82e7392a5696b/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f7eb7da0eb23aa2ba036d4f616d46906013a68caf61b7fdbe42fc8b25132e77", size = 455425, upload-time = "2025-10-14T15:05:23.348Z" }, { url = "https://files.pythonhosted.org/packages/8e/e0/82583485ea00137ddf69bc84a2db88bd92ab4a6e3c405e5fb878ead8d0e7/watchfiles-1.1.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:831a62658609f0e5c64178211c942ace999517f5770fe9436be4c2faeba0c0ef", size = 628826, upload-time = "2025-10-14T15:05:24.398Z" }, { url = "https://files.pythonhosted.org/packages/28/9a/a785356fccf9fae84c0cc90570f11702ae9571036fb25932f1242c82191c/watchfiles-1.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:f9a2ae5c91cecc9edd47e041a930490c31c3afb1f5e6d71de3dc671bfaca02bf", size = 622208, upload-time = "2025-10-14T15:05:25.45Z" }, @@ -11441,23 +11891,39 @@ sdist = { url = "https://files.pythonhosted.org/packages/02/84/30869e01909fb37a6 wheels = [ { url = "https://files.pythonhosted.org/packages/5e/ec/1cc11cd13e26ea8bc3cb4af4eaadd8d46d5014aebb67be3f71fb0b68802a/xxhash-3.6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2b6821e94346f96db75abaa6e255706fb06ebd530899ed76d32cd99f20dc52fa", size = 30809, upload-time = "2025-10-02T14:34:15.484Z" }, { url = "https://files.pythonhosted.org/packages/90/3b/d1f1a8f5442a5fd8beedae110c5af7604dc37349a8e16519c13c19a9a2de/xxhash-3.6.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b29ee68625ab37b04c0b40c3fafdf24d2f75ccd778333cfb698f65f6c463f62", size = 213550, upload-time = "2025-10-02T14:34:17.878Z" }, + { url = "https://files.pythonhosted.org/packages/c4/ef/3a9b05eb527457d5db13a135a2ae1a26c80fecd624d20f3e8dcc4cb170f3/xxhash-3.6.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6812c25fe0d6c36a46ccb002f40f27ac903bf18af9f6dd8f9669cb4d176ab18f", size = 212384, upload-time = "2025-10-02T14:34:19.182Z" }, + { url = "https://files.pythonhosted.org/packages/0f/18/ccc194ee698c6c623acbf0f8c2969811a8a4b6185af5e824cd27b9e4fd3e/xxhash-3.6.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4ccbff013972390b51a18ef1255ef5ac125c92dc9143b2d1909f59abc765540e", size = 445749, upload-time = "2025-10-02T14:34:20.659Z" }, { url = "https://files.pythonhosted.org/packages/a5/86/cf2c0321dc3940a7aa73076f4fd677a0fb3e405cb297ead7d864fd90847e/xxhash-3.6.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:297b7fbf86c82c550e12e8fb71968b3f033d27b874276ba3624ea868c11165a8", size = 193880, upload-time = "2025-10-02T14:34:22.431Z" }, { url = "https://files.pythonhosted.org/packages/82/fb/96213c8560e6f948a1ecc9a7613f8032b19ee45f747f4fca4eb31bb6d6ed/xxhash-3.6.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dea26ae1eb293db089798d3973a5fc928a18fdd97cc8801226fae705b02b14b0", size = 210912, upload-time = "2025-10-02T14:34:23.937Z" }, + { url = "https://files.pythonhosted.org/packages/67/74/b044fcd6b3d89e9b1b665924d85d3f400636c23590226feb1eb09e1176ce/xxhash-3.6.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:08d45aef063a4531b785cd72de4887766d01dc8f362a515693df349fdb825e0c", size = 210867, upload-time = "2025-10-02T14:34:27.203Z" }, + { url = "https://files.pythonhosted.org/packages/bc/fd/3ce73bf753b08cb19daee1eb14aa0d7fe331f8da9c02dd95316ddfe5275e/xxhash-3.6.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:929142361a48ee07f09121fe9e96a84950e8d4df3bb298ca5d88061969f34d7b", size = 414012, upload-time = "2025-10-02T14:34:28.409Z" }, { url = "https://files.pythonhosted.org/packages/ba/b3/5a4241309217c5c876f156b10778f3ab3af7ba7e3259e6d5f5c7d0129eb2/xxhash-3.6.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:51312c768403d8540487dbbfb557454cfc55589bbde6424456951f7fcd4facb3", size = 191409, upload-time = "2025-10-02T14:34:29.696Z" }, { url = "https://files.pythonhosted.org/packages/79/35/0429ee11d035fc33abe32dca1b2b69e8c18d236547b9a9b72c1929189b9a/xxhash-3.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b7b2df81a23f8cb99656378e72501b2cb41b1827c0f5a86f87d6b06b69f9f204", size = 30816, upload-time = "2025-10-02T14:34:36.043Z" }, { url = "https://files.pythonhosted.org/packages/4c/ed/6224ba353690d73af7a3f1c7cdb1fc1b002e38f783cb991ae338e1eb3d79/xxhash-3.6.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:93f107c673bccf0d592cdba077dedaf52fe7f42dcd7676eba1f6d6f0c3efffd2", size = 212914, upload-time = "2025-10-02T14:34:38.6Z" }, + { url = "https://files.pythonhosted.org/packages/38/86/fb6b6130d8dd6b8942cc17ab4d90e223653a89aa32ad2776f8af7064ed13/xxhash-3.6.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2aa5ee3444c25b69813663c9f8067dcfaa2e126dc55e8dddf40f4d1c25d7effa", size = 212163, upload-time = "2025-10-02T14:34:39.872Z" }, + { url = "https://files.pythonhosted.org/packages/ee/dc/e84875682b0593e884ad73b2d40767b5790d417bde603cceb6878901d647/xxhash-3.6.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f7f99123f0e1194fa59cc69ad46dbae2e07becec5df50a0509a808f90a0f03f0", size = 445411, upload-time = "2025-10-02T14:34:41.569Z" }, { url = "https://files.pythonhosted.org/packages/11/4f/426f91b96701ec2f37bb2b8cec664eff4f658a11f3fa9d94f0a887ea6d2b/xxhash-3.6.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:49e03e6fe2cac4a1bc64952dd250cf0dbc5ef4ebb7b8d96bce82e2de163c82a2", size = 193883, upload-time = "2025-10-02T14:34:43.249Z" }, { url = "https://files.pythonhosted.org/packages/53/5a/ddbb83eee8e28b778eacfc5a85c969673e4023cdeedcfcef61f36731610b/xxhash-3.6.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bd17fede52a17a4f9a7bc4472a5867cb0b160deeb431795c0e4abe158bc784e9", size = 210392, upload-time = "2025-10-02T14:34:45.042Z" }, + { url = "https://files.pythonhosted.org/packages/58/ca/faa05ac19b3b622c7c9317ac3e23954187516298a091eb02c976d0d3dd45/xxhash-3.6.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:843b52f6d88071f87eba1631b684fcb4b2068cd2180a0224122fe4ef011a9374", size = 210655, upload-time = "2025-10-02T14:34:47.571Z" }, + { url = "https://files.pythonhosted.org/packages/d4/7a/06aa7482345480cc0cb597f5c875b11a82c3953f534394f620b0be2f700c/xxhash-3.6.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:7d14a6cfaf03b1b6f5f9790f76880601ccc7896aff7ab9cd8978a939c1eb7e0d", size = 414001, upload-time = "2025-10-02T14:34:49.273Z" }, { url = "https://files.pythonhosted.org/packages/23/07/63ffb386cd47029aa2916b3d2f454e6cc5b9f5c5ada3790377d5430084e7/xxhash-3.6.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:418daf3db71e1413cfe211c2f9a528456936645c17f46b5204705581a45390ae", size = 191431, upload-time = "2025-10-02T14:34:50.798Z" }, { url = "https://files.pythonhosted.org/packages/31/a8/3fbce1cd96534a95e35d5120637bf29b0d7f5d8fa2f6374e31b4156dd419/xxhash-3.6.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7d8b8aaa30fca4f16f0c84a5c8d7ddee0e25250ec2796c973775373257dde8f1", size = 30821, upload-time = "2025-10-02T14:34:57.219Z" }, { url = "https://files.pythonhosted.org/packages/ba/0c/71435dcb99874b09a43b8d7c54071e600a7481e42b3e3ce1eb5226a5711a/xxhash-3.6.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:858dc935963a33bc33490128edc1c12b0c14d9c7ebaa4e387a7869ecc4f3e263", size = 212975, upload-time = "2025-10-02T14:35:00.816Z" }, + { url = "https://files.pythonhosted.org/packages/84/7a/c2b3d071e4bb4a90b7057228a99b10d51744878f4a8a6dd643c8bd897620/xxhash-3.6.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ba284920194615cb8edf73bf52236ce2e1664ccd4a38fdb543506413529cc546", size = 212241, upload-time = "2025-10-02T14:35:02.207Z" }, + { url = "https://files.pythonhosted.org/packages/81/5f/640b6eac0128e215f177df99eadcd0f1b7c42c274ab6a394a05059694c5a/xxhash-3.6.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4b54219177f6c6674d5378bd862c6aedf64725f70dd29c472eaae154df1a2e89", size = 445471, upload-time = "2025-10-02T14:35:03.61Z" }, { url = "https://files.pythonhosted.org/packages/5e/1e/3c3d3ef071b051cc3abbe3721ffb8365033a172613c04af2da89d5548a87/xxhash-3.6.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:42c36dd7dbad2f5238950c377fcbf6811b1cdb1c444fab447960030cea60504d", size = 193936, upload-time = "2025-10-02T14:35:05.013Z" }, { url = "https://files.pythonhosted.org/packages/2c/bd/4a5f68381939219abfe1c22a9e3a5854a4f6f6f3c4983a87d255f21f2e5d/xxhash-3.6.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f22927652cba98c44639ffdc7aaf35828dccf679b10b31c4ad72a5b530a18eb7", size = 210440, upload-time = "2025-10-02T14:35:06.239Z" }, + { url = "https://files.pythonhosted.org/packages/d7/fd/2c0a00c97b9e18f72e1f240ad4e8f8a90fd9d408289ba9c7c495ed7dc05c/xxhash-3.6.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:6f2580ffab1a8b68ef2b901cde7e55fa8da5e4be0977c68f78fc80f3c143de42", size = 210689, upload-time = "2025-10-02T14:35:09.438Z" }, + { url = "https://files.pythonhosted.org/packages/93/86/5dd8076a926b9a95db3206aba20d89a7fc14dd5aac16e5c4de4b56033140/xxhash-3.6.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:40c391dd3cd041ebc3ffe6f2c862f402e306eb571422e0aa918d8070ba31da11", size = 414068, upload-time = "2025-10-02T14:35:11.162Z" }, { url = "https://files.pythonhosted.org/packages/af/3c/0bb129170ee8f3650f08e993baee550a09593462a5cddd8e44d0011102b1/xxhash-3.6.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f205badabde7aafd1a31e8ca2a3e5a763107a71c397c4481d6a804eb5063d8bd", size = 191495, upload-time = "2025-10-02T14:35:12.971Z" }, { url = "https://files.pythonhosted.org/packages/9f/3c/0573299560d7d9f8ab1838f1efc021a280b5ae5ae2e849034ef3dee18810/xxhash-3.6.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:568a6d743219e717b07b4e03b0a828ce593833e498c3b64752e0f5df6bfe84db", size = 31072, upload-time = "2025-10-02T14:35:18.844Z" }, { url = "https://files.pythonhosted.org/packages/e3/8e/c6d158d12a79bbd0b878f8355432075fc82759e356ab5a111463422a239b/xxhash-3.6.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:78e7f2f4c521c30ad5e786fdd6bae89d47a32672a80195467b5de0480aa97b1f", size = 215736, upload-time = "2025-10-02T14:35:21.616Z" }, + { url = "https://files.pythonhosted.org/packages/bc/68/c4c80614716345d55071a396cf03d06e34b5f4917a467faf43083c995155/xxhash-3.6.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3ed0df1b11a79856df5ffcab572cbd6b9627034c1c748c5566fa79df9048a7c5", size = 214833, upload-time = "2025-10-02T14:35:23.32Z" }, + { url = "https://files.pythonhosted.org/packages/7e/e9/ae27c8ffec8b953efa84c7c4a6c6802c263d587b9fc0d6e7cea64e08c3af/xxhash-3.6.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0e4edbfc7d420925b0dd5e792478ed393d6e75ff8fc219a6546fb446b6a417b1", size = 448348, upload-time = "2025-10-02T14:35:25.111Z" }, { url = "https://files.pythonhosted.org/packages/d7/6b/33e21afb1b5b3f46b74b6bd1913639066af218d704cc0941404ca717fc57/xxhash-3.6.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fba27a198363a7ef87f8c0f6b171ec36b674fe9053742c58dd7e3201c1ab30ee", size = 196070, upload-time = "2025-10-02T14:35:26.586Z" }, { url = "https://files.pythonhosted.org/packages/96/b6/fcabd337bc5fa624e7203aa0fa7d0c49eed22f72e93229431752bddc83d9/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:794fe9145fe60191c6532fa95063765529770edcdd67b3d537793e8004cabbfd", size = 212907, upload-time = "2025-10-02T14:35:28.087Z" }, + { url = "https://files.pythonhosted.org/packages/0d/98/e8de5baa5109394baf5118f5e72ab21a86387c4f89b0e77ef3e2f6b0327b/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:f01375c0e55395b814a679b3eea205db7919ac2af213f4a6682e01220e5fe292", size = 213304, upload-time = "2025-10-02T14:35:31.222Z" }, + { url = "https://files.pythonhosted.org/packages/7b/1d/71056535dec5c3177eeb53e38e3d367dd1d16e024e63b1cee208d572a033/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:d706dca2d24d834a4661619dcacf51a75c16d65985718d6a7d73c1eeeb903ddf", size = 416930, upload-time = "2025-10-02T14:35:32.517Z" }, { url = "https://files.pythonhosted.org/packages/dc/6c/5cbde9de2cd967c322e651c65c543700b19e7ae3e0aae8ece3469bf9683d/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5f059d9faeacd49c0215d66f4056e1326c80503f51a1532ca336a385edadd033", size = 193787, upload-time = "2025-10-02T14:35:33.827Z" }, { url = "https://files.pythonhosted.org/packages/50/55/15a7b8a56590e66ccd374bbfa3f9ffc45b810886c8c3b614e3f90bd2367c/xxhash-3.6.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:881b47fc47e051b37d94d13e7455131054b56749b91b508b0907eb07900d1c13", size = 36251, upload-time = "2025-10-02T14:37:04.44Z" }, { url = "https://files.pythonhosted.org/packages/62/b2/5ac99a041a29e58e95f907876b04f7067a0242cb85b5f39e726153981503/xxhash-3.6.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c6dc31591899f5e5666f04cc2e529e69b4072827085c1ef15294d91a004bc1bd", size = 32481, upload-time = "2025-10-02T14:37:05.869Z" }, @@ -11495,26 +11961,58 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a2/aa/60da938b8f0997ba3a911263c40d82b6f645a67902a490b46f3355e10fae/yarl-1.23.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b35d13d549077713e4414f927cdc388d62e543987c572baee613bf82f11a4b99", size = 123641, upload-time = "2026-03-01T22:04:42.841Z" }, { url = "https://files.pythonhosted.org/packages/b2/0d/71ceabc14c146ba8ee3804ca7b3d42b1664c8440439de5214d366fec7d3a/yarl-1.23.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dc52310451fc7c629e13c4e061cbe2dd01684d91f2f8ee2821b083c58bd72432", size = 85988, upload-time = "2026-03-01T22:04:46.365Z" }, { url = "https://files.pythonhosted.org/packages/8c/6c/4a90d59c572e46b270ca132aca66954f1175abd691f74c1ef4c6711828e2/yarl-1.23.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b2c6b50c7b0464165472b56b42d4c76a7b864597007d9c085e8b63e185cf4a7a", size = 100566, upload-time = "2026-03-01T22:04:47.639Z" }, + { url = "https://files.pythonhosted.org/packages/49/fb/c438fb5108047e629f6282a371e6e91cf3f97ee087c4fb748a1f32ceef55/yarl-1.23.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:aafe5dcfda86c8af00386d7781d4c2181b5011b7be3f2add5e99899ea925df05", size = 92079, upload-time = "2026-03-01T22:04:48.925Z" }, + { url = "https://files.pythonhosted.org/packages/d9/13/d269aa1aed3e4f50a5a103f96327210cc5fa5dd2d50882778f13c7a14606/yarl-1.23.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9ee33b875f0b390564c1fb7bc528abf18c8ee6073b201c6ae8524aca778e2d83", size = 108741, upload-time = "2026-03-01T22:04:50.838Z" }, + { url = "https://files.pythonhosted.org/packages/85/fb/115b16f22c37ea4437d323e472945bea97301c8ec6089868fa560abab590/yarl-1.23.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4c41e021bc6d7affb3364dc1e1e5fa9582b470f283748784bd6ea0558f87f42c", size = 108099, upload-time = "2026-03-01T22:04:52.499Z" }, { url = "https://files.pythonhosted.org/packages/9a/64/c53487d9f4968045b8afa51aed7ca44f58b2589e772f32745f3744476c82/yarl-1.23.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:99c8a9ed30f4164bc4c14b37a90208836cbf50d4ce2a57c71d0f52c7fb4f7598", size = 102678, upload-time = "2026-03-01T22:04:55.176Z" }, + { url = "https://files.pythonhosted.org/packages/85/59/cd98e556fbb2bf8fab29c1a722f67ad45c5f3447cac798ab85620d1e70af/yarl-1.23.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f2af5c81a1f124609d5f33507082fc3f739959d4719b56877ab1ee7e7b3d602b", size = 100803, upload-time = "2026-03-01T22:04:56.588Z" }, { url = "https://files.pythonhosted.org/packages/9e/c0/b39770b56d4a9f0bb5f77e2f1763cd2d75cc2f6c0131e3b4c360348fcd65/yarl-1.23.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6b41389c19b07c760c7e427a3462e8ab83c4bb087d127f0e854c706ce1b9215c", size = 100163, upload-time = "2026-03-01T22:04:58.492Z" }, + { url = "https://files.pythonhosted.org/packages/e7/64/6980f99ab00e1f0ff67cb84766c93d595b067eed07439cfccfc8fb28c1a6/yarl-1.23.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:1dc702e42d0684f42d6519c8d581e49c96cefaaab16691f03566d30658ee8788", size = 93859, upload-time = "2026-03-01T22:05:00.268Z" }, + { url = "https://files.pythonhosted.org/packages/38/69/912e6c5e146793e5d4b5fe39ff5b00f4d22463dfd5a162bec565ac757673/yarl-1.23.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:0e40111274f340d32ebcc0a5668d54d2b552a6cca84c9475859d364b380e3222", size = 108202, upload-time = "2026-03-01T22:05:02.273Z" }, + { url = "https://files.pythonhosted.org/packages/59/97/35ca6767524687ad64e5f5c31ad54bc76d585585a9fcb40f649e7e82ffed/yarl-1.23.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:4764a6a7588561a9aef92f65bda2c4fb58fe7c675c0883862e6df97559de0bfb", size = 99866, upload-time = "2026-03-01T22:05:03.597Z" }, + { url = "https://files.pythonhosted.org/packages/d3/1c/1a3387ee6d73589f6f2a220ae06f2984f6c20b40c734989b0a44f5987308/yarl-1.23.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:03214408cfa590df47728b84c679ae4ef00be2428e11630277be0727eba2d7cc", size = 107852, upload-time = "2026-03-01T22:05:04.986Z" }, { url = "https://files.pythonhosted.org/packages/a4/b8/35c0750fcd5a3f781058bfd954515dd4b1eab45e218cbb85cf11132215f1/yarl-1.23.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:170e26584b060879e29fac213e4228ef063f39128723807a312e5c7fec28eff2", size = 102919, upload-time = "2026-03-01T22:05:06.397Z" }, { url = "https://files.pythonhosted.org/packages/88/8a/94615bc31022f711add374097ad4144d569e95ff3c38d39215d07ac153a0/yarl-1.23.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1932b6b8bba8d0160a9d1078aae5838a66039e8832d41d2992daa9a3a08f7860", size = 124737, upload-time = "2026-03-01T22:05:12.897Z" }, { url = "https://files.pythonhosted.org/packages/19/2a/725ecc166d53438bc88f76822ed4b1e3b10756e790bafd7b523fe97c322d/yarl-1.23.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:13a563739ae600a631c36ce096615fe307f131344588b0bc0daec108cdb47b25", size = 86310, upload-time = "2026-03-01T22:05:15.71Z" }, { url = "https://files.pythonhosted.org/packages/99/30/58260ed98e6ff7f90ba84442c1ddd758c9170d70327394a6227b310cd60f/yarl-1.23.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cbf44c5cb4a7633d078788e1b56387e3d3cf2b8139a3be38040b22d6c3221c8", size = 97587, upload-time = "2026-03-01T22:05:17.384Z" }, + { url = "https://files.pythonhosted.org/packages/76/0a/8b08aac08b50682e65759f7f8dde98ae8168f72487e7357a5d684c581ef9/yarl-1.23.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:53ad387048f6f09a8969631e4de3f1bf70c50e93545d64af4f751b2498755072", size = 92528, upload-time = "2026-03-01T22:05:18.804Z" }, + { url = "https://files.pythonhosted.org/packages/52/07/0b7179101fe5f8385ec6c6bb5d0cb9f76bd9fb4a769591ab6fb5cdbfc69a/yarl-1.23.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4a59ba56f340334766f3a4442e0efd0af895fae9e2b204741ef885c446b3a1a8", size = 105339, upload-time = "2026-03-01T22:05:20.235Z" }, + { url = "https://files.pythonhosted.org/packages/d3/8a/36d82869ab5ec829ca8574dfcb92b51286fcfb1e9c7a73659616362dc880/yarl-1.23.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:803a3c3ce4acc62eaf01eaca1208dcf0783025ef27572c3336502b9c232005e7", size = 105061, upload-time = "2026-03-01T22:05:22.268Z" }, { url = "https://files.pythonhosted.org/packages/66/3e/868e5c3364b6cee19ff3e1a122194fa4ce51def02c61023970442162859e/yarl-1.23.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3d2bff8f37f8d0f96c7ec554d16945050d54462d6e95414babaa18bfafc7f51", size = 100132, upload-time = "2026-03-01T22:05:23.638Z" }, + { url = "https://files.pythonhosted.org/packages/cf/26/9c89acf82f08a52cb52d6d39454f8d18af15f9d386a23795389d1d423823/yarl-1.23.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c75eb09e8d55bceb4367e83496ff8ef2bc7ea6960efb38e978e8073ea59ecb67", size = 99289, upload-time = "2026-03-01T22:05:25.749Z" }, { url = "https://files.pythonhosted.org/packages/6f/54/5b0db00d2cb056922356104468019c0a132e89c8d3ab67d8ede9f4483d2a/yarl-1.23.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877b0738624280e34c55680d6054a307aa94f7d52fa0e3034a9cc6e790871da7", size = 96950, upload-time = "2026-03-01T22:05:27.318Z" }, + { url = "https://files.pythonhosted.org/packages/f6/40/10fa93811fd439341fad7e0718a86aca0de9548023bbb403668d6555acab/yarl-1.23.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b5405bb8f0e783a988172993cfc627e4d9d00432d6bbac65a923041edacf997d", size = 93960, upload-time = "2026-03-01T22:05:28.738Z" }, + { url = "https://files.pythonhosted.org/packages/bc/d2/8ae2e6cd77d0805f4526e30ec43b6f9a3dfc542d401ac4990d178e4bf0cf/yarl-1.23.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1c3a3598a832590c5a3ce56ab5576361b5688c12cb1d39429cf5dba30b510760", size = 104703, upload-time = "2026-03-01T22:05:30.438Z" }, + { url = "https://files.pythonhosted.org/packages/2f/0c/b3ceacf82c3fe21183ce35fa2acf5320af003d52bc1fcf5915077681142e/yarl-1.23.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:8419ebd326430d1cbb7efb5292330a2cf39114e82df5cc3d83c9a0d5ebeaf2f2", size = 98325, upload-time = "2026-03-01T22:05:31.835Z" }, + { url = "https://files.pythonhosted.org/packages/9d/e0/12900edd28bdab91a69bd2554b85ad7b151f64e8b521fe16f9ad2f56477a/yarl-1.23.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:be61f6fff406ca40e3b1d84716fde398fc08bc63dd96d15f3a14230a0973ed86", size = 105067, upload-time = "2026-03-01T22:05:33.358Z" }, { url = "https://files.pythonhosted.org/packages/15/61/74bb1182cf79c9bbe4eb6b1f14a57a22d7a0be5e9cedf8e2d5c2086474c3/yarl-1.23.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3ceb13c5c858d01321b5d9bb65e4cf37a92169ea470b70fec6f236b2c9dd7e34", size = 100285, upload-time = "2026-03-01T22:05:35.4Z" }, { url = "https://files.pythonhosted.org/packages/9a/4b/a0a6e5d0ee8a2f3a373ddef8a4097d74ac901ac363eea1440464ccbe0898/yarl-1.23.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:16c6994ac35c3e74fb0ae93323bf8b9c2a9088d55946109489667c510a7d010e", size = 123796, upload-time = "2026-03-01T22:05:41.412Z" }, { url = "https://files.pythonhosted.org/packages/ae/50/06d511cc4b8e0360d3c94af051a768e84b755c5eb031b12adaaab6dec6e5/yarl-1.23.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7c6b9461a2a8b47c65eef63bb1c76a4f1c119618ffa99ea79bc5bb1e46c5821b", size = 85854, upload-time = "2026-03-01T22:05:44.85Z" }, { url = "https://files.pythonhosted.org/packages/c4/f4/4e30b250927ffdab4db70da08b9b8d2194d7c7b400167b8fbeca1e4701ca/yarl-1.23.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2569b67d616eab450d262ca7cb9f9e19d2f718c70a8b88712859359d0ab17035", size = 98351, upload-time = "2026-03-01T22:05:46.836Z" }, + { url = "https://files.pythonhosted.org/packages/86/fc/4118c5671ea948208bdb1492d8b76bdf1453d3e73df051f939f563e7dcc5/yarl-1.23.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e9d9a4d06d3481eab79803beb4d9bd6f6a8e781ec078ac70d7ef2dcc29d1bea5", size = 92711, upload-time = "2026-03-01T22:05:48.316Z" }, + { url = "https://files.pythonhosted.org/packages/56/11/1ed91d42bd9e73c13dc9e7eb0dd92298d75e7ac4dd7f046ad0c472e231cd/yarl-1.23.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f514f6474e04179d3d33175ed3f3e31434d3130d42ec153540d5b157deefd735", size = 106014, upload-time = "2026-03-01T22:05:50.028Z" }, + { url = "https://files.pythonhosted.org/packages/ce/c9/74e44e056a23fbc33aca71779ef450ca648a5bc472bdad7a82339918f818/yarl-1.23.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fda207c815b253e34f7e1909840fd14299567b1c0eb4908f8c2ce01a41265401", size = 105557, upload-time = "2026-03-01T22:05:51.416Z" }, { url = "https://files.pythonhosted.org/packages/66/fe/b1e10b08d287f518994f1e2ff9b6d26f0adeecd8dd7d533b01bab29a3eda/yarl-1.23.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34b6cf500e61c90f305094911f9acc9c86da1a05a7a3f5be9f68817043f486e4", size = 101559, upload-time = "2026-03-01T22:05:52.872Z" }, + { url = "https://files.pythonhosted.org/packages/72/59/c5b8d94b14e3d3c2a9c20cb100119fd534ab5a14b93673ab4cc4a4141ea5/yarl-1.23.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d7504f2b476d21653e4d143f44a175f7f751cd41233525312696c76aa3dbb23f", size = 100502, upload-time = "2026-03-01T22:05:54.954Z" }, { url = "https://files.pythonhosted.org/packages/77/4f/96976cb54cbfc5c9fd73ed4c51804f92f209481d1fb190981c0f8a07a1d7/yarl-1.23.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:578110dd426f0d209d1509244e6d4a3f1a3e9077655d98c5f22583d63252a08a", size = 98027, upload-time = "2026-03-01T22:05:56.409Z" }, + { url = "https://files.pythonhosted.org/packages/63/6e/904c4f476471afdbad6b7e5b70362fb5810e35cd7466529a97322b6f5556/yarl-1.23.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:609d3614d78d74ebe35f54953c5bbd2ac647a7ddb9c30a5d877580f5e86b22f2", size = 95369, upload-time = "2026-03-01T22:05:58.141Z" }, + { url = "https://files.pythonhosted.org/packages/9d/40/acfcdb3b5f9d68ef499e39e04d25e141fe90661f9d54114556cf83be8353/yarl-1.23.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4966242ec68afc74c122f8459abd597afd7d8a60dc93d695c1334c5fd25f762f", size = 105565, upload-time = "2026-03-01T22:06:00.286Z" }, + { url = "https://files.pythonhosted.org/packages/5e/c6/31e28f3a6ba2869c43d124f37ea5260cac9c9281df803c354b31f4dd1f3c/yarl-1.23.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:e0fd068364a6759bc794459f0a735ab151d11304346332489c7972bacbe9e72b", size = 99813, upload-time = "2026-03-01T22:06:01.712Z" }, + { url = "https://files.pythonhosted.org/packages/08/1f/6f65f59e72d54aa467119b63fc0b0b1762eff0232db1f4720cd89e2f4a17/yarl-1.23.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:39004f0ad156da43e86aa71f44e033de68a44e5a31fc53507b36dd253970054a", size = 105632, upload-time = "2026-03-01T22:06:03.188Z" }, { url = "https://files.pythonhosted.org/packages/a3/c4/18b178a69935f9e7a338127d5b77d868fdc0f0e49becd286d51b3a18c61d/yarl-1.23.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e5723c01a56c5028c807c701aa66722916d2747ad737a046853f6c46f4875543", size = 101895, upload-time = "2026-03-01T22:06:04.651Z" }, { url = "https://files.pythonhosted.org/packages/9c/fc/119dd07004f17ea43bb91e3ece6587759edd7519d6b086d16bfbd3319982/yarl-1.23.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:aecfed0b41aa72b7881712c65cf764e39ce2ec352324f5e0837c7048d9e6daaa", size = 130719, upload-time = "2026-03-01T22:06:11.708Z" }, { url = "https://files.pythonhosted.org/packages/50/93/e88f3c80971b42cfc83f50a51b9d165a1dbf154b97005f2994a79f212a07/yarl-1.23.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cde9a2ecd91668bcb7f077c4966d8ceddb60af01b52e6e3e2680e4cf00ad1a59", size = 89851, upload-time = "2026-03-01T22:06:15.53Z" }, { url = "https://files.pythonhosted.org/packages/1c/07/61c9dd8ba8f86473263b4036f70fb594c09e99c0d9737a799dfd8bc85651/yarl-1.23.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5023346c4ee7992febc0068e7593de5fa2bf611848c08404b35ebbb76b1b0512", size = 95874, upload-time = "2026-03-01T22:06:17.553Z" }, + { url = "https://files.pythonhosted.org/packages/9e/e9/f9ff8ceefba599eac6abddcfb0b3bee9b9e636e96dbf54342a8577252379/yarl-1.23.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d1009abedb49ae95b136a8904a3f71b342f849ffeced2d3747bf29caeda218c4", size = 88710, upload-time = "2026-03-01T22:06:19.004Z" }, + { url = "https://files.pythonhosted.org/packages/eb/78/0231bfcc5d4c8eec220bc2f9ef82cb4566192ea867a7c5b4148f44f6cbcd/yarl-1.23.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a8d00f29b42f534cc8aa3931cfe773b13b23e561e10d2b26f27a8d309b0e82a1", size = 101033, upload-time = "2026-03-01T22:06:21.203Z" }, + { url = "https://files.pythonhosted.org/packages/cd/9b/30ea5239a61786f18fd25797151a17fbb3be176977187a48d541b5447dd4/yarl-1.23.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:95451e6ce06c3e104556d73b559f5da6c34a069b6b62946d3ad66afcd51642ea", size = 100817, upload-time = "2026-03-01T22:06:22.738Z" }, { url = "https://files.pythonhosted.org/packages/62/e2/a4980481071791bc83bce2b7a1a1f7adcabfa366007518b4b845e92eeee3/yarl-1.23.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:531ef597132086b6cf96faa7c6c1dcd0361dd5f1694e5cc30375907b9b7d3ea9", size = 97482, upload-time = "2026-03-01T22:06:24.21Z" }, + { url = "https://files.pythonhosted.org/packages/e5/1e/304a00cf5f6100414c4b5a01fc7ff9ee724b62158a08df2f8170dfc72a2d/yarl-1.23.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:88f9fb0116fbfcefcab70f85cf4b74a2b6ce5d199c41345296f49d974ddb4123", size = 95949, upload-time = "2026-03-01T22:06:25.697Z" }, { url = "https://files.pythonhosted.org/packages/68/03/093f4055ed4cae649ac53bca3d180bd37102e9e11d048588e9ab0c0108d0/yarl-1.23.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e7b0460976dc75cb87ad9cc1f9899a4b97751e7d4e77ab840fc9b6d377b8fd24", size = 95839, upload-time = "2026-03-01T22:06:27.309Z" }, + { url = "https://files.pythonhosted.org/packages/b9/28/4c75ebb108f322aa8f917ae10a8ffa4f07cae10a8a627b64e578617df6a0/yarl-1.23.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:115136c4a426f9da976187d238e84139ff6b51a20839aa6e3720cd1026d768de", size = 90696, upload-time = "2026-03-01T22:06:29.048Z" }, + { url = "https://files.pythonhosted.org/packages/23/9c/42c2e2dd91c1a570402f51bdf066bfdb1241c2240ba001967bad778e77b7/yarl-1.23.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:ead11956716a940c1abc816b7df3fa2b84d06eaed8832ca32f5c5e058c65506b", size = 100865, upload-time = "2026-03-01T22:06:30.525Z" }, + { url = "https://files.pythonhosted.org/packages/74/05/1bcd60a8a0a914d462c305137246b6f9d167628d73568505fce3f1cb2e65/yarl-1.23.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:fe8f8f5e70e6dbdfca9882cd9deaac058729bcf323cf7a58660901e55c9c94f6", size = 96234, upload-time = "2026-03-01T22:06:32.692Z" }, + { url = "https://files.pythonhosted.org/packages/90/b2/f52381aac396d6778ce516b7bc149c79e65bfc068b5de2857ab69eeea3b7/yarl-1.23.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:a0e317df055958a0c1e79e5d2aa5a5eaa4a6d05a20d4b0c9c3f48918139c9fc6", size = 100295, upload-time = "2026-03-01T22:06:34.268Z" }, { url = "https://files.pythonhosted.org/packages/e5/e8/638bae5bbf1113a659b2435d8895474598afe38b4a837103764f603aba56/yarl-1.23.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f0fd84de0c957b2d280143522c4f91a73aada1923caee763e24a2b3fda9f8a5", size = 97784, upload-time = "2026-03-01T22:06:35.864Z" }, { url = "https://files.pythonhosted.org/packages/69/68/c8739671f5699c7dc470580a4f821ef37c32c4cb0b047ce223a7f115757f/yarl-1.23.0-py3-none-any.whl", hash = "sha256:a2df6afe50dea8ae15fa34c9f824a3ee958d785fd5d089063d960bae1daa0a3f", size = 48288, upload-time = "2026-03-01T22:07:51.388Z" }, ] @@ -11536,23 +12034,35 @@ sdist = { url = "https://files.pythonhosted.org/packages/fd/aa/3e0508d5a5dd96529 wheels = [ { url = "https://files.pythonhosted.org/packages/ac/4d/e66465c5411a7cf4866aeadc7d108081d8ceba9bc7abe6b14aa21c671ec3/zstandard-0.25.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3f79487c687b1fc69f19e487cd949bf3aae653d181dfb5fde3bf6d18894706f", size = 640559, upload-time = "2025-09-14T22:16:27.973Z" }, { url = "https://files.pythonhosted.org/packages/3b/13/2b7ed68bd85e69a2069bcc72141d378f22cae5a0f3b353a2c8f50ef30c1b/zstandard-0.25.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:01582723b3ccd6939ab7b3a78622c573799d5d8737b534b86d0e06ac18dbde4a", size = 5058126, upload-time = "2025-09-14T22:16:31.811Z" }, + { url = "https://files.pythonhosted.org/packages/c9/dd/fdaf0674f4b10d92cb120ccff58bbb6626bf8368f00ebfd2a41ba4a0dc99/zstandard-0.25.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5f1ad7bf88535edcf30038f6919abe087f606f62c00a87d7e33e7fc57cb69fcc", size = 5405390, upload-time = "2025-09-14T22:16:33.486Z" }, + { url = "https://files.pythonhosted.org/packages/0f/67/354d1555575bc2490435f90d67ca4dd65238ff2f119f30f72d5cde09c2ad/zstandard-0.25.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:06acb75eebeedb77b69048031282737717a63e71e4ae3f77cc0c3b9508320df6", size = 5452914, upload-time = "2025-09-14T22:16:35.277Z" }, { url = "https://files.pythonhosted.org/packages/bb/1f/e9cfd801a3f9190bf3e759c422bbfd2247db9d7f3d54a56ecde70137791a/zstandard-0.25.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9300d02ea7c6506f00e627e287e0492a5eb0371ec1670ae852fefffa6164b072", size = 5559635, upload-time = "2025-09-14T22:16:37.141Z" }, { url = "https://files.pythonhosted.org/packages/21/88/5ba550f797ca953a52d708c8e4f380959e7e3280af029e38fbf47b55916e/zstandard-0.25.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bfd06b1c5584b657a2892a6014c2f4c20e0db0208c159148fa78c65f7e0b0277", size = 5048277, upload-time = "2025-09-14T22:16:38.807Z" }, { url = "https://files.pythonhosted.org/packages/46/c0/ca3e533b4fa03112facbe7fbe7779cb1ebec215688e5df576fe5429172e0/zstandard-0.25.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f373da2c1757bb7f1acaf09369cdc1d51d84131e50d5fa9863982fd626466313", size = 5574377, upload-time = "2025-09-14T22:16:40.523Z" }, { url = "https://files.pythonhosted.org/packages/12/9b/3fb626390113f272abd0799fd677ea33d5fc3ec185e62e6be534493c4b60/zstandard-0.25.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6c0e5a65158a7946e7a7affa6418878ef97ab66636f13353b8502d7ea03c8097", size = 4961493, upload-time = "2025-09-14T22:16:43.3Z" }, + { url = "https://files.pythonhosted.org/packages/8c/a7/bb5a0c1c0f3f4b5e9d5b55198e39de91e04ba7c205cc46fcb0f95f0383c1/zstandard-0.25.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:98750a309eb2f020da61e727de7d7ba3c57c97cf6213f6f6277bb7fb42a8e065", size = 5443672, upload-time = "2025-09-14T22:16:47.076Z" }, + { url = "https://files.pythonhosted.org/packages/27/22/503347aa08d073993f25109c36c8d9f029c7d5949198050962cb568dfa5e/zstandard-0.25.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:22a086cff1b6ceca18a8dd6096ec631e430e93a8e70a9ca5efa7561a00f826fa", size = 5822753, upload-time = "2025-09-14T22:16:49.316Z" }, { url = "https://files.pythonhosted.org/packages/e2/be/94267dc6ee64f0f8ba2b2ae7c7a2df934a816baaa7291db9e1aa77394c3c/zstandard-0.25.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:72d35d7aa0bba323965da807a462b0966c91608ef3a48ba761678cb20ce5d8b7", size = 5366047, upload-time = "2025-09-14T22:16:51.328Z" }, { url = "https://files.pythonhosted.org/packages/aa/1c/d920d64b22f8dd028a8b90e2d756e431a5d86194caa78e3819c7bf53b4b3/zstandard-0.25.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:913cbd31a400febff93b564a23e17c3ed2d56c064006f54efec210d586171c00", size = 640436, upload-time = "2025-09-14T22:16:57.774Z" }, { url = "https://files.pythonhosted.org/packages/1e/15/efef5a2f204a64bdb5571e6161d49f7ef0fffdbca953a615efbec045f60f/zstandard-0.25.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6dffecc361d079bb48d7caef5d673c88c8988d3d33fb74ab95b7ee6da42652ea", size = 5063012, upload-time = "2025-09-14T22:17:01.156Z" }, + { url = "https://files.pythonhosted.org/packages/b7/37/a6ce629ffdb43959e92e87ebdaeebb5ac81c944b6a75c9c47e300f85abdf/zstandard-0.25.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:7149623bba7fdf7e7f24312953bcf73cae103db8cae49f8154dd1eadc8a29ecb", size = 5394148, upload-time = "2025-09-14T22:17:03.091Z" }, + { url = "https://files.pythonhosted.org/packages/e3/79/2bf870b3abeb5c070fe2d670a5a8d1057a8270f125ef7676d29ea900f496/zstandard-0.25.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:6a573a35693e03cf1d67799fd01b50ff578515a8aeadd4595d2a7fa9f3ec002a", size = 5451652, upload-time = "2025-09-14T22:17:04.979Z" }, { url = "https://files.pythonhosted.org/packages/53/60/7be26e610767316c028a2cbedb9a3beabdbe33e2182c373f71a1c0b88f36/zstandard-0.25.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5a56ba0db2d244117ed744dfa8f6f5b366e14148e00de44723413b2f3938a902", size = 5546993, upload-time = "2025-09-14T22:17:06.781Z" }, { url = "https://files.pythonhosted.org/packages/85/c7/3483ad9ff0662623f3648479b0380d2de5510abf00990468c286c6b04017/zstandard-0.25.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:10ef2a79ab8e2974e2075fb984e5b9806c64134810fac21576f0668e7ea19f8f", size = 5046806, upload-time = "2025-09-14T22:17:08.415Z" }, { url = "https://files.pythonhosted.org/packages/08/b3/206883dd25b8d1591a1caa44b54c2aad84badccf2f1de9e2d60a446f9a25/zstandard-0.25.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aaf21ba8fb76d102b696781bddaa0954b782536446083ae3fdaa6f16b25a1c4b", size = 5576659, upload-time = "2025-09-14T22:17:10.164Z" }, { url = "https://files.pythonhosted.org/packages/9d/31/76c0779101453e6c117b0ff22565865c54f48f8bd807df2b00c2c404b8e0/zstandard-0.25.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1869da9571d5e94a85a5e8d57e4e8807b175c9e4a6294e3b66fa4efb074d90f6", size = 4953933, upload-time = "2025-09-14T22:17:11.857Z" }, + { url = "https://files.pythonhosted.org/packages/1e/73/316e4010de585ac798e154e88fd81bb16afc5c5cb1a72eeb16dd37e8024a/zstandard-0.25.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f27662e4f7dbf9f9c12391cb37b4c4c3cb90ffbd3b1fb9284dadbbb8935fa708", size = 5433517, upload-time = "2025-09-14T22:17:16.103Z" }, + { url = "https://files.pythonhosted.org/packages/5b/60/dd0f8cfa8129c5a0ce3ea6b7f70be5b33d2618013a161e1ff26c2b39787c/zstandard-0.25.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:99c0c846e6e61718715a3c9437ccc625de26593fea60189567f0118dc9db7512", size = 5814292, upload-time = "2025-09-14T22:17:17.827Z" }, { url = "https://files.pythonhosted.org/packages/fc/5f/75aafd4b9d11b5407b641b8e41a57864097663699f23e9ad4dbb91dc6bfe/zstandard-0.25.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:474d2596a2dbc241a556e965fb76002c1ce655445e4e3bf38e5477d413165ffa", size = 5360237, upload-time = "2025-09-14T22:17:19.954Z" }, { url = "https://files.pythonhosted.org/packages/3f/06/9ae96a3e5dcfd119377ba33d4c42a7d89da1efabd5cb3e366b156c45ff4d/zstandard-0.25.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a1a4ae2dec3993a32247995bdfe367fc3266da832d82f8438c8570f989753de1", size = 640440, upload-time = "2025-09-14T22:17:27.366Z" }, { url = "https://files.pythonhosted.org/packages/6d/db/ddb11011826ed7db9d0e485d13df79b58586bfdec56e5c84a928a9a78c1c/zstandard-0.25.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bfc4e20784722098822e3eee42b8e576b379ed72cca4a7cb856ae733e62192ea", size = 5063001, upload-time = "2025-09-14T22:17:31.044Z" }, + { url = "https://files.pythonhosted.org/packages/db/00/87466ea3f99599d02a5238498b87bf84a6348290c19571051839ca943777/zstandard-0.25.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:457ed498fc58cdc12fc48f7950e02740d4f7ae9493dd4ab2168a47c93c31298e", size = 5394120, upload-time = "2025-09-14T22:17:32.711Z" }, + { url = "https://files.pythonhosted.org/packages/2b/95/fc5531d9c618a679a20ff6c29e2b3ef1d1f4ad66c5e161ae6ff847d102a9/zstandard-0.25.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:fd7a5004eb1980d3cefe26b2685bcb0b17989901a70a1040d1ac86f1d898c551", size = 5451230, upload-time = "2025-09-14T22:17:34.41Z" }, { url = "https://files.pythonhosted.org/packages/63/4b/e3678b4e776db00f9f7b2fe58e547e8928ef32727d7a1ff01dea010f3f13/zstandard-0.25.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8e735494da3db08694d26480f1493ad2cf86e99bdd53e8e9771b2752a5c0246a", size = 5547173, upload-time = "2025-09-14T22:17:36.084Z" }, { url = "https://files.pythonhosted.org/packages/4e/d5/ba05ed95c6b8ec30bd468dfeab20589f2cf709b5c940483e31d991f2ca58/zstandard-0.25.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3a39c94ad7866160a4a46d772e43311a743c316942037671beb264e395bdd611", size = 5046736, upload-time = "2025-09-14T22:17:37.891Z" }, { url = "https://files.pythonhosted.org/packages/50/d5/870aa06b3a76c73eced65c044b92286a3c4e00554005ff51962deef28e28/zstandard-0.25.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:172de1f06947577d3a3005416977cce6168f2261284c02080e7ad0185faeced3", size = 5576368, upload-time = "2025-09-14T22:17:40.206Z" }, { url = "https://files.pythonhosted.org/packages/5d/35/398dc2ffc89d304d59bc12f0fdd931b4ce455bddf7038a0a67733a25f550/zstandard-0.25.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3c83b0188c852a47cd13ef3bf9209fb0a77fa5374958b8c53aaa699398c6bd7b", size = 4954022, upload-time = "2025-09-14T22:17:41.879Z" }, + { url = "https://files.pythonhosted.org/packages/70/e8/2ec6b6fb7358b2ec0113ae202647ca7c0e9d15b61c005ae5225ad0995df5/zstandard-0.25.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:0be7622c37c183406f3dbf0cba104118eb16a4ea7359eeb5752f0794882fc250", size = 5433952, upload-time = "2025-09-14T22:17:45.271Z" }, + { url = "https://files.pythonhosted.org/packages/7b/01/b5f4d4dbc59ef193e870495c6f1275f5b2928e01ff5a81fecb22a06e22fb/zstandard-0.25.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:5f5e4c2a23ca271c218ac025bd7d635597048b366d6f31f420aaeb715239fc98", size = 5814054, upload-time = "2025-09-14T22:17:47.08Z" }, { url = "https://files.pythonhosted.org/packages/b2/e5/fbd822d5c6f427cf158316d012c5a12f233473c2f9c5fe5ab1ae5d21f3d8/zstandard-0.25.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f187a0bb61b35119d1926aee039524d1f93aaf38a9916b8c4b78ac8514a0aaf", size = 5360113, upload-time = "2025-09-14T22:17:48.893Z" }, ] From f273841e502978951f84a512c75a7c4a4f3b0520 Mon Sep 17 00:00:00 2001 From: Alec Khoury Date: Tue, 28 Jul 2026 13:34:01 -0500 Subject: [PATCH 2/6] refactor: keep Eval Author thin; hard-depend on Experimentalist modules Own only the agent package and AUTHOR_* config in the plugin; restore evaluator/staging/trace helpers to Experimentalist and install both via the experimentalist uv group to avoid a circular package dependency. Signed-off-by: Alec Khoury --- plugins/nemo-eval-author/.example.env | 32 ++ plugins/nemo-eval-author/README.md | 45 +- plugins/nemo-eval-author/pyproject.toml | 11 +- .../src/nemo_eval_author_plugin/backend.py | 122 ----- .../src/nemo_eval_author_plugin/cache.py | 121 ----- .../src/nemo_eval_author_plugin/client.py | 51 -- .../eval_author/README.md | 8 +- .../eval_author/agent.py | 24 +- .../eval_author/materialization.py | 4 +- .../eval_author/models.py | 2 +- .../eval_author/run.py | 16 +- .../nemo_eval_author_plugin/holdout_utils.py | 96 ---- .../nemo_eval_author_plugin/model_config.py | 146 ++++-- .../src/nemo_eval_author_plugin/repository.py | 438 ------------------ .../src/nemo_eval_author_plugin/tools.py | 221 --------- .../src/nemo_eval_author_plugin/util.py | 19 - .../workspace_entities.py | 35 -- plugins/nemo-eval-author/tests/conftest.py | 30 ++ .../tests/test_eval_author_agent.py | 4 +- .../tests/test_eval_author_materialization.py | 4 +- .../tests/test_eval_author_run.py | 10 +- .../tests/test_model_config.py | 70 +++ plugins/nemo-experimentalist/AGENTS.md | 22 +- .../nemo-experimentalist/benchmarks/run.py | 4 +- plugins/nemo-experimentalist/pyproject.toml | 1 - .../nemo_experimentalist_plugin/entities.py | 2 +- .../experimentalist/components/analyzer.py | 6 +- .../experimentalist/components/coder.py | 2 +- .../components}/dataset_staging.py | 2 +- .../components}/evaluator/__init__.py | 4 +- .../components}/evaluator/base.py | 2 +- .../components}/evaluator/factory.py | 6 +- .../components}/evaluator/harbor.py | 4 +- .../components}/evaluator/models.py | 0 .../experimentalist/components/goal_tree.py | 2 +- .../components/insight_promotion.py | 4 +- .../experimentalist/components/loop.py | 12 +- .../experimentalist/components}/rationale.py | 2 +- .../components/rationalizer.py | 20 +- .../components}/trace_analyzer.py | 17 +- .../components}/trace_explorer.py | 2 +- .../components/trace_scorer.py | 4 +- .../experimentalist/deps.py | 4 +- .../experimentalist_backend.py | 2 +- .../experimentalist/otlp.py | 2 +- .../experimentalist/run.py | 2 +- .../nemo_experimentalist_plugin/resolve.py | 2 +- .../experimentalist}/test_dataset_staging.py | 4 +- .../test_dataset_staging_loop.py | 2 +- .../test_eval_author_repair_e2e.py | 6 +- .../experimentalist}/test_evaluator_base.py | 2 +- .../test_evaluator_factory.py | 10 +- .../experimentalist}/test_evaluator_harbor.py | 52 +-- .../test_loop_insight_suite.py | 6 +- .../experimentalist/test_loop_reporting.py | 4 +- .../test_trace_explorer_from_ref.py | 4 +- .../nemo-experimentalist/tests/test_deps.py | 2 +- .../tests/test_experiment_cli.py | 2 +- .../tests/test_experimentalist_analyzer.py | 2 +- .../tests/test_experimentalist_backend.py | 2 +- .../tests/test_experimentalist_benchmark.py | 2 +- .../tests/test_experimentalist_run.py | 2 +- .../nemo-experimentalist/tests/test_otlp.py | 2 +- pyproject.toml | 8 +- uv.lock | 18 +- 65 files changed, 446 insertions(+), 1323 deletions(-) create mode 100644 plugins/nemo-eval-author/.example.env delete mode 100644 plugins/nemo-eval-author/src/nemo_eval_author_plugin/backend.py delete mode 100644 plugins/nemo-eval-author/src/nemo_eval_author_plugin/cache.py delete mode 100644 plugins/nemo-eval-author/src/nemo_eval_author_plugin/client.py delete mode 100644 plugins/nemo-eval-author/src/nemo_eval_author_plugin/holdout_utils.py delete mode 100644 plugins/nemo-eval-author/src/nemo_eval_author_plugin/repository.py delete mode 100644 plugins/nemo-eval-author/src/nemo_eval_author_plugin/tools.py delete mode 100644 plugins/nemo-eval-author/src/nemo_eval_author_plugin/util.py delete mode 100644 plugins/nemo-eval-author/src/nemo_eval_author_plugin/workspace_entities.py create mode 100644 plugins/nemo-eval-author/tests/conftest.py create mode 100644 plugins/nemo-eval-author/tests/test_model_config.py rename plugins/{nemo-eval-author/src/nemo_eval_author_plugin => nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components}/dataset_staging.py (96%) rename plugins/{nemo-eval-author/src/nemo_eval_author_plugin => nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components}/evaluator/__init__.py (83%) rename plugins/{nemo-eval-author/src/nemo_eval_author_plugin => nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components}/evaluator/base.py (98%) rename plugins/{nemo-eval-author/src/nemo_eval_author_plugin => nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components}/evaluator/factory.py (93%) rename plugins/{nemo-eval-author/src/nemo_eval_author_plugin => nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components}/evaluator/harbor.py (99%) rename plugins/{nemo-eval-author/src/nemo_eval_author_plugin => nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components}/evaluator/models.py (100%) rename plugins/{nemo-eval-author/src/nemo_eval_author_plugin => nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components}/rationale.py (81%) rename plugins/{nemo-eval-author/src/nemo_eval_author_plugin => nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components}/trace_analyzer.py (95%) rename plugins/{nemo-eval-author/src/nemo_eval_author_plugin => nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components}/trace_explorer.py (99%) rename plugins/{nemo-eval-author/tests => nemo-experimentalist/tests/experimentalist}/test_dataset_staging.py (97%) rename plugins/{nemo-eval-author/tests => nemo-experimentalist/tests/experimentalist}/test_evaluator_base.py (98%) rename plugins/{nemo-eval-author/tests => nemo-experimentalist/tests/experimentalist}/test_evaluator_factory.py (92%) rename plugins/{nemo-eval-author/tests => nemo-experimentalist/tests/experimentalist}/test_evaluator_harbor.py (95%) rename plugins/{nemo-eval-author/tests => nemo-experimentalist/tests/experimentalist}/test_trace_explorer_from_ref.py (93%) diff --git a/plugins/nemo-eval-author/.example.env b/plugins/nemo-eval-author/.example.env new file mode 100644 index 00000000000..e093dc8f788 --- /dev/null +++ b/plugins/nemo-eval-author/.example.env @@ -0,0 +1,32 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Example environment for standalone Eval Author runs. +# Copy to `.env` next to this file (or export in your shell): +# +# cp .example.env .env +# +# Eval Author reads AUTHOR_* first. When unset, it falls back to +# EXPERIMENTALIST_* so the same credentials still work when Experimentalist +# invokes Eval Author in insight mode. +# +# TODO(cli): wire `nemo eval-author` to load this `.env` the way Experimentalist +# loads the profile-dir `.env`. + +# Required for LLM-backed Eval Author / TraceAnalyzer (NOOA CompletionClient). +AUTHOR_API_BASE=https://inference-api.nvidia.com/v1 +AUTHOR_API_KEY= + +# Optional model overrides (defaults shown). +# AUTHOR_SMART_MODEL_NAME=openai/openai/openai/gpt-5.5 +# AUTHOR_MID_MODEL_NAME=openai/gcp/google/gemini-3.5-flash +# AUTHOR_FAST_MODEL_NAME=openai/openai/openai/gpt-5-mini + +# Optional: NVIDIA Inference Gateway virtual key. When AUTHOR_API_BASE is +# the NVIDIA gateway and AUTHOR_API_KEY is unset, standalone runners may +# use INFERENCE_API_KEY the same way Experimentalist does today. +# INFERENCE_API_KEY= + +# Optional: NeMo Platform URL for Insight / Fileset / agent-code access. +# Leave unset to use the active `nemo auth` context, or set for a local stack. +# NMP_BASE_URL=http://localhost:8080 diff --git a/plugins/nemo-eval-author/README.md b/plugins/nemo-eval-author/README.md index f672588ef9e..590d5bc59ea 100644 --- a/plugins/nemo-eval-author/README.md +++ b/plugins/nemo-eval-author/README.md @@ -1,8 +1,16 @@ # NeMo Eval Author Plugin -Library-only plugin that owns the Eval Author agent, Harbor evaluator stack, trace analysis helpers, and dataset staging used to author evaluation suites from Insights. +Library-only plugin that owns the Eval Author agent (`eval_author/`). Shared +evaluator, trace, staging, tools, and client helpers stay in +`nemo-experimentalist-plugin`; this package takes a hard dependency on +Experimentalist and imports those modules from there. -Experimentalist depends on this plugin the same way it depends on `nemo-insights-plugin`. +Experimentalist insight mode imports `EvalAuthor` from this plugin at runtime. +Install both via the workspace group (avoids a circular package dependency): + +```bash +uv sync --group experimentalist +``` ## Public API @@ -10,27 +18,28 @@ Experimentalist depends on this plugin the same way it depends on `nemo-insights from nemo_eval_author_plugin.eval_author.agent import EvalAuthor, build_eval_author_agent from nemo_eval_author_plugin.eval_author.models import EvalAuthorConfig, EvalAuthorResult from nemo_eval_author_plugin.eval_author.run import run_eval_author -from nemo_eval_author_plugin.evaluator import Dataset, DatasetRef, Evaluator -from nemo_eval_author_plugin.evaluator.factory import DatasetFactory, EvaluatorFactory -from nemo_eval_author_plugin.dataset_staging import stage_eval_author_inputs, stage_task_template -from nemo_eval_author_plugin.trace_analyzer import TraceAnalyzer, TraceAnalyzerConfig, Diagnostic -from nemo_eval_author_plugin.trace_explorer import TraceExplorer + +# Shared infrastructure lives in Experimentalist: +from nemo_experimentalist_plugin.experimentalist.components.evaluator import Dataset +from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import DatasetRef +from nemo_experimentalist_plugin.experimentalist.components.dataset_staging import stage_task_template +from nemo_experimentalist_plugin.experimentalist.components.trace_analyzer import TraceAnalyzer +from nemo_experimentalist_plugin.experimentalist.components.trace_explorer import TraceExplorer ``` -## Install +## Credentials (standalone) -From the repository root: +Copy [`.example.env`](.example.env) to `.env` and set `AUTHOR_API_KEY` (and optionally model names / `NMP_BASE_URL`): ```bash -uv sync --group experimentalist +cp plugins/nemo-eval-author/.example.env plugins/nemo-eval-author/.env ``` -## TODO(shared-module) - -The following modules are exact copies of Experimentalist helpers and should eventually live in a shared package: +`model_config` prefers `AUTHOR_*`. If those are unset it falls back to +`EXPERIMENTALIST_*` so Experimentalist insight-mode keeps working with a single +Experimentalist profile `.env`. On construction, Eval Author also bridges +`AUTHOR_*` into unset `EXPERIMENTALIST_*` slots so Experimentalist helpers such +as `TraceAnalyzer` see credentials during standalone runs. When the API base is +the NVIDIA Inference Gateway, `INFERENCE_API_KEY` is also accepted. -- `tools.py` -- `model_config.py` -- `cache.py` -- `client.py` -- `repository.py` (agent clone helpers used by `backend.py`) +A `nemo eval-author` CLI that auto-loads this `.env` is not wired yet. diff --git a/plugins/nemo-eval-author/pyproject.toml b/plugins/nemo-eval-author/pyproject.toml index d773c32f79d..89046f5ce44 100644 --- a/plugins/nemo-eval-author/pyproject.toml +++ b/plugins/nemo-eval-author/pyproject.toml @@ -1,19 +1,16 @@ [project] name = "nemo-eval-author-plugin" version = "0.1.0" -description = "Eval Author agent and Harbor evaluator stack for NeMo Platform." +description = "Eval Author agent for NeMo Platform (hard-depends on Experimentalist for evaluator/trace helpers)." requires-python = ">=3.11,<3.14" dependencies = [ "pydantic>=2", - "httpx", "harbor>=0.16 ; python_full_version >= '3.12'", - "opentelemetry-proto>=1.42.1", - "protobuf>=6.0.0", "nooa ; python_full_version >= '3.12'", "pyyaml>=6.0.3", + "nemo-experimentalist-plugin", "nemo-insights-plugin", "nemo-platform", - "nemo-platform-plugin", "tomlkit>=0.13.3", ] @@ -28,3 +25,7 @@ packages = ["src/nemo_eval_author_plugin"] asyncio_mode = "auto" pythonpath = ["src"] testpaths = ["tests"] + +[tool.ruff.lint.per-file-ignores] +# Bridge AUTHOR_* → EXPERIMENTALIST_* before TraceAnalyzer import (class-time LLM client). +"src/nemo_eval_author_plugin/eval_author/agent.py" = ["E402"] diff --git a/plugins/nemo-eval-author/src/nemo_eval_author_plugin/backend.py b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/backend.py deleted file mode 100644 index 3ff2eeba752..00000000000 --- a/plugins/nemo-eval-author/src/nemo_eval_author_plugin/backend.py +++ /dev/null @@ -1,122 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -"""Minimal backend for standalone Eval Author runs. - -Replicates the ``get_insight`` and ``get_agent_code`` behavior from -``LocalExperimentalistBackend`` so ``run_eval_author`` does not depend on -Experimentalist. - -TODO(shared-module): unify agent materialization with Experimentalist backend. -""" - -from __future__ import annotations - -import asyncio -import json -import shutil -import subprocess -from pathlib import Path -from typing import Any, Literal, TypeVar, cast - -import httpx -from nemo_eval_author_plugin.repository import ( - AgentSource, - clone_agent_repo, - looks_like_git, - split_agent_spec, - split_git_ref, -) -from nemo_insights_plugin.entities import Insight -from nemo_platform import AsyncNeMoPlatform - -_ModelT = TypeVar("_ModelT") - -_AGENT_COPY_EXCLUDE_NAMES = { - ".git", - ".venv", - "artifacts", - "dataset", - "eval-and-optimize", - "scratch", -} - - -def _ignore_agent_copy(directory: str, contents: list[str]) -> set[str]: - del directory - return {name for name in contents if name in _AGENT_COPY_EXCLUDE_NAMES} - - -def _load_entity(cls: type[_ModelT], path: Path) -> _ModelT: - """Deserialize *path* as JSON into *cls*, restoring the private ``_id`` field.""" - data = json.loads(path.read_text()) - entity_id = data.get("id", "") - obj = cls.model_validate(data) - if entity_id: - cast(Any, obj)._id = entity_id - return obj - - -class EvalAuthorBackend: - """Insight and agent-code materialization for Eval Author runs.""" - - def __init__(self, *, client: AsyncNeMoPlatform | None, path: Path) -> None: - self.client = client - self.path = path - - async def get_insight(self, *, workspace: str, insight_id: str) -> Insight: - p = Path(insight_id) - if p.exists(): - return _load_entity(Insight, p) - if self.client is None: - raise ValueError( - f"Insight {insight_id!r} is not an existing local file and no platform " - "client is available to fetch it from the platform." - ) - try: - return await self.client.insights.insights.get(workspace=workspace, insight_id=insight_id) - except httpx.HTTPStatusError as exc: - if exc.response.status_code == 404: - raise ValueError(f"Insight not found on the platform: {insight_id!r}") from exc - raise ValueError(f"Failed to fetch insight {insight_id!r} from the platform: {exc}") from exc - - async def get_agent_code( - self, *, workspace: str, agent: str | Path, dest: Path, clone_depth: int | None = None - ) -> AgentSource | None: - del workspace - if looks_like_git(str(agent)): - return await self._clone_git_agent(str(agent), dest, clone_depth=clone_depth) - src = Path(agent) - if not src.exists(): - raise FileNotFoundError(f"Local agent path not found: {src}") - if dest.resolve() != src.resolve(): - if dest.exists(): - shutil.rmtree(dest) - shutil.copytree(src, dest, ignore=_ignore_agent_copy) - return None - - async def _clone_git_agent(self, agent: str, dest: Path, *, clone_depth: int | None = None) -> AgentSource: - try: - return await asyncio.to_thread(clone_agent_repo, agent, dest, clone_depth=clone_depth) - except subprocess.CalledProcessError: - remote, _ = split_git_ref(split_agent_spec(agent)[0]) - from nemo_eval_author_plugin.repository import _redact_url - - raise ValueError(f"failed to fetch --agent {_redact_url(remote)!r}") from None - - -def make_eval_author_backend( - *, - client: AsyncNeMoPlatform | None, - experiments_output: str, - mode: Literal["local", "remote"], -) -> EvalAuthorBackend: - """Select the Eval Author backend for *mode*. - - Both local and remote modes use the same insight/agent materialization - implementation; remote mode requires a platform client for platform-backed - insight ids. - """ - if client is None and mode == "remote": - raise ValueError("remote Eval Author backend requires a platform client") - return EvalAuthorBackend(client=client, path=Path(experiments_output)) diff --git a/plugins/nemo-eval-author/src/nemo_eval_author_plugin/cache.py b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/cache.py deleted file mode 100644 index 25716cd5174..00000000000 --- a/plugins/nemo-eval-author/src/nemo_eval_author_plugin/cache.py +++ /dev/null @@ -1,121 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -# TODO(shared-module): exact copy of experimentalist components/cache.py; unify into a shared package. -from __future__ import annotations - -import hashlib -import json -import logging -from pathlib import Path -from typing import TypeVar -from uuid import uuid4 - -from pydantic import BaseModel, ValidationError - -_logger = logging.getLogger(__name__) - -# PEP 695 syntax would read better, but the workspace lints against Python 3.11. -T = TypeVar("T", bound=BaseModel) - - -def task_hash(task_name: str) -> str: - """Return a namespaced sha256 hex digest for a task key. - - Args: - task_name: the task identifier to hash. - - Returns: - str: a ``task-`` prefixed digest string. - - """ - digest = hashlib.sha256(task_name.encode()).hexdigest() - return f"task-{digest}" - - -def agent_hash(agent_id: str) -> str: - """Return a namespaced sha256 hex digest for an agent key. - - Args: - agent_id: the agent identifier to hash. - - Returns: - str: an ``agent-`` prefixed digest string. - - """ - digest = hashlib.sha256(agent_id.encode()).hexdigest() - return f"agent-{digest}" - - -def trace_hash(trace_path: str | Path) -> str: - """Return a namespaced sha256 hex digest of a trace file's contents. - - Args: - trace_path: path to the trace file to hash. - - Returns: - str: a ``trace-`` prefixed digest string. - - """ - h = hashlib.sha256() - with open(trace_path, "rb") as f: - for chunk in iter(lambda: f.read(1 << 20), b""): - h.update(chunk) - digest = h.hexdigest() - return f"trace-{digest}" - - -def _cache_path(workspace: Path, key: str) -> Path: - return workspace / "eval-and-optimize" / "cache" / f"{key}.json" - - -def load(workspace: Path, key: str, model: type[T]) -> T | None: - """Return a previously-stored model instance for this key, or None. - - Args: - workspace: root workspace directory containing the cache. - key: cache key returned by one of the ``*_hash`` functions. - model: Pydantic model class used to deserialise the stored payload. - - Returns: - T | None: the deserialised model instance, or None if the entry is - missing, unreadable, or fails validation. - - """ - path = _cache_path(workspace, key) - if not path.exists(): - return None - try: - payload = json.loads(path.read_text()) - except (OSError, ValueError) as exc: - _logger.warning(f"Cache read failed at {path}: {exc} -- ignoring") - return None - try: - return model.model_validate(payload) - except ValidationError as exc: - _logger.warning(f"Cache payload at {path} does not validate against {model.__name__}: {exc} -- ignoring") - return None - - -def store(workspace: Path, key: str, value: BaseModel) -> None: - """Persist a model instance under the given key, overwriting any prior entry. - - Args: - workspace: root workspace directory containing the cache. - key: cache key returned by one of the ``*_hash`` functions. - value: Pydantic model instance to serialise and store. - - """ - path = _cache_path(workspace, key) - path.parent.mkdir(parents=True, exist_ok=True) - payload = value.model_dump(mode="json") - tmp = path.parent / f"{path.name}.{uuid4().hex}.tmp" - try: - tmp.write_text(json.dumps(payload, indent=2)) - tmp.replace(path) - except OSError as exc: - _logger.warning(f"Cache write failed for key {key}: {exc} -- skipping") - try: - tmp.unlink(missing_ok=True) - except OSError: - pass diff --git a/plugins/nemo-eval-author/src/nemo_eval_author_plugin/client.py b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/client.py deleted file mode 100644 index e7335cdeb2d..00000000000 --- a/plugins/nemo-eval-author/src/nemo_eval_author_plugin/client.py +++ /dev/null @@ -1,51 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -# TODO(shared-module): exact copy of experimentalist client.py; unify into a shared package. - -"""Shared NeMo Platform SDK client construction for Insight consumers. - -Auth lives in the active ``nemo auth login`` context in -``~/.config/nmp/config.yaml``. The SDK only wires up that context (and the -transparent OIDC token refresh that comes with it) when it runs its config -bootstrap. Passing ``base_url`` *alone* puts the SDK in "direct mode", which -skips the bootstrap and injects **no** auth headers — fine for an -unauthenticated local ``nemo services run``, but it 401s against a remote -deployment. To authenticate against a remote URL we must trigger the bootstrap -(by also passing ``config_path``) so the explicit ``base_url`` is combined with -the context's credentials. - -Every Platform Insight consumer takes ``base_url`` from its workflow context, -so this helper is the one place that branch lives. -""" - -from urllib.parse import urlparse - -from nemo_platform import AsyncNeMoPlatform -from nemo_platform.config.config import Config - -# Loopback hosts are served by an unauthenticated local platform; attaching -# (and refreshing) OAuth tokens there is both unnecessary and a failure mode -# when the cached token is stale and OIDC discovery against localhost fails. -LOOPBACK_HOSTS = frozenset({"localhost", "127.0.0.1", "::1", "0.0.0.0"}) - - -def make_client(base_url: str | None) -> AsyncNeMoPlatform: - """Construct an :class:`AsyncNeMoPlatform` honoring an optional ``base_url``. - - - No ``base_url``: use the active nmp context for both URL and auth. - - Loopback ``base_url``: direct mode (local platform is unauthenticated). - - Remote ``base_url`` with an nmp config present: combine the URL with the - context's auth so the SDK injects and refreshes a Bearer token. - - Remote ``base_url`` without an nmp config: direct mode (no credentials to - use; the request will surface a clear auth error). - """ - if not base_url: - return AsyncNeMoPlatform() - - host = (urlparse(base_url).hostname or "").lower() - config_path = Config.get_default_config_path() - if host in LOOPBACK_HOSTS or not config_path.exists(): - return AsyncNeMoPlatform(base_url=base_url) - - return AsyncNeMoPlatform(base_url=base_url, config_path=config_path) diff --git a/plugins/nemo-eval-author/src/nemo_eval_author_plugin/eval_author/README.md b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/eval_author/README.md index 838d5cd1cad..59a33c09f57 100644 --- a/plugins/nemo-eval-author/src/nemo_eval_author_plugin/eval_author/README.md +++ b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/eval_author/README.md @@ -10,8 +10,10 @@ implementation. It turns an Experimentalist Insight and its production trace ref evaluator dataset changes, creating or augmenting regression signals that capture the failure mode before optimization begins. -Experimentalist is a consumer of this package: its insight mode imports and -runs the top-level Eval Author before beginning optimization. +This package hard-depends on Experimentalist for evaluator, staging, and trace +helpers. Experimentalist insight mode imports and runs this Eval Author before +beginning optimization (both packages are installed via the `experimentalist` +uv group). ## Current Files @@ -91,7 +93,7 @@ from pathlib import Path from nemo_eval_author_plugin.eval_author.models import EvalAuthorConfig from nemo_eval_author_plugin.eval_author.run import run_eval_author -from nemo_eval_author_plugin.evaluator.models import DatasetRef +from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import DatasetRef async def main() -> None: diff --git a/plugins/nemo-eval-author/src/nemo_eval_author_plugin/eval_author/agent.py b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/eval_author/agent.py index 68537381cc0..1bddfcde271 100644 --- a/plugins/nemo-eval-author/src/nemo_eval_author_plugin/eval_author/agent.py +++ b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/eval_author/agent.py @@ -12,24 +12,33 @@ from pathlib import Path from typing import Any -from nemo_eval_author_plugin import cache from nemo_eval_author_plugin.eval_author.materialization import InsightSuite from nemo_eval_author_plugin.eval_author.models import EvalAuthorConfig, EvalAuthorResult -from nemo_eval_author_plugin.evaluator import ( +from nemo_eval_author_plugin.model_config import ( + bridge_author_env_to_experimentalist, + get_fast_model, + get_smart_model, +) + +# Bridge before importing Experimentalist agents that read EXPERIMENTALIST_* at +# class-definition time (e.g. helpers pulled in by TraceAnalyzer). +bridge_author_env_to_experimentalist() + +from nemo_experimentalist_plugin.experimentalist.components import cache +from nemo_experimentalist_plugin.experimentalist.components.evaluator import ( Dataset, DatasetValidationError, Task, TrialResult, ) -from nemo_eval_author_plugin.evaluator.models import ResourceRef -from nemo_eval_author_plugin.model_config import get_fast_model, get_smart_model -from nemo_eval_author_plugin.tools import GuardedShellTools -from nemo_eval_author_plugin.trace_analyzer import ( +from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import ResourceRef +from nemo_experimentalist_plugin.experimentalist.components.tools import GuardedShellTools +from nemo_experimentalist_plugin.experimentalist.components.trace_analyzer import ( Diagnostic, TraceAnalyzer, TraceAnalyzerConfig, ) -from nemo_eval_author_plugin.trace_explorer import ( +from nemo_experimentalist_plugin.experimentalist.components.trace_explorer import ( SearchResult, SessionData, SessionSummary, @@ -63,6 +72,7 @@ def __init__(self, experiment_dir: Path, config: EvalAuthorConfig | None = None, config: Tuning parameters; defaults to ``EvalAuthorConfig()``. **kwargs: Forwarded to ``Agent.__init__``. """ + bridge_author_env_to_experimentalist() super().__init__(**kwargs) self._config = config or EvalAuthorConfig() self.experiment_dir = experiment_dir diff --git a/plugins/nemo-eval-author/src/nemo_eval_author_plugin/eval_author/materialization.py b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/eval_author/materialization.py index 783ca481c81..4e084dccb88 100644 --- a/plugins/nemo-eval-author/src/nemo_eval_author_plugin/eval_author/materialization.py +++ b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/eval_author/materialization.py @@ -18,8 +18,8 @@ import tomlkit from harbor.models.task.task import Task as HarborTask -from nemo_eval_author_plugin.evaluator.harbor import HarborDataset -from nemo_eval_author_plugin.evaluator.models import Task, local_path_from_uri +from nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor import HarborDataset +from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import Task, local_path_from_uri _MANIFEST_SCHEMA_VERSION = 3 _CONTENT_HASH_SCHEMA_VERSION = 1 diff --git a/plugins/nemo-eval-author/src/nemo_eval_author_plugin/eval_author/models.py b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/eval_author/models.py index 40b9ae15196..83f5e6f86be 100644 --- a/plugins/nemo-eval-author/src/nemo_eval_author_plugin/eval_author/models.py +++ b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/eval_author/models.py @@ -3,7 +3,7 @@ """Shared models for the top-level Eval Author.""" -from nemo_eval_author_plugin.evaluator import Dataset +from nemo_experimentalist_plugin.experimentalist.components.evaluator import Dataset from pydantic import BaseModel, ConfigDict, Field diff --git a/plugins/nemo-eval-author/src/nemo_eval_author_plugin/eval_author/run.py b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/eval_author/run.py index d966aafbb7c..945fc17735f 100644 --- a/plugins/nemo-eval-author/src/nemo_eval_author_plugin/eval_author/run.py +++ b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/eval_author/run.py @@ -7,14 +7,14 @@ from pathlib import Path from typing import Literal, Protocol, cast -from nemo_eval_author_plugin.backend import make_eval_author_backend -from nemo_eval_author_plugin.client import make_client -from nemo_eval_author_plugin.dataset_staging import stage_task_template from nemo_eval_author_plugin.eval_author.models import EvalAuthorConfig, EvalAuthorResult -from nemo_eval_author_plugin.evaluator import Dataset, Task -from nemo_eval_author_plugin.evaluator.base import EvaluatorType -from nemo_eval_author_plugin.evaluator.factory import DatasetFactory -from nemo_eval_author_plugin.evaluator.models import DatasetRef +from nemo_experimentalist_plugin.client import make_client +from nemo_experimentalist_plugin.experimentalist.components.dataset_staging import stage_task_template +from nemo_experimentalist_plugin.experimentalist.components.evaluator import Dataset, Task +from nemo_experimentalist_plugin.experimentalist.components.evaluator.base import EvaluatorType +from nemo_experimentalist_plugin.experimentalist.components.evaluator.factory import DatasetFactory +from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import DatasetRef +from nemo_experimentalist_plugin.experimentalist.experimentalist_backend import make_experimentalist_backend from nemo_insights_plugin.entities import Insight from nemo_platform import AsyncNeMoPlatform @@ -76,7 +76,7 @@ async def run_eval_author( client = make_client(base_url) try: - backend = make_eval_author_backend( + backend = make_experimentalist_backend( client=client, experiments_output=str(experiment_dir), mode=mode, diff --git a/plugins/nemo-eval-author/src/nemo_eval_author_plugin/holdout_utils.py b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/holdout_utils.py deleted file mode 100644 index e314556cd34..00000000000 --- a/plugins/nemo-eval-author/src/nemo_eval_author_plugin/holdout_utils.py +++ /dev/null @@ -1,96 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -# TODO(shared-module): exact copy of experimentalist components/holdout_utils.py; unify into a shared package. - -from __future__ import annotations - -import shutil -from pathlib import Path - -HELD_OUT_SPLITS = frozenset({"validation"}) -HELD_OUT_STORAGE_DIR = ".aad-heldout" - -# Path tokens GuardedShellTools refuses: a tripwire for direct shell access while a -# split is briefly restored for scoring. Relocation is the main obfuscation layer. -DEFAULT_BLOCKED_PATHS: tuple[str, ...] = ( - HELD_OUT_STORAGE_DIR, - *(f"dataset/{s}" for s in sorted(HELD_OUT_SPLITS)), -) - -BLOCKED_MESSAGE = ( - "blocked: the validation split is held out for scoring only. " - "Its contents are off-limits; diagnose and fix using the train split." -) - - -class HeldOutAccessError(PermissionError): - """Raised when held-out split contents or debugging artifacts are requested.""" - - -def heldout_storage_root(workspace: Path) -> Path: - """Return the workspace-local directory used to store held-out splits. - - Args: - workspace: absolute path to the eval-and-optimize workspace root. - - Returns: - Path: the hidden storage directory for held-out splits. - - """ - workspace = Path(workspace).resolve() - return workspace / HELD_OUT_STORAGE_DIR - - -def _split_paths(workspace: Path, split: str) -> tuple[Path, Path]: - """Return the ``(visible, hidden)`` locations for ``split``.""" - workspace = Path(workspace).resolve() - return workspace / "dataset" / split, heldout_storage_root(workspace) / split - - -def _move(src: Path, dst: Path, split: str) -> None: - """Delete any existing ``dst``, then rename ``src`` to ``dst``.""" - if dst.exists(): - shutil.rmtree(dst) - dst.parent.mkdir(parents=True, exist_ok=True) - src.rename(dst) - - -def ensure_heldout_hidden(workspace: Path, *, splits: frozenset[str] = HELD_OUT_SPLITS) -> None: - """Move any visible held-out splits into hidden storage. - - Idempotent — safe to call before every optimizer phase. - - Args: - workspace: absolute path to the eval-and-optimize workspace root. - splits: set of split names to hide; defaults to ``HELD_OUT_SPLITS``. - - """ - for split in splits: - visible, hidden = _split_paths(workspace, split) - if visible.exists(): - _move(visible, hidden, split) - - -def restore_heldout_splits(workspace: Path, *, splits: frozenset[str] = HELD_OUT_SPLITS) -> None: - """Restore hidden held-out splits back into ``dataset/``. - - Args: - workspace: absolute path to the eval-and-optimize workspace root. - splits: set of split names to restore; defaults to ``HELD_OUT_SPLITS``. - - """ - for split in splits: - visible, hidden = _split_paths(workspace, split) - if hidden.exists(): - _move(hidden, visible, split) - _prune_empty_storage(workspace) - - -def _prune_empty_storage(workspace: Path) -> None: - """Remove the hidden storage dir if empty.""" - root = heldout_storage_root(workspace) - try: - root.rmdir() - except OSError: - pass diff --git a/plugins/nemo-eval-author/src/nemo_eval_author_plugin/model_config.py b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/model_config.py index 865e085be39..6c3a63481e3 100644 --- a/plugins/nemo-eval-author/src/nemo_eval_author_plugin/model_config.py +++ b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/model_config.py @@ -1,23 +1,61 @@ # SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 -# TODO(shared-module): exact copy of experimentalist components/model_config.py; unify into a shared package. +# Eval Author prefers AUTHOR_* and falls back to EXPERIMENTALIST_* for insight-mode compatibility. +# Shared Experimentalist helpers still read EXPERIMENTALIST_*; bridge_author_env_to_experimentalist() +# copies AUTHOR_* into unset EXPERIMENTALIST_* slots for standalone runs. import functools import os from urllib.parse import urlparse from nooa.unifiedllm import CompletionClient +_GATEWAY_HOST = "inference-api.nvidia.com" -def _required_env(name: str) -> str: - value = os.environ.get(name, "").strip() + +def _env(*names: str, default: str | None = None) -> str | None: + for name in names: + value = os.environ.get(name, "").strip() + if value: + return value + return default + + +def _required_author_env(primary: str, fallback: str) -> str: + value = _env(primary, fallback) if not value: - raise ValueError(f"{name} must be set") + raise ValueError(f"{primary} (or {fallback}) must be set") return value -def _optional_env(name: str, default: str) -> str: - return os.environ.get(name, "").strip() or default +def _api_base() -> str: + return _required_author_env("AUTHOR_API_BASE", "EXPERIMENTALIST_API_BASE") + + +def _api_key() -> str: + """Resolve the LLM API key for Eval Author. + + Preference order: + 1. ``AUTHOR_API_KEY`` + 2. ``EXPERIMENTALIST_API_KEY`` + 3. ``INFERENCE_API_KEY`` when the resolved base is the NVIDIA Inference Gateway + """ + key = _env("AUTHOR_API_KEY", "EXPERIMENTALIST_API_KEY") + if key: + return key + base = _api_base() + try: + host = urlparse(base).hostname + except ValueError: + host = None + if host == _GATEWAY_HOST: + inference = _env("INFERENCE_API_KEY") + if inference: + return inference + raise ValueError( + "AUTHOR_API_KEY (or EXPERIMENTALIST_API_KEY) must be set; " + "when using the NVIDIA Inference Gateway, INFERENCE_API_KEY is also accepted" + ) @functools.cache @@ -28,13 +66,16 @@ def get_smart_model() -> CompletionClient: CompletionClient: the singleton smart-model client. Raises: - ValueError: if ``EXPERIMENTALIST_API_BASE`` or ``EXPERIMENTALIST_API_KEY`` are unset or empty. + ValueError: if Eval Author / Experimentalist API credentials are unset or empty. """ - api_base = _required_env("EXPERIMENTALIST_API_BASE") - api_key = _required_env("EXPERIMENTALIST_API_KEY") - name = _optional_env("EXPERIMENTALIST_SMART_MODEL_NAME", "openai/openai/openai/gpt-5.5") - return CompletionClient(name, api_base=api_base, api_key=api_key) + name = _env( + "AUTHOR_SMART_MODEL_NAME", + "EXPERIMENTALIST_SMART_MODEL_NAME", + default="openai/openai/openai/gpt-5.5", + ) + assert name is not None + return CompletionClient(name, api_base=_api_base(), api_key=_api_key()) @functools.cache @@ -45,13 +86,16 @@ def get_mid_model() -> CompletionClient: CompletionClient: the singleton mid-model client. Raises: - ValueError: if ``EXPERIMENTALIST_API_BASE`` or ``EXPERIMENTALIST_API_KEY`` are unset or empty. + ValueError: if Eval Author / Experimentalist API credentials are unset or empty. """ - api_base = _required_env("EXPERIMENTALIST_API_BASE") - api_key = _required_env("EXPERIMENTALIST_API_KEY") - name = _optional_env("EXPERIMENTALIST_MID_MODEL_NAME", "openai/gcp/google/gemini-3.5-flash") - return CompletionClient(name, api_base=api_base, api_key=api_key) + name = _env( + "AUTHOR_MID_MODEL_NAME", + "EXPERIMENTALIST_MID_MODEL_NAME", + default="openai/gcp/google/gemini-3.5-flash", + ) + assert name is not None + return CompletionClient(name, api_base=_api_base(), api_key=_api_key()) @functools.cache @@ -62,13 +106,16 @@ def get_fast_model() -> CompletionClient: CompletionClient: the singleton fast-model client. Raises: - ValueError: if ``EXPERIMENTALIST_API_BASE`` or ``EXPERIMENTALIST_API_KEY`` are unset or empty. + ValueError: if Eval Author / Experimentalist API credentials are unset or empty. """ - api_base = _required_env("EXPERIMENTALIST_API_BASE") - api_key = _required_env("EXPERIMENTALIST_API_KEY") - name = _optional_env("EXPERIMENTALIST_FAST_MODEL_NAME", "openai/openai/openai/gpt-5-mini") - return CompletionClient(name, api_base=api_base, api_key=api_key) + name = _env( + "AUTHOR_FAST_MODEL_NAME", + "EXPERIMENTALIST_FAST_MODEL_NAME", + default="openai/openai/openai/gpt-5-mini", + ) + assert name is not None + return CompletionClient(name, api_base=_api_base(), api_key=_api_key()) def _mask_key(value: str) -> str: @@ -85,23 +132,56 @@ def _sanitize_url(url: str) -> str: return f"{parsed.scheme}://{host}{port}{parsed.path}" +def bridge_author_env_to_experimentalist() -> None: + """Copy ``AUTHOR_*`` into unset ``EXPERIMENTALIST_*`` slots. + + Eval Author owns ``AUTHOR_*`` credentials for its own LLM clients. Shared + Experimentalist helpers (for example ``TraceAnalyzer``) still read + ``EXPERIMENTALIST_*``. Bridging keeps standalone Author runs working without + duplicating credentials. + """ + pairs = ( + ("AUTHOR_API_BASE", "EXPERIMENTALIST_API_BASE"), + ("AUTHOR_API_KEY", "EXPERIMENTALIST_API_KEY"), + ("AUTHOR_SMART_MODEL_NAME", "EXPERIMENTALIST_SMART_MODEL_NAME"), + ("AUTHOR_MID_MODEL_NAME", "EXPERIMENTALIST_MID_MODEL_NAME"), + ("AUTHOR_FAST_MODEL_NAME", "EXPERIMENTALIST_FAST_MODEL_NAME"), + ) + for src, dst in pairs: + value = os.environ.get(src, "").strip() + if value and not os.environ.get(dst, "").strip(): + os.environ[dst] = value + + def log_model_config() -> str: - """Return a formatted string summarising the current optimizer model configuration. + """Return a formatted string summarising the current Eval Author model configuration. Returns: str: a multi-line summary of smart model, fast model, API base, and masked API key. """ - smart = _optional_env("EXPERIMENTALIST_SMART_MODEL_NAME", "openai/openai/openai/gpt-5.5") - mid = _optional_env("EXPERIMENTALIST_MID_MODEL_NAME", "openai/gcp/google/gemini-3.5-flash") - fast = _optional_env("EXPERIMENTALIST_FAST_MODEL_NAME", "openai/openai/openai/gpt-5-mini") - api_base = _required_env("EXPERIMENTALIST_API_BASE") - api_key = _required_env("EXPERIMENTALIST_API_KEY") + smart = _env( + "AUTHOR_SMART_MODEL_NAME", + "EXPERIMENTALIST_SMART_MODEL_NAME", + default="openai/openai/openai/gpt-5.5", + ) + mid = _env( + "AUTHOR_MID_MODEL_NAME", + "EXPERIMENTALIST_MID_MODEL_NAME", + default="openai/gcp/google/gemini-3.5-flash", + ) + fast = _env( + "AUTHOR_FAST_MODEL_NAME", + "EXPERIMENTALIST_FAST_MODEL_NAME", + default="openai/openai/openai/gpt-5-mini", + ) + assert smart is not None and mid is not None and fast is not None + api_base = _api_base() + api_key = _api_key() return ( - "Experimentalist model config:\n" - f" smart model: {smart}\n" - f" mid model: {mid}\n" - f" fast model: {fast}\n" - f" api base: {_sanitize_url(api_base)}\n" - f" api key: {_mask_key(api_key)}" + f"smart model: {smart}\n" + f"mid model: {mid}\n" + f"fast model: {fast}\n" + f"api base: {_sanitize_url(api_base)}\n" + f"api key: {_mask_key(api_key)}" ) diff --git a/plugins/nemo-eval-author/src/nemo_eval_author_plugin/repository.py b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/repository.py deleted file mode 100644 index 1c135bb1f3c..00000000000 --- a/plugins/nemo-eval-author/src/nemo_eval_author_plugin/repository.py +++ /dev/null @@ -1,438 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -# TODO(shared-module): exact copy of experimentalist components/repository.py (clone helpers for backend); unify into a shared package. - -"""Fetch an agent from a git repo and publish a candidate as a draft PR/MR. - -Fetching clones ``[@]`` into a local checkout (retaining ``.git`` so -it can serve as a push target). Publishing takes that checkout plus a directory of -updated agent files, creates a branch off the source ref, commits the files, pushes, -and opens a draft pull request (GitHub) or merge request (GitLab) via the ``gh`` / -``glab`` CLIs. Branching from the exact source ref keeps the published change -reproducible against the commit the agent came from. -""" - -from __future__ import annotations - -import logging -import re -import shutil -import subprocess -from fnmatch import fnmatch -from pathlib import Path -from urllib.parse import urlsplit - -from pydantic import BaseModel - -logger = logging.getLogger(__name__) - -# Upper bound (seconds) for any single git/gh/glab call during publishing, so a -# network stall or auth prompt can't hang the optimizer's awaited publish path. -_COMMAND_TIMEOUT = 120 - -# Matches the ``userinfo@`` of a URL (e.g. ``https://oauth2:@host``). Git clone URLs -# can embed credentials there, so we strip them before logging a URL. -_CREDENTIAL_RE = re.compile(r"://[^/@]+@") - -_AGENT_PATH_ERROR = "agent path must be a normalized relative POSIX path" -_AGENT_PATH_CHECKOUT_ERROR = "agent path must stay within the checkout without symlinks" - - -def _redact_url(url: str) -> str: - """Return *url* with any embedded ``userinfo@`` credentials masked, safe for logging.""" - return _CREDENTIAL_RE.sub("://***@", url) - - -def _validated_agent_path(agent_path: str) -> str: - """Return a safe relative POSIX agent path or raise a controlled error.""" - agent_path = agent_path.removesuffix("/").removeprefix("./") - if agent_path == ".": - return agent_path - components = agent_path.split("/") - if ( - not agent_path - or agent_path.startswith(("/", ":")) - or "\\" in agent_path - or any(ord(char) < 32 or ord(char) == 127 for char in agent_path) - or any(component in {"", ".", ".."} for component in components) - or any(component.casefold() == ".git" for component in components) - ): - raise ValueError(_AGENT_PATH_ERROR) - return agent_path - - -def pr_cli_for_repo_url(url: str) -> tuple[str, str] | None: - """Return the PR CLI and hostname for a supported git remote. - - Substring match on the host family so both github.com and self-hosted - GitLab (e.g. gitlab.example.com) are recognized. - """ - if "github" in url: - cli = "gh" - elif "gitlab" in url: - cli = "glab" - else: - return None - hostname = urlsplit(url).hostname if "://" in url else url.partition("@")[2].partition(":")[0] - return cli, hostname or "" - - -def split_agent_spec(spec: str) -> tuple[str, str]: - """Split an agent spec ``#`` into ``(core_spec, agent_path)``. - - The ``#`` fragment carries the agent's location within the repo (monorepo - support), so repo, rev, and sub-path all travel together on the one ``--agent`` value. - A spec with no fragment has agent_path ``"."`` (whole-repo agent). - """ - core, separator, fragment = spec.partition("#") - agent_path = fragment if separator else "." - return core, _validated_agent_path(agent_path) - - -# Glob patterns for files never copied into the published branch: generated/run-local -# files, VCS/tool dirs, and build artifacts. Matched with fnmatch against each name. -_EXCLUDE_GLOBS = ( - "metadata.json", - "harbor_wrapper.py", - "dind_environment.py", - "architecture.md", - ".git", - "__pycache__", - ".pytest_cache", - ".mypy_cache", - ".ruff_cache", - "*.pyc", - "*.pyo", -) - - -class AgentSource(BaseModel): - """Git provenance of the agent under test. - - Set when ``--agent`` is given as ``@[#]``. ``repo_url`` is the - clone URL (the PR/MR target remote), ``ref`` is the base branch/tag/commit the agent was - checked out from, and ``agent_path`` is the agent's location within the repo (``"."`` for - a whole-repo agent) — the subtree that archival/PR overlays and commits. - """ - - repo_url: str - ref: str - agent_path: str = "." - - -class AgentCloneError(ValueError): - """A git source could not be materialized.""" - - -def split_git_ref(spec: str) -> tuple[str, str | None]: - """Split a ``.git@`` spec into ``(url, ref)``. - - Locates the first ``.git@`` marker in the repository path, excluding URL - authority/userinfo. A spec without a path marker has no ref. - """ - marker = ".git@" - if "://" in spec: - try: - parsed = urlsplit(spec) - except (ValueError, UnicodeError): - return spec, None - idx = parsed.path.find(marker) - if idx == -1: - return spec, None - repo_path = parsed.path[: idx + len(".git")] - ref = parsed.path[idx + len(marker) :] - return parsed._replace(path=repo_path).geturl(), ref - - user_end = spec.find("@") - path_start = spec.find(":", user_end + 1) if user_end >= 0 else -1 - search_start = path_start + 1 if path_start >= 0 else 0 - idx = spec.find(marker, search_start) - if idx == -1: - return spec, None - return spec[: idx + len(".git")], spec[idx + len(marker) :] - - -def looks_like_git(spec: str) -> bool: - """Return True if *spec* looks like a git URL (vs a local path).""" - url, _ = split_git_ref(spec) - return "://" in url or url.startswith("git@") or url.endswith(".git") - - -def clone_agent_repo(spec: str, dest: Path, *, clone_depth: int | None = None) -> AgentSource: - """Clone ``[@][#]`` into *dest* (retaining ``.git``) and return provenance. - - The clone keeps ``.git``/``origin`` so *dest* can serve as the PR/MR push target. The whole - repo is cloned; ``agent_path`` (the ``#`` fragment, ``"."`` when omitted) records the agent's - sub-path within it for downstream overlay/commit. When no ``@ref`` is given, the checked-out - default branch is resolved and recorded. When ``clone_depth`` is given a shallow clone is made - (it must be deep enough to branch from ``ref``). Raises on git failure (callers decide how to - handle). - """ - core, agent_path = split_agent_spec(spec) - url, ref = split_git_ref(core) - dest = Path(dest) - if dest.exists(): - shutil.rmtree(dest) - dest.parent.mkdir(parents=True, exist_ok=True) - safe_url = _redact_url(url) - logger.info(f"[AGENT] cloning {safe_url} -> {dest}") - clone_cmd = ["git", "clone", "--quiet"] - if clone_depth is not None: - clone_cmd += ["--depth", str(clone_depth)] - clone_cmd += [url, str(dest)] - - def run_git(command: list[str], operation: str) -> subprocess.CompletedProcess[str]: - try: - return subprocess.run( # noqa: S603, S607 - command, - capture_output=True, - text=True, - check=True, - stdin=subprocess.DEVNULL, - timeout=_COMMAND_TIMEOUT, - ) - except subprocess.CalledProcessError as exc: - details = _redact_url((exc.stderr or exc.stdout or "").strip()) - suffix = f": {details}" if details else "" - raise AgentCloneError(f"{operation} failed for {safe_url} (exit status {exc.returncode}){suffix}") from None - except subprocess.TimeoutExpired: - raise AgentCloneError(f"{operation} timed out after {_COMMAND_TIMEOUT}s for {safe_url}") from None - except OSError as exc: - raise AgentCloneError(f"could not run 'git' during {operation} for {safe_url}: {exc}") from None - - run_git(clone_cmd, "git clone") - if ref: - run_git(["git", "-C", str(dest), "checkout", "--quiet", ref], "git checkout") - else: - ref = run_git( - ["git", "-C", str(dest), "rev-parse", "--abbrev-ref", "HEAD"], - "git rev-parse", - ).stdout.strip() - # repo_url is provenance (it becomes the candidate's source_link), so keep it - # credential-free; the conventional ssh ``git`` user is not a secret. - repo_url = url if url.startswith("ssh://git@") or "://" not in url else _redact_url(url) - return AgentSource(repo_url=repo_url, ref=ref, agent_path=agent_path) - - -class PRPublisherError(RuntimeError): - """Raised when publishing the winner PR/MR fails.""" - - -class PRPublisher: - """Open a draft PR/MR for the winning candidate against the agent's source repo. - - Args: - agent_dir: The local git checkout of the agent under test (its ``origin`` - remote is the PR/MR target). Must be a git work tree. - """ - - def __init__(self, *, agent_dir: Path) -> None: - self.agent_dir = Path(agent_dir).resolve() - - def _run(self, cmd: list[str], *, cwd: Path | None = None) -> str: - """Run a command and return stdout; raise :class:`PRPublisherError` on failure. - - Bounded by ``_COMMAND_TIMEOUT`` so a git/gh/glab call that blocks on the - network or an auth prompt can never hang the awaited publish path. - """ - command = _redact_url(" ".join(cmd)) - try: - result = subprocess.run( # noqa: S603 - cmd, - cwd=str(cwd or self.agent_dir), - capture_output=True, - text=True, - timeout=_COMMAND_TIMEOUT, - stdin=subprocess.DEVNULL, # never block on an interactive auth prompt - ) - except subprocess.TimeoutExpired: - raise PRPublisherError(f"command timed out after {_COMMAND_TIMEOUT}s: {command}") from None - except OSError as exc: # e.g. git/gh/glab not installed -> FileNotFoundError - raise PRPublisherError(f"could not run {cmd[0]!r}: {exc}") from None - if result.returncode != 0: - stderr = _redact_url(result.stderr.strip()) - raise PRPublisherError(f"command failed ({result.returncode}): {command}\n{stderr}") - return result.stdout.strip() - - # -- helpers -------------------------------------------------------------- - - def _origin_url(self) -> str: - return self._run(["git", "remote", "get-url", "origin"]) - - def _is_remote_branch(self, ref: str) -> bool: - """Return True if *ref* is a branch on origin (vs a tag or bare commit).""" - return bool(self._run(["git", "ls-remote", "--heads", "origin", ref])) - - def _default_branch(self) -> str: - """Resolve origin's default branch (e.g. ``main``) from its local symbolic HEAD. - - Reads ``refs/remotes/origin/HEAD``, which the fresh clone in - ``clone_agent_repo`` sets, so no extra network round-trip is needed. Strips - only the ``origin/`` prefix so slashed branch names (e.g. ``release/2026.07``) - survive intact. - """ - ref = self._run(["git", "rev-parse", "--abbrev-ref", "origin/HEAD"]) # e.g. "origin/main" - prefix = "origin/" - if not ref.startswith(prefix) or ref == "origin/HEAD": - raise PRPublisherError(f"could not resolve origin default branch (got {ref!r})") - return ref[len(prefix) :] - - @staticmethod - def _overlay(winner_dir: Path, dest: Path) -> None: - """Copy the winner's agent files over *dest*, excluding files matching ``_EXCLUDE_GLOBS``. - - Exclusions apply at the top level and recursively (via ``shutil.ignore_patterns``), - so nested ``__pycache__``/``*.pyc`` and the like never reach the published branch. - """ - dest.mkdir(parents=True, exist_ok=True) - ignore = shutil.ignore_patterns(*_EXCLUDE_GLOBS) - for item in winner_dir.iterdir(): - if any(fnmatch(item.name, pat) for pat in _EXCLUDE_GLOBS): - continue - target = dest / item.name - if item.is_dir(): - shutil.copytree(item, target, dirs_exist_ok=True, ignore=ignore) - else: - shutil.copy2(item, target) - - def _validated_subtree(self, agent_path: str) -> Path: - """Resolve an agent subtree without following a path component symlink.""" - agent_path = _validated_agent_path(agent_path) - if agent_path == ".": - return self.agent_dir - subtree = self.agent_dir - try: - for component in agent_path.split("/"): - subtree /= component - if subtree.is_symlink(): - raise ValueError(_AGENT_PATH_CHECKOUT_ERROR) - if not subtree.resolve(strict=False).is_relative_to(self.agent_dir): - raise ValueError(_AGENT_PATH_CHECKOUT_ERROR) - except OSError: - raise ValueError(_AGENT_PATH_CHECKOUT_ERROR) from None - return subtree - - def _snapshot_subtree(self, src_dir: Path, agent_path: str) -> None: - """Replace the ``agent_path`` subtree with *src_dir*'s files (deletions captured). - - ``git rm`` the existing subtree first so files the candidate removed don't linger, - using literal pathspec semantics, then overlay the candidate's files. ``agent_path`` - ``"."`` targets the whole repo. - """ - self._validated_subtree(agent_path) - self._run(["git", "--literal-pathspecs", "rm", "-r", "-q", "-f", "--ignore-unmatch", "--", agent_path]) - subtree = self._validated_subtree(agent_path) - stale_items = ( - (item for item in subtree.iterdir() if item.name.casefold() != ".git") if agent_path == "." else (subtree,) - ) - for item in stale_items: - if item.is_dir() and not item.is_symlink(): - shutil.rmtree(item) - else: - item.unlink(missing_ok=True) - self._overlay(Path(src_dir), subtree) - self._run(["git", "--literal-pathspecs", "add", "-A", "--", agent_path]) - - # -- public API ----------------------------------------------------------- - - def push_branch( - self, - *, - src_dir: Path, - branch: str, - base_ref: str, - message: str, - agent_path: str = ".", - ) -> bool: - """Archive one candidate: push *src_dir* as *branch* cut from *base_ref*. - - Snapshots the ``agent_path`` subtree (``git rm`` + overlay, capturing deletions). - Skip-if-exists and idempotent without force-pushing: returns ``False`` when *branch* - already exists on origin or the result is byte-identical to *base_ref* (empty diff), - ``True`` when a new branch was pushed. Used to archive every candidate to its own - branch during a run. - """ - agent_path = _validated_agent_path(agent_path) - if self._is_remote_branch(branch): - logger.info(f"[PUSH] branch {branch} already exists on origin; skipping") - return False - self._run(["git", "fetch", "origin", base_ref]) - self._run(["git", "checkout", "-B", branch, "FETCH_HEAD"]) - self._snapshot_subtree(Path(src_dir), agent_path) - if not self._run(["git", "--literal-pathspecs", "status", "--porcelain", "--", agent_path]): - logger.info(f"[PUSH] {branch} identical to {base_ref}; skipping") - return False - self._run(["git", "--literal-pathspecs", "commit", "--only", "-m", message, "--", agent_path]) - self._run(["git", "push", "-u", "origin", branch]) - return True - - def publish( - self, - *, - winner_dir: Path, - branch: str, - base_ref: str, - title: str, - body: str, - draft: bool = True, - agent_path: str = ".", - base_branch_override: str | None = None, - labels: list[str] | None = None, - ) -> str: - """Create a branch with the winner's code and open a draft PR/MR. - - Args: - winner_dir: Directory holding the winning candidate's agent files. - branch: Name of the branch to create and push. - base_ref: Source ref the branch is created from. Used as the PR/MR base - when it is a branch; for a tag/commit source the base falls back to - the remote's default branch (a PR/MR base must be a branch). - title: PR/MR title. - body: PR/MR description. - draft: Open as a draft (default True). - agent_path: In-repo sub-path the winner's files overlay (``"."`` = whole repo). - base_branch_override: Explicit PR/MR base branch; overrides the ``base_ref``/default. - labels: Optional labels applied to the PR/MR. - - Returns: - The created PR/MR URL. - """ - agent_path = _validated_agent_path(agent_path) - origin = self._origin_url() - target = pr_cli_for_repo_url(origin) - if target is None: - raise PRPublisherError(f"unsupported remote host for PR creation: {_redact_url(origin)}") - cli, _ = target - - # Branch from the exact source ref (branch, tag, or commit) via FETCH_HEAD so - # the change is reproducible against the commit the agent came from. - self._run(["git", "fetch", "origin", base_ref]) - self._run(["git", "checkout", "-B", branch, "FETCH_HEAD"]) - self._snapshot_subtree(Path(winner_dir), agent_path) - # Nothing to commit → no PR (winner identical to base). - status = self._run(["git", "--literal-pathspecs", "status", "--porcelain", "--", agent_path]) - if not status: - logger.info("[TERMINATOR] winner is identical to base ref; skipping PR creation") - return "" - self._run(["git", "--literal-pathspecs", "commit", "--only", "-m", title, "--", agent_path]) - self._run(["git", "push", "--force-with-lease", "-u", "origin", branch]) - - # A PR/MR base must be a branch: an explicit override wins; else target base_ref when it - # is a branch, else the remote's default branch (the source may be a tag/commit). - pr_base = base_branch_override or (base_ref if self._is_remote_branch(base_ref) else self._default_branch()) - if cli == "gh": - cmd = ["gh", "pr", "create", "--base", pr_base, "--head", branch, "--title", title, "--body", body] - if draft: - cmd.append("--draft") - for label in labels or []: - cmd += ["--label", label] - return self._run(cmd) - # gitlab - cmd = ["glab", "mr", "create", "--source-branch", branch, "--target-branch", pr_base] - cmd += ["--title", title, "--description", body, "--yes"] - if draft: - cmd.append("--draft") - if labels: - cmd += ["--label", ",".join(labels)] - return self._run(cmd) diff --git a/plugins/nemo-eval-author/src/nemo_eval_author_plugin/tools.py b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/tools.py deleted file mode 100644 index 7e31562d5a4..00000000000 --- a/plugins/nemo-eval-author/src/nemo_eval_author_plugin/tools.py +++ /dev/null @@ -1,221 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -# TODO(shared-module): exact copy of experimentalist components/tools.py; unify into a shared package. - -import json -import logging -from collections.abc import Sequence -from pathlib import Path - -from nemo_eval_author_plugin.holdout_utils import ( - BLOCKED_MESSAGE, - DEFAULT_BLOCKED_PATHS, -) -from nemo_eval_author_plugin.workspace_entities import Candidate -from nooa.tools import ShellResult, ShellTools - -logger = logging.getLogger(__name__) - - -class GuardedShellTools(ShellTools): - """``ShellTools`` that refuses shell access to held-out dataset splits. - - The optimizer's sub-agents run CodeAct with a raw shell rooted at the - workspace, so they could read the held-out validation ground truth straight off disk - (``cat dataset/validation/.../tests/test.sh``) and overfit the selection - signal. This intercepts the ``ShellTools.run`` chokepoint and refuses any command that - references a held-out split. - - It is a tripwire, not a wall: a substring match can be bypassed by a - determined caller (globs, shell variables, ``find / -name test.sh``). It - defeats casual and accidental access and logs every attempt. Pair it with the - ``DatasetTool`` split guard, which covers the separate ``Path.read_text`` - channel that never touches bash. - - Args: - cwd: Working directory forwarded to ``ShellTools``. - blocked_paths: Token substrings whose presence in a command causes the - command to be rejected; defaults to ``DEFAULT_BLOCKED_PATHS``. - init_command: Optional command run once when the shell session starts. - - """ - - def __init__( - self, - cwd: str | Path = ".", - *, - blocked_paths: Sequence[str] = DEFAULT_BLOCKED_PATHS, - init_command: str | None = None, - ) -> None: - """Initialize with a working directory and an optional set of blocked path tokens.""" - super().__init__(cwd=str(cwd), init_command=init_command) - self._blocked_paths = tuple(blocked_paths) - - def is_blocked(self, command: str) -> bool: - """Return True if the command references any held-out split path token. - - Args: - command: shell command string to inspect. - - Returns: - bool: True if any blocked path token appears in the command. - - """ - return any(token in command for token in self._blocked_paths) - - async def run( - self, - command: str, - *, - stdin: str | None = None, - timeout: float = 30.0, - ) -> ShellResult: - """Execute a shell command, blocking access to held-out dataset splits. - - Args: - command: Shell command string to execute. - stdin: Optional text piped to stdin. - timeout: Timeout in seconds; forwarded to ``ShellTools.run``. - - Returns: - ShellResult: The result of the command, or a synthetic failure result - with ``returncode=1`` and ``stderr=BLOCKED_MESSAGE`` when the command - references a held-out split path. - - """ - if self.is_blocked(command): - logger.warning(f"GuardedShellTools blocked held-out access: {command}") - return ShellResult(stdout="", stderr=BLOCKED_MESSAGE, returncode=1) - return await super().run(command, stdin=stdin, timeout=timeout) - - -class WorkspaceTool: - """Navigate the eval-and-optimize workspace: agents and analysis files. - - Args: - workspace: Absolute path to the eval-and-optimize workspace root. - - """ - - def __init__(self, workspace: Path) -> None: - """Initialize the workspace tool for the given workspace.""" - self.workspace = Path(workspace).resolve() - self._eval_root = self.workspace / "eval-and-optimize" - self._agents_root = self._eval_root / "agents" - self._analysis_root = self._eval_root / "analysis" - - # ── Navigation ──────────────────────────────────────────────────────────── - - def list_agents(self) -> list[str]: - """Return all agent IDs that have a ``metadata.json``, sorted naturally. - - Returns: - list[str]: agent IDs in natural numeric order. - - """ - if not self._agents_root.exists(): - return [] - ids = [d.name for d in self._agents_root.iterdir() if d.is_dir() and (d / "metadata.json").exists()] - return sorted( - ids, - key=lambda s: int(s.split("-")[1]) if s.split("-")[1].isdigit() else 0, - ) - - def get_agent_path(self, agent_id: str) -> str: - """Return the absolute path to an agent's directory. - - Args: - agent_id: the agent to locate. - - Returns: - str: absolute path to the agent's directory under ``eval-and-optimize/agents/``. - - """ - return str(self._agents_root / agent_id) - - # ── File listing ────────────────────────────────────────────────────────── - - def list_agent_files(self, agent_id: str) -> list[str]: - """Return all files in an agent's directory (relative paths). - - Args: - agent_id: the agent to inspect. - - Returns: - list[str]: sorted relative paths of all files in the agent directory. - - """ - agent_dir = self._agents_root / agent_id - if not agent_dir.exists(): - return [] - return sorted(str(f.relative_to(agent_dir)) for f in agent_dir.rglob("*") if f.is_file()) - - # ── File reading ────────────────────────────────────────────────────────── - - def read_agent_file(self, agent_id: str, relative_path: str, limit: int | None = 8000) -> str: - """Read any file inside an agent's directory by relative path. - - Args: - agent_id: e.g. "agent-2" - relative_path: e.g. "main.py", "metadata.json" - limit: max characters (default 8000, None = no truncation) - - Returns: - str: file contents, truncated to ``limit`` characters if needed, or empty - string if the file is not found. - - """ - return self._read_file(self._agents_root / agent_id / relative_path, limit=limit) - - def read_analysis_file(self, path_or_round: "str | int", limit: int | None = 8000) -> str: - """Read a file from eval-and-optimize/analysis/. - - Args: - path_or_round: round number (e.g. 1 → round-1.md) or filename. - limit: max characters (default 8000, None = no truncation) - - Returns: - str: file contents, truncated to ``limit`` characters if needed, or empty - string if the file is not found. - - """ - if isinstance(path_or_round, int): - path = self._analysis_root / f"round-{path_or_round}.md" - else: - path = self._analysis_root / path_or_round - return self._read_file(path, limit=limit) - - def get_metadata(self, agent_id: str) -> Candidate: - """Read ``agents/{agent_id}/metadata.json`` as a :class:`Candidate`. - - Args: - agent_id: the agent whose metadata to read. - - Returns: - Candidate: parsed candidate, or a minimal candidate with default - values if the file is missing or unreadable. - - """ - path = self._agents_root / agent_id / "metadata.json" - if not path.exists(): - raise FileNotFoundError(f"Metadata file not found: {path}") - data = json.loads(path.read_text()) - candidate = Candidate.model_validate(data) - entity_id = data.get("id", "") - if entity_id: - candidate._id = entity_id # type: ignore[attr-defined] - return candidate - - # ── Private helpers ─────────────────────────────────────────────────────── - - def _read_file(self, path: Path, limit: int | None = 4000) -> str: - if not path.exists(): - return "" - try: - text = path.read_text().strip() - if limit is not None and len(text) > limit: - text = text[:limit] + f"... [truncated, {len(text)} chars total]" - return text - except (OSError, UnicodeDecodeError): - return "" diff --git a/plugins/nemo-eval-author/src/nemo_eval_author_plugin/util.py b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/util.py deleted file mode 100644 index 6e9f41793c9..00000000000 --- a/plugins/nemo-eval-author/src/nemo_eval_author_plugin/util.py +++ /dev/null @@ -1,19 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -from pathlib import Path - -from nooa import TextSkill -from nooa.skill_registry import SkillRegistry - - -def load_framework_skills(registry: SkillRegistry, dirs: list[Path]) -> None: - """Register TextSkills from user-provided framework skill directories.""" - for skill_dir in dirs: - if not skill_dir.is_dir(): - continue - if (skill_dir / "SKILL.md").exists() or (skill_dir / "skill.md").exists(): - skill = TextSkill(path=skill_dir) - registry.register(f"ext.{skill.id}", skill) - registry.load(["cmd.*", "ext.*"]) - registry.activate(["cmd.*", "ext.*"]) diff --git a/plugins/nemo-eval-author/src/nemo_eval_author_plugin/workspace_entities.py b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/workspace_entities.py deleted file mode 100644 index 6fc7b60bcd1..00000000000 --- a/plugins/nemo-eval-author/src/nemo_eval_author_plugin/workspace_entities.py +++ /dev/null @@ -1,35 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -# TODO(shared-module): exact copy of experimentalist Candidate entity for WorkspaceTool; unify with experimentalist entities. - -from typing import Any, Sequence - -from nemo_eval_author_plugin.evaluator.models import TrialResult -from nemo_platform_plugin.entity import NemoEntity -from pydantic import Field - - -class Candidate(NemoEntity, entity_type="candidate"): - """Candidate metadata used by WorkspaceTool when reading agent metadata.json.""" - - workspace: str = Field(default="default") - run_id: str = Field(...) - label: str = Field(...) - ancestor: str | None = Field(default=None) - round: int = Field(...) - optimization: str = Field(...) - optimization_type: str | None = Field(default=None) - task_ids: list[str] = Field(default_factory=list) - train_reward: dict[str, float] | None = Field(default=None) - train_reward_details: Sequence[TrialResult] | None = Field(default=None) - validation_reward: dict[str, float] | None = Field(default=None) - validation_reward_details: Sequence[TrialResult] | None = Field(default=None) - insight_reward: dict[str, float] | None = Field(default=None) - insight_reward_details: Sequence[TrialResult] | None = Field(default=None) - insight_suite_identity: str | None = Field(default=None) - insight_metric_keys: list[str] | None = Field(default=None) - validation_trajectory_reward: dict[str, float] | None = Field(default=None) - validation_trajectory_reward_details: dict[str, Any] | None = Field(default=None) - killed_round: int | None = Field(default=None) - artifacts: dict[str, Any] = Field(default_factory=dict, exclude=True) diff --git a/plugins/nemo-eval-author/tests/conftest.py b/plugins/nemo-eval-author/tests/conftest.py new file mode 100644 index 00000000000..8fed86e3581 --- /dev/null +++ b/plugins/nemo-eval-author/tests/conftest.py @@ -0,0 +1,30 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Top-level conftest: runs before any test module is imported. + +Eval Author and Experimentalist agents call get_smart_model() at class-definition +time. Placeholder credentials keep collection working when real keys are absent. +setdefault does not overwrite CI or developer-provided values. +""" + +import os + +import litellm +import pytest + +os.environ.setdefault("AUTHOR_API_BASE", "http://placeholder-for-import") +os.environ.setdefault("AUTHOR_API_KEY", "placeholder-for-import") +os.environ.setdefault("EXPERIMENTALIST_API_BASE", "http://placeholder-for-import") +os.environ.setdefault("EXPERIMENTALIST_API_KEY", "placeholder-for-import") + +litellm.drop_params = True + + +@pytest.fixture(autouse=True) +def _restore_environ(): + """Undo environment changes that monkeypatch cannot restore.""" + snapshot = os.environ.copy() + yield + os.environ.clear() + os.environ.update(snapshot) diff --git a/plugins/nemo-eval-author/tests/test_eval_author_agent.py b/plugins/nemo-eval-author/tests/test_eval_author_agent.py index 4a721c10576..c31cb66594f 100644 --- a/plugins/nemo-eval-author/tests/test_eval_author_agent.py +++ b/plugins/nemo-eval-author/tests/test_eval_author_agent.py @@ -15,13 +15,13 @@ from nemo_eval_author_plugin.eval_author import agent as eval_author_module from nemo_eval_author_plugin.eval_author.agent import EvalAuthor from nemo_eval_author_plugin.eval_author.models import EvalAuthorConfig -from nemo_eval_author_plugin.evaluator import ( +from nemo_experimentalist_plugin.experimentalist.components.evaluator import ( Dataset, DatasetValidationError, Task, TrialResult, ) -from nemo_eval_author_plugin.trace_analyzer import ( +from nemo_experimentalist_plugin.experimentalist.components.trace_analyzer import ( Diagnostic, TraceAnalyzerConfig, ) diff --git a/plugins/nemo-eval-author/tests/test_eval_author_materialization.py b/plugins/nemo-eval-author/tests/test_eval_author_materialization.py index 0e3a7bb1c77..a110fcfcf01 100644 --- a/plugins/nemo-eval-author/tests/test_eval_author_materialization.py +++ b/plugins/nemo-eval-author/tests/test_eval_author_materialization.py @@ -12,8 +12,8 @@ import pytest from nemo_eval_author_plugin.eval_author import materialization as materialization_module from nemo_eval_author_plugin.eval_author.materialization import InsightSuite -from nemo_eval_author_plugin.evaluator import Task -from nemo_eval_author_plugin.evaluator.harbor import HarborDataset +from nemo_experimentalist_plugin.experimentalist.components.evaluator import Task +from nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor import HarborDataset def _write_template(root: Path) -> Task: diff --git a/plugins/nemo-eval-author/tests/test_eval_author_run.py b/plugins/nemo-eval-author/tests/test_eval_author_run.py index 4dae6117d40..6b31bf9be52 100644 --- a/plugins/nemo-eval-author/tests/test_eval_author_run.py +++ b/plugins/nemo-eval-author/tests/test_eval_author_run.py @@ -8,7 +8,7 @@ import pytest from nemo_eval_author_plugin.eval_author import run as eval_author_run from nemo_eval_author_plugin.eval_author.models import EvalAuthorConfig, EvalAuthorResult -from nemo_eval_author_plugin.evaluator.models import Dataset, DatasetRef, Task +from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import Dataset, DatasetRef, Task from nemo_insights_plugin.entities import Insight @@ -147,7 +147,7 @@ def build_eval_author_agent(*, experiment_dir: Path, config: EvalAuthorConfig) - return eval_author monkeypatch.setattr(eval_author_run, "make_client", lambda base_url: client) - monkeypatch.setattr(eval_author_run, "make_eval_author_backend", make_backend) + monkeypatch.setattr(eval_author_run, "make_experimentalist_backend", make_backend) monkeypatch.setattr(eval_author_run, "DatasetFactory", lambda: dataset_factory) monkeypatch.setattr(eval_author_run, "build_eval_author_agent", build_eval_author_agent) monkeypatch.setattr(eval_author_run, "_enable_litellm_drop_params", lambda: litellm_calls.append(True)) @@ -227,7 +227,7 @@ async def download(self, *, remote_path: str, local_path: str, workspace: str) - eval_author = FakeEvalAuthor() monkeypatch.setattr(eval_author_run, "make_client", lambda base_url: client) - monkeypatch.setattr(eval_author_run, "make_eval_author_backend", lambda **_: backend) + monkeypatch.setattr(eval_author_run, "make_experimentalist_backend", lambda **_: backend) monkeypatch.setattr(eval_author_run, "DatasetFactory", lambda: dataset_factory) monkeypatch.setattr(eval_author_run, "build_eval_author_agent", lambda **_: eval_author) monkeypatch.setattr(eval_author_run, "_enable_litellm_drop_params", lambda: None) @@ -269,7 +269,7 @@ async def test_run_eval_author_uses_agent_override( eval_author = FakeEvalAuthor() monkeypatch.setattr(eval_author_run, "make_client", lambda base_url: client) - monkeypatch.setattr(eval_author_run, "make_eval_author_backend", lambda **_: backend) + monkeypatch.setattr(eval_author_run, "make_experimentalist_backend", lambda **_: backend) monkeypatch.setattr(eval_author_run, "DatasetFactory", lambda: dataset_factory) monkeypatch.setattr(eval_author_run, "build_eval_author_agent", lambda **_: eval_author) monkeypatch.setattr(eval_author_run, "_enable_litellm_drop_params", lambda: None) @@ -304,7 +304,7 @@ def fail_backend_creation(**_: object) -> object: raise RuntimeError("backend creation failed") monkeypatch.setattr(eval_author_run, "make_client", lambda base_url: client) - monkeypatch.setattr(eval_author_run, "make_eval_author_backend", fail_backend_creation) + monkeypatch.setattr(eval_author_run, "make_experimentalist_backend", fail_backend_creation) with pytest.raises(RuntimeError, match="backend creation failed"): await eval_author_run.run_eval_author( diff --git a/plugins/nemo-eval-author/tests/test_model_config.py b/plugins/nemo-eval-author/tests/test_model_config.py new file mode 100644 index 00000000000..e2c58394b25 --- /dev/null +++ b/plugins/nemo-eval-author/tests/test_model_config.py @@ -0,0 +1,70 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +import pytest +from nemo_eval_author_plugin import model_config + + +@pytest.fixture(autouse=True) +def _clear_model_cache() -> None: + model_config.get_smart_model.cache_clear() + model_config.get_mid_model.cache_clear() + model_config.get_fast_model.cache_clear() + + +def test_api_base_prefers_eval_author_over_experimentalist(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setenv("AUTHOR_API_BASE", "https://eval-author.example/v1") + monkeypatch.setenv("EXPERIMENTALIST_API_BASE", "https://experimentalist.example/v1") + monkeypatch.setenv("AUTHOR_API_KEY", "eval-key") + + assert model_config._api_base() == "https://eval-author.example/v1" + + +def test_api_base_falls_back_to_experimentalist(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.delenv("AUTHOR_API_BASE", raising=False) + monkeypatch.setenv("EXPERIMENTALIST_API_BASE", "https://experimentalist.example/v1") + monkeypatch.setenv("EXPERIMENTALIST_API_KEY", "exp-key") + + assert model_config._api_base() == "https://experimentalist.example/v1" + + +def test_api_key_accepts_inference_key_on_gateway(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setenv("AUTHOR_API_BASE", "https://inference-api.nvidia.com/v1") + monkeypatch.delenv("AUTHOR_API_KEY", raising=False) + monkeypatch.delenv("EXPERIMENTALIST_API_KEY", raising=False) + monkeypatch.setenv("INFERENCE_API_KEY", "sk-gateway") + + assert model_config._api_key() == "sk-gateway" + + +def test_api_key_requires_credentials_when_missing(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setenv("AUTHOR_API_BASE", "https://custom.example/v1") + monkeypatch.delenv("AUTHOR_API_KEY", raising=False) + monkeypatch.delenv("EXPERIMENTALIST_API_KEY", raising=False) + monkeypatch.delenv("INFERENCE_API_KEY", raising=False) + + with pytest.raises(ValueError, match="AUTHOR_API_KEY"): + model_config._api_key() + + +def test_bridge_author_env_fills_unset_experimentalist_slots(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setenv("AUTHOR_API_BASE", "https://eval-author.example/v1") + monkeypatch.setenv("AUTHOR_API_KEY", "eval-key") + monkeypatch.delenv("EXPERIMENTALIST_API_BASE", raising=False) + monkeypatch.delenv("EXPERIMENTALIST_API_KEY", raising=False) + + model_config.bridge_author_env_to_experimentalist() + + assert model_config.os.environ["EXPERIMENTALIST_API_BASE"] == "https://eval-author.example/v1" + assert model_config.os.environ["EXPERIMENTALIST_API_KEY"] == "eval-key" + + +def test_bridge_author_env_does_not_overwrite_existing_experimentalist( + monkeypatch: pytest.MonkeyPatch, +) -> None: + monkeypatch.setenv("AUTHOR_API_BASE", "https://eval-author.example/v1") + monkeypatch.setenv("EXPERIMENTALIST_API_BASE", "https://experimentalist.example/v1") + + model_config.bridge_author_env_to_experimentalist() + + assert model_config.os.environ["EXPERIMENTALIST_API_BASE"] == "https://experimentalist.example/v1" diff --git a/plugins/nemo-experimentalist/AGENTS.md b/plugins/nemo-experimentalist/AGENTS.md index 1367c2f3474..b1e47acfc20 100644 --- a/plugins/nemo-experimentalist/AGENTS.md +++ b/plugins/nemo-experimentalist/AGENTS.md @@ -45,16 +45,18 @@ eventual `nemo agents experimentalist`. The platform's `nemo.cli` entry-point group is flat — only `nemo.jobs` and `nemo.functions` are dot-scoped — so nesting under `nemo agents` needs a Platform-side change first. -### 2026-07-27: Eval Author extracted to `nemo-eval-author-plugin` - -Eval Author, the Harbor evaluator stack, trace analysis, and dataset staging now live in -`plugins/nemo-eval-author/` (`nemo-eval-author-plugin`). Experimentalist is a one-way -consumer: - -- import from `nemo_eval_author_plugin.eval_author`, `nemo_eval_author_plugin.evaluator`, etc. -- do not restore in-tree `eval_author/` or `components/evaluator/` under Experimentalist -- `tools.py`, `model_config.py`, `cache.py`, and `client.py` remain duplicated in both - plugins until a shared module lands (see TODO markers in eval-author) +### 2026-07-28: Eval Author plugin depends on Experimentalist modules + +`plugins/nemo-eval-author/` (`nemo-eval-author-plugin`) owns only the Eval Author +agent package (`eval_author/`) plus a thin `AUTHOR_*` `model_config`. Harbor +evaluator, dataset staging, and trace helpers stay in Experimentalist +(`experimentalist/components/`). Dependency direction: + +- Eval Author hard-depends on Experimentalist and imports shared modules from it +- Experimentalist does **not** declare a reverse package dep (avoids a cycle); + insight mode still imports `EvalAuthor` when both packages are installed via + `uv sync --group experimentalist` +- do not re-copy evaluator/trace/staging into the Eval Author plugin ### 2026-07-21: Curator renamed to Eval Author diff --git a/plugins/nemo-experimentalist/benchmarks/run.py b/plugins/nemo-experimentalist/benchmarks/run.py index a53fd16e6b5..889a696f0a0 100644 --- a/plugins/nemo-experimentalist/benchmarks/run.py +++ b/plugins/nemo-experimentalist/benchmarks/run.py @@ -15,12 +15,12 @@ import yaml from harbor.registry.client.package import PackageDatasetClient -from nemo_eval_author_plugin.evaluator.harbor import ( +from nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor import ( HarborDataset, HarborEvaluator, HarborEvaluatorConfig, ) -from nemo_eval_author_plugin.evaluator.models import ( +from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import ( DatasetRef, EvaluationResult, TrialResult, diff --git a/plugins/nemo-experimentalist/pyproject.toml b/plugins/nemo-experimentalist/pyproject.toml index 200f0352083..e7395b915a3 100644 --- a/plugins/nemo-experimentalist/pyproject.toml +++ b/plugins/nemo-experimentalist/pyproject.toml @@ -16,7 +16,6 @@ dependencies = [ "nooa ; python_full_version >= '3.12'", "pyyaml>=6.0.3", "nemo-insights-plugin", - "nemo-eval-author-plugin", "nemo-platform", "nemo-platform-plugin", "tomlkit>=0.13.3", diff --git a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/entities.py b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/entities.py index 9369910c9e8..139a3a1e754 100644 --- a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/entities.py +++ b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/entities.py @@ -5,7 +5,7 @@ from typing import Any, Literal, Sequence -from nemo_eval_author_plugin.evaluator.models import TrialResult +from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import TrialResult from nemo_platform_plugin.entity import NemoEntity from pydantic import ConfigDict, Field, model_validator diff --git a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/analyzer.py b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/analyzer.py index 56af0720486..216feb029f4 100644 --- a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/analyzer.py +++ b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/analyzer.py @@ -11,14 +11,14 @@ from pathlib import Path from typing import Any, Literal, Sequence -from nemo_eval_author_plugin.evaluator import ( +from nemo_experimentalist_plugin.experimentalist.components.evaluator import ( Dataset, EvaluationResult, Task, TrialResult, ) -from nemo_eval_author_plugin.trace_analyzer import Diagnostic, TraceAnalyzer, TraceAnalyzerConfig # noqa: F401 -from nemo_eval_author_plugin.trace_explorer import TraceExplorer # noqa: F401 +from nemo_experimentalist_plugin.experimentalist.components.trace_analyzer import Diagnostic, TraceAnalyzer, TraceAnalyzerConfig # noqa: F401 +from nemo_experimentalist_plugin.experimentalist.components.trace_explorer import TraceExplorer # noqa: F401 from nemo_platform import AsyncNeMoPlatform from nooa import Agent, CodeActStrategy, strategy from nooa.agentdoc import doc, spec diff --git a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/coder.py b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/coder.py index 53f595372f0..c5e08f63143 100644 --- a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/coder.py +++ b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/coder.py @@ -13,7 +13,7 @@ from typing import Any import httpx -from nemo_eval_author_plugin.evaluator import Dataset, EvaluationResult, Evaluator, EvaluatorConfig, Task, TrialResult +from nemo_experimentalist_plugin.experimentalist.components.evaluator import Dataset, EvaluationResult, Evaluator, EvaluatorConfig, Task, TrialResult from nemo_experimentalist_plugin.entities import Candidate from nooa import Agent, CodeActStrategy, strategy from nooa.agentdoc import doc, spec diff --git a/plugins/nemo-eval-author/src/nemo_eval_author_plugin/dataset_staging.py b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/dataset_staging.py similarity index 96% rename from plugins/nemo-eval-author/src/nemo_eval_author_plugin/dataset_staging.py rename to plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/dataset_staging.py index 36a3d29fd9c..cf99a77b47c 100644 --- a/plugins/nemo-eval-author/src/nemo_eval_author_plugin/dataset_staging.py +++ b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/dataset_staging.py @@ -8,7 +8,7 @@ from pathlib import Path from urllib.parse import urlparse -from nemo_eval_author_plugin.evaluator.models import DatasetRef, local_path_from_uri +from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import DatasetRef, local_path_from_uri from nemo_platform import AsyncNeMoPlatform diff --git a/plugins/nemo-eval-author/src/nemo_eval_author_plugin/evaluator/__init__.py b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/evaluator/__init__.py similarity index 83% rename from plugins/nemo-eval-author/src/nemo_eval_author_plugin/evaluator/__init__.py rename to plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/evaluator/__init__.py index f293250efed..59fee57c2ff 100644 --- a/plugins/nemo-eval-author/src/nemo_eval_author_plugin/evaluator/__init__.py +++ b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/evaluator/__init__.py @@ -1,12 +1,12 @@ # SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 -from nemo_eval_author_plugin.evaluator.base import ( +from nemo_experimentalist_plugin.experimentalist.components.evaluator.base import ( Evaluator, EvaluatorConfig, EvaluatorType, ) -from nemo_eval_author_plugin.evaluator.models import ( +from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import ( CommandSpec, Dataset, DatasetRef, diff --git a/plugins/nemo-eval-author/src/nemo_eval_author_plugin/evaluator/base.py b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/evaluator/base.py similarity index 98% rename from plugins/nemo-eval-author/src/nemo_eval_author_plugin/evaluator/base.py rename to plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/evaluator/base.py index 48a0e568631..25261b4e214 100644 --- a/plugins/nemo-eval-author/src/nemo_eval_author_plugin/evaluator/base.py +++ b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/evaluator/base.py @@ -10,7 +10,7 @@ from pathlib import Path from typing import Literal, TypeAlias -from nemo_eval_author_plugin.evaluator.models import ( +from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import ( Dataset, EvaluationResult, TrialResult, diff --git a/plugins/nemo-eval-author/src/nemo_eval_author_plugin/evaluator/factory.py b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/evaluator/factory.py similarity index 93% rename from plugins/nemo-eval-author/src/nemo_eval_author_plugin/evaluator/factory.py rename to plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/evaluator/factory.py index f3a3959ded7..3b59226cec2 100644 --- a/plugins/nemo-eval-author/src/nemo_eval_author_plugin/evaluator/factory.py +++ b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/evaluator/factory.py @@ -6,17 +6,17 @@ from pathlib import Path from typing import Any -from nemo_eval_author_plugin.evaluator.base import ( +from nemo_experimentalist_plugin.experimentalist.components.evaluator.base import ( Evaluator, EvaluatorConfig, EvaluatorType, ) -from nemo_eval_author_plugin.evaluator.harbor import ( +from nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor import ( HarborDataset, HarborEvaluator, HarborEvaluatorConfig, ) -from nemo_eval_author_plugin.evaluator.models import Dataset, DatasetRef, Task +from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import Dataset, DatasetRef, Task _SUPPORTED_EVALUATOR_TYPES = { "harbor": (HarborDataset, HarborEvaluator, HarborEvaluatorConfig), diff --git a/plugins/nemo-eval-author/src/nemo_eval_author_plugin/evaluator/harbor.py b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/evaluator/harbor.py similarity index 99% rename from plugins/nemo-eval-author/src/nemo_eval_author_plugin/evaluator/harbor.py rename to plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/evaluator/harbor.py index 99556972bff..24a60ffa373 100644 --- a/plugins/nemo-eval-author/src/nemo_eval_author_plugin/evaluator/harbor.py +++ b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/evaluator/harbor.py @@ -32,12 +32,12 @@ from harbor.models.task.task import Task as HarborTaskModel from harbor.models.trial.config import ServiceVolumeConfig from harbor.models.trial.paths import EnvironmentPaths, TrialPaths -from nemo_eval_author_plugin.evaluator.base import ( +from nemo_experimentalist_plugin.experimentalist.components.evaluator.base import ( Evaluator, EvaluatorConfig, EvaluatorType, ) -from nemo_eval_author_plugin.evaluator.models import ( +from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import ( Dataset, DatasetRef, DatasetValidationError, diff --git a/plugins/nemo-eval-author/src/nemo_eval_author_plugin/evaluator/models.py b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/evaluator/models.py similarity index 100% rename from plugins/nemo-eval-author/src/nemo_eval_author_plugin/evaluator/models.py rename to plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/evaluator/models.py diff --git a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/goal_tree.py b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/goal_tree.py index 1231b928bb2..753ace24198 100644 --- a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/goal_tree.py +++ b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/goal_tree.py @@ -8,7 +8,7 @@ from pathlib import Path from typing import Any -from nemo_eval_author_plugin.evaluator import Dataset +from nemo_experimentalist_plugin.experimentalist.components.evaluator import Dataset from nooa import Agent, CodeActStrategy, strategy from nooa.agentdoc import doc, spec from nooa.agents import TokenBudgetSummarizer diff --git a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/insight_promotion.py b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/insight_promotion.py index a6b27c8615d..93c7ff38930 100644 --- a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/insight_promotion.py +++ b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/insight_promotion.py @@ -10,13 +10,13 @@ from dataclasses import dataclass, replace from pathlib import Path -from nemo_eval_author_plugin.evaluator import ( +from nemo_experimentalist_plugin.experimentalist.components.evaluator import ( Dataset, EvaluationResult, Task, TrialResult, ) -from nemo_eval_author_plugin.evaluator.models import local_path_from_uri +from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import local_path_from_uri from nemo_experimentalist_plugin.entities import Candidate _GENERIC_METRIC_NAMES = frozenset({"reward", "score"}) diff --git a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/loop.py b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/loop.py index a552e3d7599..0dade050434 100644 --- a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/loop.py +++ b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/loop.py @@ -18,18 +18,18 @@ from pathlib import Path from typing import Any, Literal, cast, get_args -from nemo_eval_author_plugin.dataset_staging import stage_eval_author_inputs from nemo_eval_author_plugin.eval_author.agent import EvalAuthor -from nemo_eval_author_plugin.evaluator import ( +from nemo_experimentalist_plugin.entities import Candidate, ExperimentRun +from nemo_experimentalist_plugin.experimentalist.components.analyzer import AgentAnalyzer, AnalyzerConfig +from nemo_experimentalist_plugin.experimentalist.components.coder import Coder, CoderConfig +from nemo_experimentalist_plugin.experimentalist.components.dataset_staging import stage_eval_author_inputs +from nemo_experimentalist_plugin.experimentalist.components.evaluator import ( Dataset, EvaluationResult, Evaluator, TrialResult, ) -from nemo_eval_author_plugin.evaluator.factory import DatasetFactory, EvaluatorFactory -from nemo_experimentalist_plugin.entities import Candidate, ExperimentRun -from nemo_experimentalist_plugin.experimentalist.components.analyzer import AgentAnalyzer, AnalyzerConfig -from nemo_experimentalist_plugin.experimentalist.components.coder import Coder, CoderConfig +from nemo_experimentalist_plugin.experimentalist.components.evaluator.factory import DatasetFactory, EvaluatorFactory from nemo_experimentalist_plugin.experimentalist.components.goal_tree import ( GoalTree, GoalTreeConfig, diff --git a/plugins/nemo-eval-author/src/nemo_eval_author_plugin/rationale.py b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/rationale.py similarity index 81% rename from plugins/nemo-eval-author/src/nemo_eval_author_plugin/rationale.py rename to plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/rationale.py index 03b7bd9b61a..831961e67dd 100644 --- a/plugins/nemo-eval-author/src/nemo_eval_author_plugin/rationale.py +++ b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/rationale.py @@ -1,7 +1,7 @@ # SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 -# TODO(shared-module): Rationale models copied from experimentalist rationalizer.py; unify with rationalizer module. +"""Task-level rationale models shared by Rationalizer and TraceAnalyzer.""" from pydantic import BaseModel, Field diff --git a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/rationalizer.py b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/rationalizer.py index ba1ce574f1f..6be50d9423b 100644 --- a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/rationalizer.py +++ b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/rationalizer.py @@ -9,8 +9,8 @@ from pathlib import Path from typing import Any -from nemo_eval_author_plugin.evaluator import Task -from nemo_eval_author_plugin.evaluator.models import DependencyRuntime +from nemo_experimentalist_plugin.experimentalist.components.evaluator import Task +from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import DependencyRuntime from nooa import Agent, CodeActStrategy, strategy from nooa.agentdoc import doc, spec from nooa.agents import TokenBudgetSummarizer @@ -22,23 +22,11 @@ from . import cache from .model_config import get_fast_model, get_smart_model +from .rationale import Rationale, RationaleStep from .tools import GuardedShellTools from .util import load_framework_skills - -class RationaleStep(BaseModel): - """One reasoning step in a task rationale.""" - - thought: str - action: str - observation: str = "" - - -class Rationale(BaseModel): - """Task-level context produced by the Rationalizer before trace analysis.""" - - task_name: str - steps: list[RationaleStep] = Field(default_factory=list) +__all__ = ["Rationale", "RationaleStep", "Rationalizer", "RationalizerConfig"] class RationalizerConfig(BaseModel): diff --git a/plugins/nemo-eval-author/src/nemo_eval_author_plugin/trace_analyzer.py b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/trace_analyzer.py similarity index 95% rename from plugins/nemo-eval-author/src/nemo_eval_author_plugin/trace_analyzer.py rename to plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/trace_analyzer.py index e171391cf04..e46ae16ffe1 100644 --- a/plugins/nemo-eval-author/src/nemo_eval_author_plugin/trace_analyzer.py +++ b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/trace_analyzer.py @@ -11,14 +11,11 @@ from typing import Any, Literal from urllib.parse import unquote, urlparse -from nemo_eval_author_plugin import cache -from nemo_eval_author_plugin.evaluator import MetricResult, Task, TrialResult -from nemo_eval_author_plugin.evaluator.models import DependencyRuntime -from nemo_eval_author_plugin.model_config import get_fast_model, get_smart_model -from nemo_eval_author_plugin.rationale import Rationale -from nemo_eval_author_plugin.tools import GuardedShellTools, WorkspaceTool -from nemo_eval_author_plugin.trace_explorer import SearchResult, SessionData, SessionSummary, TraceExplorer, TurnInfo -from nemo_eval_author_plugin.util import load_framework_skills +from nemo_experimentalist_plugin.experimentalist.components import cache +from nemo_experimentalist_plugin.experimentalist.components.evaluator import MetricResult, Task, TrialResult +from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import DependencyRuntime +from nemo_experimentalist_plugin.experimentalist.components.model_config import get_fast_model, get_smart_model +from nemo_experimentalist_plugin.experimentalist.components.tools import GuardedShellTools, WorkspaceTool from nemo_insights_plugin.entities import Insight from nemo_platform import AsyncNeMoPlatform from nooa import Agent, CodeActStrategy, strategy @@ -30,6 +27,10 @@ from nooa.tools import Match, TodoManager from pydantic import BaseModel, Field +from .rationale import Rationale +from .trace_explorer import SearchResult, SessionData, SessionSummary, TraceExplorer, TurnInfo +from .util import load_framework_skills + class TraceAnalyzerConfig(BaseModel): """Configuration for TraceAnalyzer tuning parameters.""" diff --git a/plugins/nemo-eval-author/src/nemo_eval_author_plugin/trace_explorer.py b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/trace_explorer.py similarity index 99% rename from plugins/nemo-eval-author/src/nemo_eval_author_plugin/trace_explorer.py rename to plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/trace_explorer.py index 2ef9cac105f..a7f26fa2994 100644 --- a/plugins/nemo-eval-author/src/nemo_eval_author_plugin/trace_explorer.py +++ b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/trace_explorer.py @@ -12,7 +12,7 @@ from pathlib import Path from typing import Any, Literal, cast -from nemo_eval_author_plugin.evaluator.models import ResourceRef +from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import ResourceRef from nemo_platform import AsyncNeMoPlatform from pydantic import BaseModel, ConfigDict, Field diff --git a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/trace_scorer.py b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/trace_scorer.py index 84cd0dfdc5d..30d0c8ae00b 100644 --- a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/trace_scorer.py +++ b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/trace_scorer.py @@ -5,8 +5,8 @@ from pathlib import Path from typing import Any -from nemo_eval_author_plugin.evaluator import Dataset, TrialResult -from nemo_eval_author_plugin.trace_explorer import TraceExplorer # noqa: F401 +from nemo_experimentalist_plugin.experimentalist.components.evaluator import Dataset, TrialResult +from nemo_experimentalist_plugin.experimentalist.components.trace_explorer import TraceExplorer # noqa: F401 from nemo_platform import AsyncNeMoPlatform from nooa import Agent, CodeActStrategy, strategy from nooa.agentdoc import doc diff --git a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/deps.py b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/deps.py index c742c2d53d1..2d2cd5a5915 100644 --- a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/deps.py +++ b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/deps.py @@ -13,8 +13,8 @@ from pathlib import Path -from nemo_eval_author_plugin.evaluator.base import EvaluatorType -from nemo_eval_author_plugin.evaluator.models import DatasetRef +from nemo_experimentalist_plugin.experimentalist.components.evaluator.base import EvaluatorType +from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import DatasetRef from nemo_experimentalist_plugin.experimentalist.experimentalist_backend import ( ExperimentalistBackend, ) diff --git a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/experimentalist_backend.py b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/experimentalist_backend.py index a58471cbd58..b544936835f 100644 --- a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/experimentalist_backend.py +++ b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/experimentalist_backend.py @@ -24,7 +24,7 @@ from urllib.parse import urlparse import httpx -from nemo_eval_author_plugin.evaluator.models import ( +from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import ( EvaluationResult, ResourceRef, TrialResult, diff --git a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/otlp.py b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/otlp.py index 6352f4806d0..58b2dbf16d7 100644 --- a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/otlp.py +++ b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/otlp.py @@ -11,7 +11,7 @@ from urllib.parse import urlparse from google.protobuf.json_format import ParseDict -from nemo_eval_author_plugin.evaluator.models import ResourceRef +from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import ResourceRef from opentelemetry.proto.collector.trace.v1.trace_service_pb2 import ExportTraceServiceRequest logger = logging.getLogger(__name__) diff --git a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/run.py b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/run.py index 7f6ed7c7db1..5b9af230336 100644 --- a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/run.py +++ b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/run.py @@ -7,7 +7,7 @@ from pathlib import Path from typing import Literal, Protocol, cast -from nemo_eval_author_plugin.evaluator.models import DatasetRef +from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import DatasetRef from nemo_experimentalist_plugin.experimentalist.agent import build_experimentalist_agent from nemo_experimentalist_plugin.experimentalist.components.loop import EvolutionaryOptimizerConfig from nemo_experimentalist_plugin.experimentalist.deps import ExperimentalistDeps diff --git a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/resolve.py b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/resolve.py index 82c24b51a3b..82be6af0b4b 100644 --- a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/resolve.py +++ b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/resolve.py @@ -23,7 +23,7 @@ import yaml from nemo_eval_author_plugin.eval_author.models import EvalAuthorConfig -from nemo_eval_author_plugin.evaluator.models import DatasetRef +from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import DatasetRef from nemo_experimentalist_plugin.experimentalist.components.repository import looks_like_git from nemo_experimentalist_plugin.profile import AgentProfile from nemo_insights_plugin.contracts.insights import InsightsFileError, load_insights_document diff --git a/plugins/nemo-eval-author/tests/test_dataset_staging.py b/plugins/nemo-experimentalist/tests/experimentalist/test_dataset_staging.py similarity index 97% rename from plugins/nemo-eval-author/tests/test_dataset_staging.py rename to plugins/nemo-experimentalist/tests/experimentalist/test_dataset_staging.py index 2fcb1de1f89..f0462534513 100644 --- a/plugins/nemo-eval-author/tests/test_dataset_staging.py +++ b/plugins/nemo-experimentalist/tests/experimentalist/test_dataset_staging.py @@ -6,11 +6,11 @@ from typing import cast import pytest -from nemo_eval_author_plugin.dataset_staging import ( +from nemo_experimentalist_plugin.experimentalist.components.dataset_staging import ( stage_eval_author_inputs, stage_task_template, ) -from nemo_eval_author_plugin.evaluator.models import DatasetRef +from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import DatasetRef from nemo_platform import AsyncNeMoPlatform diff --git a/plugins/nemo-experimentalist/tests/experimentalist/test_dataset_staging_loop.py b/plugins/nemo-experimentalist/tests/experimentalist/test_dataset_staging_loop.py index e49e81a7d45..b45b22d3a26 100644 --- a/plugins/nemo-experimentalist/tests/experimentalist/test_dataset_staging_loop.py +++ b/plugins/nemo-experimentalist/tests/experimentalist/test_dataset_staging_loop.py @@ -8,7 +8,7 @@ from unittest.mock import AsyncMock import pytest -from nemo_eval_author_plugin.evaluator.models import DatasetRef +from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import DatasetRef from nemo_experimentalist_plugin.experimentalist.components import loop as loop_module from nemo_experimentalist_plugin.experimentalist.components.loop import EvolutionaryOptimizer from nemo_experimentalist_plugin.resolve import EvolutionaryOptimizerConfig diff --git a/plugins/nemo-experimentalist/tests/experimentalist/test_eval_author_repair_e2e.py b/plugins/nemo-experimentalist/tests/experimentalist/test_eval_author_repair_e2e.py index 7eea2ee1773..1cf4f23709f 100644 --- a/plugins/nemo-experimentalist/tests/experimentalist/test_eval_author_repair_e2e.py +++ b/plugins/nemo-experimentalist/tests/experimentalist/test_eval_author_repair_e2e.py @@ -11,16 +11,16 @@ import pytest from nemo_eval_author_plugin.eval_author.agent import EvalAuthor from nemo_eval_author_plugin.eval_author.models import EvalAuthorConfig -from nemo_eval_author_plugin.evaluator import ( +from nemo_experimentalist_plugin.experimentalist.components.evaluator import ( DatasetValidationError, local_path_from_uri, ) -from nemo_eval_author_plugin.evaluator.harbor import ( +from nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor import ( HarborDataset, HarborEvaluator, HarborEvaluatorConfig, ) -from nemo_eval_author_plugin.trace_analyzer import Diagnostic +from nemo_experimentalist_plugin.experimentalist.components.trace_analyzer import Diagnostic from nemo_experimentalist_plugin.experimentalist.components.model_config import get_fast_model from nemo_insights_plugin.entities import Insight diff --git a/plugins/nemo-eval-author/tests/test_evaluator_base.py b/plugins/nemo-experimentalist/tests/experimentalist/test_evaluator_base.py similarity index 98% rename from plugins/nemo-eval-author/tests/test_evaluator_base.py rename to plugins/nemo-experimentalist/tests/experimentalist/test_evaluator_base.py index 979dfc9d165..9f3fc4b3863 100644 --- a/plugins/nemo-eval-author/tests/test_evaluator_base.py +++ b/plugins/nemo-experimentalist/tests/experimentalist/test_evaluator_base.py @@ -6,7 +6,7 @@ from typing import Sequence import pytest -from nemo_eval_author_plugin.evaluator import ( +from nemo_experimentalist_plugin.experimentalist.components.evaluator import ( Dataset, Evaluator, EvaluatorConfig, diff --git a/plugins/nemo-eval-author/tests/test_evaluator_factory.py b/plugins/nemo-experimentalist/tests/experimentalist/test_evaluator_factory.py similarity index 92% rename from plugins/nemo-eval-author/tests/test_evaluator_factory.py rename to plugins/nemo-experimentalist/tests/experimentalist/test_evaluator_factory.py index bdbf811813d..eb7fea03535 100644 --- a/plugins/nemo-eval-author/tests/test_evaluator_factory.py +++ b/plugins/nemo-experimentalist/tests/experimentalist/test_evaluator_factory.py @@ -6,19 +6,19 @@ from typing import Sequence import pytest -from nemo_eval_author_plugin.evaluator import DatasetRef -from nemo_eval_author_plugin.evaluator.base import ( +from nemo_experimentalist_plugin.experimentalist.components.evaluator import DatasetRef +from nemo_experimentalist_plugin.experimentalist.components.evaluator.base import ( Dataset, Evaluator, EvaluatorConfig, TrialResult, ) -from nemo_eval_author_plugin.evaluator.factory import DatasetFactory, EvaluatorFactory -from nemo_eval_author_plugin.evaluator.harbor import ( +from nemo_experimentalist_plugin.experimentalist.components.evaluator.factory import DatasetFactory, EvaluatorFactory +from nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor import ( HarborEvaluator, HarborEvaluatorConfig, ) -from nemo_eval_author_plugin.evaluator.models import Task +from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import Task _UNSUPPORTED_TYPE = "unsupported" _SUPPORTED_TYPE = "concrete" diff --git a/plugins/nemo-eval-author/tests/test_evaluator_harbor.py b/plugins/nemo-experimentalist/tests/experimentalist/test_evaluator_harbor.py similarity index 95% rename from plugins/nemo-eval-author/tests/test_evaluator_harbor.py rename to plugins/nemo-experimentalist/tests/experimentalist/test_evaluator_harbor.py index 952b2056d65..14869187d5a 100644 --- a/plugins/nemo-eval-author/tests/test_evaluator_harbor.py +++ b/plugins/nemo-experimentalist/tests/experimentalist/test_evaluator_harbor.py @@ -10,7 +10,7 @@ from types import SimpleNamespace import pytest -from nemo_eval_author_plugin.evaluator.harbor import ( +from nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor import ( _TRACE_ARTIFACT_DESTINATION, _TRACE_ARTIFACT_SOURCE, HarborDataset, @@ -32,7 +32,7 @@ _trial_resources, _with_trace_artifact, ) -from nemo_eval_author_plugin.evaluator.models import ( +from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import ( CommandSpec, Dataset, DatasetRef, @@ -720,7 +720,7 @@ async def create(cls, config): async def run(self): return SimpleNamespace(id="job-id", stats=FakeStats()) - monkeypatch.setattr("nemo_eval_author_plugin.evaluator.harbor.Job", FakeJob) + monkeypatch.setattr("nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor.Job", FakeJob) result = await evaluator.run( agent=tmp_path / "agent", @@ -845,7 +845,7 @@ async def test_harbor_evaluator_rejects_invalid_python_verifiers_before_job_crea dataset = HarborDataset.from_path(dataset_dir) fake_job = _recording_job(tmp_path / "jobs" / "preflight") - monkeypatch.setattr("nemo_eval_author_plugin.evaluator.harbor.Job", fake_job) + monkeypatch.setattr("nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor.Job", fake_job) compile_calls = 0 original_compile = compile @@ -900,7 +900,7 @@ async def create_process(*_args, **_kwargs): monkeypatch.setenv("NVIDIA_API_KEY", "secret") monkeypatch.setattr("asyncio.create_subprocess_exec", create_process) monkeypatch.setattr( - "nemo_eval_author_plugin.evaluator.harbor._SHELL_SYNTAX_TIMEOUT_SEC", + "nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor._SHELL_SYNTAX_TIMEOUT_SEC", 0.01, ) @@ -966,7 +966,7 @@ async def test_harbor_evaluator_accepts_valid_python_verifier( _write(task_dir / "tests" / "check.py", "def check():\n return True\n") dataset = HarborDataset.from_path(task_dir) fake_job = _recording_job(tmp_path / "jobs" / "valid-python") - monkeypatch.setattr("nemo_eval_author_plugin.evaluator.harbor.Job", fake_job) + monkeypatch.setattr("nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor.Job", fake_job) trials = await HarborEvaluator()._run(agent_dir, dataset, HarborEvaluatorConfig()) @@ -988,7 +988,7 @@ async def test_harbor_evaluator_rejects_invalid_configured_test_sh_before_job_cr _write(task_dir / "test" / "test.sh", "if true; then\n echo broken\n") dataset = HarborDataset.from_path(task_dir) fake_job = _recording_job(tmp_path / "jobs" / "invalid-shell") - monkeypatch.setattr("nemo_eval_author_plugin.evaluator.harbor.Job", fake_job) + monkeypatch.setattr("nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor.Job", fake_job) with pytest.raises(HarborVerifierValidationError) as exc_info: await HarborEvaluator()._run(agent_dir, dataset, HarborEvaluatorConfig()) @@ -1013,7 +1013,7 @@ async def test_harbor_evaluator_accepts_valid_legacy_test_sh( _write(task_dir / "test" / "test.sh", "if true; then\n echo valid\nfi\n") dataset = HarborDataset.from_path(task_dir) fake_job = _recording_job(tmp_path / "jobs" / "valid-shell") - monkeypatch.setattr("nemo_eval_author_plugin.evaluator.harbor.Job", fake_job) + monkeypatch.setattr("nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor.Job", fake_job) trials = await HarborEvaluator()._run(agent_dir, dataset, HarborEvaluatorConfig()) @@ -1180,19 +1180,19 @@ class FakeHarborTask: config = SimpleNamespace(environment=SimpleNamespace(os="linux", healthcheck=None, build_timeout_sec=None)) monkeypatch.setattr( - "nemo_eval_author_plugin.evaluator.harbor.HarborTaskModel", + "nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor.HarborTaskModel", lambda p: FakeHarborTask(), ) monkeypatch.setattr( - "nemo_eval_author_plugin.evaluator.harbor.TrialPaths", + "nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor.TrialPaths", FakeTrialPaths, ) monkeypatch.setattr( - "nemo_eval_author_plugin.evaluator.harbor.EnvironmentPaths", + "nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor.EnvironmentPaths", SimpleNamespace(for_os=staticmethod(lambda os: FakeEnvPaths())), ) monkeypatch.setattr( - "nemo_eval_author_plugin.evaluator.harbor.EnvironmentFactory", + "nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor.EnvironmentFactory", SimpleNamespace(create_environment=staticmethod(lambda *a, **kw: FakeEnv())), ) @@ -1252,19 +1252,19 @@ class FakeHarborTask: config = SimpleNamespace(environment=SimpleNamespace(os="linux", healthcheck=None, build_timeout_sec=None)) monkeypatch.setattr( - "nemo_eval_author_plugin.evaluator.harbor.HarborTaskModel", + "nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor.HarborTaskModel", lambda p: FakeHarborTask(), ) monkeypatch.setattr( - "nemo_eval_author_plugin.evaluator.harbor.TrialPaths", + "nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor.TrialPaths", FakeTrialPaths, ) monkeypatch.setattr( - "nemo_eval_author_plugin.evaluator.harbor.EnvironmentPaths", + "nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor.EnvironmentPaths", SimpleNamespace(for_os=staticmethod(lambda os: FakeEnvPaths())), ) monkeypatch.setattr( - "nemo_eval_author_plugin.evaluator.harbor.EnvironmentFactory", + "nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor.EnvironmentFactory", SimpleNamespace(create_environment=staticmethod(lambda *a, **kw: FakeEnv())), ) @@ -1400,7 +1400,7 @@ async def test_harbor_evaluator_force_rerun(tmp_path, monkeypatch): def fake_rmtree(path, **kwargs): rmtree_calls.append(path) - monkeypatch.setattr("nemo_eval_author_plugin.evaluator.harbor.shutil.rmtree", fake_rmtree) + monkeypatch.setattr("nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor.shutil.rmtree", fake_rmtree) class FakeJob: created_config = None @@ -1417,7 +1417,7 @@ async def create(cls, config): async def run(self): return SimpleNamespace(id="job-id", stats=None) - monkeypatch.setattr("nemo_eval_author_plugin.evaluator.harbor.Job", FakeJob) + monkeypatch.setattr("nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor.Job", FakeJob) trials = await evaluator._run( agent=agent_dir, @@ -1581,7 +1581,7 @@ def from_ref(cls, ref): def test_dataset_from_ref_not_implemented(): - from nemo_eval_author_plugin.evaluator.models import DatasetRef + from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import DatasetRef class ConcreteDataset(Dataset): pass @@ -1819,19 +1819,19 @@ class FakeHarborTask: ) monkeypatch.setattr( - "nemo_eval_author_plugin.evaluator.harbor.HarborTaskModel", + "nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor.HarborTaskModel", lambda p: FakeHarborTask(), ) monkeypatch.setattr( - "nemo_eval_author_plugin.evaluator.harbor.TrialPaths", + "nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor.TrialPaths", FakeTrialPaths, ) monkeypatch.setattr( - "nemo_eval_author_plugin.evaluator.harbor.EnvironmentPaths", + "nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor.EnvironmentPaths", SimpleNamespace(for_os=staticmethod(lambda os: FakeEnvPaths())), ) monkeypatch.setattr( - "nemo_eval_author_plugin.evaluator.harbor.EnvironmentFactory", + "nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor.EnvironmentFactory", SimpleNamespace(create_environment=staticmethod(lambda *a, **kw: FakeEnv())), ) @@ -1851,7 +1851,7 @@ def test_cleanup_scoped_imports_with_sibling_prevents_parent_removal(): def test_trial_task_path_from_config_dict(): - from nemo_eval_author_plugin.evaluator.harbor import _trial_task_path + from nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor import _trial_task_path trial_data = {"config": {"task": {"path": "/tmp/task-x"}}} result = _trial_task_path(trial_data) @@ -1860,7 +1860,7 @@ def test_trial_task_path_from_config_dict(): def test_resolve_trial_task_id_invalid_uri_in_task(tmp_path): - from nemo_eval_author_plugin.evaluator.harbor import _resolve_trial_task_id + from nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor import _resolve_trial_task_id task_map = {"task-x": Task(id="task-x", uri="s3://bad-uri/task-x")} trial_data = {"task_id": {"path": str(tmp_path / "task-x")}} @@ -1869,7 +1869,7 @@ def test_resolve_trial_task_id_invalid_uri_in_task(tmp_path): def test_resolve_trial_task_id_fallback_to_trial_base(): - from nemo_eval_author_plugin.evaluator.harbor import _resolve_trial_task_id + from nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor import _resolve_trial_task_id task_map = {"task-x": Task(id="task-x")} trial_data = {"task_name": "unknown/nonexistent"} diff --git a/plugins/nemo-experimentalist/tests/experimentalist/test_loop_insight_suite.py b/plugins/nemo-experimentalist/tests/experimentalist/test_loop_insight_suite.py index 8f27ebd2165..bc036817201 100644 --- a/plugins/nemo-experimentalist/tests/experimentalist/test_loop_insight_suite.py +++ b/plugins/nemo-experimentalist/tests/experimentalist/test_loop_insight_suite.py @@ -7,15 +7,15 @@ from unittest.mock import AsyncMock import pytest -from nemo_eval_author_plugin.evaluator import ( +from nemo_experimentalist_plugin.experimentalist.components.evaluator import ( Dataset, EvaluationResult, MetricResult, Task, TrialResult, ) -from nemo_eval_author_plugin.evaluator.harbor import HarborEvaluatorConfig -from nemo_eval_author_plugin.evaluator.models import ( +from nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor import HarborEvaluatorConfig +from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import ( DatasetRef, DataValue, ResourceRef, diff --git a/plugins/nemo-experimentalist/tests/experimentalist/test_loop_reporting.py b/plugins/nemo-experimentalist/tests/experimentalist/test_loop_reporting.py index d030c590c0e..a9d2372376e 100644 --- a/plugins/nemo-experimentalist/tests/experimentalist/test_loop_reporting.py +++ b/plugins/nemo-experimentalist/tests/experimentalist/test_loop_reporting.py @@ -7,7 +7,7 @@ from unittest.mock import AsyncMock import pytest -from nemo_eval_author_plugin.evaluator import ( +from nemo_experimentalist_plugin.experimentalist.components.evaluator import ( Dataset, EvaluationResult, MetricResult, @@ -15,7 +15,7 @@ TrialResult, TrialStatus, ) -from nemo_eval_author_plugin.evaluator.models import ResourceRef +from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import ResourceRef from nemo_experimentalist_plugin.entities import Candidate from nemo_experimentalist_plugin.experimentalist.components.insight_promotion import ( _task_evidence, diff --git a/plugins/nemo-eval-author/tests/test_trace_explorer_from_ref.py b/plugins/nemo-experimentalist/tests/experimentalist/test_trace_explorer_from_ref.py similarity index 93% rename from plugins/nemo-eval-author/tests/test_trace_explorer_from_ref.py rename to plugins/nemo-experimentalist/tests/experimentalist/test_trace_explorer_from_ref.py index bd56d9697b9..75d5ff68dbc 100644 --- a/plugins/nemo-eval-author/tests/test_trace_explorer_from_ref.py +++ b/plugins/nemo-experimentalist/tests/experimentalist/test_trace_explorer_from_ref.py @@ -13,8 +13,8 @@ from typing import Any, cast import pytest -from nemo_eval_author_plugin.evaluator.models import ResourceRef -from nemo_eval_author_plugin.trace_explorer import TraceExplorer +from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import ResourceRef +from nemo_experimentalist_plugin.experimentalist.components.trace_explorer import TraceExplorer @pytest.fixture diff --git a/plugins/nemo-experimentalist/tests/test_deps.py b/plugins/nemo-experimentalist/tests/test_deps.py index 2c77386c92d..cb509f95dde 100644 --- a/plugins/nemo-experimentalist/tests/test_deps.py +++ b/plugins/nemo-experimentalist/tests/test_deps.py @@ -6,7 +6,7 @@ from pathlib import Path import pytest -from nemo_eval_author_plugin.evaluator.models import DatasetRef +from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import DatasetRef from nemo_experimentalist_plugin.experimentalist.deps import ExperimentalistDeps diff --git a/plugins/nemo-experimentalist/tests/test_experiment_cli.py b/plugins/nemo-experimentalist/tests/test_experiment_cli.py index 134d3fddb43..160f81aad5b 100644 --- a/plugins/nemo-experimentalist/tests/test_experiment_cli.py +++ b/plugins/nemo-experimentalist/tests/test_experiment_cli.py @@ -7,7 +7,7 @@ import pytest from click.testing import Result -from nemo_eval_author_plugin.evaluator.models import DatasetRef +from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import DatasetRef from nemo_experimentalist_plugin import cli from nemo_experimentalist_plugin.experimentalist.components.loop import EvolutionaryOptimizerConfig from nemo_experimentalist_plugin.preflight import Probes diff --git a/plugins/nemo-experimentalist/tests/test_experimentalist_analyzer.py b/plugins/nemo-experimentalist/tests/test_experimentalist_analyzer.py index c6f85ce7c1f..76ddd6713d5 100644 --- a/plugins/nemo-experimentalist/tests/test_experimentalist_analyzer.py +++ b/plugins/nemo-experimentalist/tests/test_experimentalist_analyzer.py @@ -21,7 +21,7 @@ from typing import Any, ClassVar, cast import pytest -from nemo_eval_author_plugin.trace_analyzer import Diagnostic +from nemo_experimentalist_plugin.experimentalist.components.trace_analyzer import Diagnostic from nemo_experimentalist_plugin.experimentalist.components import analyzer as analyzer_module from nemo_experimentalist_plugin.experimentalist.components.analyzer import ( AgentAnalyzer, diff --git a/plugins/nemo-experimentalist/tests/test_experimentalist_backend.py b/plugins/nemo-experimentalist/tests/test_experimentalist_backend.py index 02398d0aeca..ad24c6eed42 100644 --- a/plugins/nemo-experimentalist/tests/test_experimentalist_backend.py +++ b/plugins/nemo-experimentalist/tests/test_experimentalist_backend.py @@ -18,7 +18,7 @@ import httpx import pytest -from nemo_eval_author_plugin.evaluator.models import EvaluationResult +from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import EvaluationResult from nemo_experimentalist_plugin.entities import Candidate from nemo_experimentalist_plugin.experimentalist import experimentalist_backend as beim from nemo_experimentalist_plugin.experimentalist.components.repository import AgentCloneError, AgentSource diff --git a/plugins/nemo-experimentalist/tests/test_experimentalist_benchmark.py b/plugins/nemo-experimentalist/tests/test_experimentalist_benchmark.py index cddf1d4a76a..caab7cc8a65 100644 --- a/plugins/nemo-experimentalist/tests/test_experimentalist_benchmark.py +++ b/plugins/nemo-experimentalist/tests/test_experimentalist_benchmark.py @@ -7,7 +7,7 @@ from types import ModuleType import pytest -from nemo_eval_author_plugin.evaluator.models import ( +from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import ( EvaluationResult, MetricResult, ResourceRef, diff --git a/plugins/nemo-experimentalist/tests/test_experimentalist_run.py b/plugins/nemo-experimentalist/tests/test_experimentalist_run.py index 90a54f98e46..1d72ea154e3 100644 --- a/plugins/nemo-experimentalist/tests/test_experimentalist_run.py +++ b/plugins/nemo-experimentalist/tests/test_experimentalist_run.py @@ -7,7 +7,7 @@ from typing import Literal, cast import pytest -from nemo_eval_author_plugin.evaluator.models import DatasetRef +from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import DatasetRef from nemo_experimentalist_plugin.experimentalist import run as experimentalist_run from nemo_experimentalist_plugin.experimentalist.components import loop as loop_module from nemo_experimentalist_plugin.experimentalist.components.loop import EvolutionaryOptimizerConfig diff --git a/plugins/nemo-experimentalist/tests/test_otlp.py b/plugins/nemo-experimentalist/tests/test_otlp.py index 7ea2e38eef2..4a0b25915c3 100644 --- a/plugins/nemo-experimentalist/tests/test_otlp.py +++ b/plugins/nemo-experimentalist/tests/test_otlp.py @@ -14,7 +14,7 @@ from pathlib import Path import pytest -from nemo_eval_author_plugin.evaluator import ResourceRef +from nemo_experimentalist_plugin.experimentalist.components.evaluator import ResourceRef from nemo_experimentalist_plugin.experimentalist.otlp import ( jsonl_to_protobuf, read_trace_id, diff --git a/pyproject.toml b/pyproject.toml index a0fb5880aa4..22323ace5e8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,7 +72,13 @@ insights = ["nemo-insights-plugin"] # Optional Experimentalist agent-optimization plugin. Its agent framework (nooa) and # evaluator (harbor) are both 3.12-only, so the whole plugin is gated rather than # installed half-resolved on 3.11. -experimentalist = ["nemo-experimentalist-plugin ; python_full_version >= '3.12'"] +# Install Experimentalist and Eval Author together. Eval Author hard-depends on +# Experimentalist for evaluator/trace helpers; Experimentalist imports Eval Author +# at runtime for insight mode (no reverse package dep, to avoid a cycle). +experimentalist = [ + "nemo-experimentalist-plugin ; python_full_version >= '3.12'", + "nemo-eval-author-plugin ; python_full_version >= '3.12'", +] dev = [ "pre-commit>=3.8.0", "pydantic-settings>=2.6.1", diff --git a/uv.lock b/uv.lock index 7f171b20e54..f9a373b203f 100644 --- a/uv.lock +++ b/uv.lock @@ -4289,13 +4289,10 @@ version = "0.1.0" source = { editable = "plugins/nemo-eval-author" } dependencies = [ { name = "harbor", marker = "(python_full_version >= '3.12' and platform_machine == 'arm64' and sys_platform == 'darwin') or (python_full_version >= '3.12' and platform_machine == 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.12' and platform_machine == 'x86_64' and sys_platform == 'linux')" }, - { name = "httpx", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nemo-experimentalist-plugin", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-insights-plugin", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-platform", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, - { name = "nemo-platform-plugin", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nooa", marker = "(python_full_version >= '3.12' and platform_machine == 'arm64' and sys_platform == 'darwin') or (python_full_version >= '3.12' and platform_machine == 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.12' and platform_machine == 'x86_64' and sys_platform == 'linux')" }, - { name = "opentelemetry-proto", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, - { name = "protobuf", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "pydantic", extra = ["email"], marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "pyyaml", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "tomlkit", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, @@ -4304,13 +4301,10 @@ dependencies = [ [package.metadata] requires-dist = [ { name = "harbor", marker = "python_full_version >= '3.12'", specifier = ">=0.16" }, - { name = "httpx" }, + { name = "nemo-experimentalist-plugin", editable = "plugins/nemo-experimentalist" }, { name = "nemo-insights-plugin", editable = "plugins/nemo-insights" }, { name = "nemo-platform", editable = "packages/nemo_platform" }, - { name = "nemo-platform-plugin", editable = "packages/nemo_platform_plugin" }, { name = "nooa", marker = "python_full_version >= '3.12'", git = "https://github.com/NVIDIA-NeMo/labs-OO-Agents.git?tag=v0.0.6" }, - { name = "opentelemetry-proto", specifier = ">=1.42.1" }, - { name = "protobuf", specifier = ">=6.0.0" }, { name = "pydantic", specifier = ">=2" }, { name = "pyyaml", specifier = ">=6.0.3" }, { name = "tomlkit", specifier = ">=0.13.3" }, @@ -4426,7 +4420,6 @@ source = { editable = "plugins/nemo-experimentalist" } dependencies = [ { name = "harbor", marker = "(python_full_version >= '3.12' and platform_machine == 'arm64' and sys_platform == 'darwin') or (python_full_version >= '3.12' and platform_machine == 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.12' and platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "httpx", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, - { name = "nemo-eval-author-plugin", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-insights-plugin", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-platform", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-platform-plugin", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, @@ -4442,7 +4435,6 @@ dependencies = [ requires-dist = [ { name = "harbor", marker = "python_full_version >= '3.12'", specifier = ">=0.16" }, { name = "httpx" }, - { name = "nemo-eval-author-plugin", editable = "plugins/nemo-eval-author" }, { name = "nemo-insights-plugin", editable = "plugins/nemo-insights" }, { name = "nemo-platform", editable = "packages/nemo_platform" }, { name = "nemo-platform-plugin", editable = "packages/nemo_platform_plugin" }, @@ -6418,6 +6410,7 @@ enabled-plugins = [ { name = "nemo-unsloth-plugin", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, ] experimentalist = [ + { name = "nemo-eval-author-plugin", marker = "(python_full_version >= '3.12' and platform_machine == 'arm64' and sys_platform == 'darwin') or (python_full_version >= '3.12' and platform_machine == 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.12' and platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-experimentalist-plugin", marker = "(python_full_version >= '3.12' and platform_machine == 'arm64' and sys_platform == 'darwin') or (python_full_version >= '3.12' and platform_machine == 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.12' and platform_machine == 'x86_64' and sys_platform == 'linux')" }, ] functional-services = [ @@ -6624,7 +6617,10 @@ enabled-plugins = [ { name = "nemo-switchyard", editable = "plugins/nemo-switchyard" }, { name = "nemo-unsloth-plugin", editable = "plugins/nemo-unsloth" }, ] -experimentalist = [{ name = "nemo-experimentalist-plugin", marker = "python_full_version >= '3.12'", editable = "plugins/nemo-experimentalist" }] +experimentalist = [ + { name = "nemo-eval-author-plugin", marker = "python_full_version >= '3.12'", editable = "plugins/nemo-eval-author" }, + { name = "nemo-experimentalist-plugin", marker = "python_full_version >= '3.12'", editable = "plugins/nemo-experimentalist" }, +] functional-services = [ { name = "nemo-agents-plugin", editable = "plugins/nemo-agents" }, { name = "nemo-anonymizer-plugin", editable = "plugins/nemo-anonymizer" }, From 06a9c46848e67c8bf3a7315604728b91bb837c4f Mon Sep 17 00:00:00 2001 From: Alec Khoury Date: Tue, 28 Jul 2026 15:00:05 -0500 Subject: [PATCH 3/6] fix(ci): gate Eval Author plugin lint and test discovery like Experimentalist Eval Author ships in the 3.12-only experimentalist group, so its tests must be skipped when root CI collects on 3.11 and its nooa/harbor imports and agent method stubs need the same ty overrides Experimentalist already has. Also apply ruff import sorting and formatting missed by the module move. Signed-off-by: Alec Khoury --- .../eval_author/agent.py | 66 ++++++-- .../eval_author/materialization.py | 122 ++++++++++--- .../eval_author/run.py | 54 ++++-- .../tests/test_eval_author_agent.py | 87 +++++++--- .../tests/test_eval_author_materialization.py | 160 +++++++++++++----- .../tests/test_eval_author_run.py | 126 +++++++++++--- .../tests/test_model_config.py | 30 +++- .../experimentalist/components/analyzer.py | 6 +- .../experimentalist/components/coder.py | 9 +- .../components/insight_promotion.py | 2 +- .../experimentalist_backend.py | 2 +- .../experimentalist/run.py | 2 +- .../test_dataset_staging_loop.py | 2 +- .../test_eval_author_repair_e2e.py | 2 +- .../experimentalist/test_evaluator_harbor.py | 4 +- .../test_loop_insight_suite.py | 4 +- .../experimentalist/test_loop_reporting.py | 2 +- .../tests/test_experiment_cli.py | 2 +- .../tests/test_experimentalist_analyzer.py | 2 +- .../tests/test_experimentalist_backend.py | 2 +- .../tests/test_experimentalist_run.py | 2 +- pyproject.toml | 15 +- tests/discovery_exclusions.py | 5 + 23 files changed, 541 insertions(+), 167 deletions(-) diff --git a/plugins/nemo-eval-author/src/nemo_eval_author_plugin/eval_author/agent.py b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/eval_author/agent.py index 1bddfcde271..48c26d3150e 100644 --- a/plugins/nemo-eval-author/src/nemo_eval_author_plugin/eval_author/agent.py +++ b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/eval_author/agent.py @@ -13,7 +13,10 @@ from typing import Any from nemo_eval_author_plugin.eval_author.materialization import InsightSuite -from nemo_eval_author_plugin.eval_author.models import EvalAuthorConfig, EvalAuthorResult +from nemo_eval_author_plugin.eval_author.models import ( + EvalAuthorConfig, + EvalAuthorResult, +) from nemo_eval_author_plugin.model_config import ( bridge_author_env_to_experimentalist, get_fast_model, @@ -31,8 +34,12 @@ Task, TrialResult, ) -from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import ResourceRef -from nemo_experimentalist_plugin.experimentalist.components.tools import GuardedShellTools +from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import ( + ResourceRef, +) +from nemo_experimentalist_plugin.experimentalist.components.tools import ( + GuardedShellTools, +) from nemo_experimentalist_plugin.experimentalist.components.trace_analyzer import ( Diagnostic, TraceAnalyzer, @@ -64,7 +71,12 @@ class EvalAuthor(Agent, llm=get_smart_model()): This suite will be used for optimization and regression testing. """ - def __init__(self, experiment_dir: Path, config: EvalAuthorConfig | None = None, **kwargs: Any) -> None: + def __init__( + self, + experiment_dir: Path, + config: EvalAuthorConfig | None = None, + **kwargs: Any, + ) -> None: """Initialize the Eval Author for the given experiment directory. Args: @@ -92,7 +104,9 @@ def __init__(self, experiment_dir: Path, config: EvalAuthorConfig | None = None, config=TokenBudgetConfig(max_tokens=self._config.max_summary_tokens), ) - @strategy(CodeActStrategy(config=CodeActConfig(max_iterations=15, cell_timeout=60.0))) + @strategy( + CodeActStrategy(config=CodeActConfig(max_iterations=15, cell_timeout=60.0)) + ) async def discover_runner(self, dataset: Dataset) -> str: """Discover how this dataset's evaluation runner works. @@ -108,7 +122,9 @@ async def discover_runner(self, dataset: Dataset) -> str: """ # noqa: D413 ... - @strategy(CodeActStrategy(config=CodeActConfig(max_iterations=60, cell_timeout=3600.0))) + @strategy( + CodeActStrategy(config=CodeActConfig(max_iterations=60, cell_timeout=3600.0)) + ) async def author_insight_metrics( self, insight: Insight, @@ -191,7 +207,9 @@ async def author_insight_metrics( """ # noqa: D413 ... - @strategy(CodeActStrategy(config=CodeActConfig(max_iterations=20, cell_timeout=60.0))) + @strategy( + CodeActStrategy(config=CodeActConfig(max_iterations=20, cell_timeout=60.0)) + ) async def fill_task_template( self, trace_ref: str, @@ -233,7 +251,10 @@ def _trace_trial( id=f"insight-trace-{index}", task_id=task.id, status="completed", - trace=ResourceRef(uri=f"intake://{trace_ref}", description="Production trace attached to the insight."), + trace=ResourceRef( + uri=f"intake://{trace_ref}", + description="Production trace attached to the insight.", + ), metadata={ "source": "insight", "trace_ref": trace_ref, @@ -287,7 +308,9 @@ async def _run( resolved_agent = self.experiment_dir / agent_path insight_id = insight.id if not insight_id: - raise ValueError("Eval Author requires a persisted Insight with a durable id") + raise ValueError( + "Eval Author requires a persisted Insight with a durable id" + ) refs = insight.trace_refs[: self._config.max_traces] if not refs: @@ -305,7 +328,9 @@ async def _run( try: staged_tasks = insight_suite.stage(refs) for staged in staged_tasks: - await self.fill_task_template(staged.trace_ref, staged.task, client, insight.workspace) + await self.fill_task_template( + staged.trace_ref, staged.task, client, insight.workspace + ) insight_suite.validate(staged) materialized_dataset = insight_suite.promote_local(refs, staged_tasks) except BaseException: @@ -317,8 +342,13 @@ async def _run( for index, (task, ref) in enumerate(zip(tasks, refs, strict=True), start=1) ] diagnostics: list[tuple[str, Diagnostic]] = [] - analyzer_config = TraceAnalyzerConfig(max_summary_tokens=self._config.max_summary_tokens) - analyzers = [TraceAnalyzer(experiment_dir=self.experiment_dir, config=analyzer_config) for _ in trials] + analyzer_config = TraceAnalyzerConfig( + max_summary_tokens=self._config.max_summary_tokens + ) + analyzers = [ + TraceAnalyzer(experiment_dir=self.experiment_dir, config=analyzer_config) + for _ in trials + ] raw_diagnostics: list[Diagnostic | BaseException] = list( await asyncio.gather( *[ @@ -330,7 +360,9 @@ async def _run( client=client, workspace=insight.workspace, ) - for analyzer, trial, task in zip(analyzers, trials, tasks, strict=True) + for analyzer, trial, task in zip( + analyzers, trials, tasks, strict=True + ) ], return_exceptions=True, ) @@ -343,12 +375,16 @@ async def _run( logger.warning("Trace analysis failed for %s: %s", ref, result) analysis_statuses[task.id] = ("failed", str(result)) continue - cache.store(self.experiment_dir, cache.task_hash(f"eval_author:{ref}"), result) + cache.store( + self.experiment_dir, cache.task_hash(f"eval_author:{ref}"), result + ) diagnostics.append((ref, result)) analysis_statuses[task.id] = ("completed", None) insight_suite.record_analysis(analysis_statuses) - self.context["dataset_documentation"] = doc(type(materialized_dataset), inline_depth=1) + self.context["dataset_documentation"] = doc( + type(materialized_dataset), inline_depth=1 + ) runner_conventions = await self.discover_runner(materialized_dataset) summary = await self.author_insight_metrics( insight, diff --git a/plugins/nemo-eval-author/src/nemo_eval_author_plugin/eval_author/materialization.py b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/eval_author/materialization.py index 4e084dccb88..2cfea78fcd5 100644 --- a/plugins/nemo-eval-author/src/nemo_eval_author_plugin/eval_author/materialization.py +++ b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/eval_author/materialization.py @@ -18,8 +18,13 @@ import tomlkit from harbor.models.task.task import Task as HarborTask -from nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor import HarborDataset -from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import Task, local_path_from_uri +from nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor import ( + HarborDataset, +) +from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import ( + Task, + local_path_from_uri, +) _MANIFEST_SCHEMA_VERSION = 3 _CONTENT_HASH_SCHEMA_VERSION = 1 @@ -41,7 +46,11 @@ def _sha256_bytes(value: bytes) -> str: def _canonical_digest(value: object) -> str: - return _sha256_bytes(json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=False).encode("utf-8")) + return _sha256_bytes( + json.dumps( + value, sort_keys=True, separators=(",", ":"), ensure_ascii=False + ).encode("utf-8") + ) def _file_hashes(root: Path) -> dict[str, str]: @@ -67,28 +76,40 @@ def _verifier_dir(task_dir: Path) -> Path: raise ValueError(f"Materialized task has no verifier directory: {task_dir}") -def _content_provenance(suite_dir: Path, manifest: dict[str, object]) -> tuple[list[dict[str, object]], str, str]: +def _content_provenance( + suite_dir: Path, manifest: dict[str, object] +) -> tuple[list[dict[str, object]], str, str]: raw_tasks = manifest.get("tasks") if not isinstance(raw_tasks, list): - raise ValueError(f"Insight suite manifest has invalid tasks: {suite_dir / 'manifest.json'}") + raise ValueError( + f"Insight suite manifest has invalid tasks: {suite_dir / 'manifest.json'}" + ) tasks: list[dict[str, object]] = [] scorer_inputs: list[dict[str, str]] = [] suite_inputs: list[dict[str, str]] = [] for raw_task in raw_tasks: if not isinstance(raw_task, dict): - raise ValueError(f"Insight suite manifest has invalid task entry: {raw_task!r}") + raise ValueError( + f"Insight suite manifest has invalid task entry: {raw_task!r}" + ) if not all(isinstance(key, str) for key in raw_task): - raise ValueError(f"Insight suite manifest task has invalid keys: {raw_task!r}") + raise ValueError( + f"Insight suite manifest task has invalid keys: {raw_task!r}" + ) task_entry = cast(dict[str, object], raw_task) relative_path = task_entry.get("path") if not isinstance(relative_path, str) or not relative_path: - raise ValueError(f"Insight suite manifest task has invalid path: {relative_path!r}") + raise ValueError( + f"Insight suite manifest task has invalid path: {relative_path!r}" + ) task_dir = (suite_dir / relative_path).resolve() try: task_dir.relative_to(suite_dir.resolve()) except ValueError as exc: - raise ValueError(f"Insight suite manifest task escapes the suite: {relative_path!r}") from exc + raise ValueError( + f"Insight suite manifest task escapes the suite: {relative_path!r}" + ) from exc if not task_dir.is_dir(): raise ValueError(f"Insight suite manifest task path is missing: {task_dir}") @@ -97,12 +118,16 @@ def _content_provenance(suite_dir: Path, manifest: dict[str, object]) -> tuple[l try: verifier_path = verifier_dir.relative_to(task_dir).as_posix() except ValueError as exc: - raise ValueError(f"Insight suite verifier must be contained in its task: {verifier_dir}") from exc + raise ValueError( + f"Insight suite verifier must be contained in its task: {verifier_dir}" + ) from exc verifier_files = _file_hashes(verifier_dir) content_hash = f"sha256:{_canonical_digest(files)}" verifier_hash = f"sha256:{_canonical_digest(verifier_files)}" task_metadata = { - key: value for key, value in task_entry.items() if key not in {"content_hash", "files", "verifier"} + key: value + for key, value in task_entry.items() + if key not in {"content_hash", "files", "verifier"} } tasks.append( { @@ -159,12 +184,16 @@ class FinalizedInsightSuite: class InsightSuite: """Build one experiment-local persisted Harbor dataset for an Insight.""" - def __init__(self, *, experiment_dir: Path, insight_id: str, task_template: Task) -> None: + def __init__( + self, *, experiment_dir: Path, insight_id: str, task_template: Task + ) -> None: """Initialize deterministic paths and template provenance for a suite.""" if not insight_id: raise ValueError("Insight id is required to materialize an insight suite") if not task_template.uri: - raise ValueError("Task template URI is required to materialize an insight suite") + raise ValueError( + "Task template URI is required to materialize an insight suite" + ) self.experiment_dir = experiment_dir.resolve() self.insight_id = insight_id @@ -173,10 +202,14 @@ def __init__(self, *, experiment_dir: Path, insight_id: str, task_template: Task context="Eval Author task template", ).resolve() if not self.template_dir.is_dir(): - raise ValueError(f"Eval Author task template is not a directory: {self.template_dir}") + raise ValueError( + f"Eval Author task template is not a directory: {self.template_dir}" + ) self.template_uri = self.template_dir.as_uri() insight_slug = f"{_slug(insight_id, fallback='insight')}-{_digest(insight_id)}" - self.root = self.experiment_dir / "eval-and-optimize" / "eval_author" / insight_slug + self.root = ( + self.experiment_dir / "eval-and-optimize" / "eval_author" / insight_slug + ) self.suite_dir = self.root / "insight-suite" self._candidate_root: Path | None = None self._candidate_suite: Path | None = None @@ -200,7 +233,15 @@ def stage(self, trace_refs: list[str]) -> list[StagedInsightTask]: task_dir = self._candidate_suite / slug shutil.copytree(self.template_dir, task_dir) task = list(HarborDataset.from_path(task_dir).list_tasks())[0] - staged.append(StagedInsightTask(index=index, trace_ref=trace_ref, slug=slug, path=task_dir, task=task)) + staged.append( + StagedInsightTask( + index=index, + trace_ref=trace_ref, + slug=slug, + path=task_dir, + task=task, + ) + ) return staged def discard(self) -> None: @@ -219,7 +260,9 @@ def validate(self, staged: StagedInsightTask) -> None: raise ValueError(f"Materialized task has no [task] table: {toml_path}") raw_name = task_table.get("name") if not isinstance(raw_name, str) or "/" not in raw_name: - raise ValueError(f"Materialized task [task].name must use org/name format: {raw_name!r}") + raise ValueError( + f"Materialized task [task].name must use org/name format: {raw_name!r}" + ) organization, short_name = raw_name.rsplit("/", 1) base_name = short_name.split("__", 1)[0] task_table["name"] = f"{organization}/{base_name}__{staged.slug}" @@ -237,15 +280,24 @@ def validate(self, staged: StagedInsightTask) -> None: toml_path.write_text(tomlkit.dumps(document), encoding="utf-8") instruction_path = staged.path / "instruction.md" - if not instruction_path.is_file() or not instruction_path.read_text(encoding="utf-8").strip(): - raise ValueError(f"Materialized task instruction is missing or empty: {instruction_path}") + if ( + not instruction_path.is_file() + or not instruction_path.read_text(encoding="utf-8").strip() + ): + raise ValueError( + f"Materialized task instruction is missing or empty: {instruction_path}" + ) for directory_name in ("environment", "tests"): directory = staged.path / directory_name if not directory.is_dir(): - raise ValueError(f"Materialized task is missing required {directory_name}/ directory: {directory}") + raise ValueError( + f"Materialized task is missing required {directory_name}/ directory: {directory}" + ) HarborTask(staged.path) - def promote_local(self, trace_refs: list[str], staged_tasks: list[StagedInsightTask]) -> HarborDataset: + def promote_local( + self, trace_refs: list[str], staged_tasks: list[StagedInsightTask] + ) -> HarborDataset: """Promote the validated candidate to the experiment-local working copy.""" if self._candidate_root is None or self._candidate_suite is None: raise RuntimeError("Insight suite has not been staged") @@ -282,7 +334,9 @@ def promote_local(self, trace_refs: list[str], staged_tasks: list[StagedInsightT dataset_id=f"insight-{_digest(self.insight_id, 12)}", ) for task in dataset.list_tasks(): - HarborTask(local_path_from_uri(task.uri, context="Materialized Harbor task")) + HarborTask( + local_path_from_uri(task.uri, context="Materialized Harbor task") + ) except BaseException as exc: try: if had_existing and backup.exists(): @@ -293,7 +347,9 @@ def promote_local(self, trace_refs: list[str], staged_tasks: list[StagedInsightT shutil.rmtree(self.suite_dir) self.discard() except BaseException as rollback_exc: - rollback_exc.add_note(f"Local Insight suite promotion also failed before rollback: {exc!r}") + rollback_exc.add_note( + f"Local Insight suite promotion also failed before rollback: {exc!r}" + ) raise rollback_exc from exc raise @@ -312,14 +368,18 @@ def record_analysis(self, statuses: dict[str, tuple[str, str | None]]) -> None: if error is not None: task["analysis"]["error"] = error pending_path = manifest_path.with_suffix(".json.pending") - pending_path.write_text(json.dumps(manifest, indent=2, sort_keys=True) + "\n", encoding="utf-8") + pending_path.write_text( + json.dumps(manifest, indent=2, sort_keys=True) + "\n", encoding="utf-8" + ) os.replace(pending_path, manifest_path) def finalize(self) -> FinalizedInsightSuite: """Persist content identities on the experiment-local authored suite.""" manifest_path = self.suite_dir / "manifest.json" manifest = json.loads(manifest_path.read_text(encoding="utf-8")) - tasks, scorer_identity, suite_identity = _content_provenance(self.suite_dir, manifest) + tasks, scorer_identity, suite_identity = _content_provenance( + self.suite_dir, manifest + ) digest = suite_identity.removeprefix("sha256:") manifest.pop("artifact", None) manifest.update( @@ -336,7 +396,9 @@ def finalize(self) -> FinalizedInsightSuite: } ) pending_path = manifest_path.with_suffix(".json.pending") - pending_path.write_text(json.dumps(manifest, indent=2, sort_keys=True) + "\n", encoding="utf-8") + pending_path.write_text( + json.dumps(manifest, indent=2, sort_keys=True) + "\n", encoding="utf-8" + ) os.replace(pending_path, manifest_path) dataset = HarborDataset.from_path( @@ -348,13 +410,17 @@ def finalize(self) -> FinalizedInsightSuite: task_path = task.get("path") content_hash = task.get("content_hash") verifier = task.get("verifier") - verifier_hash = verifier.get("content_hash") if isinstance(verifier, dict) else None + verifier_hash = ( + verifier.get("content_hash") if isinstance(verifier, dict) else None + ) if ( not isinstance(task_path, str) or not isinstance(content_hash, str) or not isinstance(verifier_hash, str) ): - raise ValueError(f"Finalized Insight suite has invalid task provenance: {task!r}") + raise ValueError( + f"Finalized Insight suite has invalid task provenance: {task!r}" + ) task_hashes[task_path] = { "content_hash": content_hash, "verifier_hash": verifier_hash, diff --git a/plugins/nemo-eval-author/src/nemo_eval_author_plugin/eval_author/run.py b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/eval_author/run.py index 945fc17735f..79bb4a24353 100644 --- a/plugins/nemo-eval-author/src/nemo_eval_author_plugin/eval_author/run.py +++ b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/eval_author/run.py @@ -7,14 +7,30 @@ from pathlib import Path from typing import Literal, Protocol, cast -from nemo_eval_author_plugin.eval_author.models import EvalAuthorConfig, EvalAuthorResult +from nemo_eval_author_plugin.eval_author.models import ( + EvalAuthorConfig, + EvalAuthorResult, +) from nemo_experimentalist_plugin.client import make_client -from nemo_experimentalist_plugin.experimentalist.components.dataset_staging import stage_task_template -from nemo_experimentalist_plugin.experimentalist.components.evaluator import Dataset, Task -from nemo_experimentalist_plugin.experimentalist.components.evaluator.base import EvaluatorType -from nemo_experimentalist_plugin.experimentalist.components.evaluator.factory import DatasetFactory -from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import DatasetRef -from nemo_experimentalist_plugin.experimentalist.experimentalist_backend import make_experimentalist_backend +from nemo_experimentalist_plugin.experimentalist.components.dataset_staging import ( + stage_task_template, +) +from nemo_experimentalist_plugin.experimentalist.components.evaluator import ( + Dataset, + Task, +) +from nemo_experimentalist_plugin.experimentalist.components.evaluator.base import ( + EvaluatorType, +) +from nemo_experimentalist_plugin.experimentalist.components.evaluator.factory import ( + DatasetFactory, +) +from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import ( + DatasetRef, +) +from nemo_experimentalist_plugin.experimentalist.experimentalist_backend import ( + make_experimentalist_backend, +) from nemo_insights_plugin.entities import Insight from nemo_platform import AsyncNeMoPlatform @@ -81,10 +97,14 @@ async def run_eval_author( experiments_output=str(experiment_dir), mode=mode, ) - resolved_insight = await backend.get_insight(workspace=workspace, insight_id=str(insight)) + resolved_insight = await backend.get_insight( + workspace=workspace, insight_id=str(insight) + ) agent_ref = agent if agent is not None else resolved_insight.agent agent_path = experiment_dir / "eval_author" / "source-agent" - await backend.get_agent_code(workspace=workspace, agent=agent_ref, dest=agent_path) + await backend.get_agent_code( + workspace=workspace, agent=agent_ref, dest=agent_path + ) dataset_factory = DatasetFactory() staged_task_template = await stage_task_template( @@ -100,18 +120,26 @@ async def run_eval_author( return await eval_author.run( insight=resolved_insight, agent_path=agent_path, - task_template=dataset_factory.build_task_template(evaluator_type, staged_task_template), + task_template=dataset_factory.build_task_template( + evaluator_type, staged_task_template + ), train_dataset=dataset_factory.build_dataset(evaluator_type, train_dataset), - validation_dataset=dataset_factory.build_dataset(evaluator_type, validation_dataset), + validation_dataset=dataset_factory.build_dataset( + evaluator_type, validation_dataset + ), client=client, ) finally: await client.close() -def build_eval_author_agent(*, experiment_dir: Path, config: EvalAuthorConfig) -> _EvalAuthorAgent: +def build_eval_author_agent( + *, experiment_dir: Path, config: EvalAuthorConfig +) -> _EvalAuthorAgent: """Build the LLM-backed Eval Author agent lazily.""" - from nemo_eval_author_plugin.eval_author.agent import build_eval_author_agent as _build_eval_author_agent + from nemo_eval_author_plugin.eval_author.agent import ( + build_eval_author_agent as _build_eval_author_agent, + ) return _build_eval_author_agent(experiment_dir=experiment_dir, config=config) diff --git a/plugins/nemo-eval-author/tests/test_eval_author_agent.py b/plugins/nemo-eval-author/tests/test_eval_author_agent.py index c31cb66594f..02f7f55c154 100644 --- a/plugins/nemo-eval-author/tests/test_eval_author_agent.py +++ b/plugins/nemo-eval-author/tests/test_eval_author_agent.py @@ -59,7 +59,9 @@ class _PipelineCalls: analyzer_init: list[_AnalyzerInitCall] analyzer_run: list[_AnalyzerRunCall] discovered_datasets: list[Dataset] - author_args: list[tuple[Insight, list[tuple[str, Diagnostic]], Dataset, str, str | None]] + author_args: list[ + tuple[Insight, list[tuple[str, Diagnostic]], Dataset, str, str | None] + ] suite_discards: int @@ -103,7 +105,9 @@ def _eval_author( def _diagnostic(summary: str) -> Diagnostic: - return Diagnostic(outcome="FAILURE", summary=summary, failure_point=1, root_cause="wrong tool") + return Diagnostic( + outcome="FAILURE", summary=summary, failure_point=1, root_cause="wrong tool" + ) def _prompt(method: Any) -> str: @@ -118,16 +122,24 @@ def test_eval_author_prompts_scope_metrics_to_materialized_insight_suite() -> No assert "read it first" in discover_prompt assert "inspect the actual files" in discover_prompt - assert "authoritative reference for what artifacts exist at evaluation runtime" in author_prompt + assert ( + "authoritative reference for what artifacts exist at evaluation runtime" + in author_prompt + ) assert "how tasks are structured, and how to add metrics" in author_prompt - assert "Add at least one new Insight-specific metric key to every task in ``insight_suite``" in author_prompt + assert ( + "Add at least one new Insight-specific metric key to every task in ``insight_suite``" + in author_prompt + ) assert "Preserve every existing verifier metric" in author_prompt assert "Do not modify the user's train or validation datasets" in author_prompt assert "call ``await insight_suite.validate()``" in author_prompt assert "fix every reported failure and revalidate the suite" in author_prompt -def test_eval_author_prompts_retain_root_cause_and_normalized_scoring_guidance() -> None: +def test_eval_author_prompts_retain_root_cause_and_normalized_scoring_guidance() -> ( + None +): prompt = _prompt(EvalAuthor.author_insight_metrics) assert "Focus the metric on the root cause, not the surface symptom" in prompt @@ -175,7 +187,11 @@ def stage(self, trace_refs: list[str]) -> list[Any]: type( "StagedTask", (), - {"trace_ref": trace_ref, "task": self.task_template, "result": None}, + { + "trace_ref": trace_ref, + "task": self.task_template, + "result": None, + }, )() for trace_ref in trace_refs ] @@ -184,7 +200,9 @@ def stage(self, trace_refs: list[str]) -> list[Any]: def validate(self, staged: Any) -> None: staged.result = calls.fill_task_template[-1].result - def promote_local(self, trace_refs: list[str], staged_tasks: list[Any]) -> Dataset: + def promote_local( + self, trace_refs: list[str], staged_tasks: list[Any] + ) -> Dataset: assert trace_refs == [staged.trace_ref for staged in staged_tasks] tasks = [staged.result for staged in staged_tasks] if materialized_dataset is not None: @@ -243,11 +261,15 @@ async def __call__( return result class FakeTraceAnalyzer: - def __init__(self, *, experiment_dir: Path, config: TraceAnalyzerConfig) -> None: + def __init__( + self, *, experiment_dir: Path, config: TraceAnalyzerConfig + ) -> None: nonlocal next_analyzer self._index = next_analyzer next_analyzer += 1 - calls.analyzer_init.append(_AnalyzerInitCall(experiment_dir=experiment_dir, config=config)) + calls.analyzer_init.append( + _AnalyzerInitCall(experiment_dir=experiment_dir, config=config) + ) async def run( self, @@ -308,7 +330,9 @@ async def __call__( @pytest.mark.asyncio -async def test_run_without_traces_returns_input_datasets_unchanged(tmp_path: Path) -> None: +async def test_run_without_traces_returns_input_datasets_unchanged( + tmp_path: Path, +) -> None: eval_author = _eval_author(tmp_path) train_dataset = Dataset(id="train") validation_dataset = Dataset(id="validation") @@ -368,8 +392,14 @@ async def test_run_enforces_max_traces( client=cast(Any, object()), ) - assert [call.trace_ref for call in calls.fill_task_template] == ["trace-1", "trace-2"] - assert [call.trial.metadata["trace_ref"] for call in calls.analyzer_run] == ["trace-1", "trace-2"] + assert [call.trace_ref for call in calls.fill_task_template] == [ + "trace-1", + "trace-2", + ] + assert [call.trial.metadata["trace_ref"] for call in calls.analyzer_run] == [ + "trace-1", + "trace-2", + ] @pytest.mark.asyncio @@ -403,7 +433,9 @@ async def __call__(self, *_: Any) -> Task: def test_trace_trial_uses_intake_uri_and_insight_metadata(tmp_path: Path) -> None: insight = _insight(["trace-7"]) - trial = _eval_author(tmp_path)._trace_trial(insight, Task(id="task-7"), "trace-7", 3, insight.id) + trial = _eval_author(tmp_path)._trace_trial( + insight, Task(id="task-7"), "trace-7", 3, insight.id + ) assert trial.id == "insight-trace-3" assert trial.task_id == "task-7" @@ -428,7 +460,9 @@ async def test_run_caches_each_successful_diagnostic( eval_author = _eval_author(tmp_path) _install_pipeline(monkeypatch, [first, second], eval_author) stored: list[tuple[Path, str, Diagnostic]] = [] - monkeypatch.setattr(eval_author_module.cache, "store", lambda *args: stored.append(args)) + monkeypatch.setattr( + eval_author_module.cache, "store", lambda *args: stored.append(args) + ) await eval_author.run( _insight(["trace-a", "trace-b"]), @@ -453,9 +487,15 @@ async def test_run_skips_failed_trace_analysis_and_keeps_successes( ) -> None: successful = _diagnostic("successful") eval_author = _eval_author(tmp_path) - calls = _install_pipeline(monkeypatch, [RuntimeError("analysis failed"), successful], eval_author) + calls = _install_pipeline( + monkeypatch, [RuntimeError("analysis failed"), successful], eval_author + ) stored: list[Diagnostic] = [] - monkeypatch.setattr(eval_author_module.cache, "store", lambda workspace, key, value: stored.append(value)) + monkeypatch.setattr( + eval_author_module.cache, + "store", + lambda workspace, key, value: stored.append(value), + ) insight = _insight(["trace-bad", "trace-good"]) result = await eval_author.run( @@ -600,7 +640,9 @@ async def test_run_feeds_validation_failures_back_for_one_repair_attempt( monkeypatch: pytest.MonkeyPatch, ) -> None: eval_author = _eval_author(tmp_path) - insight_dataset = _RepairableDataset("insight-suite", "task 'insight-a': check.py:2:1: invalid syntax") + insight_dataset = _RepairableDataset( + "insight-suite", "task 'insight-a': check.py:2:1: invalid syntax" + ) _install_pipeline( monkeypatch, [_diagnostic("diagnostic")], @@ -652,7 +694,9 @@ async def test_run_raises_after_validation_repair_budget_is_exhausted( monkeypatch: pytest.MonkeyPatch, ) -> None: eval_author = _eval_author(tmp_path, max_validation_repair_attempts=1) - insight_dataset = _RepairableDataset("insight-suite", "task 'insight-a': check.py:2:1: invalid syntax") + insight_dataset = _RepairableDataset( + "insight-suite", "task 'insight-a': check.py:2:1: invalid syntax" + ) calls = _install_pipeline( monkeypatch, [_diagnostic("diagnostic")], @@ -679,6 +723,11 @@ async def test_run_raises_after_validation_repair_budget_is_exhausted( def test_eval_author_config_bounds_validation_repair_attempts() -> None: - assert EvalAuthorConfig(max_validation_repair_attempts=10).max_validation_repair_attempts == 10 + assert ( + EvalAuthorConfig( + max_validation_repair_attempts=10 + ).max_validation_repair_attempts + == 10 + ) with pytest.raises(ValueError, match="less than or equal to 10"): EvalAuthorConfig(max_validation_repair_attempts=11) diff --git a/plugins/nemo-eval-author/tests/test_eval_author_materialization.py b/plugins/nemo-eval-author/tests/test_eval_author_materialization.py index a110fcfcf01..ee7825ed078 100644 --- a/plugins/nemo-eval-author/tests/test_eval_author_materialization.py +++ b/plugins/nemo-eval-author/tests/test_eval_author_materialization.py @@ -10,10 +10,14 @@ from pathlib import Path import pytest -from nemo_eval_author_plugin.eval_author import materialization as materialization_module +from nemo_eval_author_plugin.eval_author import ( + materialization as materialization_module, +) from nemo_eval_author_plugin.eval_author.materialization import InsightSuite from nemo_experimentalist_plugin.experimentalist.components.evaluator import Task -from nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor import HarborDataset +from nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor import ( + HarborDataset, +) def _write_template(root: Path) -> Task: @@ -42,22 +46,34 @@ def _write_template(root: Path) -> Task: (environment / "Dockerfile").write_text("FROM ubuntu:24.04\n", encoding="utf-8") tests = root / "tests" tests.mkdir() - (tests / "test.sh").write_text("#!/bin/sh\nmkdir -p /logs/verifier\necho 1 > /logs/verifier/reward.txt\n") + (tests / "test.sh").write_text( + "#!/bin/sh\nmkdir -p /logs/verifier\necho 1 > /logs/verifier/reward.txt\n" + ) return Task(id="task-template", uri=root.as_uri()) -def test_insight_suite_materializes_discoverable_tasks_with_provenance(tmp_path: Path) -> None: +def test_insight_suite_materializes_discoverable_tasks_with_provenance( + tmp_path: Path, +) -> None: template = _write_template(tmp_path / "template") refs = ["intake/traces/unsafe ref", "intake/traces/unsafe ref"] - suite = InsightSuite(experiment_dir=tmp_path, insight_id="insight/unsafe id", task_template=template) + suite = InsightSuite( + experiment_dir=tmp_path, insight_id="insight/unsafe id", task_template=template + ) staged = suite.stage(refs) for index, task in enumerate(staged, start=1): - (task.path / "instruction.md").write_text(f"Reproduce production scenario {index}.\n", encoding="utf-8") + (task.path / "instruction.md").write_text( + f"Reproduce production scenario {index}.\n", encoding="utf-8" + ) suite.validate(task) dataset = suite.promote_local(refs, staged) - assert suite.suite_dir == next((tmp_path / "eval-and-optimize" / "eval_author").iterdir()) / "insight-suite" + assert ( + suite.suite_dir + == next((tmp_path / "eval-and-optimize" / "eval_author").iterdir()) + / "insight-suite" + ) assert [task.id for task in dataset.list_tasks()] == [task.slug for task in staged] assert len(HarborDataset.from_path(suite.suite_dir).list_tasks()) == 2 names: list[str] = [] @@ -77,45 +93,65 @@ def test_insight_suite_materializes_discoverable_tasks_with_provenance(tmp_path: assert len(set(names)) == 2 assert all(name.startswith("example/template__") for name in names) - manifest = json.loads((suite.suite_dir / "manifest.json").read_text(encoding="utf-8")) + manifest = json.loads( + (suite.suite_dir / "manifest.json").read_text(encoding="utf-8") + ) assert manifest["insight_id"] == "insight/unsafe id" assert manifest["trace_refs"] == refs assert manifest["task_template"] == {"uri": template.uri} assert [task["source_trace_ref"] for task in manifest["tasks"]] == refs -def test_insight_suite_second_materialization_replaces_first_at_stable_path(tmp_path: Path) -> None: +def test_insight_suite_second_materialization_replaces_first_at_stable_path( + tmp_path: Path, +) -> None: template = _write_template(tmp_path / "template") refs = ["trace-1"] - first_suite = InsightSuite(experiment_dir=tmp_path, insight_id="insight-1", task_template=template) + first_suite = InsightSuite( + experiment_dir=tmp_path, insight_id="insight-1", task_template=template + ) first_staged = first_suite.stage(refs) - (first_staged[0].path / "instruction.md").write_text("First generated instruction.\n", encoding="utf-8") + (first_staged[0].path / "instruction.md").write_text( + "First generated instruction.\n", encoding="utf-8" + ) first_suite.validate(first_staged[0]) first_suite.promote_local(refs, first_staged) materialized_path = first_suite.suite_dir - second_suite = InsightSuite(experiment_dir=tmp_path, insight_id="insight-1", task_template=template) + second_suite = InsightSuite( + experiment_dir=tmp_path, insight_id="insight-1", task_template=template + ) second_staged = second_suite.stage(refs) - (second_staged[0].path / "instruction.md").write_text("Second generated instruction.\n", encoding="utf-8") + (second_staged[0].path / "instruction.md").write_text( + "Second generated instruction.\n", encoding="utf-8" + ) second_suite.validate(second_staged[0]) second_suite.promote_local(refs, second_staged) assert second_suite.suite_dir == materialized_path task = list(HarborDataset.from_path(materialized_path).list_tasks())[0] task_dir = Path(task.uri.removeprefix("file://")) - assert (task_dir / "instruction.md").read_text(encoding="utf-8") == "Second generated instruction.\n" + assert (task_dir / "instruction.md").read_text( + encoding="utf-8" + ) == "Second generated instruction.\n" def test_failed_rebuild_preserves_previous_materialized_suite(tmp_path: Path) -> None: template = _write_template(tmp_path / "template") refs = ["trace-1"] - first_suite = InsightSuite(experiment_dir=tmp_path, insight_id="insight-1", task_template=template) + first_suite = InsightSuite( + experiment_dir=tmp_path, insight_id="insight-1", task_template=template + ) first_staged = first_suite.stage(refs) - (first_staged[0].path / "instruction.md").write_text("Valid materialized instruction.\n", encoding="utf-8") + (first_staged[0].path / "instruction.md").write_text( + "Valid materialized instruction.\n", encoding="utf-8" + ) first_suite.validate(first_staged[0]) first_suite.promote_local(refs, first_staged) - failed_suite = InsightSuite(experiment_dir=tmp_path, insight_id="insight-1", task_template=template) + failed_suite = InsightSuite( + experiment_dir=tmp_path, insight_id="insight-1", task_template=template + ) failed_staged = failed_suite.stage(refs) (failed_staged[0].path / "instruction.md").write_text("\n", encoding="utf-8") @@ -124,7 +160,9 @@ def test_failed_rebuild_preserves_previous_materialized_suite(tmp_path: Path) -> task = list(HarborDataset.from_path(first_suite.suite_dir).list_tasks())[0] task_dir = Path(task.uri.removeprefix("file://")) - assert (task_dir / "instruction.md").read_text(encoding="utf-8") == "Valid materialized instruction.\n" + assert (task_dir / "instruction.md").read_text( + encoding="utf-8" + ) == "Valid materialized instruction.\n" def test_post_promotion_validation_failure_restores_previous_suite( @@ -133,34 +171,50 @@ def test_post_promotion_validation_failure_restores_previous_suite( ) -> None: template = _write_template(tmp_path / "template") refs = ["trace-1"] - first_suite = InsightSuite(experiment_dir=tmp_path, insight_id="insight-1", task_template=template) + first_suite = InsightSuite( + experiment_dir=tmp_path, insight_id="insight-1", task_template=template + ) first_staged = first_suite.stage(refs) - (first_staged[0].path / "instruction.md").write_text("Previously materialized.\n", encoding="utf-8") + (first_staged[0].path / "instruction.md").write_text( + "Previously materialized.\n", encoding="utf-8" + ) first_suite.validate(first_staged[0]) first_suite.promote_local(refs, first_staged) - failed_suite = InsightSuite(experiment_dir=tmp_path, insight_id="insight-1", task_template=template) + failed_suite = InsightSuite( + experiment_dir=tmp_path, insight_id="insight-1", task_template=template + ) failed_staged = failed_suite.stage(refs) - (failed_staged[0].path / "instruction.md").write_text("Broken promotion.\n", encoding="utf-8") + (failed_staged[0].path / "instruction.md").write_text( + "Broken promotion.\n", encoding="utf-8" + ) failed_suite.validate(failed_staged[0]) def fail_post_promotion_validation(_: Path) -> None: raise RuntimeError("post-promotion validation failed") - monkeypatch.setattr(materialization_module, "HarborTask", fail_post_promotion_validation) + monkeypatch.setattr( + materialization_module, "HarborTask", fail_post_promotion_validation + ) with pytest.raises(RuntimeError, match="post-promotion validation failed"): failed_suite.promote_local(refs, failed_staged) task = list(HarborDataset.from_path(first_suite.suite_dir).list_tasks())[0] task_dir = Path(task.uri.removeprefix("file://")) - assert (task_dir / "instruction.md").read_text(encoding="utf-8") == "Previously materialized.\n" + assert (task_dir / "instruction.md").read_text( + encoding="utf-8" + ) == "Previously materialized.\n" assert list(first_suite.root.glob(".insight-suite-backup-*")) == [] -def test_insight_suite_rejects_empty_instruction_before_materialization(tmp_path: Path) -> None: +def test_insight_suite_rejects_empty_instruction_before_materialization( + tmp_path: Path, +) -> None: template = _write_template(tmp_path / "template") - suite = InsightSuite(experiment_dir=tmp_path, insight_id="insight-1", task_template=template) + suite = InsightSuite( + experiment_dir=tmp_path, insight_id="insight-1", task_template=template + ) staged = suite.stage(["trace-1"]) (staged[0].path / "instruction.md").write_text("\n", encoding="utf-8") @@ -172,7 +226,9 @@ def test_insight_suite_rejects_empty_instruction_before_materialization(tmp_path def test_discard_removes_candidate_and_allows_restaging(tmp_path: Path) -> None: template = _write_template(tmp_path / "template") - suite = InsightSuite(experiment_dir=tmp_path, insight_id="insight-1", task_template=template) + suite = InsightSuite( + experiment_dir=tmp_path, insight_id="insight-1", task_template=template + ) staged = suite.stage(["trace-1"]) candidate_root = staged[0].path.parent.parent @@ -185,12 +241,18 @@ def test_discard_removes_candidate_and_allows_restaging(tmp_path: Path) -> None: assert not restaged[0].path.exists() -def test_insight_suite_records_analysis_without_removing_failed_tasks(tmp_path: Path) -> None: +def test_insight_suite_records_analysis_without_removing_failed_tasks( + tmp_path: Path, +) -> None: template = _write_template(tmp_path / "template") - suite = InsightSuite(experiment_dir=tmp_path, insight_id="insight-1", task_template=template) + suite = InsightSuite( + experiment_dir=tmp_path, insight_id="insight-1", task_template=template + ) staged = suite.stage(["trace-good", "trace-bad"]) for task in staged: - (task.path / "instruction.md").write_text(f"Run {task.trace_ref}.\n", encoding="utf-8") + (task.path / "instruction.md").write_text( + f"Run {task.trace_ref}.\n", encoding="utf-8" + ) suite.validate(task) suite.promote_local([task.trace_ref for task in staged], staged) @@ -201,7 +263,9 @@ def test_insight_suite_records_analysis_without_removing_failed_tasks(tmp_path: } ) - manifest = json.loads((suite.suite_dir / "manifest.json").read_text(encoding="utf-8")) + manifest = json.loads( + (suite.suite_dir / "manifest.json").read_text(encoding="utf-8") + ) assert [task["analysis"] for task in manifest["tasks"]] == [ {"status": "completed"}, {"error": "analysis failed", "status": "failed"}, @@ -212,14 +276,20 @@ def test_insight_suite_records_analysis_without_removing_failed_tasks(tmp_path: def test_finalized_suite_persists_identities_in_the_local_suite(tmp_path: Path) -> None: template = _write_template(tmp_path / "template") refs = ["trace-1"] - suite = InsightSuite(experiment_dir=tmp_path, insight_id="insight-1", task_template=template) + suite = InsightSuite( + experiment_dir=tmp_path, insight_id="insight-1", task_template=template + ) staged = suite.stage(refs) - (staged[0].path / "instruction.md").write_text("Reproduce the motivating failure.\n") + (staged[0].path / "instruction.md").write_text( + "Reproduce the motivating failure.\n" + ) suite.validate(staged[0]) suite.promote_local(refs, staged) finalized = suite.finalize() - manifest = json.loads((finalized.path / "manifest.json").read_text(encoding="utf-8")) + manifest = json.loads( + (finalized.path / "manifest.json").read_text(encoding="utf-8") + ) assert finalized.identity.startswith("sha256:") assert finalized.scorer_identity.startswith("sha256:") @@ -237,9 +307,14 @@ def test_finalized_suite_persists_identities_in_the_local_suite(tmp_path: Path) assert not (suite.root / "artifacts").exists() assert finalized.dataset.metadata["insight_suite_identity"] == finalized.identity assert "insight_suite_artifact_ref" not in finalized.dataset.metadata - assert list(finalized.dataset.list_tasks())[0].uri.startswith(finalized.path.as_uri()) + assert list(finalized.dataset.list_tasks())[0].uri.startswith( + finalized.path.as_uri() + ) - instruction = next(path for path in finalized.path.iterdir() if path.is_dir()) / "instruction.md" + instruction = ( + next(path for path in finalized.path.iterdir() if path.is_dir()) + / "instruction.md" + ) instruction.write_text("changed after finalization\n") changed = suite.finalize() assert changed.identity != finalized.identity @@ -269,10 +344,17 @@ def build(instruction: str, verifier_suffix: str = "") -> tuple[str, str]: first_identity, first_scorer_identity = build("Same authored task.\n") identical_identity, identical_scorer_identity = build("Same authored task.\n") changed_task_identity, _ = build("Changed authored task.\n") - changed_verifier_identity, changed_scorer_identity = build("Same authored task.\n", "\n# changed scorer\n") + changed_verifier_identity, changed_scorer_identity = build( + "Same authored task.\n", "\n# changed scorer\n" + ) - assert (identical_identity, identical_scorer_identity) == (first_identity, first_scorer_identity) + assert (identical_identity, identical_scorer_identity) == ( + first_identity, + first_scorer_identity, + ) assert changed_task_identity != first_identity assert changed_verifier_identity != first_identity assert changed_scorer_identity != first_scorer_identity - assert list((tmp_path / "eval-and-optimize" / "eval_author").glob("*/artifacts")) == [] + assert ( + list((tmp_path / "eval-and-optimize" / "eval_author").glob("*/artifacts")) == [] + ) diff --git a/plugins/nemo-eval-author/tests/test_eval_author_run.py b/plugins/nemo-eval-author/tests/test_eval_author_run.py index 6b31bf9be52..29f54192397 100644 --- a/plugins/nemo-eval-author/tests/test_eval_author_run.py +++ b/plugins/nemo-eval-author/tests/test_eval_author_run.py @@ -7,8 +7,15 @@ import pytest from nemo_eval_author_plugin.eval_author import run as eval_author_run -from nemo_eval_author_plugin.eval_author.models import EvalAuthorConfig, EvalAuthorResult -from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import Dataset, DatasetRef, Task +from nemo_eval_author_plugin.eval_author.models import ( + EvalAuthorConfig, + EvalAuthorResult, +) +from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import ( + Dataset, + DatasetRef, + Task, +) from nemo_insights_plugin.entities import Insight @@ -61,8 +68,12 @@ async def get_insight(self, *, workspace: str, insight_id: str) -> Insight: self.insight_calls.append({"workspace": workspace, "insight_id": insight_id}) return self.insight - async def get_agent_code(self, *, workspace: str, agent: str | Path, dest: Path) -> None: - self.agent_code_calls.append(AgentCodeCall(workspace=workspace, agent=agent, dest=dest)) + async def get_agent_code( + self, *, workspace: str, agent: str | Path, dest: Path + ) -> None: + self.agent_code_calls.append( + AgentCodeCall(workspace=workspace, agent=agent, dest=dest) + ) class FakeDatasetFactory: @@ -79,7 +90,9 @@ def build_dataset(self, evaluator_type: str, dataset_ref: DatasetRef) -> Dataset return self.validation return self.train - def build_task_template(self, evaluator_type: str, template_ref: DatasetRef) -> Task: + def build_task_template( + self, evaluator_type: str, template_ref: DatasetRef + ) -> Task: self.template_refs.append((evaluator_type, template_ref)) return self.template @@ -139,22 +152,38 @@ def make_backend( experiments_output: str, mode: Literal["local", "remote"], ) -> FakeBackend: - backend_calls.append(BackendFactoryCall(client=client, experiments_output=experiments_output, mode=mode)) + backend_calls.append( + BackendFactoryCall( + client=client, experiments_output=experiments_output, mode=mode + ) + ) return backend - def build_eval_author_agent(*, experiment_dir: Path, config: EvalAuthorConfig) -> FakeEvalAuthor: - eval_author_calls.append(EvalAuthorFactoryCall(experiment_dir=experiment_dir, config=config)) + def build_eval_author_agent( + *, experiment_dir: Path, config: EvalAuthorConfig + ) -> FakeEvalAuthor: + eval_author_calls.append( + EvalAuthorFactoryCall(experiment_dir=experiment_dir, config=config) + ) return eval_author monkeypatch.setattr(eval_author_run, "make_client", lambda base_url: client) monkeypatch.setattr(eval_author_run, "make_experimentalist_backend", make_backend) monkeypatch.setattr(eval_author_run, "DatasetFactory", lambda: dataset_factory) - monkeypatch.setattr(eval_author_run, "build_eval_author_agent", build_eval_author_agent) - monkeypatch.setattr(eval_author_run, "_enable_litellm_drop_params", lambda: litellm_calls.append(True)) + monkeypatch.setattr( + eval_author_run, "build_eval_author_agent", build_eval_author_agent + ) + monkeypatch.setattr( + eval_author_run, + "_enable_litellm_drop_params", + lambda: litellm_calls.append(True), + ) config = EvalAuthorConfig(max_traces=2) train_ref = DatasetRef(uri=str(tmp_path / "train"), metadata={"id": "train"}) - validation_ref = DatasetRef(uri=str(tmp_path / "validation"), metadata={"id": "validation"}) + validation_ref = DatasetRef( + uri=str(tmp_path / "validation"), metadata={"id": "validation"} + ) template_path = tmp_path / "template" template_path.mkdir() template_ref = DatasetRef(uri=str(template_path), metadata={"id": "task-template"}) @@ -177,9 +206,13 @@ def build_eval_author_agent(*, experiment_dir: Path, config: EvalAuthorConfig) - assert result.validation_dataset is dataset_factory.validation assert litellm_calls == [True] assert backend_calls == [ - BackendFactoryCall(client=client, experiments_output=str(experiment_dir), mode="local"), + BackendFactoryCall( + client=client, experiments_output=str(experiment_dir), mode="local" + ), + ] + assert backend.insight_calls == [ + {"workspace": "workspace-a", "insight_id": "insight-remote-123"} ] - assert backend.insight_calls == [{"workspace": "workspace-a", "insight_id": "insight-remote-123"}] assert backend.agent_code_calls == [ AgentCodeCall( workspace="workspace-a", @@ -187,14 +220,21 @@ def build_eval_author_agent(*, experiment_dir: Path, config: EvalAuthorConfig) - dest=experiment_dir / "eval_author" / "source-agent", ) ] - assert dataset_factory.dataset_refs == [("harbor", train_ref), ("harbor", validation_ref)] + assert dataset_factory.dataset_refs == [ + ("harbor", train_ref), + ("harbor", validation_ref), + ] assert dataset_factory.template_refs == [ ( "harbor", - template_ref.model_copy(update={"uri": str(experiment_dir / "dataset" / "task-template")}), + template_ref.model_copy( + update={"uri": str(experiment_dir / "dataset" / "task-template")} + ), ) ] - assert eval_author_calls == [EvalAuthorFactoryCall(experiment_dir=experiment_dir, config=config)] + assert eval_author_calls == [ + EvalAuthorFactoryCall(experiment_dir=experiment_dir, config=config) + ] assert eval_author.call == EvalAuthorCall( insight=insight, agent_path=experiment_dir / "eval_author" / "source-agent", @@ -214,25 +254,44 @@ async def test_run_eval_author_hydrates_fileset_task_template( download_calls: list[dict[str, str]] = [] class FakeFiles: - async def download(self, *, remote_path: str, local_path: str, workspace: str) -> None: - download_calls.append({"remote_path": remote_path, "local_path": local_path, "workspace": workspace}) + async def download( + self, *, remote_path: str, local_path: str, workspace: str + ) -> None: + download_calls.append( + { + "remote_path": remote_path, + "local_path": local_path, + "workspace": workspace, + } + ) destination = Path(local_path) destination.mkdir(parents=True) (destination / "task.toml").write_text("template", encoding="utf-8") client = ClosingClient(files=FakeFiles()) - insight = Insight(workspace="workspace-a", title="failure", description="description", agent="insight-agent") + insight = Insight( + workspace="workspace-a", + title="failure", + description="description", + agent="insight-agent", + ) backend = FakeBackend(insight) dataset_factory = FakeDatasetFactory() eval_author = FakeEvalAuthor() monkeypatch.setattr(eval_author_run, "make_client", lambda base_url: client) - monkeypatch.setattr(eval_author_run, "make_experimentalist_backend", lambda **_: backend) + monkeypatch.setattr( + eval_author_run, "make_experimentalist_backend", lambda **_: backend + ) monkeypatch.setattr(eval_author_run, "DatasetFactory", lambda: dataset_factory) - monkeypatch.setattr(eval_author_run, "build_eval_author_agent", lambda **_: eval_author) + monkeypatch.setattr( + eval_author_run, "build_eval_author_agent", lambda **_: eval_author + ) monkeypatch.setattr(eval_author_run, "_enable_litellm_drop_params", lambda: None) - template_ref = DatasetRef(uri="fileset://workspace-a/task-template", metadata={"id": "task-template"}) + template_ref = DatasetRef( + uri="fileset://workspace-a/task-template", metadata={"id": "task-template"} + ) experiment_dir = (tmp_path / "eval_author").resolve() await eval_author_run.run_eval_author( insight="insight-remote-123", @@ -253,7 +312,9 @@ async def download(self, *, remote_path: str, local_path: str, workspace: str) - "workspace": "workspace-a", } ] - assert dataset_factory.template_refs == [("harbor", template_ref.model_copy(update={"uri": str(staged_path)}))] + assert dataset_factory.template_refs == [ + ("harbor", template_ref.model_copy(update={"uri": str(staged_path)})) + ] assert client.closed @@ -263,15 +324,24 @@ async def test_run_eval_author_uses_agent_override( tmp_path: Path, ) -> None: client = ClosingClient() - insight = Insight(workspace="workspace-a", title="failure", description="description", agent="insight-agent") + insight = Insight( + workspace="workspace-a", + title="failure", + description="description", + agent="insight-agent", + ) backend = FakeBackend(insight) dataset_factory = FakeDatasetFactory() eval_author = FakeEvalAuthor() monkeypatch.setattr(eval_author_run, "make_client", lambda base_url: client) - monkeypatch.setattr(eval_author_run, "make_experimentalist_backend", lambda **_: backend) + monkeypatch.setattr( + eval_author_run, "make_experimentalist_backend", lambda **_: backend + ) monkeypatch.setattr(eval_author_run, "DatasetFactory", lambda: dataset_factory) - monkeypatch.setattr(eval_author_run, "build_eval_author_agent", lambda **_: eval_author) + monkeypatch.setattr( + eval_author_run, "build_eval_author_agent", lambda **_: eval_author + ) monkeypatch.setattr(eval_author_run, "_enable_litellm_drop_params", lambda: None) override = tmp_path / "override-agent" @@ -304,7 +374,9 @@ def fail_backend_creation(**_: object) -> object: raise RuntimeError("backend creation failed") monkeypatch.setattr(eval_author_run, "make_client", lambda base_url: client) - monkeypatch.setattr(eval_author_run, "make_experimentalist_backend", fail_backend_creation) + monkeypatch.setattr( + eval_author_run, "make_experimentalist_backend", fail_backend_creation + ) with pytest.raises(RuntimeError, match="backend creation failed"): await eval_author_run.run_eval_author( diff --git a/plugins/nemo-eval-author/tests/test_model_config.py b/plugins/nemo-eval-author/tests/test_model_config.py index e2c58394b25..1b573c0fd51 100644 --- a/plugins/nemo-eval-author/tests/test_model_config.py +++ b/plugins/nemo-eval-author/tests/test_model_config.py @@ -12,7 +12,9 @@ def _clear_model_cache() -> None: model_config.get_fast_model.cache_clear() -def test_api_base_prefers_eval_author_over_experimentalist(monkeypatch: pytest.MonkeyPatch) -> None: +def test_api_base_prefers_eval_author_over_experimentalist( + monkeypatch: pytest.MonkeyPatch, +) -> None: monkeypatch.setenv("AUTHOR_API_BASE", "https://eval-author.example/v1") monkeypatch.setenv("EXPERIMENTALIST_API_BASE", "https://experimentalist.example/v1") monkeypatch.setenv("AUTHOR_API_KEY", "eval-key") @@ -20,7 +22,9 @@ def test_api_base_prefers_eval_author_over_experimentalist(monkeypatch: pytest.M assert model_config._api_base() == "https://eval-author.example/v1" -def test_api_base_falls_back_to_experimentalist(monkeypatch: pytest.MonkeyPatch) -> None: +def test_api_base_falls_back_to_experimentalist( + monkeypatch: pytest.MonkeyPatch, +) -> None: monkeypatch.delenv("AUTHOR_API_BASE", raising=False) monkeypatch.setenv("EXPERIMENTALIST_API_BASE", "https://experimentalist.example/v1") monkeypatch.setenv("EXPERIMENTALIST_API_KEY", "exp-key") @@ -28,7 +32,9 @@ def test_api_base_falls_back_to_experimentalist(monkeypatch: pytest.MonkeyPatch) assert model_config._api_base() == "https://experimentalist.example/v1" -def test_api_key_accepts_inference_key_on_gateway(monkeypatch: pytest.MonkeyPatch) -> None: +def test_api_key_accepts_inference_key_on_gateway( + monkeypatch: pytest.MonkeyPatch, +) -> None: monkeypatch.setenv("AUTHOR_API_BASE", "https://inference-api.nvidia.com/v1") monkeypatch.delenv("AUTHOR_API_KEY", raising=False) monkeypatch.delenv("EXPERIMENTALIST_API_KEY", raising=False) @@ -37,7 +43,9 @@ def test_api_key_accepts_inference_key_on_gateway(monkeypatch: pytest.MonkeyPatc assert model_config._api_key() == "sk-gateway" -def test_api_key_requires_credentials_when_missing(monkeypatch: pytest.MonkeyPatch) -> None: +def test_api_key_requires_credentials_when_missing( + monkeypatch: pytest.MonkeyPatch, +) -> None: monkeypatch.setenv("AUTHOR_API_BASE", "https://custom.example/v1") monkeypatch.delenv("AUTHOR_API_KEY", raising=False) monkeypatch.delenv("EXPERIMENTALIST_API_KEY", raising=False) @@ -47,7 +55,9 @@ def test_api_key_requires_credentials_when_missing(monkeypatch: pytest.MonkeyPat model_config._api_key() -def test_bridge_author_env_fills_unset_experimentalist_slots(monkeypatch: pytest.MonkeyPatch) -> None: +def test_bridge_author_env_fills_unset_experimentalist_slots( + monkeypatch: pytest.MonkeyPatch, +) -> None: monkeypatch.setenv("AUTHOR_API_BASE", "https://eval-author.example/v1") monkeypatch.setenv("AUTHOR_API_KEY", "eval-key") monkeypatch.delenv("EXPERIMENTALIST_API_BASE", raising=False) @@ -55,7 +65,10 @@ def test_bridge_author_env_fills_unset_experimentalist_slots(monkeypatch: pytest model_config.bridge_author_env_to_experimentalist() - assert model_config.os.environ["EXPERIMENTALIST_API_BASE"] == "https://eval-author.example/v1" + assert ( + model_config.os.environ["EXPERIMENTALIST_API_BASE"] + == "https://eval-author.example/v1" + ) assert model_config.os.environ["EXPERIMENTALIST_API_KEY"] == "eval-key" @@ -67,4 +80,7 @@ def test_bridge_author_env_does_not_overwrite_existing_experimentalist( model_config.bridge_author_env_to_experimentalist() - assert model_config.os.environ["EXPERIMENTALIST_API_BASE"] == "https://experimentalist.example/v1" + assert ( + model_config.os.environ["EXPERIMENTALIST_API_BASE"] + == "https://experimentalist.example/v1" + ) diff --git a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/analyzer.py b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/analyzer.py index 216feb029f4..335441e571e 100644 --- a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/analyzer.py +++ b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/analyzer.py @@ -17,7 +17,11 @@ Task, TrialResult, ) -from nemo_experimentalist_plugin.experimentalist.components.trace_analyzer import Diagnostic, TraceAnalyzer, TraceAnalyzerConfig # noqa: F401 +from nemo_experimentalist_plugin.experimentalist.components.trace_analyzer import ( # noqa: F401 + Diagnostic, + TraceAnalyzer, + TraceAnalyzerConfig, +) from nemo_experimentalist_plugin.experimentalist.components.trace_explorer import TraceExplorer # noqa: F401 from nemo_platform import AsyncNeMoPlatform from nooa import Agent, CodeActStrategy, strategy diff --git a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/coder.py b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/coder.py index c5e08f63143..352e0376851 100644 --- a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/coder.py +++ b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/coder.py @@ -13,8 +13,15 @@ from typing import Any import httpx -from nemo_experimentalist_plugin.experimentalist.components.evaluator import Dataset, EvaluationResult, Evaluator, EvaluatorConfig, Task, TrialResult from nemo_experimentalist_plugin.entities import Candidate +from nemo_experimentalist_plugin.experimentalist.components.evaluator import ( + Dataset, + EvaluationResult, + Evaluator, + EvaluatorConfig, + Task, + TrialResult, +) from nooa import Agent, CodeActStrategy, strategy from nooa.agentdoc import doc, spec from nooa.agents import TokenBudgetSummarizer diff --git a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/insight_promotion.py b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/insight_promotion.py index 93c7ff38930..660aa94b5a3 100644 --- a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/insight_promotion.py +++ b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/components/insight_promotion.py @@ -10,6 +10,7 @@ from dataclasses import dataclass, replace from pathlib import Path +from nemo_experimentalist_plugin.entities import Candidate from nemo_experimentalist_plugin.experimentalist.components.evaluator import ( Dataset, EvaluationResult, @@ -17,7 +18,6 @@ TrialResult, ) from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import local_path_from_uri -from nemo_experimentalist_plugin.entities import Candidate _GENERIC_METRIC_NAMES = frozenset({"reward", "score"}) _MAX_REPEAT_SPREAD = 0.1 diff --git a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/experimentalist_backend.py b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/experimentalist_backend.py index b544936835f..a66d2e227aa 100644 --- a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/experimentalist_backend.py +++ b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/experimentalist_backend.py @@ -24,12 +24,12 @@ from urllib.parse import urlparse import httpx +from nemo_experimentalist_plugin.entities import Candidate, ExperimentRun from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import ( EvaluationResult, ResourceRef, TrialResult, ) -from nemo_experimentalist_plugin.entities import Candidate, ExperimentRun from nemo_experimentalist_plugin.experimentalist.components.repository import ( AgentSource, PRPublisher, diff --git a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/run.py b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/run.py index 5b9af230336..fd5480c5e22 100644 --- a/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/run.py +++ b/plugins/nemo-experimentalist/src/nemo_experimentalist_plugin/experimentalist/run.py @@ -7,8 +7,8 @@ from pathlib import Path from typing import Literal, Protocol, cast -from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import DatasetRef from nemo_experimentalist_plugin.experimentalist.agent import build_experimentalist_agent +from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import DatasetRef from nemo_experimentalist_plugin.experimentalist.components.loop import EvolutionaryOptimizerConfig from nemo_experimentalist_plugin.experimentalist.deps import ExperimentalistDeps from nemo_experimentalist_plugin.experimentalist.experimentalist_backend import ( diff --git a/plugins/nemo-experimentalist/tests/experimentalist/test_dataset_staging_loop.py b/plugins/nemo-experimentalist/tests/experimentalist/test_dataset_staging_loop.py index b45b22d3a26..a03990f009b 100644 --- a/plugins/nemo-experimentalist/tests/experimentalist/test_dataset_staging_loop.py +++ b/plugins/nemo-experimentalist/tests/experimentalist/test_dataset_staging_loop.py @@ -8,8 +8,8 @@ from unittest.mock import AsyncMock import pytest -from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import DatasetRef from nemo_experimentalist_plugin.experimentalist.components import loop as loop_module +from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import DatasetRef from nemo_experimentalist_plugin.experimentalist.components.loop import EvolutionaryOptimizer from nemo_experimentalist_plugin.resolve import EvolutionaryOptimizerConfig diff --git a/plugins/nemo-experimentalist/tests/experimentalist/test_eval_author_repair_e2e.py b/plugins/nemo-experimentalist/tests/experimentalist/test_eval_author_repair_e2e.py index 1cf4f23709f..817a1cf70cb 100644 --- a/plugins/nemo-experimentalist/tests/experimentalist/test_eval_author_repair_e2e.py +++ b/plugins/nemo-experimentalist/tests/experimentalist/test_eval_author_repair_e2e.py @@ -20,8 +20,8 @@ HarborEvaluator, HarborEvaluatorConfig, ) -from nemo_experimentalist_plugin.experimentalist.components.trace_analyzer import Diagnostic from nemo_experimentalist_plugin.experimentalist.components.model_config import get_fast_model +from nemo_experimentalist_plugin.experimentalist.components.trace_analyzer import Diagnostic from nemo_insights_plugin.entities import Insight _HAS_EXPERIMENTALIST_LLM = bool( diff --git a/plugins/nemo-experimentalist/tests/experimentalist/test_evaluator_harbor.py b/plugins/nemo-experimentalist/tests/experimentalist/test_evaluator_harbor.py index 14869187d5a..f869a74586a 100644 --- a/plugins/nemo-experimentalist/tests/experimentalist/test_evaluator_harbor.py +++ b/plugins/nemo-experimentalist/tests/experimentalist/test_evaluator_harbor.py @@ -1400,7 +1400,9 @@ async def test_harbor_evaluator_force_rerun(tmp_path, monkeypatch): def fake_rmtree(path, **kwargs): rmtree_calls.append(path) - monkeypatch.setattr("nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor.shutil.rmtree", fake_rmtree) + monkeypatch.setattr( + "nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor.shutil.rmtree", fake_rmtree + ) class FakeJob: created_config = None diff --git a/plugins/nemo-experimentalist/tests/experimentalist/test_loop_insight_suite.py b/plugins/nemo-experimentalist/tests/experimentalist/test_loop_insight_suite.py index bc036817201..e959de65be1 100644 --- a/plugins/nemo-experimentalist/tests/experimentalist/test_loop_insight_suite.py +++ b/plugins/nemo-experimentalist/tests/experimentalist/test_loop_insight_suite.py @@ -7,6 +7,8 @@ from unittest.mock import AsyncMock import pytest +from nemo_experimentalist_plugin.entities import Candidate +from nemo_experimentalist_plugin.experimentalist.components import loop as loop_module from nemo_experimentalist_plugin.experimentalist.components.evaluator import ( Dataset, EvaluationResult, @@ -20,8 +22,6 @@ DataValue, ResourceRef, ) -from nemo_experimentalist_plugin.entities import Candidate -from nemo_experimentalist_plugin.experimentalist.components import loop as loop_module from nemo_experimentalist_plugin.experimentalist.components.loop import EvolutionaryOptimizer from nemo_experimentalist_plugin.resolve import EvolutionaryOptimizerConfig diff --git a/plugins/nemo-experimentalist/tests/experimentalist/test_loop_reporting.py b/plugins/nemo-experimentalist/tests/experimentalist/test_loop_reporting.py index a9d2372376e..b5cbef7d0d0 100644 --- a/plugins/nemo-experimentalist/tests/experimentalist/test_loop_reporting.py +++ b/plugins/nemo-experimentalist/tests/experimentalist/test_loop_reporting.py @@ -7,6 +7,7 @@ from unittest.mock import AsyncMock import pytest +from nemo_experimentalist_plugin.entities import Candidate from nemo_experimentalist_plugin.experimentalist.components.evaluator import ( Dataset, EvaluationResult, @@ -16,7 +17,6 @@ TrialStatus, ) from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import ResourceRef -from nemo_experimentalist_plugin.entities import Candidate from nemo_experimentalist_plugin.experimentalist.components.insight_promotion import ( _task_evidence, insight_suite_provenance, diff --git a/plugins/nemo-experimentalist/tests/test_experiment_cli.py b/plugins/nemo-experimentalist/tests/test_experiment_cli.py index 160f81aad5b..f4460ae4a9b 100644 --- a/plugins/nemo-experimentalist/tests/test_experiment_cli.py +++ b/plugins/nemo-experimentalist/tests/test_experiment_cli.py @@ -7,8 +7,8 @@ import pytest from click.testing import Result -from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import DatasetRef from nemo_experimentalist_plugin import cli +from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import DatasetRef from nemo_experimentalist_plugin.experimentalist.components.loop import EvolutionaryOptimizerConfig from nemo_experimentalist_plugin.preflight import Probes from nemo_platform import AsyncNeMoPlatform diff --git a/plugins/nemo-experimentalist/tests/test_experimentalist_analyzer.py b/plugins/nemo-experimentalist/tests/test_experimentalist_analyzer.py index 76ddd6713d5..48ab2c4f449 100644 --- a/plugins/nemo-experimentalist/tests/test_experimentalist_analyzer.py +++ b/plugins/nemo-experimentalist/tests/test_experimentalist_analyzer.py @@ -21,7 +21,6 @@ from typing import Any, ClassVar, cast import pytest -from nemo_experimentalist_plugin.experimentalist.components.trace_analyzer import Diagnostic from nemo_experimentalist_plugin.experimentalist.components import analyzer as analyzer_module from nemo_experimentalist_plugin.experimentalist.components.analyzer import ( AgentAnalyzer, @@ -30,6 +29,7 @@ PeerComparison, ) from nemo_experimentalist_plugin.experimentalist.components.rationalizer import Rationale +from nemo_experimentalist_plugin.experimentalist.components.trace_analyzer import Diagnostic @dataclass diff --git a/plugins/nemo-experimentalist/tests/test_experimentalist_backend.py b/plugins/nemo-experimentalist/tests/test_experimentalist_backend.py index ad24c6eed42..331572e8a52 100644 --- a/plugins/nemo-experimentalist/tests/test_experimentalist_backend.py +++ b/plugins/nemo-experimentalist/tests/test_experimentalist_backend.py @@ -18,9 +18,9 @@ import httpx import pytest -from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import EvaluationResult from nemo_experimentalist_plugin.entities import Candidate from nemo_experimentalist_plugin.experimentalist import experimentalist_backend as beim +from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import EvaluationResult from nemo_experimentalist_plugin.experimentalist.components.repository import AgentCloneError, AgentSource from nemo_experimentalist_plugin.experimentalist.experimentalist_backend import ( CandidateStorageConfig, diff --git a/plugins/nemo-experimentalist/tests/test_experimentalist_run.py b/plugins/nemo-experimentalist/tests/test_experimentalist_run.py index 1d72ea154e3..35fd4db09e1 100644 --- a/plugins/nemo-experimentalist/tests/test_experimentalist_run.py +++ b/plugins/nemo-experimentalist/tests/test_experimentalist_run.py @@ -7,9 +7,9 @@ from typing import Literal, cast import pytest -from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import DatasetRef from nemo_experimentalist_plugin.experimentalist import run as experimentalist_run from nemo_experimentalist_plugin.experimentalist.components import loop as loop_module +from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import DatasetRef from nemo_experimentalist_plugin.experimentalist.components.loop import EvolutionaryOptimizerConfig from nemo_experimentalist_plugin.experimentalist.deps import ExperimentalistDeps from nemo_experimentalist_plugin.experimentalist.experimentalist_backend import LocalExperimentalistBackend diff --git a/pyproject.toml b/pyproject.toml index 22323ace5e8..686ed71d035 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -645,18 +645,25 @@ exclude = [ # nooa agents declare their LLM-generated methods as annotated signatures with an # ellipsis body; the framework fills them in at runtime. ty reads that as a missing -# return, so the rule is off for the Experimentalist's agent classes only. +# return, so the rule is off for the Experimentalist and Eval Author agent classes only. [[tool.ty.overrides]] -include = ["plugins/nemo-experimentalist/src/**"] +include = [ + "plugins/nemo-experimentalist/src/**", + "plugins/nemo-eval-author/src/**", +] [tool.ty.overrides.rules] empty-body = "ignore" # The Experimentalist's agent framework (nooa) and evaluator (harbor) are both # 3.12-only, so uv skips them when the lint job resolves on 3.11 and every import -# of them goes unresolved. Everything else about the plugin still gets checked. +# of them goes unresolved. Eval Author shares both. Everything else about the +# plugins still gets checked. [[tool.ty.overrides]] -include = ["plugins/nemo-experimentalist/**"] +include = [ + "plugins/nemo-experimentalist/**", + "plugins/nemo-eval-author/**", +] [tool.ty.overrides.rules] unresolved-import = "ignore" diff --git a/tests/discovery_exclusions.py b/tests/discovery_exclusions.py index 0648577791e..2376232c5e1 100644 --- a/tests/discovery_exclusions.py +++ b/tests/discovery_exclusions.py @@ -19,3 +19,8 @@ TEST_DISCOVERY_EXCLUSIONS[Path("plugins/nemo-experimentalist/tests")] = ( "The optional experimentalist dependency group is 3.12-only, so it is absent from a 3.11 test environment." ) + +if find_spec("nemo_eval_author_plugin") is None: + TEST_DISCOVERY_EXCLUSIONS[Path("plugins/nemo-eval-author/tests")] = ( + "Eval Author ships in the 3.12-only experimentalist dependency group, so it is absent from a 3.11 test environment." + ) From 38f2e8bcf78e168e52e906025e608744a5fe0196 Mon Sep 17 00:00:00 2001 From: Alec Khoury Date: Tue, 28 Jul 2026 15:23:21 -0500 Subject: [PATCH 4/6] fix(eval-author): use unroutable HTTPS placeholder for test credentials A plaintext placeholder base paired with a real key from the environment could send that key unencrypted on an unmocked call. Also document why the plugin declares a 3.11 floor while nooa and harbor are gated to 3.12. Signed-off-by: Alec Khoury --- plugins/nemo-eval-author/pyproject.toml | 5 +++++ plugins/nemo-eval-author/tests/conftest.py | 6 ++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/plugins/nemo-eval-author/pyproject.toml b/plugins/nemo-eval-author/pyproject.toml index 89046f5ce44..b940fa0571f 100644 --- a/plugins/nemo-eval-author/pyproject.toml +++ b/plugins/nemo-eval-author/pyproject.toml @@ -2,6 +2,11 @@ name = "nemo-eval-author-plugin" version = "0.1.0" description = "Eval Author agent for NeMo Platform (hard-depends on Experimentalist for evaluator/trace helpers)." +# Like Experimentalist, this plugin really needs 3.12 (so do nooa and harbor), but a +# workspace member cannot raise its floor above the workspace's without dragging every +# other package up with it. The floor is declared here and enforced by the markers below +# plus the `experimentalist` dependency group in the workspace root, which installs both +# plugins together. requires-python = ">=3.11,<3.14" dependencies = [ "pydantic>=2", diff --git a/plugins/nemo-eval-author/tests/conftest.py b/plugins/nemo-eval-author/tests/conftest.py index 8fed86e3581..4d33bd91661 100644 --- a/plugins/nemo-eval-author/tests/conftest.py +++ b/plugins/nemo-eval-author/tests/conftest.py @@ -13,9 +13,11 @@ import litellm import pytest -os.environ.setdefault("AUTHOR_API_BASE", "http://placeholder-for-import") +# An unroutable HTTPS host: if a real key is present but the base is not, an +# unmocked call fails to connect rather than sending the key over plaintext. +os.environ.setdefault("AUTHOR_API_BASE", "https://placeholder.invalid") os.environ.setdefault("AUTHOR_API_KEY", "placeholder-for-import") -os.environ.setdefault("EXPERIMENTALIST_API_BASE", "http://placeholder-for-import") +os.environ.setdefault("EXPERIMENTALIST_API_BASE", "https://placeholder.invalid") os.environ.setdefault("EXPERIMENTALIST_API_KEY", "placeholder-for-import") litellm.drop_params = True From aa21cde19f4dd51e79be5ee67b62794783168668 Mon Sep 17 00:00:00 2001 From: Alec Khoury Date: Tue, 28 Jul 2026 17:07:36 -0500 Subject: [PATCH 5/6] fix(eval-author): address plugin extraction review findings Restore the repo lint config for the plugin. A local [tool.ruff] table had made the plugin's pyproject the root ruff config for that subtree, which disabled import sorting and reflowed the moved Eval Author modules at 88 columns, hiding that the extraction was a pure move. The E402 waiver that table existed for is gone too, replaced by an _env_bridge module imported for its side effect, so isort maintains the ordering the credential bridge depends on rather than a comment asking readers not to reshuffle imports. Only forward INFERENCE_API_KEY to the NVIDIA gateway over HTTPS, matching nemo_experimentalist_plugin.cli. Matching on hostname alone would have sent the key to a plain-http base in cleartext. Declare Experimentalist's dependency on Eval Author, which insight mode already imports at module scope. That is the arrow that survives, so pin the modules Eval Author still borrows in tests/test_plugin_boundary.py to keep the reverse coupling shrinking, and tag the transitional credential fallback and env bridge TODO(eval-author-standalone). Cover the previously untested bridge with a subprocess test that imports the agent with EXPERIMENTALIST_* stripped, move the Eval Author e2e canary into the plugin and fix a credential gate that could never skip, drop dead code and the unused pyyaml dependency, and rename .example.env to .env.example to match the rest of the repo. Signed-off-by: Alec Khoury --- .gitignore | 1 + plugins/nemo-eval-author/.env.example | 43 ++++ plugins/nemo-eval-author/.example.env | 32 --- plugins/nemo-eval-author/README.md | 51 ++-- plugins/nemo-eval-author/pyproject.toml | 5 - .../nemo_eval_author_plugin/_env_bridge.py | 23 ++ .../eval_author/agent.py | 81 ++----- .../eval_author/materialization.py | 122 +++------- .../eval_author/run.py | 50 +--- .../nemo_eval_author_plugin/model_config.py | 222 +++++++++--------- plugins/nemo-eval-author/tests/conftest.py | 5 + .../tests/test_eval_author_agent.py | 92 ++------ .../tests/test_eval_author_materialization.py | 160 +++---------- .../tests}/test_eval_author_repair_e2e.py | 40 ++-- .../tests/test_eval_author_run.py | 126 +++------- .../tests/test_model_config.py | 175 ++++++++++++-- .../tests/test_plugin_boundary.py | 100 ++++++++ plugins/nemo-experimentalist/AGENTS.md | 48 +++- plugins/nemo-experimentalist/pyproject.toml | 1 + pyproject.toml | 8 +- uv.lock | 4 +- 21 files changed, 700 insertions(+), 689 deletions(-) create mode 100644 plugins/nemo-eval-author/.env.example delete mode 100644 plugins/nemo-eval-author/.example.env create mode 100644 plugins/nemo-eval-author/src/nemo_eval_author_plugin/_env_bridge.py rename plugins/{nemo-experimentalist/tests/experimentalist => nemo-eval-author/tests}/test_eval_author_repair_e2e.py (89%) create mode 100644 plugins/nemo-eval-author/tests/test_plugin_boundary.py diff --git a/.gitignore b/.gitignore index 96eac96c6e3..0c3bd69c06d 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,7 @@ venv* .venv* .nemo/ .env* +!**/.env.example !**/env.py !packages/data_designer/**/environment.py !packages/nemo_data_designer/**/environment.py diff --git a/plugins/nemo-eval-author/.env.example b/plugins/nemo-eval-author/.env.example new file mode 100644 index 00000000000..d45305de9a1 --- /dev/null +++ b/plugins/nemo-eval-author/.env.example @@ -0,0 +1,43 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Example environment for standalone Eval Author runs. +# Copy to `.env` next to this file (or export in your shell): +# +# cp .env.example .env +# +# AUTHOR_* is Eval Author's credential contract. The EXPERIMENTALIST_* interplay +# below is transitional and goes away once Eval Author no longer reuses any +# Experimentalist code: +# +# - an unset AUTHOR_* falls back to its EXPERIMENTALIST_* equivalent, so insight +# mode works from one Experimentalist profile .env +# - a set AUTHOR_* is copied into any unset EXPERIMENTALIST_* slot, because the +# Experimentalist helpers Eval Author still borrows read only EXPERIMENTALIST_* +# +# TODO(eval-author-standalone): remove both, at which point AUTHOR_* becomes required +# here rather than optional. Set AUTHOR_* explicitly now to avoid that break. +# +# TODO(cli): wire `nemo eval-author` to load this `.env` the way Experimentalist +# loads the profile-dir `.env`. + +# Required for LLM-backed Eval Author / TraceAnalyzer (NOOA CompletionClient). +AUTHOR_API_BASE=https://inference-api.nvidia.com/v1 +AUTHOR_API_KEY= + +# Optional model overrides (defaults shown). Eval Author itself uses the smart and fast +# tiers; the mid tier is listed only because it is bridged to Experimentalist helpers, +# and it goes away with the bridge. +# AUTHOR_SMART_MODEL_NAME=openai/openai/openai/gpt-5.5 +# AUTHOR_MID_MODEL_NAME=openai/gcp/google/gemini-3.5-flash +# AUTHOR_FAST_MODEL_NAME=openai/openai/openai/gpt-5-mini + +# Optional: NVIDIA Inference Gateway virtual key. When AUTHOR_API_KEY is unset and +# AUTHOR_API_BASE is the gateway over HTTPS, standalone runners fall back to +# INFERENCE_API_KEY the same way Experimentalist does today. The HTTPS requirement +# is deliberate: the key is never forwarded to a plain-http base. +# INFERENCE_API_KEY= + +# Optional: NeMo Platform URL for Insight / Fileset / agent-code access. +# Leave unset to use the active `nemo auth` context, or set for a local stack. +# NMP_BASE_URL=http://localhost:8080 diff --git a/plugins/nemo-eval-author/.example.env b/plugins/nemo-eval-author/.example.env deleted file mode 100644 index e093dc8f788..00000000000 --- a/plugins/nemo-eval-author/.example.env +++ /dev/null @@ -1,32 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 -# -# Example environment for standalone Eval Author runs. -# Copy to `.env` next to this file (or export in your shell): -# -# cp .example.env .env -# -# Eval Author reads AUTHOR_* first. When unset, it falls back to -# EXPERIMENTALIST_* so the same credentials still work when Experimentalist -# invokes Eval Author in insight mode. -# -# TODO(cli): wire `nemo eval-author` to load this `.env` the way Experimentalist -# loads the profile-dir `.env`. - -# Required for LLM-backed Eval Author / TraceAnalyzer (NOOA CompletionClient). -AUTHOR_API_BASE=https://inference-api.nvidia.com/v1 -AUTHOR_API_KEY= - -# Optional model overrides (defaults shown). -# AUTHOR_SMART_MODEL_NAME=openai/openai/openai/gpt-5.5 -# AUTHOR_MID_MODEL_NAME=openai/gcp/google/gemini-3.5-flash -# AUTHOR_FAST_MODEL_NAME=openai/openai/openai/gpt-5-mini - -# Optional: NVIDIA Inference Gateway virtual key. When AUTHOR_API_BASE is -# the NVIDIA gateway and AUTHOR_API_KEY is unset, standalone runners may -# use INFERENCE_API_KEY the same way Experimentalist does today. -# INFERENCE_API_KEY= - -# Optional: NeMo Platform URL for Insight / Fileset / agent-code access. -# Leave unset to use the active `nemo auth` context, or set for a local stack. -# NMP_BASE_URL=http://localhost:8080 diff --git a/plugins/nemo-eval-author/README.md b/plugins/nemo-eval-author/README.md index 590d5bc59ea..7f22176cac3 100644 --- a/plugins/nemo-eval-author/README.md +++ b/plugins/nemo-eval-author/README.md @@ -1,12 +1,23 @@ # NeMo Eval Author Plugin -Library-only plugin that owns the Eval Author agent (`eval_author/`). Shared -evaluator, trace, staging, tools, and client helpers stay in -`nemo-experimentalist-plugin`; this package takes a hard dependency on -Experimentalist and imports those modules from there. +Library-only plugin that owns the Eval Author agent (`eval_author/`). -Experimentalist insight mode imports `EvalAuthor` from this plugin at runtime. -Install both via the workspace group (avoids a circular package dependency): +## Direction of travel + +**Eval Author is meant to become standalone, with nothing imported from +Experimentalist.** Prefer duplicating a helper over sharing one, even when sharing +looks tidier. + +Right now the two packages depend on each other: + +| Arrow | Status | Why | +| --- | --- | --- | +| Experimentalist → Eval Author | permanent | insight mode imports `EvalAuthor` and `EvalAuthorConfig` at module scope | +| Eval Author → Experimentalist | temporary | still borrows evaluator/Harbor, staging, trace, tools, cache, backend | + +[`tests/test_plugin_boundary.py`](tests/test_plugin_boundary.py) pins the second list +so it can only shrink, and names what each remaining import is still for. `uv` +resolves the current cycle; install both packages with: ```bash uv sync --group experimentalist @@ -19,7 +30,8 @@ from nemo_eval_author_plugin.eval_author.agent import EvalAuthor, build_eval_aut from nemo_eval_author_plugin.eval_author.models import EvalAuthorConfig, EvalAuthorResult from nemo_eval_author_plugin.eval_author.run import run_eval_author -# Shared infrastructure lives in Experimentalist: +# Still borrowed from Experimentalist, and on the way out. Treat these as Eval Author's +# own types once they move; do not build new code on the Experimentalist paths. from nemo_experimentalist_plugin.experimentalist.components.evaluator import Dataset from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import DatasetRef from nemo_experimentalist_plugin.experimentalist.components.dataset_staging import stage_task_template @@ -29,17 +41,26 @@ from nemo_experimentalist_plugin.experimentalist.components.trace_explorer impor ## Credentials (standalone) -Copy [`.example.env`](.example.env) to `.env` and set `AUTHOR_API_KEY` (and optionally model names / `NMP_BASE_URL`): +Copy [`.env.example`](.env.example) to `.env` and set `AUTHOR_API_KEY` (and optionally model names / `NMP_BASE_URL`): ```bash -cp plugins/nemo-eval-author/.example.env plugins/nemo-eval-author/.env +cp plugins/nemo-eval-author/.env.example plugins/nemo-eval-author/.env ``` -`model_config` prefers `AUTHOR_*`. If those are unset it falls back to -`EXPERIMENTALIST_*` so Experimentalist insight-mode keeps working with a single -Experimentalist profile `.env`. On construction, Eval Author also bridges -`AUTHOR_*` into unset `EXPERIMENTALIST_*` slots so Experimentalist helpers such -as `TraceAnalyzer` see credentials during standalone runs. When the API base is -the NVIDIA Inference Gateway, `INFERENCE_API_KEY` is also accepted. +`AUTHOR_*` is Eval Author's credential contract, and `model_config` imports nothing +from Experimentalist. When the API base is the NVIDIA Inference Gateway over HTTPS, +`INFERENCE_API_KEY` is also accepted. + +Two pieces of that module are transitional and disappear with the last +Experimentalist import, both tagged `TODO(eval-author-standalone)`: + +- unset `AUTHOR_*` variables fall back to `EXPERIMENTALIST_*`, so insight mode works + from a single Experimentalist profile `.env`. Setting `AUTHOR_*` explicitly today + avoids the break when the fallback is removed. +- importing `nemo_eval_author_plugin._env_bridge` copies `AUTHOR_*` into unset + `EXPERIMENTALIST_*` slots, so the Experimentalist helpers Eval Author still + borrows see credentials during a standalone run. `eval_author.agent` imports it + ahead of any Experimentalist agent, because those agents read the environment when + their class body executes. A `nemo eval-author` CLI that auto-loads this `.env` is not wired yet. diff --git a/plugins/nemo-eval-author/pyproject.toml b/plugins/nemo-eval-author/pyproject.toml index b940fa0571f..1481ff35596 100644 --- a/plugins/nemo-eval-author/pyproject.toml +++ b/plugins/nemo-eval-author/pyproject.toml @@ -12,7 +12,6 @@ dependencies = [ "pydantic>=2", "harbor>=0.16 ; python_full_version >= '3.12'", "nooa ; python_full_version >= '3.12'", - "pyyaml>=6.0.3", "nemo-experimentalist-plugin", "nemo-insights-plugin", "nemo-platform", @@ -30,7 +29,3 @@ packages = ["src/nemo_eval_author_plugin"] asyncio_mode = "auto" pythonpath = ["src"] testpaths = ["tests"] - -[tool.ruff.lint.per-file-ignores] -# Bridge AUTHOR_* → EXPERIMENTALIST_* before TraceAnalyzer import (class-time LLM client). -"src/nemo_eval_author_plugin/eval_author/agent.py" = ["E402"] diff --git a/plugins/nemo-eval-author/src/nemo_eval_author_plugin/_env_bridge.py b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/_env_bridge.py new file mode 100644 index 00000000000..c4df059e32b --- /dev/null +++ b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/_env_bridge.py @@ -0,0 +1,23 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Import this module for its side effect: bridging ``AUTHOR_*`` credentials. + +Transitional. This module exists only because Eval Author still reuses Experimentalist +agents, and it should be deleted along with the last ``nemo_experimentalist_plugin`` import +in this package. ``tests/test_plugin_boundary.py`` tracks what is left to remove. + +``TraceAnalyzer`` and the other Experimentalist agents Eval Author reuses build their LLM +client in the class body, so they read ``EXPERIMENTALIST_*`` the moment their module is +first imported. A module that imports them therefore has to bridge *before* that import, +which no function call inside the module can do. + +Importing this module is how Eval Author expresses that ordering. ``import +nemo_eval_author_plugin._env_bridge`` sorts ahead of every ``from ...`` line in the same +isort section, so the ordering is maintained by the linter rather than by an ``E402`` +waiver and a comment asking future readers not to reshuffle the imports. +""" + +from nemo_eval_author_plugin.model_config import bridge_author_env_to_experimentalist + +bridge_author_env_to_experimentalist() diff --git a/plugins/nemo-eval-author/src/nemo_eval_author_plugin/eval_author/agent.py b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/eval_author/agent.py index 48c26d3150e..780a372c10c 100644 --- a/plugins/nemo-eval-author/src/nemo_eval_author_plugin/eval_author/agent.py +++ b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/eval_author/agent.py @@ -12,21 +12,12 @@ from pathlib import Path from typing import Any +# Populates EXPERIMENTALIST_* from AUTHOR_*, which the Experimentalist agent imports below +# read when their class bodies execute. Must stay ahead of them; isort keeps it there. +import nemo_eval_author_plugin._env_bridge # noqa: F401 from nemo_eval_author_plugin.eval_author.materialization import InsightSuite -from nemo_eval_author_plugin.eval_author.models import ( - EvalAuthorConfig, - EvalAuthorResult, -) -from nemo_eval_author_plugin.model_config import ( - bridge_author_env_to_experimentalist, - get_fast_model, - get_smart_model, -) - -# Bridge before importing Experimentalist agents that read EXPERIMENTALIST_* at -# class-definition time (e.g. helpers pulled in by TraceAnalyzer). -bridge_author_env_to_experimentalist() - +from nemo_eval_author_plugin.eval_author.models import EvalAuthorConfig, EvalAuthorResult +from nemo_eval_author_plugin.model_config import get_fast_model, get_smart_model from nemo_experimentalist_plugin.experimentalist.components import cache from nemo_experimentalist_plugin.experimentalist.components.evaluator import ( Dataset, @@ -34,12 +25,8 @@ Task, TrialResult, ) -from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import ( - ResourceRef, -) -from nemo_experimentalist_plugin.experimentalist.components.tools import ( - GuardedShellTools, -) +from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import ResourceRef +from nemo_experimentalist_plugin.experimentalist.components.tools import GuardedShellTools from nemo_experimentalist_plugin.experimentalist.components.trace_analyzer import ( Diagnostic, TraceAnalyzer, @@ -71,12 +58,7 @@ class EvalAuthor(Agent, llm=get_smart_model()): This suite will be used for optimization and regression testing. """ - def __init__( - self, - experiment_dir: Path, - config: EvalAuthorConfig | None = None, - **kwargs: Any, - ) -> None: + def __init__(self, experiment_dir: Path, config: EvalAuthorConfig | None = None, **kwargs: Any) -> None: """Initialize the Eval Author for the given experiment directory. Args: @@ -84,7 +66,6 @@ def __init__( config: Tuning parameters; defaults to ``EvalAuthorConfig()``. **kwargs: Forwarded to ``Agent.__init__``. """ - bridge_author_env_to_experimentalist() super().__init__(**kwargs) self._config = config or EvalAuthorConfig() self.experiment_dir = experiment_dir @@ -104,9 +85,7 @@ def __init__( config=TokenBudgetConfig(max_tokens=self._config.max_summary_tokens), ) - @strategy( - CodeActStrategy(config=CodeActConfig(max_iterations=15, cell_timeout=60.0)) - ) + @strategy(CodeActStrategy(config=CodeActConfig(max_iterations=15, cell_timeout=60.0))) async def discover_runner(self, dataset: Dataset) -> str: """Discover how this dataset's evaluation runner works. @@ -122,9 +101,7 @@ async def discover_runner(self, dataset: Dataset) -> str: """ # noqa: D413 ... - @strategy( - CodeActStrategy(config=CodeActConfig(max_iterations=60, cell_timeout=3600.0)) - ) + @strategy(CodeActStrategy(config=CodeActConfig(max_iterations=60, cell_timeout=3600.0))) async def author_insight_metrics( self, insight: Insight, @@ -207,9 +184,7 @@ async def author_insight_metrics( """ # noqa: D413 ... - @strategy( - CodeActStrategy(config=CodeActConfig(max_iterations=20, cell_timeout=60.0)) - ) + @strategy(CodeActStrategy(config=CodeActConfig(max_iterations=20, cell_timeout=60.0))) async def fill_task_template( self, trace_ref: str, @@ -251,10 +226,7 @@ def _trace_trial( id=f"insight-trace-{index}", task_id=task.id, status="completed", - trace=ResourceRef( - uri=f"intake://{trace_ref}", - description="Production trace attached to the insight.", - ), + trace=ResourceRef(uri=f"intake://{trace_ref}", description="Production trace attached to the insight."), metadata={ "source": "insight", "trace_ref": trace_ref, @@ -308,9 +280,7 @@ async def _run( resolved_agent = self.experiment_dir / agent_path insight_id = insight.id if not insight_id: - raise ValueError( - "Eval Author requires a persisted Insight with a durable id" - ) + raise ValueError("Eval Author requires a persisted Insight with a durable id") refs = insight.trace_refs[: self._config.max_traces] if not refs: @@ -328,9 +298,7 @@ async def _run( try: staged_tasks = insight_suite.stage(refs) for staged in staged_tasks: - await self.fill_task_template( - staged.trace_ref, staged.task, client, insight.workspace - ) + await self.fill_task_template(staged.trace_ref, staged.task, client, insight.workspace) insight_suite.validate(staged) materialized_dataset = insight_suite.promote_local(refs, staged_tasks) except BaseException: @@ -342,13 +310,8 @@ async def _run( for index, (task, ref) in enumerate(zip(tasks, refs, strict=True), start=1) ] diagnostics: list[tuple[str, Diagnostic]] = [] - analyzer_config = TraceAnalyzerConfig( - max_summary_tokens=self._config.max_summary_tokens - ) - analyzers = [ - TraceAnalyzer(experiment_dir=self.experiment_dir, config=analyzer_config) - for _ in trials - ] + analyzer_config = TraceAnalyzerConfig(max_summary_tokens=self._config.max_summary_tokens) + analyzers = [TraceAnalyzer(experiment_dir=self.experiment_dir, config=analyzer_config) for _ in trials] raw_diagnostics: list[Diagnostic | BaseException] = list( await asyncio.gather( *[ @@ -360,9 +323,7 @@ async def _run( client=client, workspace=insight.workspace, ) - for analyzer, trial, task in zip( - analyzers, trials, tasks, strict=True - ) + for analyzer, trial, task in zip(analyzers, trials, tasks, strict=True) ], return_exceptions=True, ) @@ -375,16 +336,12 @@ async def _run( logger.warning("Trace analysis failed for %s: %s", ref, result) analysis_statuses[task.id] = ("failed", str(result)) continue - cache.store( - self.experiment_dir, cache.task_hash(f"eval_author:{ref}"), result - ) + cache.store(self.experiment_dir, cache.task_hash(f"eval_author:{ref}"), result) diagnostics.append((ref, result)) analysis_statuses[task.id] = ("completed", None) insight_suite.record_analysis(analysis_statuses) - self.context["dataset_documentation"] = doc( - type(materialized_dataset), inline_depth=1 - ) + self.context["dataset_documentation"] = doc(type(materialized_dataset), inline_depth=1) runner_conventions = await self.discover_runner(materialized_dataset) summary = await self.author_insight_metrics( insight, diff --git a/plugins/nemo-eval-author/src/nemo_eval_author_plugin/eval_author/materialization.py b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/eval_author/materialization.py index 2cfea78fcd5..4e084dccb88 100644 --- a/plugins/nemo-eval-author/src/nemo_eval_author_plugin/eval_author/materialization.py +++ b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/eval_author/materialization.py @@ -18,13 +18,8 @@ import tomlkit from harbor.models.task.task import Task as HarborTask -from nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor import ( - HarborDataset, -) -from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import ( - Task, - local_path_from_uri, -) +from nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor import HarborDataset +from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import Task, local_path_from_uri _MANIFEST_SCHEMA_VERSION = 3 _CONTENT_HASH_SCHEMA_VERSION = 1 @@ -46,11 +41,7 @@ def _sha256_bytes(value: bytes) -> str: def _canonical_digest(value: object) -> str: - return _sha256_bytes( - json.dumps( - value, sort_keys=True, separators=(",", ":"), ensure_ascii=False - ).encode("utf-8") - ) + return _sha256_bytes(json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=False).encode("utf-8")) def _file_hashes(root: Path) -> dict[str, str]: @@ -76,40 +67,28 @@ def _verifier_dir(task_dir: Path) -> Path: raise ValueError(f"Materialized task has no verifier directory: {task_dir}") -def _content_provenance( - suite_dir: Path, manifest: dict[str, object] -) -> tuple[list[dict[str, object]], str, str]: +def _content_provenance(suite_dir: Path, manifest: dict[str, object]) -> tuple[list[dict[str, object]], str, str]: raw_tasks = manifest.get("tasks") if not isinstance(raw_tasks, list): - raise ValueError( - f"Insight suite manifest has invalid tasks: {suite_dir / 'manifest.json'}" - ) + raise ValueError(f"Insight suite manifest has invalid tasks: {suite_dir / 'manifest.json'}") tasks: list[dict[str, object]] = [] scorer_inputs: list[dict[str, str]] = [] suite_inputs: list[dict[str, str]] = [] for raw_task in raw_tasks: if not isinstance(raw_task, dict): - raise ValueError( - f"Insight suite manifest has invalid task entry: {raw_task!r}" - ) + raise ValueError(f"Insight suite manifest has invalid task entry: {raw_task!r}") if not all(isinstance(key, str) for key in raw_task): - raise ValueError( - f"Insight suite manifest task has invalid keys: {raw_task!r}" - ) + raise ValueError(f"Insight suite manifest task has invalid keys: {raw_task!r}") task_entry = cast(dict[str, object], raw_task) relative_path = task_entry.get("path") if not isinstance(relative_path, str) or not relative_path: - raise ValueError( - f"Insight suite manifest task has invalid path: {relative_path!r}" - ) + raise ValueError(f"Insight suite manifest task has invalid path: {relative_path!r}") task_dir = (suite_dir / relative_path).resolve() try: task_dir.relative_to(suite_dir.resolve()) except ValueError as exc: - raise ValueError( - f"Insight suite manifest task escapes the suite: {relative_path!r}" - ) from exc + raise ValueError(f"Insight suite manifest task escapes the suite: {relative_path!r}") from exc if not task_dir.is_dir(): raise ValueError(f"Insight suite manifest task path is missing: {task_dir}") @@ -118,16 +97,12 @@ def _content_provenance( try: verifier_path = verifier_dir.relative_to(task_dir).as_posix() except ValueError as exc: - raise ValueError( - f"Insight suite verifier must be contained in its task: {verifier_dir}" - ) from exc + raise ValueError(f"Insight suite verifier must be contained in its task: {verifier_dir}") from exc verifier_files = _file_hashes(verifier_dir) content_hash = f"sha256:{_canonical_digest(files)}" verifier_hash = f"sha256:{_canonical_digest(verifier_files)}" task_metadata = { - key: value - for key, value in task_entry.items() - if key not in {"content_hash", "files", "verifier"} + key: value for key, value in task_entry.items() if key not in {"content_hash", "files", "verifier"} } tasks.append( { @@ -184,16 +159,12 @@ class FinalizedInsightSuite: class InsightSuite: """Build one experiment-local persisted Harbor dataset for an Insight.""" - def __init__( - self, *, experiment_dir: Path, insight_id: str, task_template: Task - ) -> None: + def __init__(self, *, experiment_dir: Path, insight_id: str, task_template: Task) -> None: """Initialize deterministic paths and template provenance for a suite.""" if not insight_id: raise ValueError("Insight id is required to materialize an insight suite") if not task_template.uri: - raise ValueError( - "Task template URI is required to materialize an insight suite" - ) + raise ValueError("Task template URI is required to materialize an insight suite") self.experiment_dir = experiment_dir.resolve() self.insight_id = insight_id @@ -202,14 +173,10 @@ def __init__( context="Eval Author task template", ).resolve() if not self.template_dir.is_dir(): - raise ValueError( - f"Eval Author task template is not a directory: {self.template_dir}" - ) + raise ValueError(f"Eval Author task template is not a directory: {self.template_dir}") self.template_uri = self.template_dir.as_uri() insight_slug = f"{_slug(insight_id, fallback='insight')}-{_digest(insight_id)}" - self.root = ( - self.experiment_dir / "eval-and-optimize" / "eval_author" / insight_slug - ) + self.root = self.experiment_dir / "eval-and-optimize" / "eval_author" / insight_slug self.suite_dir = self.root / "insight-suite" self._candidate_root: Path | None = None self._candidate_suite: Path | None = None @@ -233,15 +200,7 @@ def stage(self, trace_refs: list[str]) -> list[StagedInsightTask]: task_dir = self._candidate_suite / slug shutil.copytree(self.template_dir, task_dir) task = list(HarborDataset.from_path(task_dir).list_tasks())[0] - staged.append( - StagedInsightTask( - index=index, - trace_ref=trace_ref, - slug=slug, - path=task_dir, - task=task, - ) - ) + staged.append(StagedInsightTask(index=index, trace_ref=trace_ref, slug=slug, path=task_dir, task=task)) return staged def discard(self) -> None: @@ -260,9 +219,7 @@ def validate(self, staged: StagedInsightTask) -> None: raise ValueError(f"Materialized task has no [task] table: {toml_path}") raw_name = task_table.get("name") if not isinstance(raw_name, str) or "/" not in raw_name: - raise ValueError( - f"Materialized task [task].name must use org/name format: {raw_name!r}" - ) + raise ValueError(f"Materialized task [task].name must use org/name format: {raw_name!r}") organization, short_name = raw_name.rsplit("/", 1) base_name = short_name.split("__", 1)[0] task_table["name"] = f"{organization}/{base_name}__{staged.slug}" @@ -280,24 +237,15 @@ def validate(self, staged: StagedInsightTask) -> None: toml_path.write_text(tomlkit.dumps(document), encoding="utf-8") instruction_path = staged.path / "instruction.md" - if ( - not instruction_path.is_file() - or not instruction_path.read_text(encoding="utf-8").strip() - ): - raise ValueError( - f"Materialized task instruction is missing or empty: {instruction_path}" - ) + if not instruction_path.is_file() or not instruction_path.read_text(encoding="utf-8").strip(): + raise ValueError(f"Materialized task instruction is missing or empty: {instruction_path}") for directory_name in ("environment", "tests"): directory = staged.path / directory_name if not directory.is_dir(): - raise ValueError( - f"Materialized task is missing required {directory_name}/ directory: {directory}" - ) + raise ValueError(f"Materialized task is missing required {directory_name}/ directory: {directory}") HarborTask(staged.path) - def promote_local( - self, trace_refs: list[str], staged_tasks: list[StagedInsightTask] - ) -> HarborDataset: + def promote_local(self, trace_refs: list[str], staged_tasks: list[StagedInsightTask]) -> HarborDataset: """Promote the validated candidate to the experiment-local working copy.""" if self._candidate_root is None or self._candidate_suite is None: raise RuntimeError("Insight suite has not been staged") @@ -334,9 +282,7 @@ def promote_local( dataset_id=f"insight-{_digest(self.insight_id, 12)}", ) for task in dataset.list_tasks(): - HarborTask( - local_path_from_uri(task.uri, context="Materialized Harbor task") - ) + HarborTask(local_path_from_uri(task.uri, context="Materialized Harbor task")) except BaseException as exc: try: if had_existing and backup.exists(): @@ -347,9 +293,7 @@ def promote_local( shutil.rmtree(self.suite_dir) self.discard() except BaseException as rollback_exc: - rollback_exc.add_note( - f"Local Insight suite promotion also failed before rollback: {exc!r}" - ) + rollback_exc.add_note(f"Local Insight suite promotion also failed before rollback: {exc!r}") raise rollback_exc from exc raise @@ -368,18 +312,14 @@ def record_analysis(self, statuses: dict[str, tuple[str, str | None]]) -> None: if error is not None: task["analysis"]["error"] = error pending_path = manifest_path.with_suffix(".json.pending") - pending_path.write_text( - json.dumps(manifest, indent=2, sort_keys=True) + "\n", encoding="utf-8" - ) + pending_path.write_text(json.dumps(manifest, indent=2, sort_keys=True) + "\n", encoding="utf-8") os.replace(pending_path, manifest_path) def finalize(self) -> FinalizedInsightSuite: """Persist content identities on the experiment-local authored suite.""" manifest_path = self.suite_dir / "manifest.json" manifest = json.loads(manifest_path.read_text(encoding="utf-8")) - tasks, scorer_identity, suite_identity = _content_provenance( - self.suite_dir, manifest - ) + tasks, scorer_identity, suite_identity = _content_provenance(self.suite_dir, manifest) digest = suite_identity.removeprefix("sha256:") manifest.pop("artifact", None) manifest.update( @@ -396,9 +336,7 @@ def finalize(self) -> FinalizedInsightSuite: } ) pending_path = manifest_path.with_suffix(".json.pending") - pending_path.write_text( - json.dumps(manifest, indent=2, sort_keys=True) + "\n", encoding="utf-8" - ) + pending_path.write_text(json.dumps(manifest, indent=2, sort_keys=True) + "\n", encoding="utf-8") os.replace(pending_path, manifest_path) dataset = HarborDataset.from_path( @@ -410,17 +348,13 @@ def finalize(self) -> FinalizedInsightSuite: task_path = task.get("path") content_hash = task.get("content_hash") verifier = task.get("verifier") - verifier_hash = ( - verifier.get("content_hash") if isinstance(verifier, dict) else None - ) + verifier_hash = verifier.get("content_hash") if isinstance(verifier, dict) else None if ( not isinstance(task_path, str) or not isinstance(content_hash, str) or not isinstance(verifier_hash, str) ): - raise ValueError( - f"Finalized Insight suite has invalid task provenance: {task!r}" - ) + raise ValueError(f"Finalized Insight suite has invalid task provenance: {task!r}") task_hashes[task_path] = { "content_hash": content_hash, "verifier_hash": verifier_hash, diff --git a/plugins/nemo-eval-author/src/nemo_eval_author_plugin/eval_author/run.py b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/eval_author/run.py index 79bb4a24353..b0ea41dddf8 100644 --- a/plugins/nemo-eval-author/src/nemo_eval_author_plugin/eval_author/run.py +++ b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/eval_author/run.py @@ -7,27 +7,13 @@ from pathlib import Path from typing import Literal, Protocol, cast -from nemo_eval_author_plugin.eval_author.models import ( - EvalAuthorConfig, - EvalAuthorResult, -) +from nemo_eval_author_plugin.eval_author.models import EvalAuthorConfig, EvalAuthorResult from nemo_experimentalist_plugin.client import make_client -from nemo_experimentalist_plugin.experimentalist.components.dataset_staging import ( - stage_task_template, -) -from nemo_experimentalist_plugin.experimentalist.components.evaluator import ( - Dataset, - Task, -) -from nemo_experimentalist_plugin.experimentalist.components.evaluator.base import ( - EvaluatorType, -) -from nemo_experimentalist_plugin.experimentalist.components.evaluator.factory import ( - DatasetFactory, -) -from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import ( - DatasetRef, -) +from nemo_experimentalist_plugin.experimentalist.components.dataset_staging import stage_task_template +from nemo_experimentalist_plugin.experimentalist.components.evaluator import Dataset, Task +from nemo_experimentalist_plugin.experimentalist.components.evaluator.base import EvaluatorType +from nemo_experimentalist_plugin.experimentalist.components.evaluator.factory import DatasetFactory +from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import DatasetRef from nemo_experimentalist_plugin.experimentalist.experimentalist_backend import ( make_experimentalist_backend, ) @@ -97,14 +83,10 @@ async def run_eval_author( experiments_output=str(experiment_dir), mode=mode, ) - resolved_insight = await backend.get_insight( - workspace=workspace, insight_id=str(insight) - ) + resolved_insight = await backend.get_insight(workspace=workspace, insight_id=str(insight)) agent_ref = agent if agent is not None else resolved_insight.agent agent_path = experiment_dir / "eval_author" / "source-agent" - await backend.get_agent_code( - workspace=workspace, agent=agent_ref, dest=agent_path - ) + await backend.get_agent_code(workspace=workspace, agent=agent_ref, dest=agent_path) dataset_factory = DatasetFactory() staged_task_template = await stage_task_template( @@ -120,26 +102,18 @@ async def run_eval_author( return await eval_author.run( insight=resolved_insight, agent_path=agent_path, - task_template=dataset_factory.build_task_template( - evaluator_type, staged_task_template - ), + task_template=dataset_factory.build_task_template(evaluator_type, staged_task_template), train_dataset=dataset_factory.build_dataset(evaluator_type, train_dataset), - validation_dataset=dataset_factory.build_dataset( - evaluator_type, validation_dataset - ), + validation_dataset=dataset_factory.build_dataset(evaluator_type, validation_dataset), client=client, ) finally: await client.close() -def build_eval_author_agent( - *, experiment_dir: Path, config: EvalAuthorConfig -) -> _EvalAuthorAgent: +def build_eval_author_agent(*, experiment_dir: Path, config: EvalAuthorConfig) -> _EvalAuthorAgent: """Build the LLM-backed Eval Author agent lazily.""" - from nemo_eval_author_plugin.eval_author.agent import ( - build_eval_author_agent as _build_eval_author_agent, - ) + from nemo_eval_author_plugin.eval_author.agent import build_eval_author_agent as _build_eval_author_agent return _build_eval_author_agent(experiment_dir=experiment_dir, config=config) diff --git a/plugins/nemo-eval-author/src/nemo_eval_author_plugin/model_config.py b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/model_config.py index 6c3a63481e3..6caca81e809 100644 --- a/plugins/nemo-eval-author/src/nemo_eval_author_plugin/model_config.py +++ b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/model_config.py @@ -1,35 +1,86 @@ # SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 -# Eval Author prefers AUTHOR_* and falls back to EXPERIMENTALIST_* for insight-mode compatibility. -# Shared Experimentalist helpers still read EXPERIMENTALIST_*; bridge_author_env_to_experimentalist() -# copies AUTHOR_* into unset EXPERIMENTALIST_* slots for standalone runs. +"""LLM clients for Eval Author. + +This module deliberately imports nothing from Experimentalist. Eval Author is meant to +become a standalone plugin, so its credential handling duplicates Experimentalist's rather +than sharing it. See the plugin README for the wider split, and +``tests/test_plugin_boundary.py`` for the check that holds this module independent. + +``AUTHOR_*`` is the real contract. The ``EXPERIMENTALIST_*`` fallback and +:func:`bridge_author_env_to_experimentalist` are both transitional, and exist only while +Eval Author still reuses Experimentalist agents. + +TODO(eval-author-standalone): drop the ``EXPERIMENTALIST_*`` fallback and the bridge once +this plugin imports nothing from ``nemo_experimentalist_plugin``. Concretely: delete +``_BRIDGED_ENV_PAIRS`` and :func:`bridge_author_env_to_experimentalist`, delete +``_env_bridge.py`` and its import in ``eval_author/agent.py``, and narrow every ``_env`` / +``_env_or_default`` call here to its ``AUTHOR_*`` name. That is a breaking change for +anyone running insight mode from an Experimentalist-only profile ``.env``, so it needs a +release note. ``rg 'eval-author-standalone'`` finds the other sites. +""" + import functools +import logging import os -from urllib.parse import urlparse +from urllib.parse import urlsplit from nooa.unifiedllm import CompletionClient +logger = logging.getLogger(__name__) + +# Duplicated from `nemo_experimentalist_plugin.cli` on purpose: sharing it would mean +# importing Experimentalist from a module that is otherwise already standalone. _GATEWAY_HOST = "inference-api.nvidia.com" +_SMART_MODEL_DEFAULT = "openai/openai/openai/gpt-5.5" +_FAST_MODEL_DEFAULT = "openai/openai/openai/gpt-5-mini" + +# Transitional; see TODO(eval-author-standalone) above. Every pair targets an Experimentalist +# helper rather than a client Eval Author owns, which is why the mid tier appears here +# despite Eval Author having no mid-tier client of its own. +_BRIDGED_ENV_PAIRS = ( + ("AUTHOR_API_BASE", "EXPERIMENTALIST_API_BASE"), + ("AUTHOR_API_KEY", "EXPERIMENTALIST_API_KEY"), + ("AUTHOR_SMART_MODEL_NAME", "EXPERIMENTALIST_SMART_MODEL_NAME"), + ("AUTHOR_MID_MODEL_NAME", "EXPERIMENTALIST_MID_MODEL_NAME"), + ("AUTHOR_FAST_MODEL_NAME", "EXPERIMENTALIST_FAST_MODEL_NAME"), +) + -def _env(*names: str, default: str | None = None) -> str | None: +def _env(*names: str) -> str | None: + """Return the first non-empty value among ``names``, or ``None`` when all are empty.""" for name in names: value = os.environ.get(name, "").strip() if value: return value - return default + return None -def _required_author_env(primary: str, fallback: str) -> str: - value = _env(primary, fallback) - if not value: - raise ValueError(f"{primary} (or {fallback}) must be set") - return value +def _env_or_default(*names: str, default: str) -> str: + """Return the first non-empty value among ``names``, falling back to ``default``.""" + return _env(*names) or default def _api_base() -> str: - return _required_author_env("AUTHOR_API_BASE", "EXPERIMENTALIST_API_BASE") + base = _env("AUTHOR_API_BASE", "EXPERIMENTALIST_API_BASE") + if not base: + raise ValueError("AUTHOR_API_BASE (or EXPERIMENTALIST_API_BASE) must be set") + return base + + +def _is_gateway_base(value: str) -> bool: + """Report whether ``value`` is the NVIDIA Inference Gateway reached over HTTPS. + + Mirrors ``nemo_experimentalist_plugin.cli._is_gateway_base``. The scheme check is not + cosmetic: a host-only match would forward ``INFERENCE_API_KEY`` to a plain-HTTP base. + """ + try: + parsed = urlsplit(value) + except ValueError: + return False + return parsed.scheme == "https" and parsed.hostname == _GATEWAY_HOST def _api_key() -> str: @@ -38,17 +89,20 @@ def _api_key() -> str: Preference order: 1. ``AUTHOR_API_KEY`` 2. ``EXPERIMENTALIST_API_KEY`` - 3. ``INFERENCE_API_KEY`` when the resolved base is the NVIDIA Inference Gateway + 3. ``INFERENCE_API_KEY``, but only when the resolved base is the HTTPS NVIDIA + Inference Gateway, matching how the Experimentalist CLI forwards that key. + + Returns: + str: the resolved API key. + + Raises: + ValueError: if no key is available for the resolved base. + """ key = _env("AUTHOR_API_KEY", "EXPERIMENTALIST_API_KEY") if key: return key - base = _api_base() - try: - host = urlparse(base).hostname - except ValueError: - host = None - if host == _GATEWAY_HOST: + if _is_gateway_base(_api_base()): inference = _env("INFERENCE_API_KEY") if inference: return inference @@ -59,43 +113,42 @@ def _api_key() -> str: @functools.cache -def get_smart_model() -> CompletionClient: - """Return the cached smart (high-capability) LLM client configured from environment variables. +def _completion_client(name: str, api_base: str, api_key: str) -> CompletionClient: + """Return Eval Author's client for an already-resolved model triple. - Returns: - CompletionClient: the singleton smart-model client. + Cached on the triple rather than per tier so that two tiers pointing at the same model + share a connection pool. Experimentalist caches its own clients separately; that + duplication is the price of Eval Author not importing from it. - Raises: - ValueError: if Eval Author / Experimentalist API credentials are unset or empty. + Args: + name: Fully qualified model name, as ``CompletionClient`` expects it. + api_base: Resolved API base URL. + api_key: Resolved API key. + + Returns: + CompletionClient: the client for this triple. """ - name = _env( - "AUTHOR_SMART_MODEL_NAME", - "EXPERIMENTALIST_SMART_MODEL_NAME", - default="openai/openai/openai/gpt-5.5", - ) - assert name is not None - return CompletionClient(name, api_base=_api_base(), api_key=_api_key()) + return CompletionClient(name, api_base=api_base, api_key=api_key) @functools.cache -def get_mid_model() -> CompletionClient: - """Return the cached mid-tier LLM client configured from environment variables. +def get_smart_model() -> CompletionClient: + """Return the cached smart (high-capability) LLM client configured from environment variables. Returns: - CompletionClient: the singleton mid-model client. + CompletionClient: the singleton smart-model client. Raises: - ValueError: if Eval Author / Experimentalist API credentials are unset or empty. + ValueError: if Eval Author API credentials are unset or empty. """ - name = _env( - "AUTHOR_MID_MODEL_NAME", - "EXPERIMENTALIST_MID_MODEL_NAME", - default="openai/gcp/google/gemini-3.5-flash", + name = _env_or_default( + "AUTHOR_SMART_MODEL_NAME", + "EXPERIMENTALIST_SMART_MODEL_NAME", + default=_SMART_MODEL_DEFAULT, ) - assert name is not None - return CompletionClient(name, api_base=_api_base(), api_key=_api_key()) + return _completion_client(name, _api_base(), _api_key()) @functools.cache @@ -106,82 +159,37 @@ def get_fast_model() -> CompletionClient: CompletionClient: the singleton fast-model client. Raises: - ValueError: if Eval Author / Experimentalist API credentials are unset or empty. + ValueError: if Eval Author API credentials are unset or empty. """ - name = _env( + name = _env_or_default( "AUTHOR_FAST_MODEL_NAME", "EXPERIMENTALIST_FAST_MODEL_NAME", - default="openai/openai/openai/gpt-5-mini", + default=_FAST_MODEL_DEFAULT, ) - assert name is not None - return CompletionClient(name, api_base=_api_base(), api_key=_api_key()) - + return _completion_client(name, _api_base(), _api_key()) -def _mask_key(value: str) -> str: - if len(value) <= 4: - return "*" * len(value) - return f"*****{value[-4:]}" +def bridge_author_env_to_experimentalist() -> list[str]: + """Copy ``AUTHOR_*`` into unset ``EXPERIMENTALIST_*`` slots. -def _sanitize_url(url: str) -> str: - """Strip userinfo, query, and fragment so credentials never leak via the log banner.""" - parsed = urlparse(url) - host = parsed.hostname or "" - port = f":{parsed.port}" if parsed.port else "" - return f"{parsed.scheme}://{host}{port}{parsed.path}" - + Eval Author owns ``AUTHOR_*`` for its own clients, but the Experimentalist helpers it + reuses (``TraceAnalyzer`` and the summarizers it pulls in) bind their LLM in the class + body from ``EXPERIMENTALIST_*``. Bridging lets a standalone ``AUTHOR_*``-only run + supply those helpers without the caller duplicating credentials. Values already present + in ``EXPERIMENTALIST_*`` always win, so insight-mode runs are left alone. -def bridge_author_env_to_experimentalist() -> None: - """Copy ``AUTHOR_*`` into unset ``EXPERIMENTALIST_*`` slots. + Returns: + list[str]: the ``EXPERIMENTALIST_*`` names this call populated, in declaration + order. Empty once everything is in place, which makes repeat calls no-ops. - Eval Author owns ``AUTHOR_*`` credentials for its own LLM clients. Shared - Experimentalist helpers (for example ``TraceAnalyzer``) still read - ``EXPERIMENTALIST_*``. Bridging keeps standalone Author runs working without - duplicating credentials. """ - pairs = ( - ("AUTHOR_API_BASE", "EXPERIMENTALIST_API_BASE"), - ("AUTHOR_API_KEY", "EXPERIMENTALIST_API_KEY"), - ("AUTHOR_SMART_MODEL_NAME", "EXPERIMENTALIST_SMART_MODEL_NAME"), - ("AUTHOR_MID_MODEL_NAME", "EXPERIMENTALIST_MID_MODEL_NAME"), - ("AUTHOR_FAST_MODEL_NAME", "EXPERIMENTALIST_FAST_MODEL_NAME"), - ) - for src, dst in pairs: + applied: list[str] = [] + for src, dst in _BRIDGED_ENV_PAIRS: value = os.environ.get(src, "").strip() if value and not os.environ.get(dst, "").strip(): os.environ[dst] = value - - -def log_model_config() -> str: - """Return a formatted string summarising the current Eval Author model configuration. - - Returns: - str: a multi-line summary of smart model, fast model, API base, and masked API key. - - """ - smart = _env( - "AUTHOR_SMART_MODEL_NAME", - "EXPERIMENTALIST_SMART_MODEL_NAME", - default="openai/openai/openai/gpt-5.5", - ) - mid = _env( - "AUTHOR_MID_MODEL_NAME", - "EXPERIMENTALIST_MID_MODEL_NAME", - default="openai/gcp/google/gemini-3.5-flash", - ) - fast = _env( - "AUTHOR_FAST_MODEL_NAME", - "EXPERIMENTALIST_FAST_MODEL_NAME", - default="openai/openai/openai/gpt-5-mini", - ) - assert smart is not None and mid is not None and fast is not None - api_base = _api_base() - api_key = _api_key() - return ( - f"smart model: {smart}\n" - f"mid model: {mid}\n" - f"fast model: {fast}\n" - f"api base: {_sanitize_url(api_base)}\n" - f"api key: {_mask_key(api_key)}" - ) + applied.append(dst) + if applied: + logger.info("Bridged Eval Author credentials into %s", ", ".join(applied)) + return applied diff --git a/plugins/nemo-eval-author/tests/conftest.py b/plugins/nemo-eval-author/tests/conftest.py index 4d33bd91661..391f6fa2cc8 100644 --- a/plugins/nemo-eval-author/tests/conftest.py +++ b/plugins/nemo-eval-author/tests/conftest.py @@ -6,6 +6,11 @@ Eval Author and Experimentalist agents call get_smart_model() at class-definition time. Placeholder credentials keep collection working when real keys are absent. setdefault does not overwrite CI or developer-provided values. + +Both stand-in values contain "placeholder" on purpose: real-model canaries such as +test_eval_author_repair_e2e cannot use "is it set?" to decide whether to skip, because +these assignments make every credential look present. Keep the substring if you change +the values. """ import os diff --git a/plugins/nemo-eval-author/tests/test_eval_author_agent.py b/plugins/nemo-eval-author/tests/test_eval_author_agent.py index 02f7f55c154..e6177c7318f 100644 --- a/plugins/nemo-eval-author/tests/test_eval_author_agent.py +++ b/plugins/nemo-eval-author/tests/test_eval_author_agent.py @@ -59,9 +59,7 @@ class _PipelineCalls: analyzer_init: list[_AnalyzerInitCall] analyzer_run: list[_AnalyzerRunCall] discovered_datasets: list[Dataset] - author_args: list[ - tuple[Insight, list[tuple[str, Diagnostic]], Dataset, str, str | None] - ] + author_args: list[tuple[Insight, list[tuple[str, Diagnostic]], Dataset, str, str | None]] suite_discards: int @@ -105,9 +103,7 @@ def _eval_author( def _diagnostic(summary: str) -> Diagnostic: - return Diagnostic( - outcome="FAILURE", summary=summary, failure_point=1, root_cause="wrong tool" - ) + return Diagnostic(outcome="FAILURE", summary=summary, failure_point=1, root_cause="wrong tool") def _prompt(method: Any) -> str: @@ -122,24 +118,16 @@ def test_eval_author_prompts_scope_metrics_to_materialized_insight_suite() -> No assert "read it first" in discover_prompt assert "inspect the actual files" in discover_prompt - assert ( - "authoritative reference for what artifacts exist at evaluation runtime" - in author_prompt - ) + assert "authoritative reference for what artifacts exist at evaluation runtime" in author_prompt assert "how tasks are structured, and how to add metrics" in author_prompt - assert ( - "Add at least one new Insight-specific metric key to every task in ``insight_suite``" - in author_prompt - ) + assert "Add at least one new Insight-specific metric key to every task in ``insight_suite``" in author_prompt assert "Preserve every existing verifier metric" in author_prompt assert "Do not modify the user's train or validation datasets" in author_prompt assert "call ``await insight_suite.validate()``" in author_prompt assert "fix every reported failure and revalidate the suite" in author_prompt -def test_eval_author_prompts_retain_root_cause_and_normalized_scoring_guidance() -> ( - None -): +def test_eval_author_prompts_retain_root_cause_and_normalized_scoring_guidance() -> None: prompt = _prompt(EvalAuthor.author_insight_metrics) assert "Focus the metric on the root cause, not the surface symptom" in prompt @@ -187,11 +175,7 @@ def stage(self, trace_refs: list[str]) -> list[Any]: type( "StagedTask", (), - { - "trace_ref": trace_ref, - "task": self.task_template, - "result": None, - }, + {"trace_ref": trace_ref, "task": self.task_template, "result": None}, )() for trace_ref in trace_refs ] @@ -200,9 +184,7 @@ def stage(self, trace_refs: list[str]) -> list[Any]: def validate(self, staged: Any) -> None: staged.result = calls.fill_task_template[-1].result - def promote_local( - self, trace_refs: list[str], staged_tasks: list[Any] - ) -> Dataset: + def promote_local(self, trace_refs: list[str], staged_tasks: list[Any]) -> Dataset: assert trace_refs == [staged.trace_ref for staged in staged_tasks] tasks = [staged.result for staged in staged_tasks] if materialized_dataset is not None: @@ -261,15 +243,11 @@ async def __call__( return result class FakeTraceAnalyzer: - def __init__( - self, *, experiment_dir: Path, config: TraceAnalyzerConfig - ) -> None: + def __init__(self, *, experiment_dir: Path, config: TraceAnalyzerConfig) -> None: nonlocal next_analyzer self._index = next_analyzer next_analyzer += 1 - calls.analyzer_init.append( - _AnalyzerInitCall(experiment_dir=experiment_dir, config=config) - ) + calls.analyzer_init.append(_AnalyzerInitCall(experiment_dir=experiment_dir, config=config)) async def run( self, @@ -330,9 +308,7 @@ async def __call__( @pytest.mark.asyncio -async def test_run_without_traces_returns_input_datasets_unchanged( - tmp_path: Path, -) -> None: +async def test_run_without_traces_returns_input_datasets_unchanged(tmp_path: Path) -> None: eval_author = _eval_author(tmp_path) train_dataset = Dataset(id="train") validation_dataset = Dataset(id="validation") @@ -392,14 +368,8 @@ async def test_run_enforces_max_traces( client=cast(Any, object()), ) - assert [call.trace_ref for call in calls.fill_task_template] == [ - "trace-1", - "trace-2", - ] - assert [call.trial.metadata["trace_ref"] for call in calls.analyzer_run] == [ - "trace-1", - "trace-2", - ] + assert [call.trace_ref for call in calls.fill_task_template] == ["trace-1", "trace-2"] + assert [call.trial.metadata["trace_ref"] for call in calls.analyzer_run] == ["trace-1", "trace-2"] @pytest.mark.asyncio @@ -433,9 +403,7 @@ async def __call__(self, *_: Any) -> Task: def test_trace_trial_uses_intake_uri_and_insight_metadata(tmp_path: Path) -> None: insight = _insight(["trace-7"]) - trial = _eval_author(tmp_path)._trace_trial( - insight, Task(id="task-7"), "trace-7", 3, insight.id - ) + trial = _eval_author(tmp_path)._trace_trial(insight, Task(id="task-7"), "trace-7", 3, insight.id) assert trial.id == "insight-trace-3" assert trial.task_id == "task-7" @@ -460,9 +428,7 @@ async def test_run_caches_each_successful_diagnostic( eval_author = _eval_author(tmp_path) _install_pipeline(monkeypatch, [first, second], eval_author) stored: list[tuple[Path, str, Diagnostic]] = [] - monkeypatch.setattr( - eval_author_module.cache, "store", lambda *args: stored.append(args) - ) + monkeypatch.setattr(eval_author_module.cache, "store", lambda *args: stored.append(args)) await eval_author.run( _insight(["trace-a", "trace-b"]), @@ -487,15 +453,9 @@ async def test_run_skips_failed_trace_analysis_and_keeps_successes( ) -> None: successful = _diagnostic("successful") eval_author = _eval_author(tmp_path) - calls = _install_pipeline( - monkeypatch, [RuntimeError("analysis failed"), successful], eval_author - ) + calls = _install_pipeline(monkeypatch, [RuntimeError("analysis failed"), successful], eval_author) stored: list[Diagnostic] = [] - monkeypatch.setattr( - eval_author_module.cache, - "store", - lambda workspace, key, value: stored.append(value), - ) + monkeypatch.setattr(eval_author_module.cache, "store", lambda workspace, key, value: stored.append(value)) insight = _insight(["trace-bad", "trace-good"]) result = await eval_author.run( @@ -640,9 +600,7 @@ async def test_run_feeds_validation_failures_back_for_one_repair_attempt( monkeypatch: pytest.MonkeyPatch, ) -> None: eval_author = _eval_author(tmp_path) - insight_dataset = _RepairableDataset( - "insight-suite", "task 'insight-a': check.py:2:1: invalid syntax" - ) + insight_dataset = _RepairableDataset("insight-suite", "task 'insight-a': check.py:2:1: invalid syntax") _install_pipeline( monkeypatch, [_diagnostic("diagnostic")], @@ -694,9 +652,7 @@ async def test_run_raises_after_validation_repair_budget_is_exhausted( monkeypatch: pytest.MonkeyPatch, ) -> None: eval_author = _eval_author(tmp_path, max_validation_repair_attempts=1) - insight_dataset = _RepairableDataset( - "insight-suite", "task 'insight-a': check.py:2:1: invalid syntax" - ) + insight_dataset = _RepairableDataset("insight-suite", "task 'insight-a': check.py:2:1: invalid syntax") calls = _install_pipeline( monkeypatch, [_diagnostic("diagnostic")], @@ -722,12 +678,10 @@ async def test_run_raises_after_validation_repair_budget_is_exhausted( assert insight_dataset.validate_calls == 2 -def test_eval_author_config_bounds_validation_repair_attempts() -> None: - assert ( - EvalAuthorConfig( - max_validation_repair_attempts=10 - ).max_validation_repair_attempts - == 10 - ) +def test_eval_author_config_defaults_and_bounds_validation_repair_attempts() -> None: + # Experimentalist's loop config asserts this default too, but from the other side of + # the plugin boundary; owning it here is what keeps the default a plugin contract. + assert EvalAuthorConfig().max_validation_repair_attempts == 5 + assert EvalAuthorConfig(max_validation_repair_attempts=10).max_validation_repair_attempts == 10 with pytest.raises(ValueError, match="less than or equal to 10"): EvalAuthorConfig(max_validation_repair_attempts=11) diff --git a/plugins/nemo-eval-author/tests/test_eval_author_materialization.py b/plugins/nemo-eval-author/tests/test_eval_author_materialization.py index ee7825ed078..a110fcfcf01 100644 --- a/plugins/nemo-eval-author/tests/test_eval_author_materialization.py +++ b/plugins/nemo-eval-author/tests/test_eval_author_materialization.py @@ -10,14 +10,10 @@ from pathlib import Path import pytest -from nemo_eval_author_plugin.eval_author import ( - materialization as materialization_module, -) +from nemo_eval_author_plugin.eval_author import materialization as materialization_module from nemo_eval_author_plugin.eval_author.materialization import InsightSuite from nemo_experimentalist_plugin.experimentalist.components.evaluator import Task -from nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor import ( - HarborDataset, -) +from nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor import HarborDataset def _write_template(root: Path) -> Task: @@ -46,34 +42,22 @@ def _write_template(root: Path) -> Task: (environment / "Dockerfile").write_text("FROM ubuntu:24.04\n", encoding="utf-8") tests = root / "tests" tests.mkdir() - (tests / "test.sh").write_text( - "#!/bin/sh\nmkdir -p /logs/verifier\necho 1 > /logs/verifier/reward.txt\n" - ) + (tests / "test.sh").write_text("#!/bin/sh\nmkdir -p /logs/verifier\necho 1 > /logs/verifier/reward.txt\n") return Task(id="task-template", uri=root.as_uri()) -def test_insight_suite_materializes_discoverable_tasks_with_provenance( - tmp_path: Path, -) -> None: +def test_insight_suite_materializes_discoverable_tasks_with_provenance(tmp_path: Path) -> None: template = _write_template(tmp_path / "template") refs = ["intake/traces/unsafe ref", "intake/traces/unsafe ref"] - suite = InsightSuite( - experiment_dir=tmp_path, insight_id="insight/unsafe id", task_template=template - ) + suite = InsightSuite(experiment_dir=tmp_path, insight_id="insight/unsafe id", task_template=template) staged = suite.stage(refs) for index, task in enumerate(staged, start=1): - (task.path / "instruction.md").write_text( - f"Reproduce production scenario {index}.\n", encoding="utf-8" - ) + (task.path / "instruction.md").write_text(f"Reproduce production scenario {index}.\n", encoding="utf-8") suite.validate(task) dataset = suite.promote_local(refs, staged) - assert ( - suite.suite_dir - == next((tmp_path / "eval-and-optimize" / "eval_author").iterdir()) - / "insight-suite" - ) + assert suite.suite_dir == next((tmp_path / "eval-and-optimize" / "eval_author").iterdir()) / "insight-suite" assert [task.id for task in dataset.list_tasks()] == [task.slug for task in staged] assert len(HarborDataset.from_path(suite.suite_dir).list_tasks()) == 2 names: list[str] = [] @@ -93,65 +77,45 @@ def test_insight_suite_materializes_discoverable_tasks_with_provenance( assert len(set(names)) == 2 assert all(name.startswith("example/template__") for name in names) - manifest = json.loads( - (suite.suite_dir / "manifest.json").read_text(encoding="utf-8") - ) + manifest = json.loads((suite.suite_dir / "manifest.json").read_text(encoding="utf-8")) assert manifest["insight_id"] == "insight/unsafe id" assert manifest["trace_refs"] == refs assert manifest["task_template"] == {"uri": template.uri} assert [task["source_trace_ref"] for task in manifest["tasks"]] == refs -def test_insight_suite_second_materialization_replaces_first_at_stable_path( - tmp_path: Path, -) -> None: +def test_insight_suite_second_materialization_replaces_first_at_stable_path(tmp_path: Path) -> None: template = _write_template(tmp_path / "template") refs = ["trace-1"] - first_suite = InsightSuite( - experiment_dir=tmp_path, insight_id="insight-1", task_template=template - ) + first_suite = InsightSuite(experiment_dir=tmp_path, insight_id="insight-1", task_template=template) first_staged = first_suite.stage(refs) - (first_staged[0].path / "instruction.md").write_text( - "First generated instruction.\n", encoding="utf-8" - ) + (first_staged[0].path / "instruction.md").write_text("First generated instruction.\n", encoding="utf-8") first_suite.validate(first_staged[0]) first_suite.promote_local(refs, first_staged) materialized_path = first_suite.suite_dir - second_suite = InsightSuite( - experiment_dir=tmp_path, insight_id="insight-1", task_template=template - ) + second_suite = InsightSuite(experiment_dir=tmp_path, insight_id="insight-1", task_template=template) second_staged = second_suite.stage(refs) - (second_staged[0].path / "instruction.md").write_text( - "Second generated instruction.\n", encoding="utf-8" - ) + (second_staged[0].path / "instruction.md").write_text("Second generated instruction.\n", encoding="utf-8") second_suite.validate(second_staged[0]) second_suite.promote_local(refs, second_staged) assert second_suite.suite_dir == materialized_path task = list(HarborDataset.from_path(materialized_path).list_tasks())[0] task_dir = Path(task.uri.removeprefix("file://")) - assert (task_dir / "instruction.md").read_text( - encoding="utf-8" - ) == "Second generated instruction.\n" + assert (task_dir / "instruction.md").read_text(encoding="utf-8") == "Second generated instruction.\n" def test_failed_rebuild_preserves_previous_materialized_suite(tmp_path: Path) -> None: template = _write_template(tmp_path / "template") refs = ["trace-1"] - first_suite = InsightSuite( - experiment_dir=tmp_path, insight_id="insight-1", task_template=template - ) + first_suite = InsightSuite(experiment_dir=tmp_path, insight_id="insight-1", task_template=template) first_staged = first_suite.stage(refs) - (first_staged[0].path / "instruction.md").write_text( - "Valid materialized instruction.\n", encoding="utf-8" - ) + (first_staged[0].path / "instruction.md").write_text("Valid materialized instruction.\n", encoding="utf-8") first_suite.validate(first_staged[0]) first_suite.promote_local(refs, first_staged) - failed_suite = InsightSuite( - experiment_dir=tmp_path, insight_id="insight-1", task_template=template - ) + failed_suite = InsightSuite(experiment_dir=tmp_path, insight_id="insight-1", task_template=template) failed_staged = failed_suite.stage(refs) (failed_staged[0].path / "instruction.md").write_text("\n", encoding="utf-8") @@ -160,9 +124,7 @@ def test_failed_rebuild_preserves_previous_materialized_suite(tmp_path: Path) -> task = list(HarborDataset.from_path(first_suite.suite_dir).list_tasks())[0] task_dir = Path(task.uri.removeprefix("file://")) - assert (task_dir / "instruction.md").read_text( - encoding="utf-8" - ) == "Valid materialized instruction.\n" + assert (task_dir / "instruction.md").read_text(encoding="utf-8") == "Valid materialized instruction.\n" def test_post_promotion_validation_failure_restores_previous_suite( @@ -171,50 +133,34 @@ def test_post_promotion_validation_failure_restores_previous_suite( ) -> None: template = _write_template(tmp_path / "template") refs = ["trace-1"] - first_suite = InsightSuite( - experiment_dir=tmp_path, insight_id="insight-1", task_template=template - ) + first_suite = InsightSuite(experiment_dir=tmp_path, insight_id="insight-1", task_template=template) first_staged = first_suite.stage(refs) - (first_staged[0].path / "instruction.md").write_text( - "Previously materialized.\n", encoding="utf-8" - ) + (first_staged[0].path / "instruction.md").write_text("Previously materialized.\n", encoding="utf-8") first_suite.validate(first_staged[0]) first_suite.promote_local(refs, first_staged) - failed_suite = InsightSuite( - experiment_dir=tmp_path, insight_id="insight-1", task_template=template - ) + failed_suite = InsightSuite(experiment_dir=tmp_path, insight_id="insight-1", task_template=template) failed_staged = failed_suite.stage(refs) - (failed_staged[0].path / "instruction.md").write_text( - "Broken promotion.\n", encoding="utf-8" - ) + (failed_staged[0].path / "instruction.md").write_text("Broken promotion.\n", encoding="utf-8") failed_suite.validate(failed_staged[0]) def fail_post_promotion_validation(_: Path) -> None: raise RuntimeError("post-promotion validation failed") - monkeypatch.setattr( - materialization_module, "HarborTask", fail_post_promotion_validation - ) + monkeypatch.setattr(materialization_module, "HarborTask", fail_post_promotion_validation) with pytest.raises(RuntimeError, match="post-promotion validation failed"): failed_suite.promote_local(refs, failed_staged) task = list(HarborDataset.from_path(first_suite.suite_dir).list_tasks())[0] task_dir = Path(task.uri.removeprefix("file://")) - assert (task_dir / "instruction.md").read_text( - encoding="utf-8" - ) == "Previously materialized.\n" + assert (task_dir / "instruction.md").read_text(encoding="utf-8") == "Previously materialized.\n" assert list(first_suite.root.glob(".insight-suite-backup-*")) == [] -def test_insight_suite_rejects_empty_instruction_before_materialization( - tmp_path: Path, -) -> None: +def test_insight_suite_rejects_empty_instruction_before_materialization(tmp_path: Path) -> None: template = _write_template(tmp_path / "template") - suite = InsightSuite( - experiment_dir=tmp_path, insight_id="insight-1", task_template=template - ) + suite = InsightSuite(experiment_dir=tmp_path, insight_id="insight-1", task_template=template) staged = suite.stage(["trace-1"]) (staged[0].path / "instruction.md").write_text("\n", encoding="utf-8") @@ -226,9 +172,7 @@ def test_insight_suite_rejects_empty_instruction_before_materialization( def test_discard_removes_candidate_and_allows_restaging(tmp_path: Path) -> None: template = _write_template(tmp_path / "template") - suite = InsightSuite( - experiment_dir=tmp_path, insight_id="insight-1", task_template=template - ) + suite = InsightSuite(experiment_dir=tmp_path, insight_id="insight-1", task_template=template) staged = suite.stage(["trace-1"]) candidate_root = staged[0].path.parent.parent @@ -241,18 +185,12 @@ def test_discard_removes_candidate_and_allows_restaging(tmp_path: Path) -> None: assert not restaged[0].path.exists() -def test_insight_suite_records_analysis_without_removing_failed_tasks( - tmp_path: Path, -) -> None: +def test_insight_suite_records_analysis_without_removing_failed_tasks(tmp_path: Path) -> None: template = _write_template(tmp_path / "template") - suite = InsightSuite( - experiment_dir=tmp_path, insight_id="insight-1", task_template=template - ) + suite = InsightSuite(experiment_dir=tmp_path, insight_id="insight-1", task_template=template) staged = suite.stage(["trace-good", "trace-bad"]) for task in staged: - (task.path / "instruction.md").write_text( - f"Run {task.trace_ref}.\n", encoding="utf-8" - ) + (task.path / "instruction.md").write_text(f"Run {task.trace_ref}.\n", encoding="utf-8") suite.validate(task) suite.promote_local([task.trace_ref for task in staged], staged) @@ -263,9 +201,7 @@ def test_insight_suite_records_analysis_without_removing_failed_tasks( } ) - manifest = json.loads( - (suite.suite_dir / "manifest.json").read_text(encoding="utf-8") - ) + manifest = json.loads((suite.suite_dir / "manifest.json").read_text(encoding="utf-8")) assert [task["analysis"] for task in manifest["tasks"]] == [ {"status": "completed"}, {"error": "analysis failed", "status": "failed"}, @@ -276,20 +212,14 @@ def test_insight_suite_records_analysis_without_removing_failed_tasks( def test_finalized_suite_persists_identities_in_the_local_suite(tmp_path: Path) -> None: template = _write_template(tmp_path / "template") refs = ["trace-1"] - suite = InsightSuite( - experiment_dir=tmp_path, insight_id="insight-1", task_template=template - ) + suite = InsightSuite(experiment_dir=tmp_path, insight_id="insight-1", task_template=template) staged = suite.stage(refs) - (staged[0].path / "instruction.md").write_text( - "Reproduce the motivating failure.\n" - ) + (staged[0].path / "instruction.md").write_text("Reproduce the motivating failure.\n") suite.validate(staged[0]) suite.promote_local(refs, staged) finalized = suite.finalize() - manifest = json.loads( - (finalized.path / "manifest.json").read_text(encoding="utf-8") - ) + manifest = json.loads((finalized.path / "manifest.json").read_text(encoding="utf-8")) assert finalized.identity.startswith("sha256:") assert finalized.scorer_identity.startswith("sha256:") @@ -307,14 +237,9 @@ def test_finalized_suite_persists_identities_in_the_local_suite(tmp_path: Path) assert not (suite.root / "artifacts").exists() assert finalized.dataset.metadata["insight_suite_identity"] == finalized.identity assert "insight_suite_artifact_ref" not in finalized.dataset.metadata - assert list(finalized.dataset.list_tasks())[0].uri.startswith( - finalized.path.as_uri() - ) + assert list(finalized.dataset.list_tasks())[0].uri.startswith(finalized.path.as_uri()) - instruction = ( - next(path for path in finalized.path.iterdir() if path.is_dir()) - / "instruction.md" - ) + instruction = next(path for path in finalized.path.iterdir() if path.is_dir()) / "instruction.md" instruction.write_text("changed after finalization\n") changed = suite.finalize() assert changed.identity != finalized.identity @@ -344,17 +269,10 @@ def build(instruction: str, verifier_suffix: str = "") -> tuple[str, str]: first_identity, first_scorer_identity = build("Same authored task.\n") identical_identity, identical_scorer_identity = build("Same authored task.\n") changed_task_identity, _ = build("Changed authored task.\n") - changed_verifier_identity, changed_scorer_identity = build( - "Same authored task.\n", "\n# changed scorer\n" - ) + changed_verifier_identity, changed_scorer_identity = build("Same authored task.\n", "\n# changed scorer\n") - assert (identical_identity, identical_scorer_identity) == ( - first_identity, - first_scorer_identity, - ) + assert (identical_identity, identical_scorer_identity) == (first_identity, first_scorer_identity) assert changed_task_identity != first_identity assert changed_verifier_identity != first_identity assert changed_scorer_identity != first_scorer_identity - assert ( - list((tmp_path / "eval-and-optimize" / "eval_author").glob("*/artifacts")) == [] - ) + assert list((tmp_path / "eval-and-optimize" / "eval_author").glob("*/artifacts")) == [] diff --git a/plugins/nemo-experimentalist/tests/experimentalist/test_eval_author_repair_e2e.py b/plugins/nemo-eval-author/tests/test_eval_author_repair_e2e.py similarity index 89% rename from plugins/nemo-experimentalist/tests/experimentalist/test_eval_author_repair_e2e.py rename to plugins/nemo-eval-author/tests/test_eval_author_repair_e2e.py index 817a1cf70cb..3dfbf4cfb08 100644 --- a/plugins/nemo-experimentalist/tests/experimentalist/test_eval_author_repair_e2e.py +++ b/plugins/nemo-eval-author/tests/test_eval_author_repair_e2e.py @@ -11,6 +11,7 @@ import pytest from nemo_eval_author_plugin.eval_author.agent import EvalAuthor from nemo_eval_author_plugin.eval_author.models import EvalAuthorConfig +from nemo_eval_author_plugin.model_config import get_fast_model from nemo_experimentalist_plugin.experimentalist.components.evaluator import ( DatasetValidationError, local_path_from_uri, @@ -20,13 +21,24 @@ HarborEvaluator, HarborEvaluatorConfig, ) -from nemo_experimentalist_plugin.experimentalist.components.model_config import get_fast_model from nemo_experimentalist_plugin.experimentalist.components.trace_analyzer import Diagnostic from nemo_insights_plugin.entities import Insight -_HAS_EXPERIMENTALIST_LLM = bool( - os.environ.get("EXPERIMENTALIST_API_BASE") and os.environ.get("EXPERIMENTALIST_API_KEY") -) + +def _has_real_llm_credentials() -> bool: + """Report whether real credentials are present, rather than the conftest stand-ins. + + conftest fills placeholder credentials so agent classes can be imported at collection + time, which means a plain "is it set?" check always passes. These canaries call a live + model, so they have to look past the stand-ins. + """ + base = os.environ.get("AUTHOR_API_BASE") or os.environ.get("EXPERIMENTALIST_API_BASE") or "" + key = os.environ.get("AUTHOR_API_KEY") or os.environ.get("EXPERIMENTALIST_API_KEY") or "" + return bool(base and key) and "placeholder" not in base and "placeholder" not in key + + +_HAS_LLM = _has_real_llm_credentials() +_CREDENTIALS_HINT = "AUTHOR_API_BASE and AUTHOR_API_KEY (or their EXPERIMENTALIST_* equivalents)" _RUN_EVAL_AUTHOR_REPAIR_E2E = os.environ.get("RUN_EVAL_AUTHOR_REPAIR_E2E") == "1" _RUN_EVAL_AUTHOR_HARBOR_E2E = os.environ.get("RUN_EVAL_AUTHOR_HARBOR_E2E") == "1" _MALFORMED_VERIFIER = """\ @@ -191,10 +203,8 @@ async def run( @pytest.mark.skipif( - not (_RUN_EVAL_AUTHOR_REPAIR_E2E and _HAS_EXPERIMENTALIST_LLM), - reason=( - "Set RUN_EVAL_AUTHOR_REPAIR_E2E=1 with EXPERIMENTALIST_API_BASE and EXPERIMENTALIST_API_KEY to run the Eval Author repair canary." - ), + not (_RUN_EVAL_AUTHOR_REPAIR_E2E and _HAS_LLM), + reason=f"Set RUN_EVAL_AUTHOR_REPAIR_E2E=1 with {_CREDENTIALS_HINT} to run the Eval Author repair canary.", ) async def test_gpt5_mini_repairs_malformed_harbor_verifiers( tmp_path: Path, @@ -215,7 +225,9 @@ async def test_gpt5_mini_repairs_malformed_harbor_verifiers( assert "SyntaxError: expected 'except' or 'finally' block" in validation_feedback llm = get_fast_model() - monkeypatch.delenv("EXPERIMENTALIST_API_KEY") + # Construction must not need EXPERIMENTALIST_API_KEY: Eval Author owns AUTHOR_* and only + # bridges into the Experimentalist slots, which are already resolved by this point. + monkeypatch.delenv("EXPERIMENTALIST_API_KEY", raising=False) eval_author = EvalAuthor( experiment_dir=tmp_path, config=EvalAuthorConfig(), @@ -259,11 +271,8 @@ async def test_gpt5_mini_repairs_malformed_harbor_verifiers( @pytest.mark.skipif( - not (_RUN_EVAL_AUTHOR_HARBOR_E2E and _HAS_EXPERIMENTALIST_LLM), - reason=( - "Set RUN_EVAL_AUTHOR_HARBOR_E2E=1 with EXPERIMENTALIST_API_BASE and EXPERIMENTALIST_API_KEY " - "to run the live Harbor metric canary." - ), + not (_RUN_EVAL_AUTHOR_HARBOR_E2E and _HAS_LLM), + reason=f"Set RUN_EVAL_AUTHOR_HARBOR_E2E=1 with {_CREDENTIALS_HINT} to run the live Harbor metric canary.", ) async def test_eval_author_metric_scores_known_failing_harbor_baseline_low( tmp_path: Path, @@ -279,7 +288,8 @@ async def test_eval_author_metric_scores_known_failing_harbor_baseline_low( llm = get_fast_model() llm.config["temperature"] = 0.0 - monkeypatch.delenv("EXPERIMENTALIST_API_KEY") + # See the repair canary above: construction must not need EXPERIMENTALIST_API_KEY. + monkeypatch.delenv("EXPERIMENTALIST_API_KEY", raising=False) eval_author = EvalAuthor( experiment_dir=tmp_path, config=EvalAuthorConfig(), diff --git a/plugins/nemo-eval-author/tests/test_eval_author_run.py b/plugins/nemo-eval-author/tests/test_eval_author_run.py index 29f54192397..6b31bf9be52 100644 --- a/plugins/nemo-eval-author/tests/test_eval_author_run.py +++ b/plugins/nemo-eval-author/tests/test_eval_author_run.py @@ -7,15 +7,8 @@ import pytest from nemo_eval_author_plugin.eval_author import run as eval_author_run -from nemo_eval_author_plugin.eval_author.models import ( - EvalAuthorConfig, - EvalAuthorResult, -) -from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import ( - Dataset, - DatasetRef, - Task, -) +from nemo_eval_author_plugin.eval_author.models import EvalAuthorConfig, EvalAuthorResult +from nemo_experimentalist_plugin.experimentalist.components.evaluator.models import Dataset, DatasetRef, Task from nemo_insights_plugin.entities import Insight @@ -68,12 +61,8 @@ async def get_insight(self, *, workspace: str, insight_id: str) -> Insight: self.insight_calls.append({"workspace": workspace, "insight_id": insight_id}) return self.insight - async def get_agent_code( - self, *, workspace: str, agent: str | Path, dest: Path - ) -> None: - self.agent_code_calls.append( - AgentCodeCall(workspace=workspace, agent=agent, dest=dest) - ) + async def get_agent_code(self, *, workspace: str, agent: str | Path, dest: Path) -> None: + self.agent_code_calls.append(AgentCodeCall(workspace=workspace, agent=agent, dest=dest)) class FakeDatasetFactory: @@ -90,9 +79,7 @@ def build_dataset(self, evaluator_type: str, dataset_ref: DatasetRef) -> Dataset return self.validation return self.train - def build_task_template( - self, evaluator_type: str, template_ref: DatasetRef - ) -> Task: + def build_task_template(self, evaluator_type: str, template_ref: DatasetRef) -> Task: self.template_refs.append((evaluator_type, template_ref)) return self.template @@ -152,38 +139,22 @@ def make_backend( experiments_output: str, mode: Literal["local", "remote"], ) -> FakeBackend: - backend_calls.append( - BackendFactoryCall( - client=client, experiments_output=experiments_output, mode=mode - ) - ) + backend_calls.append(BackendFactoryCall(client=client, experiments_output=experiments_output, mode=mode)) return backend - def build_eval_author_agent( - *, experiment_dir: Path, config: EvalAuthorConfig - ) -> FakeEvalAuthor: - eval_author_calls.append( - EvalAuthorFactoryCall(experiment_dir=experiment_dir, config=config) - ) + def build_eval_author_agent(*, experiment_dir: Path, config: EvalAuthorConfig) -> FakeEvalAuthor: + eval_author_calls.append(EvalAuthorFactoryCall(experiment_dir=experiment_dir, config=config)) return eval_author monkeypatch.setattr(eval_author_run, "make_client", lambda base_url: client) monkeypatch.setattr(eval_author_run, "make_experimentalist_backend", make_backend) monkeypatch.setattr(eval_author_run, "DatasetFactory", lambda: dataset_factory) - monkeypatch.setattr( - eval_author_run, "build_eval_author_agent", build_eval_author_agent - ) - monkeypatch.setattr( - eval_author_run, - "_enable_litellm_drop_params", - lambda: litellm_calls.append(True), - ) + monkeypatch.setattr(eval_author_run, "build_eval_author_agent", build_eval_author_agent) + monkeypatch.setattr(eval_author_run, "_enable_litellm_drop_params", lambda: litellm_calls.append(True)) config = EvalAuthorConfig(max_traces=2) train_ref = DatasetRef(uri=str(tmp_path / "train"), metadata={"id": "train"}) - validation_ref = DatasetRef( - uri=str(tmp_path / "validation"), metadata={"id": "validation"} - ) + validation_ref = DatasetRef(uri=str(tmp_path / "validation"), metadata={"id": "validation"}) template_path = tmp_path / "template" template_path.mkdir() template_ref = DatasetRef(uri=str(template_path), metadata={"id": "task-template"}) @@ -206,13 +177,9 @@ def build_eval_author_agent( assert result.validation_dataset is dataset_factory.validation assert litellm_calls == [True] assert backend_calls == [ - BackendFactoryCall( - client=client, experiments_output=str(experiment_dir), mode="local" - ), - ] - assert backend.insight_calls == [ - {"workspace": "workspace-a", "insight_id": "insight-remote-123"} + BackendFactoryCall(client=client, experiments_output=str(experiment_dir), mode="local"), ] + assert backend.insight_calls == [{"workspace": "workspace-a", "insight_id": "insight-remote-123"}] assert backend.agent_code_calls == [ AgentCodeCall( workspace="workspace-a", @@ -220,21 +187,14 @@ def build_eval_author_agent( dest=experiment_dir / "eval_author" / "source-agent", ) ] - assert dataset_factory.dataset_refs == [ - ("harbor", train_ref), - ("harbor", validation_ref), - ] + assert dataset_factory.dataset_refs == [("harbor", train_ref), ("harbor", validation_ref)] assert dataset_factory.template_refs == [ ( "harbor", - template_ref.model_copy( - update={"uri": str(experiment_dir / "dataset" / "task-template")} - ), + template_ref.model_copy(update={"uri": str(experiment_dir / "dataset" / "task-template")}), ) ] - assert eval_author_calls == [ - EvalAuthorFactoryCall(experiment_dir=experiment_dir, config=config) - ] + assert eval_author_calls == [EvalAuthorFactoryCall(experiment_dir=experiment_dir, config=config)] assert eval_author.call == EvalAuthorCall( insight=insight, agent_path=experiment_dir / "eval_author" / "source-agent", @@ -254,44 +214,25 @@ async def test_run_eval_author_hydrates_fileset_task_template( download_calls: list[dict[str, str]] = [] class FakeFiles: - async def download( - self, *, remote_path: str, local_path: str, workspace: str - ) -> None: - download_calls.append( - { - "remote_path": remote_path, - "local_path": local_path, - "workspace": workspace, - } - ) + async def download(self, *, remote_path: str, local_path: str, workspace: str) -> None: + download_calls.append({"remote_path": remote_path, "local_path": local_path, "workspace": workspace}) destination = Path(local_path) destination.mkdir(parents=True) (destination / "task.toml").write_text("template", encoding="utf-8") client = ClosingClient(files=FakeFiles()) - insight = Insight( - workspace="workspace-a", - title="failure", - description="description", - agent="insight-agent", - ) + insight = Insight(workspace="workspace-a", title="failure", description="description", agent="insight-agent") backend = FakeBackend(insight) dataset_factory = FakeDatasetFactory() eval_author = FakeEvalAuthor() monkeypatch.setattr(eval_author_run, "make_client", lambda base_url: client) - monkeypatch.setattr( - eval_author_run, "make_experimentalist_backend", lambda **_: backend - ) + monkeypatch.setattr(eval_author_run, "make_experimentalist_backend", lambda **_: backend) monkeypatch.setattr(eval_author_run, "DatasetFactory", lambda: dataset_factory) - monkeypatch.setattr( - eval_author_run, "build_eval_author_agent", lambda **_: eval_author - ) + monkeypatch.setattr(eval_author_run, "build_eval_author_agent", lambda **_: eval_author) monkeypatch.setattr(eval_author_run, "_enable_litellm_drop_params", lambda: None) - template_ref = DatasetRef( - uri="fileset://workspace-a/task-template", metadata={"id": "task-template"} - ) + template_ref = DatasetRef(uri="fileset://workspace-a/task-template", metadata={"id": "task-template"}) experiment_dir = (tmp_path / "eval_author").resolve() await eval_author_run.run_eval_author( insight="insight-remote-123", @@ -312,9 +253,7 @@ async def download( "workspace": "workspace-a", } ] - assert dataset_factory.template_refs == [ - ("harbor", template_ref.model_copy(update={"uri": str(staged_path)})) - ] + assert dataset_factory.template_refs == [("harbor", template_ref.model_copy(update={"uri": str(staged_path)}))] assert client.closed @@ -324,24 +263,15 @@ async def test_run_eval_author_uses_agent_override( tmp_path: Path, ) -> None: client = ClosingClient() - insight = Insight( - workspace="workspace-a", - title="failure", - description="description", - agent="insight-agent", - ) + insight = Insight(workspace="workspace-a", title="failure", description="description", agent="insight-agent") backend = FakeBackend(insight) dataset_factory = FakeDatasetFactory() eval_author = FakeEvalAuthor() monkeypatch.setattr(eval_author_run, "make_client", lambda base_url: client) - monkeypatch.setattr( - eval_author_run, "make_experimentalist_backend", lambda **_: backend - ) + monkeypatch.setattr(eval_author_run, "make_experimentalist_backend", lambda **_: backend) monkeypatch.setattr(eval_author_run, "DatasetFactory", lambda: dataset_factory) - monkeypatch.setattr( - eval_author_run, "build_eval_author_agent", lambda **_: eval_author - ) + monkeypatch.setattr(eval_author_run, "build_eval_author_agent", lambda **_: eval_author) monkeypatch.setattr(eval_author_run, "_enable_litellm_drop_params", lambda: None) override = tmp_path / "override-agent" @@ -374,9 +304,7 @@ def fail_backend_creation(**_: object) -> object: raise RuntimeError("backend creation failed") monkeypatch.setattr(eval_author_run, "make_client", lambda base_url: client) - monkeypatch.setattr( - eval_author_run, "make_experimentalist_backend", fail_backend_creation - ) + monkeypatch.setattr(eval_author_run, "make_experimentalist_backend", fail_backend_creation) with pytest.raises(RuntimeError, match="backend creation failed"): await eval_author_run.run_eval_author( diff --git a/plugins/nemo-eval-author/tests/test_model_config.py b/plugins/nemo-eval-author/tests/test_model_config.py index 1b573c0fd51..625f319a920 100644 --- a/plugins/nemo-eval-author/tests/test_model_config.py +++ b/plugins/nemo-eval-author/tests/test_model_config.py @@ -1,15 +1,29 @@ # SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 +"""Credential and model-client resolution for Eval Author.""" + +import os +import subprocess +import sys +from collections.abc import Iterator + import pytest from nemo_eval_author_plugin import model_config @pytest.fixture(autouse=True) -def _clear_model_cache() -> None: - model_config.get_smart_model.cache_clear() - model_config.get_mid_model.cache_clear() - model_config.get_fast_model.cache_clear() +def _clear_model_cache() -> Iterator[None]: + """Drop every cached client so each test resolves credentials from scratch.""" + + def clear() -> None: + model_config.get_smart_model.cache_clear() + model_config.get_fast_model.cache_clear() + model_config._completion_client.cache_clear() + + clear() + yield + clear() def test_api_base_prefers_eval_author_over_experimentalist( @@ -32,6 +46,21 @@ def test_api_base_falls_back_to_experimentalist( assert model_config._api_base() == "https://experimentalist.example/v1" +def test_api_base_requires_configuration(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.delenv("AUTHOR_API_BASE", raising=False) + monkeypatch.delenv("EXPERIMENTALIST_API_BASE", raising=False) + + with pytest.raises(ValueError, match="AUTHOR_API_BASE"): + model_config._api_base() + + +def test_whitespace_only_values_count_as_unset(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setenv("AUTHOR_API_BASE", " ") + monkeypatch.setenv("EXPERIMENTALIST_API_BASE", " https://experimentalist.example/v1 ") + + assert model_config._api_base() == "https://experimentalist.example/v1" + + def test_api_key_accepts_inference_key_on_gateway( monkeypatch: pytest.MonkeyPatch, ) -> None: @@ -43,6 +72,30 @@ def test_api_key_accepts_inference_key_on_gateway( assert model_config._api_key() == "sk-gateway" +def test_api_key_refuses_to_forward_inference_key_over_plain_http( + monkeypatch: pytest.MonkeyPatch, +) -> None: + monkeypatch.setenv("AUTHOR_API_BASE", "http://inference-api.nvidia.com/v1") + monkeypatch.delenv("AUTHOR_API_KEY", raising=False) + monkeypatch.delenv("EXPERIMENTALIST_API_KEY", raising=False) + monkeypatch.setenv("INFERENCE_API_KEY", "sk-gateway") + + with pytest.raises(ValueError, match="AUTHOR_API_KEY"): + model_config._api_key() + + +def test_api_key_refuses_to_forward_inference_key_to_a_lookalike_host( + monkeypatch: pytest.MonkeyPatch, +) -> None: + monkeypatch.setenv("AUTHOR_API_BASE", "https://inference-api.nvidia.com.attacker.example/v1") + monkeypatch.delenv("AUTHOR_API_KEY", raising=False) + monkeypatch.delenv("EXPERIMENTALIST_API_KEY", raising=False) + monkeypatch.setenv("INFERENCE_API_KEY", "sk-gateway") + + with pytest.raises(ValueError, match="AUTHOR_API_KEY"): + model_config._api_key() + + def test_api_key_requires_credentials_when_missing( monkeypatch: pytest.MonkeyPatch, ) -> None: @@ -55,6 +108,52 @@ def test_api_key_requires_credentials_when_missing( model_config._api_key() +@pytest.mark.parametrize("tier", ["SMART", "FAST"]) +def test_model_name_prefers_author_then_experimentalist_then_default( + monkeypatch: pytest.MonkeyPatch, + tier: str, +) -> None: + getter = {"SMART": model_config.get_smart_model, "FAST": model_config.get_fast_model}[tier] + default = {"SMART": model_config._SMART_MODEL_DEFAULT, "FAST": model_config._FAST_MODEL_DEFAULT}[tier] + author_var = f"AUTHOR_{tier}_MODEL_NAME" + experimentalist_var = f"EXPERIMENTALIST_{tier}_MODEL_NAME" + + monkeypatch.setenv("AUTHOR_API_BASE", "https://eval-author.example/v1") + monkeypatch.setenv("AUTHOR_API_KEY", "eval-key") + + monkeypatch.setenv(author_var, "vendor/from-author") + monkeypatch.setenv(experimentalist_var, "vendor/from-experimentalist") + assert getter().model == "vendor/from-author" + + getter.cache_clear() + monkeypatch.delenv(author_var) + assert getter().model == "vendor/from-experimentalist" + + getter.cache_clear() + monkeypatch.delenv(experimentalist_var) + assert getter().model == default + + +def test_tiers_pointing_at_one_model_share_a_client(monkeypatch: pytest.MonkeyPatch) -> None: + # The @functools.cache on each getter only promises one client per tier. Caching the + # factory on the resolved triple is what collapses tiers that resolve identically. + monkeypatch.setenv("AUTHOR_API_BASE", "https://eval-author.example/v1") + monkeypatch.setenv("AUTHOR_API_KEY", "eval-key") + monkeypatch.setenv("AUTHOR_SMART_MODEL_NAME", "vendor/one-model") + monkeypatch.setenv("AUTHOR_FAST_MODEL_NAME", "vendor/one-model") + + assert model_config.get_smart_model() is model_config.get_fast_model() + + +def test_differing_tiers_get_separate_clients(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setenv("AUTHOR_API_BASE", "https://eval-author.example/v1") + monkeypatch.setenv("AUTHOR_API_KEY", "eval-key") + monkeypatch.setenv("AUTHOR_SMART_MODEL_NAME", "vendor/smart") + monkeypatch.setenv("AUTHOR_FAST_MODEL_NAME", "vendor/fast") + + assert model_config.get_smart_model() is not model_config.get_fast_model() + + def test_bridge_author_env_fills_unset_experimentalist_slots( monkeypatch: pytest.MonkeyPatch, ) -> None: @@ -63,13 +162,25 @@ def test_bridge_author_env_fills_unset_experimentalist_slots( monkeypatch.delenv("EXPERIMENTALIST_API_BASE", raising=False) monkeypatch.delenv("EXPERIMENTALIST_API_KEY", raising=False) - model_config.bridge_author_env_to_experimentalist() + applied = model_config.bridge_author_env_to_experimentalist() - assert ( - model_config.os.environ["EXPERIMENTALIST_API_BASE"] - == "https://eval-author.example/v1" - ) - assert model_config.os.environ["EXPERIMENTALIST_API_KEY"] == "eval-key" + assert applied == ["EXPERIMENTALIST_API_BASE", "EXPERIMENTALIST_API_KEY"] + assert os.environ["EXPERIMENTALIST_API_BASE"] == "https://eval-author.example/v1" + assert os.environ["EXPERIMENTALIST_API_KEY"] == "eval-key" + + +def test_bridge_author_env_covers_every_declared_pair( + monkeypatch: pytest.MonkeyPatch, +) -> None: + for src, dst in model_config._BRIDGED_ENV_PAIRS: + monkeypatch.setenv(src, f"value-from-{src}") + monkeypatch.delenv(dst, raising=False) + + applied = model_config.bridge_author_env_to_experimentalist() + + assert applied == [dst for _, dst in model_config._BRIDGED_ENV_PAIRS] + for src, dst in model_config._BRIDGED_ENV_PAIRS: + assert os.environ[dst] == f"value-from-{src}" def test_bridge_author_env_does_not_overwrite_existing_experimentalist( @@ -78,9 +189,45 @@ def test_bridge_author_env_does_not_overwrite_existing_experimentalist( monkeypatch.setenv("AUTHOR_API_BASE", "https://eval-author.example/v1") monkeypatch.setenv("EXPERIMENTALIST_API_BASE", "https://experimentalist.example/v1") - model_config.bridge_author_env_to_experimentalist() + applied = model_config.bridge_author_env_to_experimentalist() - assert ( - model_config.os.environ["EXPERIMENTALIST_API_BASE"] - == "https://experimentalist.example/v1" + assert applied == [] + assert os.environ["EXPERIMENTALIST_API_BASE"] == "https://experimentalist.example/v1" + + +def test_bridge_author_env_is_a_no_op_once_applied( + monkeypatch: pytest.MonkeyPatch, +) -> None: + monkeypatch.setenv("AUTHOR_API_BASE", "https://eval-author.example/v1") + monkeypatch.delenv("EXPERIMENTALIST_API_BASE", raising=False) + + assert model_config.bridge_author_env_to_experimentalist() == ["EXPERIMENTALIST_API_BASE"] + assert model_config.bridge_author_env_to_experimentalist() == [] + + +def test_agent_module_imports_under_author_credentials_alone() -> None: + """Guard the import ordering that lets an AUTHOR_*-only run build Experimentalist agents. + + ``TraceAnalyzer`` and friends read ``EXPERIMENTALIST_*`` while their class body runs, so + ``agent.py`` has to import ``_env_bridge`` before importing them. That ordering cannot be + checked in-process: this test suite's conftest already populates ``EXPERIMENTALIST_*``, + and the modules are imported once per session. Hence the subprocess with a pruned + environment -- it fails with ``ValueError`` if the bridge stops running early enough. + """ + env = { + key: value + for key, value in os.environ.items() + if not key.startswith(("AUTHOR_", "EXPERIMENTALIST_", "INFERENCE_API_KEY")) + } + env["AUTHOR_API_BASE"] = "https://placeholder.invalid" + env["AUTHOR_API_KEY"] = "placeholder-for-import" + + result = subprocess.run( + [sys.executable, "-c", "import nemo_eval_author_plugin.eval_author.agent"], + env=env, + capture_output=True, + text=True, + check=False, ) + + assert result.returncode == 0, result.stderr diff --git a/plugins/nemo-eval-author/tests/test_plugin_boundary.py b/plugins/nemo-eval-author/tests/test_plugin_boundary.py new file mode 100644 index 00000000000..4c851ce06ae --- /dev/null +++ b/plugins/nemo-eval-author/tests/test_plugin_boundary.py @@ -0,0 +1,100 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Ratchet on Eval Author's remaining dependency on Experimentalist. + +Eval Author is meant to end up standalone, with Experimentalist depending on it and not the +other way around. Until then this test pins the modules Eval Author still reaches for, so +the coupling can only shrink. Prefer duplicating a helper over adding a row here. + +Emptying the allowlist is what unblocks the TODO(eval-author-standalone) cleanups: the +``EXPERIMENTALIST_*`` credential fallback and the ``_env_bridge`` side-effect module both +exist only to serve the imports listed below. +""" + +import ast +from pathlib import Path + +_SRC = Path(__file__).resolve().parents[1] / "src" / "nemo_eval_author_plugin" + +# Each entry is the module named in the import statement, so `from ...components import +# cache` is recorded as `...components`. Each one is debt: deleting a row is the goal, and +# adding a row needs a deliberate argument for why duplicating the helper is worse. What +# Eval Author still borrows, by row: +# +# client -> make_client, the platform client factory +# ...components -> the cache module, for run artifacts +# ...dataset_staging -> stage_task_template +# ...evaluator -> Dataset / Task / TrialResult / DatasetValidationError +# ...evaluator.base -> EvaluatorType +# ...evaluator.factory-> DatasetFactory +# ...evaluator.harbor -> HarborDataset +# ...evaluator.models -> DatasetRef, ResourceRef, Task, local_path_from_uri +# ...tools -> GuardedShellTools +# ...trace_analyzer -> TraceAnalyzer, TraceAnalyzerConfig, Diagnostic +# ...trace_explorer -> TraceExplorer and its view models +# experimentalist_backend -> make_experimentalist_backend +_ALLOWED_EXPERIMENTALIST_IMPORTS = { + "nemo_experimentalist_plugin.client", + "nemo_experimentalist_plugin.experimentalist.components", + "nemo_experimentalist_plugin.experimentalist.components.dataset_staging", + "nemo_experimentalist_plugin.experimentalist.components.evaluator", + "nemo_experimentalist_plugin.experimentalist.components.evaluator.base", + "nemo_experimentalist_plugin.experimentalist.components.evaluator.factory", + "nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor", + "nemo_experimentalist_plugin.experimentalist.components.evaluator.models", + "nemo_experimentalist_plugin.experimentalist.components.tools", + "nemo_experimentalist_plugin.experimentalist.components.trace_analyzer", + "nemo_experimentalist_plugin.experimentalist.components.trace_explorer", + "nemo_experimentalist_plugin.experimentalist.experimentalist_backend", +} + + +def _imported_experimentalist_modules() -> dict[str, set[str]]: + """Map each Experimentalist module imported under ``src`` to the files importing it.""" + found: dict[str, set[str]] = {} + for path in sorted(_SRC.rglob("*.py")): + tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path)) + for node in ast.walk(tree): + if isinstance(node, ast.Import): + names = [alias.name for alias in node.names] + elif isinstance(node, ast.ImportFrom): + # level > 0 is a relative import, which cannot reach another distribution. + names = [node.module] if node.module and not node.level else [] + else: + continue + for name in names: + if name.split(".")[0] == "nemo_experimentalist_plugin": + found.setdefault(name, set()).add(path.name) + return found + + +def test_experimentalist_imports_only_shrink() -> None: + found = _imported_experimentalist_modules() + + added = set(found) - _ALLOWED_EXPERIMENTALIST_IMPORTS + assert not added, ( + "New Experimentalist imports in the Eval Author plugin: " + + ", ".join(f"{name} (in {', '.join(sorted(found[name]))})" for name in sorted(added)) + + ". Eval Author is moving toward standalone, so duplicate the helper instead of " + "importing it. If the import is genuinely unavoidable, add it to " + "_ALLOWED_EXPERIMENTALIST_IMPORTS with a note explaining why." + ) + + removed = _ALLOWED_EXPERIMENTALIST_IMPORTS - set(found) + assert not removed, ( + "These Experimentalist imports are gone, which is the point: " + + ", ".join(sorted(removed)) + + ". Drop them from _ALLOWED_EXPERIMENTALIST_IMPORTS so the list keeps meaning something." + ) + + +def test_model_config_is_already_standalone() -> None: + """Credential resolution is the one part of the plugin that owes Experimentalist nothing. + + It was briefly refactored to share Experimentalist's client cache, which read as a + cleanup but moved the plugin away from standalone. Keep it independent. + """ + importers = {file for files in _imported_experimentalist_modules().values() for file in files} + + assert "model_config.py" not in importers diff --git a/plugins/nemo-experimentalist/AGENTS.md b/plugins/nemo-experimentalist/AGENTS.md index b1e47acfc20..f3a5d43939a 100644 --- a/plugins/nemo-experimentalist/AGENTS.md +++ b/plugins/nemo-experimentalist/AGENTS.md @@ -13,6 +13,41 @@ Inherited from the NeMo Platform monorepo that now hosts this plugin: ## Active migrations +### 2026-07-28: Eval Author extracted to its own plugin, heading for standalone + +`plugins/nemo-eval-author/` (`nemo-eval-author-plugin`) owns the Eval Author agent package +(`eval_author/`) plus its own `AUTHOR_*` `model_config`. + +**The target is one arrow: Experimentalist → Eval Author.** Eval Author is meant to stop +depending on Experimentalist entirely, even where that means duplicating code. Today the +arrow points both ways: + +- Experimentalist → Eval Author is permanent. Insight mode imports `EvalAuthor` in + `components/loop.py` and `EvalAuthorConfig` in `resolve.py`, both at module scope, so + the dependency is declared in `pyproject.toml`. +- Eval Author → Experimentalist is temporary. It still borrows the evaluator/Harbor + abstractions, dataset staging, trace analyzer/explorer, `GuardedShellTools`, the cache, + and the backend factory. + +`plugins/nemo-eval-author/tests/test_plugin_boundary.py` pins that second list so it can +only shrink. **When you are tempted to share a helper between the two plugins, duplicate it +into Eval Author instead.** Sharing reads like a cleanup and is a regression here; the +boundary test will reject it, which is the intended answer, not an obstacle to route +around. `uv` resolves the current cycle fine — install both with +`uv sync --group experimentalist`. + +Two transitional mechanisms exist only because of the second arrow. Both should be deleted +with the last `nemo_experimentalist_plugin` import, and both are tagged +`TODO(eval-author-standalone)` — `rg 'eval-author-standalone'` lists every site: + +- `nemo_eval_author_plugin/_env_bridge.py` copies `AUTHOR_*` into unset `EXPERIMENTALIST_*` + slots and is imported for that side effect. `eval_author/agent.py` imports it ahead of + every Experimentalist agent, because those agents build their LLM in the class body and + so read the environment at import time. isort keeps a plain `import` ahead of `from` + imports in the same section, so do not convert it to a `from` import. +- `AUTHOR_*` falls back to `EXPERIMENTALIST_*` in `model_config`. `AUTHOR_*` is the real + contract; the fallback only keeps one credential set working in insight mode. + ### 2026-07-24: Optimizer renamed to Experimentalist Ahead of the move into the `nemo-platform` monorepo, the plugin was renamed from @@ -45,19 +80,6 @@ eventual `nemo agents experimentalist`. The platform's `nemo.cli` entry-point group is flat — only `nemo.jobs` and `nemo.functions` are dot-scoped — so nesting under `nemo agents` needs a Platform-side change first. -### 2026-07-28: Eval Author plugin depends on Experimentalist modules - -`plugins/nemo-eval-author/` (`nemo-eval-author-plugin`) owns only the Eval Author -agent package (`eval_author/`) plus a thin `AUTHOR_*` `model_config`. Harbor -evaluator, dataset staging, and trace helpers stay in Experimentalist -(`experimentalist/components/`). Dependency direction: - -- Eval Author hard-depends on Experimentalist and imports shared modules from it -- Experimentalist does **not** declare a reverse package dep (avoids a cycle); - insight mode still imports `EvalAuthor` when both packages are installed via - `uv sync --group experimentalist` -- do not re-copy evaluator/trace/staging into the Eval Author plugin - ### 2026-07-21: Curator renamed to Eval Author The Curator agent was renamed directly to Eval Author in ASE-643. This is a diff --git a/plugins/nemo-experimentalist/pyproject.toml b/plugins/nemo-experimentalist/pyproject.toml index e7395b915a3..95335314704 100644 --- a/plugins/nemo-experimentalist/pyproject.toml +++ b/plugins/nemo-experimentalist/pyproject.toml @@ -15,6 +15,7 @@ dependencies = [ "protobuf>=6.0.0", "nooa ; python_full_version >= '3.12'", "pyyaml>=6.0.3", + "nemo-eval-author-plugin ; python_full_version >= '3.12'", "nemo-insights-plugin", "nemo-platform", "nemo-platform-plugin", diff --git a/pyproject.toml b/pyproject.toml index 686ed71d035..97a222395ba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,9 +72,11 @@ insights = ["nemo-insights-plugin"] # Optional Experimentalist agent-optimization plugin. Its agent framework (nooa) and # evaluator (harbor) are both 3.12-only, so the whole plugin is gated rather than # installed half-resolved on 3.11. -# Install Experimentalist and Eval Author together. Eval Author hard-depends on -# Experimentalist for evaluator/trace helpers; Experimentalist imports Eval Author -# at runtime for insight mode (no reverse package dep, to avoid a cycle). +# Install Experimentalist and Eval Author together. Experimentalist depends on Eval Author +# because insight mode imports it at module scope; Eval Author still depends back on +# Experimentalist for evaluator/trace helpers, but that arrow is being removed (see +# plugins/nemo-eval-author/README.md). Both are listed here so the 3.12 marker is applied +# consistently, not because the current cycle needs help resolving. experimentalist = [ "nemo-experimentalist-plugin ; python_full_version >= '3.12'", "nemo-eval-author-plugin ; python_full_version >= '3.12'", diff --git a/uv.lock b/uv.lock index f9a373b203f..818fea5e7d6 100644 --- a/uv.lock +++ b/uv.lock @@ -4294,7 +4294,6 @@ dependencies = [ { name = "nemo-platform", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nooa", marker = "(python_full_version >= '3.12' and platform_machine == 'arm64' and sys_platform == 'darwin') or (python_full_version >= '3.12' and platform_machine == 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.12' and platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "pydantic", extra = ["email"], marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, - { name = "pyyaml", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "tomlkit", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, ] @@ -4306,7 +4305,6 @@ requires-dist = [ { name = "nemo-platform", editable = "packages/nemo_platform" }, { name = "nooa", marker = "python_full_version >= '3.12'", git = "https://github.com/NVIDIA-NeMo/labs-OO-Agents.git?tag=v0.0.6" }, { name = "pydantic", specifier = ">=2" }, - { name = "pyyaml", specifier = ">=6.0.3" }, { name = "tomlkit", specifier = ">=0.13.3" }, ] @@ -4420,6 +4418,7 @@ source = { editable = "plugins/nemo-experimentalist" } dependencies = [ { name = "harbor", marker = "(python_full_version >= '3.12' and platform_machine == 'arm64' and sys_platform == 'darwin') or (python_full_version >= '3.12' and platform_machine == 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.12' and platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "httpx", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nemo-eval-author-plugin", marker = "(python_full_version >= '3.12' and platform_machine == 'arm64' and sys_platform == 'darwin') or (python_full_version >= '3.12' and platform_machine == 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.12' and platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-insights-plugin", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-platform", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-platform-plugin", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, @@ -4435,6 +4434,7 @@ dependencies = [ requires-dist = [ { name = "harbor", marker = "python_full_version >= '3.12'", specifier = ">=0.16" }, { name = "httpx" }, + { name = "nemo-eval-author-plugin", marker = "python_full_version >= '3.12'", editable = "plugins/nemo-eval-author" }, { name = "nemo-insights-plugin", editable = "plugins/nemo-insights" }, { name = "nemo-platform", editable = "packages/nemo_platform" }, { name = "nemo-platform-plugin", editable = "packages/nemo_platform_plugin" }, From 2fdd2149b56fc0599bd8414b4bc2007552c2219e Mon Sep 17 00:00:00 2001 From: Alec Khoury Date: Wed, 29 Jul 2026 13:31:56 -0500 Subject: [PATCH 6/6] docs(eval-author): name the client cache key instead of calling it a triple The _completion_client docstring called its arguments a "model triple", which made a reader decode the term before learning what the cache is keyed on. Name the three values, and say why the base URL and key are part of the key rather than leaving that implicit: reusing a client across endpoints or credentials would be a correctness bug, not just a missed optimization. Also record which alternative was rejected. The previous note asserted that the duplicate client Experimentalist holds "is the price of Eval Author not importing from it", which only reads as a reason if you already know an earlier revision shared one factory and was reverted to keep this module standalone. Signed-off-by: Alec Khoury --- .../nemo_eval_author_plugin/model_config.py | 18 +++++++++++++----- .../tests/test_model_config.py | 4 ++-- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/plugins/nemo-eval-author/src/nemo_eval_author_plugin/model_config.py b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/model_config.py index 6caca81e809..584a4bcd7fd 100644 --- a/plugins/nemo-eval-author/src/nemo_eval_author_plugin/model_config.py +++ b/plugins/nemo-eval-author/src/nemo_eval_author_plugin/model_config.py @@ -114,11 +114,19 @@ def _api_key() -> str: @functools.cache def _completion_client(name: str, api_base: str, api_key: str) -> CompletionClient: - """Return Eval Author's client for an already-resolved model triple. + """Return Eval Author's client for an already-resolved model name, base URL, and key. - Cached on the triple rather than per tier so that two tiers pointing at the same model - share a connection pool. Experimentalist caches its own clients separately; that - duplication is the price of Eval Author not importing from it. + Those three arguments are the cache key, so any tier resolving to the same model gets the + same client. The tier getters below are cached as well, but that alone only promises one + client per tier: two tiers configured with the same model would each build their own, and + every ``CompletionClient`` opens its own pool of keepalive connections. The base URL and + key belong in the key too, since the same model reached through a different endpoint, or + with different credentials, is a different client and must not be reused. + + Experimentalist caches its clients separately, so an insight-mode run holds two clients + for what is usually the same model. Sharing one factory across the plugins would fix + that, but only by importing Experimentalist into the one Eval Author module that has no + such import (see the module docstring). A duplicate connection pool is the cheaper cost. Args: name: Fully qualified model name, as ``CompletionClient`` expects it. @@ -126,7 +134,7 @@ def _completion_client(name: str, api_base: str, api_key: str) -> CompletionClie api_key: Resolved API key. Returns: - CompletionClient: the client for this triple. + CompletionClient: the client for this name, base URL, and key. """ return CompletionClient(name, api_base=api_base, api_key=api_key) diff --git a/plugins/nemo-eval-author/tests/test_model_config.py b/plugins/nemo-eval-author/tests/test_model_config.py index 625f319a920..55cfb96732e 100644 --- a/plugins/nemo-eval-author/tests/test_model_config.py +++ b/plugins/nemo-eval-author/tests/test_model_config.py @@ -135,8 +135,8 @@ def test_model_name_prefers_author_then_experimentalist_then_default( def test_tiers_pointing_at_one_model_share_a_client(monkeypatch: pytest.MonkeyPatch) -> None: - # The @functools.cache on each getter only promises one client per tier. Caching the - # factory on the resolved triple is what collapses tiers that resolve identically. + # The @functools.cache on each getter only promises one client per tier. Keying the + # factory on the resolved name, base URL, and key is what collapses identical tiers. monkeypatch.setenv("AUTHOR_API_BASE", "https://eval-author.example/v1") monkeypatch.setenv("AUTHOR_API_KEY", "eval-key") monkeypatch.setenv("AUTHOR_SMART_MODEL_NAME", "vendor/one-model")