diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..1c3a312 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,149 @@ +# agentflow-api (API server + CLI) — Engineering Guide + +This file documents the **API server and CLI package** only (`10xscale-agentflow-cli`). For the +core framework see `agentflow/CLAUDE.md`; for the TS client, docs, or playground see their folders; +for the monorepo overview see the workspace-root `CLAUDE.md`. + +- Package name (PyPI): `10xscale-agentflow-cli` +- Version: `0.3.2.9` (`pyproject.toml`) — note the CLI's own `CLI_VERSION` constant and + `agentflow_cli.__version__` both say `1.0.0`; these are out of sync (see Known Doc Drift). +- Requires: Python >= 3.12 · Status: `4 - Beta` +- Console entry point: `agentflow = agentflow_cli.cli.main:main` +- Depends on the core framework: `10xscale-agentflow>=0.7.0`. + +## What this package is + +It turns an Agentflow `CompiledGraph` into a production FastAPI service, plus a Typer CLI to +scaffold, run, build, test, and evaluate that service. You write a graph, point `agentflow.json` +at it, and `agentflow api` serves it over REST + WebSocket with auth, rate limiting, media +handling, checkpointer/thread management, and a memory store API. + +## Package layout + +Importable package: `agentflow_cli/`. Two halves: + +| Path | What lives there | +|---|---| +| `agentflow_cli/cli/` | The Typer CLI. `main.py` (command definitions), `commands/` (one class per command: api, build, eval, init, skills, test, version), `core/` (config, output, validation), `constants.py`, `templates/` (project scaffolds: `dev/` minimal, `prod/` full) | +| `agentflow_cli/src/app/` | The FastAPI app. `main.py` + `loader.py` (build app from `agentflow.json`), `routers/` (graph, checkpointer, store, media, ping; a2a/a2ui present but not mounted), `core/auth/`, `core/config/`, `core/middleware/` (rate_limit, security_headers, request_limits), `tasks/`, `utils/`, `worker.py` | + +Public exports from the package root (`from agentflow_cli import ...`): `BaseAuth`, +`SnowFlakeIdGenerator`, `ThreadNameGenerator`. + +## CLI commands (verified against `cli/main.py`) + +| Command | Purpose | Notable options | +|---|---|---| +| `agentflow api` | Start the API server | `--config/-c` (default `agentflow.json`), `--host/-H`, `--port/-p` (8000), `--reload/--no-reload`, `-v/-q` | +| `agentflow play` | Start the server and open the hosted playground | same as `api` | +| `agentflow init` | Interactively scaffold a project (questionary prompts pick dev vs production, auth, rate limit) | `--path/-p`, `--force/-f`. There is **no `--prod` flag**; setup type is chosen interactively | +| `agentflow build` | Generate a `Dockerfile` (and optionally `docker-compose.yml`) | `--output/-o`, `--python-version` (3.13), `--port`, `--docker-compose/--no-docker-compose`, `--service-name` | +| `agentflow eval` | Run agent evaluations; discovers `*_eval.py`/`eval_*.py`, runs cases (optionally `--parallel`), writes HTML+JSON to `eval_reports/` | `--output/-o`, `--no-report`, `--threshold/-t`, `--open`, `--parallel/-p`, `--max-concurrency/-c` | +| `agentflow test` | Run project tests via pytest (args after `--` forwarded verbatim) | `--coverage/-C`, `--html`, `-k`, path arg | +| `agentflow skills` | Install bundled Agentflow skills for Codex/Claude/GitHub | `--agent/-a`, `--path/-p`, `--force/-f`, `--all`, `--list/-l` | +| `agentflow version` | Show CLI + package version | reads `CLI_VERSION` constant + package version from `pyproject.toml` | + +Defaults (from `cli/constants.py`): `DEFAULT_HOST="127.0.0.1"`, `DEFAULT_PORT=8000`, +`DEFAULT_CONFIG_FILE="agentflow.json"`. + +## `agentflow.json` (the config contract) + +Parsed by `agentflow_cli/src/app/core/config/graph_config.py`. Supported keys: + +| Key | Meaning | +|---|---| +| `agent` (required) | `"module:attribute"` resolving to a `CompiledGraph`. The loader accepts a `CompiledGraph` object, a sync/async factory returning one, or a callable. | +| `env` | Path to a `.env` file, loaded at config-load time | +| `thread_name_generator` | `"module:attr"` -> a `ThreadNameGenerator` | +| `auth` | `null`, the string `"jwt"`, or `{"method": "custom", "path": "module:attr"}` | +| `authorization` | `"module:attr"` -> an `AuthorizationBackend` (RBAC / per-tool access) | +| `checkpointer` | `"module:attr"` -> a `BaseCheckpointer` | +| `injectq` | `"module:attr"` -> an InjectQ container | +| `store` | `"module:attr"` -> a `BaseStore` | +| `redis` | Redis URL string | +| `rate_limit` | Object (see below) | + +`rate_limit` object: `enabled`, `requests` (default 100), `window` secs (60), `by` (`ip` | +`global`), `backend` (`memory` | `redis` | `custom`), `trusted_proxy_headers` (honour +`X-Forwarded-For` only when true), `exclude_paths`, `fail_open` (on backend error: allow vs deny), +and for redis backend a `redis` sub-object `{ "url", "prefix" }` (or shorthand URL string). For +`custom`, bind a `BaseRateLimitBackend` in InjectQ. + +## Auth + +- `"auth": "jwt"` requires `JWT_SECRET_KEY` and `JWT_ALGORITHM` in the environment (raises at + load if missing). JWT logic lives in `core/auth/jwt_auth.py`. +- `"auth": {"method": "custom", "path": "module:attr"}` loads your `BaseAuth` subclass + (`from agentflow_cli import BaseAuth`). +- Authorization (RBAC, per-tool) is separate: `core/auth/authorization.py` + (`AuthorizationBackend` / `DefaultAuthorizationBackend`), wired via the `authorization` key. + +## HTTP + WebSocket surface (all under `/v1` except ping) + +- **Graph** (`tags=["Graph"]`): `POST /v1/graph/invoke`, `POST /v1/graph/stream`, + `POST /v1/graph/stop`, `POST /v1/graph/setup`, `POST /v1/graph/fix`, `GET /v1/graph`, + `WS /v1/graph/ws`. +- **Checkpointer / threads**: `GET/POST /v1/threads`, `GET/DELETE /v1/threads/{thread_id}`, + `GET /v1/threads/{thread_id}/state`, `GET /v1/threads/{thread_id}/messages`, + `... /messages/{message_id}`. +- **Store (memory)**: `POST /v1/store/memories`, `/v1/store/memories/list`, + `/v1/store/memories/forget`, `/v1/store/memories/{memory_id}`, `POST /v1/store/search`. +- **Media / files** (`tags=["Files"]`): `POST /v1/files/upload`, `GET /v1/files/{file_id}`, + `/{file_id}/info`, `/{file_id}/url`, `GET /v1/config/multimodal`. +- **Ping**: `GET /ping`. + +Routers are wired in `routers/setup_router.py` (`init_routes`). `a2a.py` and `a2ui.py` exist but +are **not** mounted there yet. + +## Settings / environment + +`core/config/settings.py` is a `pydantic-settings` `Settings` (with `extra="allow"`, so unknown +env vars are tolerated). Notable vars: `APP_NAME`, `APP_VERSION`, `MODE` (`development` | +`production`), `LOG_LEVEL`, `IS_DEBUG`, `MAX_REQUEST_SIZE` (10MB default), security headers +(`SECURITY_HEADERS_ENABLED`, `HSTS_*`, `FRAME_OPTIONS`, `CSP_POLICY`, ...), `ORIGINS` (CORS, +default `*` with a wildcard warning), `ALLOWED_HOST`, `ROOT_PATH`/`DOCS_PATH`/`REDOCS_PATH`, +`REDIS_URL`, `SENTRY_DSN`, `SNOWFLAKE_*` (epoch/node/worker/bit layout), `JWT_SECRET_KEY`/ +`JWT_ALGORITHM`, `OTEL_ENABLED`/`OTEL_SERVICE_NAME`/`OTEL_EXPORTER_OTLP_ENDPOINT`/`OTEL_LEVEL`. +In production: set `MODE=production`, `IS_DEBUG=false`, a non-`*` `ORIGINS`, and a strong +`JWT_SECRET_KEY`. + +## Optional extras (`pyproject.toml`) + +`sentry`, `firebase`, `snowflakekit`, `redis`, `jwt`, `media` (document text extraction via +`textxtract`), `gcloud` (Cloud Logging), `otel` (includes FastAPI instrumentation + OTLP exporter). + +## Development workflow + +```bash +# from this folder (agentflow-api/); a .venv is present +.venv/bin/python -m pytest # tests in tests/ +agentflow init # scaffold (interactive) +agentflow api --reload # dev server on 127.0.0.1:8000 +agentflow play # server + hosted playground +agentflow build --docker-compose # Dockerfile + compose +ruff check . && ruff format . +``` + +- Tests in `tests/`; `pytest` config and ruff/bandit are in `pyproject.toml`. Templates under + `cli/templates/{dev,prod}` are excluded from lint/type/bandit (they are emitted code, not lib). +- The `prod` template is the reference for a real project: it scaffolds `graph/` (agent, state, + tools, validators, thread_name_generator), `auth/`, `evals/`, and `tests/`. + +## Known doc drift (do not trust without checking) + +- **Version is inconsistent.** `pyproject.toml` = `0.3.2.9`, but `CLI_VERSION` constant and + `agentflow_cli.__version__` = `1.0.0`. `agentflow version` prints both the (hardcoded) CLI + version and the pyproject version, so it shows `1.0.0` and `0.3.2.9` side by side. +- **README shows `agentflow init --prod`** — that flag does not exist. `init` is interactive and + only accepts `--path` / `--force`. +- **`api`/`play` help text claims default host `0.0.0.0`** but `DEFAULT_HOST` is `127.0.0.1`. +- **"Pyagenity" branding leftovers.** The CLI app help, `agentflow_cli.__init__` docstring, the + `version` banner, and several router docstrings still say "Pyagenity" (the framework's former + name). Cosmetic but pervasive; rename to Agentflow when touching those files. +- **a2a / a2ui routers are not mounted.** Don't document a2a HTTP endpoints as live until + `setup_router.init_routes` includes them. +- **`pyproject.toml` URLs** point at `github.com/10xHub/agentflow-cli` and + `agentflow-cli.readthedocs.io`; confirm these are canonical vs the core's `agentflow.10xscale.ai`. +- The workspace-root `CLAUDE.md` lists only `init/api/play/build` and an older `agentflow.json` + shape; the real CLI has `eval/test/skills/version` too and the config supports `rate_limit`, + `thread_name_generator`, and `authorization`. diff --git a/README.md b/README.md index 49d082f..37c2324 100644 --- a/README.md +++ b/README.md @@ -38,11 +38,8 @@ pip install "10xscale-agentflow-cli[media]" ### Initialize a New Project ```bash -# Create project structure +# Create project structure (interactive: prompts for dev vs production, auth, rate limiting) agentflow init - -# Or with production config -agentflow init --prod ``` ### Start Development Server @@ -85,12 +82,9 @@ For detailed command documentation, see the [CLI Guide](./docs/cli-guide.md). Initialize a new project with configuration and sample graph. ```bash -# Basic initialization +# Basic initialization (interactive prompts choose dev vs production setup) agentflow init -# With production config (pyproject.toml, pre-commit hooks) -agentflow init --prod - # Custom directory agentflow init --path ./my-project diff --git a/agentflow_cli/__init__.py b/agentflow_cli/__init__.py index f97318a..79a2655 100644 --- a/agentflow_cli/__init__.py +++ b/agentflow_cli/__init__.py @@ -1,6 +1,8 @@ -"""Pyagenity API - A Python API framework For Pyagenity Graphs.""" +"""Agentflow API - A Python API framework for Agentflow graphs.""" + +from agentflow_cli.cli.constants import CLI_VERSION as __version__ + -__version__ = "1.0.0" __author__ = "Shudipto Trafder" __email__ = "shudiptotrafder@gmail.com" @@ -17,4 +19,5 @@ "BaseAuth", "SnowFlakeIdGenerator", "ThreadNameGenerator", + "__version__", ] diff --git a/agentflow_cli/cli/__init__.py b/agentflow_cli/cli/__init__.py index 2456f18..6706b2c 100644 --- a/agentflow_cli/cli/__init__.py +++ b/agentflow_cli/cli/__init__.py @@ -1,3 +1,6 @@ -"""Pyagenity CLI package.""" +"""Agentflow CLI package.""" -__version__ = "1.0.0" +from agentflow_cli.cli.constants import CLI_VERSION as __version__ + + +__all__ = ["__version__"] diff --git a/agentflow_cli/cli/commands/__init__.py b/agentflow_cli/cli/commands/__init__.py index fed032d..c935a0d 100644 --- a/agentflow_cli/cli/commands/__init__.py +++ b/agentflow_cli/cli/commands/__init__.py @@ -39,9 +39,9 @@ def handle_error(self, error: Exception) -> int: self.logger.error("Command failed: %s", error) # Import here to avoid circular imports - from agentflow_cli.cli.exceptions import PyagenityCLIError + from agentflow_cli.cli.exceptions import AgentflowCLIError - if isinstance(error, PyagenityCLIError): + if isinstance(error, AgentflowCLIError): self.output.error(error.message) return error.exit_code diff --git a/agentflow_cli/cli/commands/api.py b/agentflow_cli/cli/commands/api.py index 2b3617e..c786688 100644 --- a/agentflow_cli/cli/commands/api.py +++ b/agentflow_cli/cli/commands/api.py @@ -27,7 +27,7 @@ class APICommand(BaseCommand): - """Command to start the Pyagenity API server.""" + """Command to start the Agentflow API server.""" _PLAYGROUND_WAIT_TIMEOUT_SECONDS = 30.0 _PLAYGROUND_WAIT_INTERVAL_SECONDS = 0.25 diff --git a/agentflow_cli/cli/commands/eval.py b/agentflow_cli/cli/commands/eval.py index 3552085..d23535a 100644 --- a/agentflow_cli/cli/commands/eval.py +++ b/agentflow_cli/cli/commands/eval.py @@ -533,9 +533,7 @@ async def _run_one( # Report merging # ------------------------------------------------------------------ - def _merge_reports( - self, reports: list[EvalReport], base_config: Any = None - ) -> EvalReport: + def _merge_reports(self, reports: list[EvalReport], base_config: Any = None) -> EvalReport: if len(reports) == 1: return reports[0] @@ -680,9 +678,7 @@ def execute( # noqa: PLR0912, PLR0915 # Build per-eval-set config map from pending before results are consumed group_configs: dict[str, Any] = { - pc.eval_set_id: pc.config - for pc in pending - if isinstance(pc, _PendingCase) + pc.eval_set_id: pc.config for pc in pending if isinstance(pc, _PendingCase) } # 7. Group by eval_set_id → one EvalReport per set diff --git a/agentflow_cli/cli/commands/version.py b/agentflow_cli/cli/commands/version.py index 870cf2a..8c0dd20 100644 --- a/agentflow_cli/cli/commands/version.py +++ b/agentflow_cli/cli/commands/version.py @@ -20,7 +20,7 @@ def execute(self, **kwargs: Any) -> int: # Print banner self.output.print_banner( "Version", - "Show pyagenity CLI and package version info", + "Show Agentflow CLI and package version info", color="green", ) diff --git a/agentflow_cli/cli/constants.py b/agentflow_cli/cli/constants.py index 737811d..960a86b 100644 --- a/agentflow_cli/cli/constants.py +++ b/agentflow_cli/cli/constants.py @@ -1,11 +1,34 @@ """CLI constants and configuration values.""" +import tomllib +from importlib.metadata import PackageNotFoundError +from importlib.metadata import version as _pkg_version from pathlib import Path from typing import Final -# Version information -CLI_VERSION: Final[str] = "1.0.0" +_PACKAGE_NAME: Final[str] = "10xscale-agentflow-cli" + + +def _resolve_version() -> str: + """Resolve the package version from installed metadata, falling back to pyproject.toml. + + Single source of truth: the installed distribution's version. When running from a + source checkout that is not installed, read ``pyproject.toml`` instead. + """ + try: + return _pkg_version(_PACKAGE_NAME) + except PackageNotFoundError: + pyproject = Path(__file__).resolve().parents[2] / "pyproject.toml" + try: + with pyproject.open("rb") as f: + return tomllib.load(f).get("project", {}).get("version", "unknown") + except (OSError, tomllib.TOMLDecodeError): + return "unknown" + + +# Version information (single-sourced from package metadata / pyproject.toml) +CLI_VERSION: Final[str] = _resolve_version() # Default configuration values DEFAULT_HOST: Final[str] = "127.0.0.1" diff --git a/agentflow_cli/cli/core/config.py b/agentflow_cli/cli/core/config.py index 792c530..f756255 100644 --- a/agentflow_cli/cli/core/config.py +++ b/agentflow_cli/cli/core/config.py @@ -1,4 +1,4 @@ -"""Configuration management for the Pyagenity CLI.""" +"""Configuration management for the Agentflow CLI.""" from __future__ import annotations diff --git a/agentflow_cli/cli/exceptions.py b/agentflow_cli/cli/exceptions.py index adeb51e..7314f2f 100644 --- a/agentflow_cli/cli/exceptions.py +++ b/agentflow_cli/cli/exceptions.py @@ -1,8 +1,8 @@ -"""Custom exceptions for the Pyagenity CLI.""" +"""Custom exceptions for the Agentflow CLI.""" -class PyagenityCLIError(Exception): - """Base exception for all Pyagenity CLI errors.""" +class AgentflowCLIError(Exception): + """Base exception for all Agentflow CLI errors.""" def __init__(self, message: str, exit_code: int = 1) -> None: """Initialize the exception with a message and exit code. @@ -16,7 +16,7 @@ def __init__(self, message: str, exit_code: int = 1) -> None: self.exit_code = exit_code -class ConfigurationError(PyagenityCLIError): +class ConfigurationError(AgentflowCLIError): """Raised when there are configuration-related errors.""" def __init__(self, message: str, config_path: str | None = None) -> None: @@ -30,7 +30,7 @@ def __init__(self, message: str, config_path: str | None = None) -> None: self.config_path = config_path -class ValidationError(PyagenityCLIError): +class ValidationError(AgentflowCLIError): """Raised when input validation fails.""" def __init__(self, message: str, field: str | None = None) -> None: @@ -44,7 +44,7 @@ def __init__(self, message: str, field: str | None = None) -> None: self.field = field -class FileOperationError(PyagenityCLIError): +class FileOperationError(AgentflowCLIError): """Raised when file operations fail.""" def __init__(self, message: str, file_path: str | None = None) -> None: @@ -58,7 +58,7 @@ def __init__(self, message: str, file_path: str | None = None) -> None: self.file_path = file_path -class TemplateError(PyagenityCLIError): +class TemplateError(AgentflowCLIError): """Raised when template operations fail.""" def __init__(self, message: str, template_name: str | None = None) -> None: @@ -72,7 +72,7 @@ def __init__(self, message: str, template_name: str | None = None) -> None: self.template_name = template_name -class ServerError(PyagenityCLIError): +class ServerError(AgentflowCLIError): """Raised when server operations fail.""" def __init__(self, message: str, host: str | None = None, port: int | None = None) -> None: @@ -88,7 +88,7 @@ def __init__(self, message: str, host: str | None = None, port: int | None = Non self.port = port -class DockerError(PyagenityCLIError): +class DockerError(AgentflowCLIError): """Raised when Docker-related operations fail.""" def __init__(self, message: str, dockerfile_path: str | None = None) -> None: diff --git a/agentflow_cli/cli/logger.py b/agentflow_cli/cli/logger.py index 7c688b5..fdd3b62 100644 --- a/agentflow_cli/cli/logger.py +++ b/agentflow_cli/cli/logger.py @@ -1,4 +1,4 @@ -"""Logging configuration for the Pyagenity CLI.""" +"""Logging configuration for the Agentflow CLI.""" import logging import sys diff --git a/agentflow_cli/cli/main.py b/agentflow_cli/cli/main.py index 2f65a70..091ef6f 100644 --- a/agentflow_cli/cli/main.py +++ b/agentflow_cli/cli/main.py @@ -1,4 +1,4 @@ -"""Professional Pyagenity CLI main entry point.""" +"""Professional Agentflow CLI main entry point.""" import sys @@ -18,7 +18,7 @@ DEFAULT_PORT, ) from agentflow_cli.cli.core.output import OutputFormatter -from agentflow_cli.cli.exceptions import PyagenityCLIError +from agentflow_cli.cli.exceptions import AgentflowCLIError from agentflow_cli.cli.logger import setup_cli_logging @@ -29,7 +29,7 @@ app = typer.Typer( name="agentflow", help=( - "Pyagenity API CLI - Professional tool for managing Pyagenity API " + "Agentflow API CLI - Professional tool for managing Agentflow API " "servers and configurations" ), context_settings={"help_option_names": ["-h", "--help"]}, @@ -49,7 +49,7 @@ def handle_exception(e: Exception) -> int: Returns: Appropriate exit code """ - if isinstance(e, PyagenityCLIError): + if isinstance(e, AgentflowCLIError): output.error(e.message) return e.exit_code @@ -69,8 +69,8 @@ def api( DEFAULT_HOST, "--host", "-H", - help="Host to run the API on (default: 0.0.0.0, binds to all interfaces; " - "use 127.0.0.1 for localhost only)", + help="Host to run the API on (default: 127.0.0.1, localhost only; " + "use 0.0.0.0 to bind all interfaces)", ), port: int = typer.Option( DEFAULT_PORT, @@ -96,7 +96,7 @@ def api( help="Suppress all output except errors", ), ) -> None: - """Start the Pyagenity API server.""" + """Start the Agentflow API server.""" # Setup logging setup_cli_logging(verbose=verbose, quiet=quiet) @@ -284,7 +284,7 @@ def build( help="Suppress all output except errors", ), ) -> None: - """Generate a Dockerfile for the Pyagenity API application.""" + """Generate a Dockerfile for the Agentflow API application.""" # Setup logging setup_cli_logging(verbose=verbose, quiet=quiet) diff --git a/agentflow_cli/cli/templates/defaults.py b/agentflow_cli/cli/templates/defaults.py index 2d1c168..db4385c 100644 --- a/agentflow_cli/cli/templates/defaults.py +++ b/agentflow_cli/cli/templates/defaults.py @@ -25,7 +25,7 @@ """ Graph-based React Agent Implementation -This module implements a reactive agent system using PyAgenity's StateGraph. +This module implements a reactive agent system using Agentflow's StateGraph. The agent can interact with tools (like weather checking) and maintain conversation state through a checkpointer. The graph orchestrates the flow between the main agent logic and tool execution. @@ -50,7 +50,7 @@ these schemas for tool selection. Dependencies: -- PyAgenity: For graph and state management +- Agentflow: For graph and state management - LiteLLM: For AI model interactions - InjectQ: For dependency injection - Python logging: For debug and info messages @@ -368,7 +368,7 @@ def should_use_tools(state: AgentState) -> str: [project] name = "agentflow-cli-app" version = "0.1.0" -description = "Pyagenity API application" +description = "Agentflow API application" readme = "README.md" license = {text = "MIT"} requires-python = ">=3.10" @@ -378,7 +378,7 @@ def should_use_tools(state: AgentState) -> str: maintainers = [ {name = "Your Name", email = "you@example.com"}, ] -keywords = ["pyagenity", "api", "fastapi", "cli", "agentflow"] +keywords = ["agentflow", "api", "fastapi", "cli", "agentflow"] classifiers = [ "Development Status :: 4 - Beta", "Intended Audience :: Developers", @@ -486,7 +486,7 @@ def generate_dockerfile_content( ) -> str: """Generate the content for the Dockerfile.""" dockerfile_lines = [ - "# Dockerfile for Pyagenity API", + "# Dockerfile for Agentflow API", "# Generated by agentflow-cli CLI", "", f"FROM python:{python_version}-slim", diff --git a/agentflow_cli/cli/templates/skills/agent-skills/references/cli-commands.md b/agentflow_cli/cli/templates/skills/agent-skills/references/cli-commands.md index 617f774..2206b7a 100644 --- a/agentflow_cli/cli/templates/skills/agent-skills/references/cli-commands.md +++ b/agentflow_cli/cli/templates/skills/agent-skills/references/cli-commands.md @@ -28,7 +28,7 @@ agentflow = agentflow_cli.cli.main:main - Starts the FastAPI server for a compiled graph. - Options include `--config/-c`, `--host/-H`, `--port/-p`, `--reload/--no-reload`, `--verbose/-v`, and `--quiet/-q`. -- Defaults are config `agentflow.json`, host `0.0.0.0`, port `8000`, reload enabled. +- Defaults are config `agentflow.json`, host `127.0.0.1`, port `8000`, reload enabled. `agentflow play` diff --git a/agentflow_cli/src/app/routers/setup_router.py b/agentflow_cli/src/app/routers/setup_router.py index 8f83a66..a371fb4 100644 --- a/agentflow_cli/src/app/routers/setup_router.py +++ b/agentflow_cli/src/app/routers/setup_router.py @@ -11,7 +11,7 @@ def init_routes(app: FastAPI): """ Initialize the routes for the FastAPI application. - This function includes the graph and checkpointer routers for pyagenity functionality. + This function includes the graph and checkpointer routers for agentflow functionality. Auth and GraphQL routers are disabled for now. Args: diff --git a/agentflow_cli/src/app/utils/media/__init__.py b/agentflow_cli/src/app/utils/media/__init__.py index f710a7a..ce441a4 100644 --- a/agentflow_cli/src/app/utils/media/__init__.py +++ b/agentflow_cli/src/app/utils/media/__init__.py @@ -1,6 +1,6 @@ -"""Media processing module for pyagenity-api. +"""Media processing module for agentflow-api. -Document extraction lives here (not in PyAgenity core) because +Document extraction lives here (not in Agentflow core) because the core library stays lightweight — SDK users extract text themselves. The API platform auto-extracts using textxtract. """ diff --git a/agentflow_cli/src/app/utils/media/extractor.py b/agentflow_cli/src/app/utils/media/extractor.py index 0c54a42..23b46d3 100644 --- a/agentflow_cli/src/app/utils/media/extractor.py +++ b/agentflow_cli/src/app/utils/media/extractor.py @@ -1,7 +1,7 @@ """Document extraction service using textxtract. Wraps ``AsyncTextExtractor`` from the textxtract library. -This module lives in pyagenity-api, NOT in the core PyAgenity library, +This module lives in agentflow-api, NOT in the core Agentflow library, because extraction is an API-platform concern. """ diff --git a/tests/STORE_TESTS_VISUAL_SUMMARY.txt b/tests/STORE_TESTS_VISUAL_SUMMARY.txt index 338e2d8..947d1c0 100644 --- a/tests/STORE_TESTS_VISUAL_SUMMARY.txt +++ b/tests/STORE_TESTS_VISUAL_SUMMARY.txt @@ -1,5 +1,5 @@ ╔══════════════════════════════════════════════════════════════════════════════════════╗ -║ PYAGENITY-API STORE MODULE TEST SUITE ║ +║ AGENTFLOW-API STORE MODULE TEST SUITE ║ ║ Comprehensive Testing Report ║ ╚══════════════════════════════════════════════════════════════════════════════════════╝ @@ -144,7 +144,7 @@ Python Version: 3.13.7 Dependencies Tested: - ├── pyagenity (Message, BaseStore, MemorySearchResult, MemoryType) + ├── agentflow (Message, BaseStore, MemorySearchResult, MemoryType) ├── injectq (InjectAPI - dependency injection) ├── pydantic (Schema validation) └── fastapi (API framework) @@ -157,7 +157,7 @@ $ pytest tests/unit_tests/store/ -v Run with coverage: - $ pytest tests/unit_tests/store/ --cov=pyagenity_api/src/app/routers/store --cov-report=term-missing + $ pytest tests/unit_tests/store/ --cov=agentflow_api/src/app/routers/store --cov-report=term-missing Run specific test file: $ pytest tests/unit_tests/store/test_store_service.py -v @@ -185,7 +185,7 @@ Coverage Report: Name Stmts Miss Cover ------------------------------------------------------------------------- - pyagenity_api/src/app/routers/store/ + agentflow_api/src/app/routers/store/ schemas/store_schemas.py 43 0 100% services/store_service.py 67 0 100% ------------------------------------------------------------------------- diff --git a/tests/cli/test_cli_commands_core.py b/tests/cli/test_cli_commands_core.py index 186bafd..ab56bf1 100644 --- a/tests/cli/test_cli_commands_core.py +++ b/tests/cli/test_cli_commands_core.py @@ -6,7 +6,7 @@ from agentflow_cli.cli.commands.version import VersionCommand from agentflow_cli.cli.constants import CLI_VERSION from agentflow_cli.cli.core.output import OutputFormatter -from agentflow_cli.cli.exceptions import PyagenityCLIError +from agentflow_cli.cli.exceptions import AgentflowCLIError CLI_CUSTOM_EXIT = 5 @@ -40,7 +40,7 @@ def execute(self, *args, **kwargs): # pragma: no cover - not used directly def test_basecommand_handle_error_cli_error(): out = DummyOutput() cmd = ErrorCommand(output=out) - err = PyagenityCLIError("boom", exit_code=CLI_CUSTOM_EXIT) + err = AgentflowCLIError("boom", exit_code=CLI_CUSTOM_EXIT) code = cmd.handle_error(err) assert code == CLI_CUSTOM_EXIT assert out.errors and "boom" in out.errors[0] diff --git a/tests/cli/test_cli_main.py b/tests/cli/test_cli_main.py index 22d748d..dfd2824 100644 --- a/tests/cli/test_cli_main.py +++ b/tests/cli/test_cli_main.py @@ -2,7 +2,7 @@ import pytest from unittest.mock import MagicMock, patch import agentflow_cli.cli.main as main_mod -from agentflow_cli.cli.exceptions import PyagenityCLIError +from agentflow_cli.cli.exceptions import AgentflowCLIError runner = CliRunner() @@ -160,12 +160,12 @@ def test_a2a_command_is_not_exposed(): assert "No such command 'a2a'" in result.output -def test_handle_pyagenity_cli_error(monkeypatch): +def test_handle_agentflow_cli_error(monkeypatch): monkeypatch.setattr(main_mod, "setup_cli_logging", lambda **kwargs: None) monkeypatch.setattr( main_mod.VersionCommand, "execute", - lambda self: (_ for _ in ()).throw(PyagenityCLIError("Custom error message", exit_code=42)) + lambda self: (_ for _ in ()).throw(AgentflowCLIError("Custom error message", exit_code=42)) ) result = runner.invoke(main_mod.app, ["version"]) assert result.exit_code == 42 diff --git a/tests/test_multimodal_sprint2_extraction.py b/tests/test_multimodal_sprint2_extraction.py index 19fb9b6..b56bc8f 100644 --- a/tests/test_multimodal_sprint2_extraction.py +++ b/tests/test_multimodal_sprint2_extraction.py @@ -1,6 +1,6 @@ """Tests for Sprint 2 – DocumentExtractor & DocumentPipeline. -These tests live in pyagenity-api because document extraction is an API concern +These tests live in agentflow-api because document extraction is an API concern (not a core library concern). """ diff --git a/tests/unit_tests/store/README.md b/tests/unit_tests/store/README.md deleted file mode 100644 index 395e084..0000000 --- a/tests/unit_tests/store/README.md +++ /dev/null @@ -1,208 +0,0 @@ -# Store Module Unit Tests - -This directory contains comprehensive unit tests for the agentflow-cli store module. - -## Test Coverage - -### 1. Store Service Tests (`test_store_service.py`) -Comprehensive tests for all `StoreService` methods: - -#### StoreMemory Tests -- ✅ Store memory with string content -- ✅ Store memory with Message content -- ✅ Store memory with custom configuration -- ✅ Store memory with additional options -- ✅ Error handling when store is not configured - -#### SearchMemories Tests -- ✅ Basic memory search -- ✅ Search with filters (memory_type, category, limit, score_threshold) -- ✅ Search with retrieval strategy and distance metrics -- ✅ Handle empty search results - -#### GetMemory Tests -- ✅ Successfully retrieve memory by ID -- ✅ Retrieve with custom config -- ✅ Retrieve with options -- ✅ Handle non-existent memory - -#### ListMemories Tests -- ✅ List memories with default limit -- ✅ List memories with custom limit -- ✅ List memories with options -- ✅ Handle empty memory list - -#### UpdateMemory Tests -- ✅ Update memory with string content -- ✅ Update memory with Message content -- ✅ Update memory with options - -#### DeleteMemory Tests -- ✅ Successfully delete memory -- ✅ Delete with custom config -- ✅ Delete with options - -#### ForgetMemory Tests -- ✅ Forget memories by type -- ✅ Forget memories by category -- ✅ Forget memories with filters -- ✅ Forget memories with options -- ✅ Exclude None values from forget call - -**Total Service Tests: 30 tests** -**Service Coverage: 100%** - ---- - -### 2. Schema Validation Tests (`test_store_schemas.py`) -Comprehensive tests for all Pydantic schemas: - -#### StoreMemorySchema Tests -- ✅ Valid with string content -- ✅ Valid with Message content -- ✅ Default values -- ✅ With config and options -- ✅ Missing content raises error -- ✅ All memory types - -#### SearchMemorySchema Tests -- ✅ Valid basic search -- ✅ With all filters -- ✅ With retrieval strategy options -- ✅ Default values -- ✅ Missing query raises error -- ✅ Invalid limit raises error -- ✅ Invalid max_tokens raises error - -#### UpdateMemorySchema Tests -- ✅ Valid with string content -- ✅ Valid with Message content -- ✅ With config and options -- ✅ Metadata optional -- ✅ Missing content raises error - -#### DeleteMemorySchema Tests -- ✅ Valid empty schema -- ✅ With config -- ✅ With options - -#### ForgetMemorySchema Tests -- ✅ Valid with memory type -- ✅ Valid with category -- ✅ Valid with filters -- ✅ With all fields -- ✅ Default values - -#### Edge Cases Tests -- ✅ Empty string content -- ✅ Large metadata (100+ keys) -- ✅ Nested filter structures -- ✅ Unicode content (emojis, special chars) -- ✅ Very long content (10,000 chars) -- ✅ Score threshold boundaries - -**Total Schema Tests: 34 tests** -**Schema Coverage: 100%** - ---- - -## Running the Tests - -### Run all store unit tests: -```bash -pytest tests/unit_tests/store/ -v -``` - -### Run with coverage: -```bash -pytest tests/unit_tests/store/ --cov=pyagenity_api/src/app/routers/store --cov-report=term-missing -``` - -### Run specific test file: -```bash -pytest tests/unit_tests/store/test_store_service.py -v -pytest tests/unit_tests/store/test_store_schemas.py -v -``` - -### Run specific test class: -```bash -pytest tests/unit_tests/store/test_store_service.py::TestStoreMemory -v -``` - -### Run specific test method: -```bash -pytest tests/unit_tests/store/test_store_service.py::TestStoreMemory::test_store_memory_with_string_content -v -``` - ---- - -## Test Fixtures - -All fixtures are defined in `conftest.py`: - -- `mock_store`: AsyncMock of BaseStore for testing -- `store_service`: StoreService instance with mocked store -- `mock_user`: Mock authenticated user data -- `sample_memory_id`: Sample UUID for memory ID -- `sample_message`: Sample Message object with TextBlock -- `sample_memory_result`: Sample MemorySearchResult -- `sample_memory_results`: Sample list of MemorySearchResult - ---- - -## Test Results - -``` -====================================================== test session starts ======================================================= -platform linux -- Python 3.13.7, pytest-8.4.2, pluggy-1.6.0 -collected 62 items - -tests/unit_tests/store/test_store_schemas.py::TestStoreMemorySchema::test_valid_with_string_content PASSED [ 1%] -tests/unit_tests/store/test_store_schemas.py::TestStoreMemorySchema::test_valid_with_message_content PASSED [ 3%] -... -tests/unit_tests/store/test_store_service.py::TestForgetMemory::test_forget_memory_excludes_none_values PASSED [100%] - -================================================= 62 passed, 3 warnings in 1.17s ================================================= - -Coverage: -- pyagenity_api/src/app/routers/store/schemas/store_schemas.py: 100% -- pyagenity_api/src/app/routers/store/services/store_service.py: 100% -``` - ---- - -## Test Organization - -- **Unit Tests**: Test individual functions and methods in isolation -- **Mocking**: All external dependencies (BaseStore) are mocked -- **Fixtures**: Shared test data and mocks in conftest.py -- **AAA Pattern**: All tests follow Arrange-Act-Assert pattern -- **Docstrings**: Every test has a clear docstring explaining what it tests - ---- - -## Key Testing Strategies - -1. **Comprehensive Coverage**: All service methods and schema validations are tested -2. **Edge Cases**: Tests include boundary conditions, empty data, and error scenarios -3. **Mock Verification**: Tests verify that mocked methods are called correctly -4. **Validation Testing**: Schema tests ensure proper Pydantic validation -5. **Error Handling**: Tests verify proper error handling and exceptions - ---- - -## Future Enhancements - -- Add integration tests with real database (requires InjectQ container setup) -- Add performance benchmarks for large-scale operations -- Add tests for concurrent operations -- Add tests for rate limiting and throttling - ---- - -## Notes - -- Integration tests are prepared but require InjectQ container configuration -- All unit tests pass with 100% coverage on store module -- Tests use pytest-asyncio for async test support -- Message objects use TextBlock for content as per pyagenity API diff --git a/uv.lock b/uv.lock index 84bd6a0..411d3d4 100644 --- a/uv.lock +++ b/uv.lock @@ -2,28 +2,30 @@ version = 1 revision = 3 requires-python = ">=3.12" resolution-markers = [ - "python_full_version >= '3.13'", + "python_full_version >= '3.14'", + "python_full_version == '3.13.*'", "python_full_version < '3.13'", ] [[package]] name = "10xscale-agentflow" -version = "0.7.0" +version = "0.7.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "injectq" }, { name = "pillow" }, { name = "pydantic" }, { name = "python-dotenv" }, + { name = "pyyaml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ef/87/c68ad65b41dcbee73be2ceddea1b8c8ff0b37e02b4fc0ca217a18271a6df/10xscale_agentflow-0.7.0.tar.gz", hash = "sha256:caf79b75d4d7451bb6123e5cf28251936924a8e6d3f3e0923d8c38b60bb0b3a2", size = 388433, upload-time = "2026-04-05T17:16:49.801Z" } +sdist = { url = "https://files.pythonhosted.org/packages/67/35/4d648d2ccbbebd8d1d3fa3158a39c652e108c46dbb9528a9e75ab5800c8a/10xscale_agentflow-0.7.5.1.tar.gz", hash = "sha256:0390f64f7478b492a78db4743c0702c87a05d00e15810a0158665b3e2b286784", size = 433352, upload-time = "2026-05-31T09:11:25.543Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/46/01/f31656c7ea2561d50050ed6e571345c2c3b8af79fce01e3c48050608f0dd/10xscale_agentflow-0.7.0-py3-none-any.whl", hash = "sha256:093b51c2de521e1c7208a1b0a8fb9a4b02df6c98ada94ca2c43cfba2e4a3939e", size = 423797, upload-time = "2026-04-05T17:16:47.432Z" }, + { url = "https://files.pythonhosted.org/packages/23/5c/eb4e23184648a0ac355a0f81ebfb395330b56b592cc47782bbfe4dd15a62/10xscale_agentflow-0.7.5.1-py3-none-any.whl", hash = "sha256:2ac5642d4f41ffd6b8904d21ac19deae9e16db895c91b33272eff921618a0d39", size = 473278, upload-time = "2026-05-31T09:11:22.711Z" }, ] [[package]] name = "10xscale-agentflow-cli" -version = "0.3.2.6" +version = "0.3.2.9" source = { editable = "." } dependencies = [ { name = "10xscale-agentflow" }, @@ -53,6 +55,12 @@ jwt = [ media = [ { name = "textxtract", extra = ["docx", "html", "md", "pdf", "xml"] }, ] +otel = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-otlp" }, + { name = "opentelemetry-instrumentation-fastapi" }, + { name = "opentelemetry-sdk" }, +] redis = [ { name = "redis" }, ] @@ -65,6 +73,7 @@ snowflakekit = [ [package.dev-dependencies] dev = [ + { name = "10xscale-agentflow" }, { name = "httpx" }, { name = "lib" }, { name = "markdown-it-py" }, @@ -93,6 +102,10 @@ requires-dist = [ { name = "google-cloud-logging", marker = "extra == 'gcloud'" }, { name = "gunicorn" }, { name = "oauth2client", marker = "extra == 'firebase'", specifier = ">=4.1.3" }, + { name = "opentelemetry-api", marker = "extra == 'otel'", specifier = ">=1.20.0" }, + { name = "opentelemetry-exporter-otlp", marker = "extra == 'otel'", specifier = ">=1.20.0" }, + { name = "opentelemetry-instrumentation-fastapi", marker = "extra == 'otel'", specifier = ">=0.40b0" }, + { name = "opentelemetry-sdk", marker = "extra == 'otel'", specifier = ">=1.20.0" }, { name = "orjson" }, { name = "pydantic" }, { name = "pydantic-settings" }, @@ -107,10 +120,11 @@ requires-dist = [ { name = "typer" }, { name = "uvicorn" }, ] -provides-extras = ["sentry", "firebase", "snowflakekit", "redis", "jwt", "media", "gcloud"] +provides-extras = ["sentry", "firebase", "snowflakekit", "redis", "jwt", "media", "gcloud", "otel"] [package.metadata.requires-dev] dev = [ + { name = "10xscale-agentflow", specifier = ">=0.7.4.7" }, { name = "httpx", specifier = "==0.27.0" }, { name = "lib", specifier = "==4.0.0" }, { name = "markdown-it-py", specifier = "==3.0.0" }, @@ -154,6 +168,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6f/12/e5e0282d673bb9746bacfb6e2dba8719989d3660cdb2ea79aee9a9651afb/anyio-4.10.0-py3-none-any.whl", hash = "sha256:60e474ac86736bbfd6f210f7a61218939c318f43f9972497381f1c5e930ed3d1", size = 107213, upload-time = "2025-08-04T08:54:24.882Z" }, ] +[[package]] +name = "asgiref" +version = "3.11.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/63/40/f03da1264ae8f7cfdbf9146542e5e7e8100a4c66ab48e791df9a03d3f6c0/asgiref-3.11.1.tar.gz", hash = "sha256:5f184dc43b7e763efe848065441eac62229c9f7b0475f41f80e207a114eda4ce", size = 38550, upload-time = "2026-02-03T13:30:14.33Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/0a/a72d10ed65068e115044937873362e6e32fab1b7dce0046aeb224682c989/asgiref-3.11.1-py3-none-any.whl", hash = "sha256:e8667a091e69529631969fd45dc268fa79b99c92c5fcdda727757e52146ec133", size = 24345, upload-time = "2026-02-03T13:30:13.039Z" }, +] + [[package]] name = "beautifulsoup4" version = "4.14.3" @@ -1219,6 +1242,162 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/91/48/28ed9e55dcf2f453128df738210a980e09f4e468a456fa3c763dbc8be70a/opentelemetry_api-1.37.0-py3-none-any.whl", hash = "sha256:accf2024d3e89faec14302213bc39550ec0f4095d1cf5ca688e1bfb1c8612f47", size = 65732, upload-time = "2025-09-11T10:28:41.826Z" }, ] +[[package]] +name = "opentelemetry-exporter-otlp" +version = "1.37.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-exporter-otlp-proto-grpc" }, + { name = "opentelemetry-exporter-otlp-proto-http" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/64/df/47fde1de15a3d5ad410e98710fac60cd3d509df5dc7ec1359b71d6bf7e70/opentelemetry_exporter_otlp-1.37.0.tar.gz", hash = "sha256:f85b1929dd0d750751cc9159376fb05aa88bb7a08b6cdbf84edb0054d93e9f26", size = 6145, upload-time = "2025-09-11T10:29:03.075Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f5/23/7e35e41111e3834d918e414eca41555d585e8860c9149507298bb3b9b061/opentelemetry_exporter_otlp-1.37.0-py3-none-any.whl", hash = "sha256:bd44592c6bc7fc3e5c0a9b60f2ee813c84c2800c449e59504ab93f356cc450fc", size = 7019, upload-time = "2025-09-11T10:28:44.094Z" }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-proto-common" +version = "1.37.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-proto" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/dc/6c/10018cbcc1e6fff23aac67d7fd977c3d692dbe5f9ef9bb4db5c1268726cc/opentelemetry_exporter_otlp_proto_common-1.37.0.tar.gz", hash = "sha256:c87a1bdd9f41fdc408d9cc9367bb53f8d2602829659f2b90be9f9d79d0bfe62c", size = 20430, upload-time = "2025-09-11T10:29:03.605Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/13/b4ef09837409a777f3c0af2a5b4ba9b7af34872bc43609dda0c209e4060d/opentelemetry_exporter_otlp_proto_common-1.37.0-py3-none-any.whl", hash = "sha256:53038428449c559b0c564b8d718df3314da387109c4d36bd1b94c9a641b0292e", size = 18359, upload-time = "2025-09-11T10:28:44.939Z" }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-proto-grpc" +version = "1.37.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "googleapis-common-protos" }, + { name = "grpcio" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-otlp-proto-common" }, + { name = "opentelemetry-proto" }, + { name = "opentelemetry-sdk" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d1/11/4ad0979d0bb13ae5a845214e97c8d42da43980034c30d6f72d8e0ebe580e/opentelemetry_exporter_otlp_proto_grpc-1.37.0.tar.gz", hash = "sha256:f55bcb9fc848ce05ad3dd954058bc7b126624d22c4d9e958da24d8537763bec5", size = 24465, upload-time = "2025-09-11T10:29:04.172Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/17/46630b74751031a658706bef23ac99cdc2953cd3b2d28ec90590a0766b3e/opentelemetry_exporter_otlp_proto_grpc-1.37.0-py3-none-any.whl", hash = "sha256:aee5104835bf7993b7ddaaf380b6467472abaedb1f1dbfcc54a52a7d781a3890", size = 19305, upload-time = "2025-09-11T10:28:45.776Z" }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-proto-http" +version = "1.37.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "googleapis-common-protos" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-otlp-proto-common" }, + { name = "opentelemetry-proto" }, + { name = "opentelemetry-sdk" }, + { name = "requests" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5d/e3/6e320aeb24f951449e73867e53c55542bebbaf24faeee7623ef677d66736/opentelemetry_exporter_otlp_proto_http-1.37.0.tar.gz", hash = "sha256:e52e8600f1720d6de298419a802108a8f5afa63c96809ff83becb03f874e44ac", size = 17281, upload-time = "2025-09-11T10:29:04.844Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/e9/70d74a664d83976556cec395d6bfedd9b85ec1498b778367d5f93e373397/opentelemetry_exporter_otlp_proto_http-1.37.0-py3-none-any.whl", hash = "sha256:54c42b39945a6cc9d9a2a33decb876eabb9547e0dcb49df090122773447f1aef", size = 19576, upload-time = "2025-09-11T10:28:46.726Z" }, +] + +[[package]] +name = "opentelemetry-instrumentation" +version = "0.58b0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "packaging" }, + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f6/36/7c307d9be8ce4ee7beb86d7f1d31027f2a6a89228240405a858d6e4d64f9/opentelemetry_instrumentation-0.58b0.tar.gz", hash = "sha256:df640f3ac715a3e05af145c18f527f4422c6ab6c467e40bd24d2ad75a00cb705", size = 31549, upload-time = "2025-09-11T11:42:14.084Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/db/5ff1cd6c5ca1d12ecf1b73be16fbb2a8af2114ee46d4b0e6d4b23f4f4db7/opentelemetry_instrumentation-0.58b0-py3-none-any.whl", hash = "sha256:50f97ac03100676c9f7fc28197f8240c7290ca1baa12da8bfbb9a1de4f34cc45", size = 33019, upload-time = "2025-09-11T11:41:00.624Z" }, +] + +[[package]] +name = "opentelemetry-instrumentation-asgi" +version = "0.58b0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "asgiref" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-instrumentation" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "opentelemetry-util-http" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7b/e2/03ff707d881d590c7adaed5e9d1979aed7e5e53fc1ed89035e5ed9f304af/opentelemetry_instrumentation_asgi-0.58b0.tar.gz", hash = "sha256:3ccc0c9c1c8c71e8d9da5945c6dcd9c0c8d147839f208536b7042c6dd98e65c9", size = 25116, upload-time = "2025-09-11T11:42:18.437Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8c/71/a00884c6655387c70070138acbf79a6616ad5d4489680f40708d75b598a7/opentelemetry_instrumentation_asgi-0.58b0-py3-none-any.whl", hash = "sha256:508a6d79e333d648d2afee0e140b6e80eb5d443be183be58e81d9ff88373168a", size = 16798, upload-time = "2025-09-11T11:41:08.105Z" }, +] + +[[package]] +name = "opentelemetry-instrumentation-fastapi" +version = "0.58b0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-instrumentation" }, + { name = "opentelemetry-instrumentation-asgi" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "opentelemetry-util-http" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/64/09/4f8fcab834af6b403e5e2d94bdfb2d0835ba8cd1049bcc156995f47b65fb/opentelemetry_instrumentation_fastapi-0.58b0.tar.gz", hash = "sha256:03da470d694116a0a40f4e76319e42f3ff9efc49abf804b2acc2c07f96661497", size = 24598, upload-time = "2025-09-11T11:42:35.325Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/45/fb/82de06eba54e5cb979274f073065ebc374794853502d342b5155073d1194/opentelemetry_instrumentation_fastapi-0.58b0-py3-none-any.whl", hash = "sha256:d89bfec69c9ffc5d9f3fe58655d6660a66b2bca863b9132712c06edcde68b6fa", size = 13460, upload-time = "2025-09-11T11:41:28.507Z" }, +] + +[[package]] +name = "opentelemetry-proto" +version = "1.37.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/dd/ea/a75f36b463a36f3c5a10c0b5292c58b31dbdde74f6f905d3d0ab2313987b/opentelemetry_proto-1.37.0.tar.gz", hash = "sha256:30f5c494faf66f77faeaefa35ed4443c5edb3b0aa46dad073ed7210e1a789538", size = 46151, upload-time = "2025-09-11T10:29:11.04Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c4/25/f89ea66c59bd7687e218361826c969443c4fa15dfe89733f3bf1e2a9e971/opentelemetry_proto-1.37.0-py3-none-any.whl", hash = "sha256:8ed8c066ae8828bbf0c39229979bdf583a126981142378a9cbe9d6fd5701c6e2", size = 72534, upload-time = "2025-09-11T10:28:56.831Z" }, +] + +[[package]] +name = "opentelemetry-sdk" +version = "1.37.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f4/62/2e0ca80d7fe94f0b193135375da92c640d15fe81f636658d2acf373086bc/opentelemetry_sdk-1.37.0.tar.gz", hash = "sha256:cc8e089c10953ded765b5ab5669b198bbe0af1b3f89f1007d19acd32dc46dda5", size = 170404, upload-time = "2025-09-11T10:29:11.779Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/62/9f4ad6a54126fb00f7ed4bb5034964c6e4f00fcd5a905e115bd22707e20d/opentelemetry_sdk-1.37.0-py3-none-any.whl", hash = "sha256:8f3c3c22063e52475c5dbced7209495c2c16723d016d39287dfc215d1771257c", size = 131941, upload-time = "2025-09-11T10:28:57.83Z" }, +] + +[[package]] +name = "opentelemetry-semantic-conventions" +version = "0.58b0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/aa/1b/90701d91e6300d9f2fb352153fb1721ed99ed1f6ea14fa992c756016e63a/opentelemetry_semantic_conventions-0.58b0.tar.gz", hash = "sha256:6bd46f51264279c433755767bb44ad00f1c9e2367e1b42af563372c5a6fa0c25", size = 129867, upload-time = "2025-09-11T10:29:12.597Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/90/68152b7465f50285d3ce2481b3aec2f82822e3f52e5152eeeaf516bab841/opentelemetry_semantic_conventions-0.58b0-py3-none-any.whl", hash = "sha256:5564905ab1458b96684db1340232729fce3b5375a06e140e8904c78e4f815b28", size = 207954, upload-time = "2025-09-11T10:28:59.218Z" }, +] + +[[package]] +name = "opentelemetry-util-http" +version = "0.58b0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c6/5f/02f31530faf50ef8a41ab34901c05cbbf8e9d76963ba2fb852b0b4065f4e/opentelemetry_util_http-0.58b0.tar.gz", hash = "sha256:de0154896c3472c6599311c83e0ecee856c4da1b17808d39fdc5cce5312e4d89", size = 9411, upload-time = "2025-09-11T11:43:05.602Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a5/a3/0a1430c42c6d34d8372a16c104e7408028f0c30270d8f3eb6cccf2e82934/opentelemetry_util_http-0.58b0-py3-none-any.whl", hash = "sha256:6c6b86762ed43025fbd593dc5f700ba0aa3e09711aedc36fd48a13b23d8cb1e7", size = 7652, upload-time = "2025-09-11T11:42:09.682Z" }, +] + [[package]] name = "orjson" version = "3.11.3" @@ -2014,6 +2193,55 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/41/52/e465037f5375f43533d1a80b6923955201596a99142ed524d77b571a1418/wcwidth-0.7.0-py3-none-any.whl", hash = "sha256:5d69154c429a82910e241c738cd0e2976fac8a2dd47a1a805f4afed1c0f136f2", size = 110825, upload-time = "2026-05-02T16:04:11.033Z" }, ] +[[package]] +name = "wrapt" +version = "1.17.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/8f/aeb76c5b46e273670962298c23e7ddde79916cb74db802131d49a85e4b7d/wrapt-1.17.3.tar.gz", hash = "sha256:f66eb08feaa410fe4eebd17f2a2c8e2e46d3476e9f8c783daa8e09e0faa666d0", size = 55547, upload-time = "2025-08-12T05:53:21.714Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/41/cad1aba93e752f1f9268c77270da3c469883d56e2798e7df6240dcb2287b/wrapt-1.17.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ab232e7fdb44cdfbf55fc3afa31bcdb0d8980b9b95c38b6405df2acb672af0e0", size = 53998, upload-time = "2025-08-12T05:51:47.138Z" }, + { url = "https://files.pythonhosted.org/packages/60/f8/096a7cc13097a1869fe44efe68dace40d2a16ecb853141394047f0780b96/wrapt-1.17.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9baa544e6acc91130e926e8c802a17f3b16fbea0fd441b5a60f5cf2cc5c3deba", size = 39020, upload-time = "2025-08-12T05:51:35.906Z" }, + { url = "https://files.pythonhosted.org/packages/33/df/bdf864b8997aab4febb96a9ae5c124f700a5abd9b5e13d2a3214ec4be705/wrapt-1.17.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6b538e31eca1a7ea4605e44f81a48aa24c4632a277431a6ed3f328835901f4fd", size = 39098, upload-time = "2025-08-12T05:51:57.474Z" }, + { url = "https://files.pythonhosted.org/packages/9f/81/5d931d78d0eb732b95dc3ddaeeb71c8bb572fb01356e9133916cd729ecdd/wrapt-1.17.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:042ec3bb8f319c147b1301f2393bc19dba6e176b7da446853406d041c36c7828", size = 88036, upload-time = "2025-08-12T05:52:34.784Z" }, + { url = "https://files.pythonhosted.org/packages/ca/38/2e1785df03b3d72d34fc6252d91d9d12dc27a5c89caef3335a1bbb8908ca/wrapt-1.17.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3af60380ba0b7b5aeb329bc4e402acd25bd877e98b3727b0135cb5c2efdaefe9", size = 88156, upload-time = "2025-08-12T05:52:13.599Z" }, + { url = "https://files.pythonhosted.org/packages/b3/8b/48cdb60fe0603e34e05cffda0b2a4adab81fd43718e11111a4b0100fd7c1/wrapt-1.17.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0b02e424deef65c9f7326d8c19220a2c9040c51dc165cddb732f16198c168396", size = 87102, upload-time = "2025-08-12T05:52:14.56Z" }, + { url = "https://files.pythonhosted.org/packages/3c/51/d81abca783b58f40a154f1b2c56db1d2d9e0d04fa2d4224e357529f57a57/wrapt-1.17.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:74afa28374a3c3a11b3b5e5fca0ae03bef8450d6aa3ab3a1e2c30e3a75d023dc", size = 87732, upload-time = "2025-08-12T05:52:36.165Z" }, + { url = "https://files.pythonhosted.org/packages/9e/b1/43b286ca1392a006d5336412d41663eeef1ad57485f3e52c767376ba7e5a/wrapt-1.17.3-cp312-cp312-win32.whl", hash = "sha256:4da9f45279fff3543c371d5ababc57a0384f70be244de7759c85a7f989cb4ebe", size = 36705, upload-time = "2025-08-12T05:53:07.123Z" }, + { url = "https://files.pythonhosted.org/packages/28/de/49493f962bd3c586ab4b88066e967aa2e0703d6ef2c43aa28cb83bf7b507/wrapt-1.17.3-cp312-cp312-win_amd64.whl", hash = "sha256:e71d5c6ebac14875668a1e90baf2ea0ef5b7ac7918355850c0908ae82bcb297c", size = 38877, upload-time = "2025-08-12T05:53:05.436Z" }, + { url = "https://files.pythonhosted.org/packages/f1/48/0f7102fe9cb1e8a5a77f80d4f0956d62d97034bbe88d33e94699f99d181d/wrapt-1.17.3-cp312-cp312-win_arm64.whl", hash = "sha256:604d076c55e2fdd4c1c03d06dc1a31b95130010517b5019db15365ec4a405fc6", size = 36885, upload-time = "2025-08-12T05:52:54.367Z" }, + { url = "https://files.pythonhosted.org/packages/fc/f6/759ece88472157acb55fc195e5b116e06730f1b651b5b314c66291729193/wrapt-1.17.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a47681378a0439215912ef542c45a783484d4dd82bac412b71e59cf9c0e1cea0", size = 54003, upload-time = "2025-08-12T05:51:48.627Z" }, + { url = "https://files.pythonhosted.org/packages/4f/a9/49940b9dc6d47027dc850c116d79b4155f15c08547d04db0f07121499347/wrapt-1.17.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:54a30837587c6ee3cd1a4d1c2ec5d24e77984d44e2f34547e2323ddb4e22eb77", size = 39025, upload-time = "2025-08-12T05:51:37.156Z" }, + { url = "https://files.pythonhosted.org/packages/45/35/6a08de0f2c96dcdd7fe464d7420ddb9a7655a6561150e5fc4da9356aeaab/wrapt-1.17.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:16ecf15d6af39246fe33e507105d67e4b81d8f8d2c6598ff7e3ca1b8a37213f7", size = 39108, upload-time = "2025-08-12T05:51:58.425Z" }, + { url = "https://files.pythonhosted.org/packages/0c/37/6faf15cfa41bf1f3dba80cd3f5ccc6622dfccb660ab26ed79f0178c7497f/wrapt-1.17.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6fd1ad24dc235e4ab88cda009e19bf347aabb975e44fd5c2fb22a3f6e4141277", size = 88072, upload-time = "2025-08-12T05:52:37.53Z" }, + { url = "https://files.pythonhosted.org/packages/78/f2/efe19ada4a38e4e15b6dff39c3e3f3f73f5decf901f66e6f72fe79623a06/wrapt-1.17.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ed61b7c2d49cee3c027372df5809a59d60cf1b6c2f81ee980a091f3afed6a2d", size = 88214, upload-time = "2025-08-12T05:52:15.886Z" }, + { url = "https://files.pythonhosted.org/packages/40/90/ca86701e9de1622b16e09689fc24b76f69b06bb0150990f6f4e8b0eeb576/wrapt-1.17.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:423ed5420ad5f5529db9ce89eac09c8a2f97da18eb1c870237e84c5a5c2d60aa", size = 87105, upload-time = "2025-08-12T05:52:17.914Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e0/d10bd257c9a3e15cbf5523025252cc14d77468e8ed644aafb2d6f54cb95d/wrapt-1.17.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e01375f275f010fcbf7f643b4279896d04e571889b8a5b3f848423d91bf07050", size = 87766, upload-time = "2025-08-12T05:52:39.243Z" }, + { url = "https://files.pythonhosted.org/packages/e8/cf/7d848740203c7b4b27eb55dbfede11aca974a51c3d894f6cc4b865f42f58/wrapt-1.17.3-cp313-cp313-win32.whl", hash = "sha256:53e5e39ff71b3fc484df8a522c933ea2b7cdd0d5d15ae82e5b23fde87d44cbd8", size = 36711, upload-time = "2025-08-12T05:53:10.074Z" }, + { url = "https://files.pythonhosted.org/packages/57/54/35a84d0a4d23ea675994104e667ceff49227ce473ba6a59ba2c84f250b74/wrapt-1.17.3-cp313-cp313-win_amd64.whl", hash = "sha256:1f0b2f40cf341ee8cc1a97d51ff50dddb9fcc73241b9143ec74b30fc4f44f6cb", size = 38885, upload-time = "2025-08-12T05:53:08.695Z" }, + { url = "https://files.pythonhosted.org/packages/01/77/66e54407c59d7b02a3c4e0af3783168fff8e5d61def52cda8728439d86bc/wrapt-1.17.3-cp313-cp313-win_arm64.whl", hash = "sha256:7425ac3c54430f5fc5e7b6f41d41e704db073309acfc09305816bc6a0b26bb16", size = 36896, upload-time = "2025-08-12T05:52:55.34Z" }, + { url = "https://files.pythonhosted.org/packages/02/a2/cd864b2a14f20d14f4c496fab97802001560f9f41554eef6df201cd7f76c/wrapt-1.17.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cf30f6e3c077c8e6a9a7809c94551203c8843e74ba0c960f4a98cd80d4665d39", size = 54132, upload-time = "2025-08-12T05:51:49.864Z" }, + { url = "https://files.pythonhosted.org/packages/d5/46/d011725b0c89e853dc44cceb738a307cde5d240d023d6d40a82d1b4e1182/wrapt-1.17.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e228514a06843cae89621384cfe3a80418f3c04aadf8a3b14e46a7be704e4235", size = 39091, upload-time = "2025-08-12T05:51:38.935Z" }, + { url = "https://files.pythonhosted.org/packages/2e/9e/3ad852d77c35aae7ddebdbc3b6d35ec8013af7d7dddad0ad911f3d891dae/wrapt-1.17.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:5ea5eb3c0c071862997d6f3e02af1d055f381b1d25b286b9d6644b79db77657c", size = 39172, upload-time = "2025-08-12T05:51:59.365Z" }, + { url = "https://files.pythonhosted.org/packages/c3/f7/c983d2762bcce2326c317c26a6a1e7016f7eb039c27cdf5c4e30f4160f31/wrapt-1.17.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:281262213373b6d5e4bb4353bc36d1ba4084e6d6b5d242863721ef2bf2c2930b", size = 87163, upload-time = "2025-08-12T05:52:40.965Z" }, + { url = "https://files.pythonhosted.org/packages/e4/0f/f673f75d489c7f22d17fe0193e84b41540d962f75fce579cf6873167c29b/wrapt-1.17.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc4a8d2b25efb6681ecacad42fca8859f88092d8732b170de6a5dddd80a1c8fa", size = 87963, upload-time = "2025-08-12T05:52:20.326Z" }, + { url = "https://files.pythonhosted.org/packages/df/61/515ad6caca68995da2fac7a6af97faab8f78ebe3bf4f761e1b77efbc47b5/wrapt-1.17.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:373342dd05b1d07d752cecbec0c41817231f29f3a89aa8b8843f7b95992ed0c7", size = 86945, upload-time = "2025-08-12T05:52:21.581Z" }, + { url = "https://files.pythonhosted.org/packages/d3/bd/4e70162ce398462a467bc09e768bee112f1412e563620adc353de9055d33/wrapt-1.17.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d40770d7c0fd5cbed9d84b2c3f2e156431a12c9a37dc6284060fb4bec0b7ffd4", size = 86857, upload-time = "2025-08-12T05:52:43.043Z" }, + { url = "https://files.pythonhosted.org/packages/2b/b8/da8560695e9284810b8d3df8a19396a6e40e7518059584a1a394a2b35e0a/wrapt-1.17.3-cp314-cp314-win32.whl", hash = "sha256:fbd3c8319de8e1dc79d346929cd71d523622da527cca14e0c1d257e31c2b8b10", size = 37178, upload-time = "2025-08-12T05:53:12.605Z" }, + { url = "https://files.pythonhosted.org/packages/db/c8/b71eeb192c440d67a5a0449aaee2310a1a1e8eca41676046f99ed2487e9f/wrapt-1.17.3-cp314-cp314-win_amd64.whl", hash = "sha256:e1a4120ae5705f673727d3253de3ed0e016f7cd78dc463db1b31e2463e1f3cf6", size = 39310, upload-time = "2025-08-12T05:53:11.106Z" }, + { url = "https://files.pythonhosted.org/packages/45/20/2cda20fd4865fa40f86f6c46ed37a2a8356a7a2fde0773269311f2af56c7/wrapt-1.17.3-cp314-cp314-win_arm64.whl", hash = "sha256:507553480670cab08a800b9463bdb881b2edeed77dc677b0a5915e6106e91a58", size = 37266, upload-time = "2025-08-12T05:52:56.531Z" }, + { url = "https://files.pythonhosted.org/packages/77/ed/dd5cf21aec36c80443c6f900449260b80e2a65cf963668eaef3b9accce36/wrapt-1.17.3-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:ed7c635ae45cfbc1a7371f708727bf74690daedc49b4dba310590ca0bd28aa8a", size = 56544, upload-time = "2025-08-12T05:51:51.109Z" }, + { url = "https://files.pythonhosted.org/packages/8d/96/450c651cc753877ad100c7949ab4d2e2ecc4d97157e00fa8f45df682456a/wrapt-1.17.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:249f88ed15503f6492a71f01442abddd73856a0032ae860de6d75ca62eed8067", size = 40283, upload-time = "2025-08-12T05:51:39.912Z" }, + { url = "https://files.pythonhosted.org/packages/d1/86/2fcad95994d9b572db57632acb6f900695a648c3e063f2cd344b3f5c5a37/wrapt-1.17.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a03a38adec8066d5a37bea22f2ba6bbf39fcdefbe2d91419ab864c3fb515454", size = 40366, upload-time = "2025-08-12T05:52:00.693Z" }, + { url = "https://files.pythonhosted.org/packages/64/0e/f4472f2fdde2d4617975144311f8800ef73677a159be7fe61fa50997d6c0/wrapt-1.17.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5d4478d72eb61c36e5b446e375bbc49ed002430d17cdec3cecb36993398e1a9e", size = 108571, upload-time = "2025-08-12T05:52:44.521Z" }, + { url = "https://files.pythonhosted.org/packages/cc/01/9b85a99996b0a97c8a17484684f206cbb6ba73c1ce6890ac668bcf3838fb/wrapt-1.17.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:223db574bb38637e8230eb14b185565023ab624474df94d2af18f1cdb625216f", size = 113094, upload-time = "2025-08-12T05:52:22.618Z" }, + { url = "https://files.pythonhosted.org/packages/25/02/78926c1efddcc7b3aa0bc3d6b33a822f7d898059f7cd9ace8c8318e559ef/wrapt-1.17.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e405adefb53a435f01efa7ccdec012c016b5a1d3f35459990afc39b6be4d5056", size = 110659, upload-time = "2025-08-12T05:52:24.057Z" }, + { url = "https://files.pythonhosted.org/packages/dc/ee/c414501ad518ac3e6fe184753632fe5e5ecacdcf0effc23f31c1e4f7bfcf/wrapt-1.17.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:88547535b787a6c9ce4086917b6e1d291aa8ed914fdd3a838b3539dc95c12804", size = 106946, upload-time = "2025-08-12T05:52:45.976Z" }, + { url = "https://files.pythonhosted.org/packages/be/44/a1bd64b723d13bb151d6cc91b986146a1952385e0392a78567e12149c7b4/wrapt-1.17.3-cp314-cp314t-win32.whl", hash = "sha256:41b1d2bc74c2cac6f9074df52b2efbef2b30bdfe5f40cb78f8ca22963bc62977", size = 38717, upload-time = "2025-08-12T05:53:15.214Z" }, + { url = "https://files.pythonhosted.org/packages/79/d9/7cfd5a312760ac4dd8bf0184a6ee9e43c33e47f3dadc303032ce012b8fa3/wrapt-1.17.3-cp314-cp314t-win_amd64.whl", hash = "sha256:73d496de46cd2cdbdbcce4ae4bcdb4afb6a11234a1df9c085249d55166b95116", size = 41334, upload-time = "2025-08-12T05:53:14.178Z" }, + { url = "https://files.pythonhosted.org/packages/46/78/10ad9781128ed2f99dbc474f43283b13fea8ba58723e98844367531c18e9/wrapt-1.17.3-cp314-cp314t-win_arm64.whl", hash = "sha256:f38e60678850c42461d4202739f9bf1e3a737c7ad283638251e79cc49effb6b6", size = 38471, upload-time = "2025-08-12T05:52:57.784Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f6/a933bd70f98e9cf3e08167fc5cd7aaaca49147e48411c0bd5ae701bb2194/wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22", size = 23591, upload-time = "2025-08-12T05:53:20.674Z" }, +] + [[package]] name = "zipp" version = "3.23.0"